Compare commits

..
370 Commits
Author SHA1 Message Date
Chris Troutner 0f5e694682 Added GET /sm/list/all/0 endpoint 2025-07-01 18:45:53 -07:00
Chris Troutner 615638d705 Timer controller checks creates new social media DB models 2025-07-01 18:23:09 -07:00
Chris Troutner 9b46bbd7bb feat(smAccount): Tracking content creator social media accounts 2025-07-01 15:47:44 -07:00
Chris Troutner 1b58bda5a7 Merge pull request #99 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream ipfs-service-provider
2025-06-29 07:42:53 -07:00
Chris Troutner c1f15a4079 Fixing merge conflicts, commenting out broken tests 2025-06-29 07:38:58 -07:00
Chris Troutner 18ea03a4d6 Merge pull request #178 from Permissionless-Software-Foundation/dh-test-coverage
feat(tests): Increased unit test coverage
2025-06-28 07:28:04 -07:00
Daniel Gonzalez 3d911d1062 feat(tests): Increased unit test coverage 2025-06-27 20:11:27 -04:00
Chris Troutner 935ab94159 Merge pull request #98 from Permissionless-Software-Foundation/ct-unstable
fix(docker): Improved routing for dex-ui docker container
2025-06-27 09:10:45 -07:00
Chris Troutner 18dad062f6 fix(docker): Improved routing for dex-ui docker container 2025-06-27 09:09:14 -07:00
Chris Troutner 1e8fc1825c Merge pull request #97 from Permissionless-Software-Foundation/ct-unstable
Updating README
2025-06-13 12:13:46 -07:00
Chris Troutner f64795706d Editing README 2025-06-13 08:58:11 -07:00
Chris Troutner 67a9914a8b Editing README 2025-06-13 08:52:54 -07:00
Chris Troutner b01d15a67d Editing README 2025-06-13 08:52:06 -07:00
Chris Troutner e4c9d15e60 fix(README): Updating installation instructions 2025-06-13 08:51:29 -07:00
Chris Troutner 3c00ce6644 Merge pull request #96 from Permissionless-Software-Foundation/ct-unstable
fix(error): Removing confusing non-error error
2025-06-06 11:48:14 -07:00
Chris Troutner e9d1b763fd fix(error): Removing confusing non-error error 2025-06-06 11:46:43 -07:00
Chris Troutner cf88fadfdc Merge pull request #177 from Permissionless-Software-Foundation/ct-unstable
Usage: back up to DB to track usage over restarts.
2025-06-05 18:27:06 -07:00
Chris Troutner 4977ae7003 Increasing backup to 10 minutes 2025-06-05 18:25:45 -07:00
Chris Troutner f0f6970a08 Added timer controllers for managing usage stats 2025-06-05 18:25:10 -07:00
Chris Troutner fcb4acb11d feat(usage-use-cases.js): Added functions for persisting usage data to database 2025-06-04 12:26:03 -07:00
Chris Troutner 98b5e300c0 Merge pull request #95 from Permissionless-Software-Foundation/ct-unstable
Using User wallet to create and settle Orders, not app wallet.
2025-06-01 18:49:24 -07:00
Chris Troutner 2ea062ae2b Removing e2e tests from unit tests 2025-06-01 18:47:15 -07:00
Chris Troutner c6e5eb9dc9 Updating package-lock 2025-06-01 18:42:16 -07:00
Chris Troutner 46a19b0fb1 linting 2025-06-01 18:34:14 -07:00
Chris Troutner 40cf8cc8de got tests passing again 2025-06-01 18:34:01 -07:00
Chris Troutner ddc70d6fbc feat(completeTx()): Uses user wallet to complete sale 2025-06-01 18:18:34 -07:00
Chris Troutner 625b8895b0 feat(moveTokensFromCustomWallet()): Using user wallet, not system wallet to stash new Offer UTXOs 2025-06-01 17:23:35 -07:00
Chris Troutner 5fd8d8fcd1 Adding llm-bch-dex.md for AI consuption 2025-06-01 16:18:04 -07:00
Chris Troutner 28e012feb6 Merge pull request #176 from Permissionless-Software-Foundation/e2e-tests
Separating E2E tests from Unit tests
2025-05-30 17:18:40 -07:00
Chris Troutner 958c637c24 Commenting out failing test 2025-05-30 17:17:22 -07:00
Daniel Gonzalez 34381e6e93 Improved e2e tests 2025-05-30 11:53:57 -04:00
Daniel Gonzalez 8843b3e2b0 Separate e2e tests 2025-05-28 20:52:25 -04:00
Chris Troutner 3f91653d23 feat(e2e): separating e2e from unit tests 2025-05-23 07:26:51 -07:00
Chris Troutner 153ebc204a Merge pull request #94 from Permissionless-Software-Foundation/ct-unstable
feat(NOSTR_TOPIC): Changing default nostr topic
2025-05-19 15:40:19 -07:00
Chris Troutner 589c16550c Merge branch 'master' into ct-unstable 2025-05-19 15:37:01 -07:00
Chris Troutner 49063456b6 feat(NOSTR_TOPIC): Changing default nostr topic 2025-05-19 15:34:36 -07:00
Chris Troutner 87eb804cd1 Merge pull request #93 from Permissionless-Software-Foundation/ct-unstable
fix(util): Adding scripts for creating new users
2025-05-19 13:49:31 -07:00
Chris Troutner 23d5118b52 fix(util): Adding scripts for creating new users 2025-05-19 13:48:15 -07:00
Chris Troutner 94eb342435 Merge pull request #92 from Permissionless-Software-Foundation/ct-unstable
Syncing admin settings with upstream ipfs-service-provider
2025-05-19 13:18:38 -07:00
Chris Troutner ac326b8e34 fix(adapter/admin.js): Adding mnemonic for user 2025-05-19 13:16:32 -07:00
Chris Troutner 164a38ad20 linting 2025-05-19 13:11:23 -07:00
Chris Troutner 3300e0fc80 fix(config): Fixing merge conflict in common.js 2025-05-19 13:11:13 -07:00
Chris Troutner 3a4569c33f Merge pull request #175 from Permissionless-Software-Foundation/dh-env-var-admin
feat(admin): added env var setting so only admin can create new users
2025-05-19 13:06:40 -07:00
Daniel Gonzalez 05a0620663 feat(admin): added env var setting so only admin can create new users 2025-05-19 12:17:09 -04:00
Chris Troutner af83c06b43 Merge pull request #174 from Permissionless-Software-Foundation/ct-unstable
fix(usage): Debugging issue with usage tracking
2025-05-18 15:37:46 -07:00
Chris Troutner e6317c3282 fix(usage): Debugging issue with usage tracking 2025-05-18 15:36:32 -07:00
Chris Troutner ddce40bb52 Merge pull request #91 from Permissionless-Software-Foundation/ct-unstable
Production improvements
2025-05-16 07:54:30 -07:00
Chris Troutner 746f9a3488 fix(production): Adding script to get users in prod env 2025-05-16 07:52:58 -07:00
Chris Troutner a611580f48 Turning off CI option 2025-05-15 19:18:41 -07:00
Chris Troutner 07c671ede4 fix(production): Using v3 UI 2025-05-15 17:58:29 -07:00
Chris Troutner 7c8d73810d linting 2025-05-15 07:43:24 -07:00
Chris Troutner 9829e8581c Adding sweep-funds2 script 2025-05-15 07:43:13 -07:00
Chris Troutner a12aa5609d Merge pull request #90 from Permissionless-Software-Foundation/dh-refactor-counteroffer
feat(order): refactored acceptCounterOffer()
2025-05-15 06:01:47 -07:00
Daniel Gonzalez abf834c5a8 feat(order): refactored acceptCounterOffer() 2025-05-14 20:33:25 -04:00
Chris Troutner 7ae557d143 Merge pull request #89 from Permissionless-Software-Foundation/ct-unstable
fix(debug): Adding debugging conosole.logs to bch-dex
2025-05-09 13:17:04 -07:00
Chris Troutner 5f4b0c8da3 fix(debug): Adding debugging conosole.logs to bch-dex 2025-05-09 13:15:39 -07:00
Chris Troutner 6ef197dae3 Merge pull request #88 from Permissionless-Software-Foundation/dh-create-order
feat(order): Modify createOrder() to work with the user model
2025-05-06 18:45:09 -07:00
Daniel Gonzalez fafa7eecd9 feat(order): Modify createOrder() to work with the user model 2025-05-05 18:26:04 -04:00
Chris Troutner 5ede934be5 Merge pull request #87 from Permissionless-Software-Foundation/ct-unstable
fix(wallet adapter): Binding 'this' object to class methods
2025-04-26 14:36:28 -07:00
Chris Troutner 8d036ea9ba fix(wallet adapter): Binding 'this' object to class methods 2025-04-26 14:30:54 -07:00
Chris Troutner bc6acc846a Merge pull request #86 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream
2025-04-26 14:13:25 -07:00
Chris Troutner afa050261f Merge remote-tracking branch 'upstream/master' into merge-upstream 2025-04-26 14:11:39 -07:00
Chris Troutner 1715d064fb Merge pull request #85 from Permissionless-Software-Foundation/dh-user-mnemonic
feat(user): Added mnemonic to user model
2025-04-26 12:35:37 -07:00
Daniel Gonzalez cf2f2af90f feat(user): Added mnemonic to user model 2025-04-24 12:45:55 -04:00
Chris Troutner 354321c956 Merge pull request #173 from Permissionless-Software-Foundation/ct-unstable
fix(wallet.adapter): Binding this object to all methods
2025-04-22 19:07:06 -07:00
Chris Troutner af2572a493 fix(wallet.adapter): Binding this object to all methods 2025-04-22 18:58:03 -07:00
Chris Troutner 1a5dadbf35 Merge pull request #84 from Permissionless-Software-Foundation/ct-unstable
fix(README): Updating README details
2025-04-11 16:18:37 -07:00
Chris Troutner ced7a60f14 More README edits 2025-04-11 16:15:29 -07:00
Chris Troutner 043c4a8b2b fix(README): Updating README details 2025-04-11 16:09:31 -07:00
Chris Troutner f9b4bf06ab Merge pull request #83 from Permissionless-Software-Foundation/ct-unstable
Syncing with upstream repo ipfs-service-provider
2025-04-10 18:06:27 -07:00
Chris Troutner 8048182f46 fixed tests 2025-04-10 18:04:51 -07:00
Chris Troutner cb4d1d3eba fix(ipfs-service-provider): Syncing with upstream repo 2025-04-10 17:57:44 -07:00
Chris Troutner 3209f96e7f fix(createOffer()): Removing broken NSFW check 2025-03-08 09:09:38 -08:00
Chris Troutner c65d662630 Merge pull request #172 from Permissionless-Software-Foundation/ct-unstable
fix(config): Added webRTC port & connection preference reporting
2025-02-23 10:37:30 -07:00
Chris Troutner 1b99f8afa1 fix(config): Added webRTC port & connection preference reporting 2025-02-23 10:34:00 -07:00
Chris Troutner c6f0a1317c Merge pull request #171 from Permissionless-Software-Foundation/ct-unstable
fix(prod): Switching Docker container to master branch
2025-02-23 09:57:41 -07:00
Chris Troutner fad5f8e23d fix(prod): Switching Docker container to master branch 2025-02-23 09:56:13 -07:00
Chris Troutner ec6e0f5e46 Merge pull request #170 from Permissionless-Software-Foundation/helia-v5
Helia v5
2025-02-23 09:46:10 -07:00
Chris Troutner f1caef2c0d fix(prod): Adding connection prefrence to production startup script 2025-02-23 09:41:55 -07:00
Chris Troutner 55bf9424f9 fix(libp2p): Updating deps to prevent AbortError issue 2702 2025-02-23 09:01:17 -07:00
Chris Troutner aa100c97ed fix(helia-coord): Updating to v1.7.1 2025-02-20 22:41:25 -07:00
Chris Troutner 9fa5eafe95 removing relay advetising deprecated setting 2025-02-20 20:51:40 -07:00
Chris Troutner 4d9e948e30 fix(helia): Manually porting @achingbrian PR to v5 branch 2025-02-20 20:51:09 -07:00
Chris Troutner e5cdf28e34 Catching and reporting AbortError race condition 2025-02-20 19:03:45 -07:00
Chris Troutner 1d82b36105 fix(helia): Updated to 1.7.0 with webRTC and circuit relay enabled 2025-02-16 15:27:43 -07:00
Chris Troutner b910cd1e6d fix(webRTC): Adding /p2p-circuit multiaddr + ping service 2025-02-16 09:29:18 -07:00
Chris Troutner 536861ba99 Re-enabled webRTC and circuit relays 2025-02-14 12:53:12 -07:00
Chris Troutner ef030e3dc6 fix(libp2p): Updated to v2.6.2 with node.js webrtc fix 2025-02-14 09:52:09 -07:00
Chris Troutner 3da46c3d6f Using helia-v5 branch when building docker container 2025-02-12 16:22:44 -07:00
Chris Troutner 82f5552836 feat(helia v5): Generating and saving new private key 2025-02-12 14:58:11 -07:00
Chris Troutner 4d25681c51 fix(helia v5): Changes to get node to connect to other nodes with helia v5 2025-02-12 09:22:36 -07:00
Chris Troutner 98a1631498 Merge pull request #167 from Permissionless-Software-Foundation/ct-unstable
Removing logging of usage middleware
2025-01-27 18:55:29 -07:00
Chris Troutner 8dce8cbf8d Removing logging of usage middleware 2025-01-27 18:53:17 -07:00
Chris Troutner 0b3049138b Merge pull request #166 from Permissionless-Software-Foundation/dh-usage-tests
feat(tests): Added Usage Unit Tests
2025-01-17 05:32:27 -07:00
Daniel Gonzalez d7136583b8 feat(tests): Added Usage Unit Tests 2025-01-15 17:59:09 -04:00
Chris Troutner 19b13e5d6e Updating docker-compose .env 2024-12-29 09:23:05 -08:00
Chris Troutner c92ffac645 Merge pull request #82 from Permissionless-Software-Foundation/ct-unstable
Refactored to use Nostr instead of P2WDB
2024-12-29 09:16:40 -08:00
Chris Troutner 216f0b6910 fix(tests): fixed broken tests 2024-12-29 09:14:49 -08:00
Chris Troutner c31bf96986 fix(timers): Disabling unneeded timer controller designed for P2WDB issues. 2024-12-28 13:59:27 -08:00
Chris Troutner 7f8ef994a7 feat(Docker): Updating docker containers 2024-12-16 17:53:36 -08:00
Chris Troutner 72ba1c48c2 Can create and take offers using Nostr 2024-12-16 05:29:03 -08:00
Chris Troutner c721da1562 Merge pull request #165 from Permissionless-Software-Foundation/usage
Usage
2024-12-07 21:28:57 -08:00
Chris Troutner 3606a2d31b code cleanup 2024-12-07 21:27:41 -08:00
Chris Troutner 764905b041 fix(usage): Adding endpoints to analyize 24 hour usage of the REST API 2024-12-07 21:25:10 -08:00
Chris Troutner 556c6ec522 fix(usage): Adding Usage REST API 2024-12-07 20:48:08 -08:00
Chris Troutner 2a78145170 Debugging counter-offer 2024-12-07 20:33:25 -08:00
Chris Troutner 04395b70a6 Syncing with ct-unstable. Fixing merge conflict 2024-12-07 18:08:25 -08:00
Chris Troutner d15fee7c7a Merge pull request #81 from Permissionless-Software-Foundation/ct-unstable
fix(offers): Fixed workflow for creating offers from Nostr posts
2024-12-07 10:56:11 -08:00
Chris Troutner 9437b8fb80 fix(offers): Fixed workflow for creating offers from Nostr posts 2024-12-07 10:54:34 -08:00
Chris Troutner b611663379 Merge pull request #80 from Permissionless-Software-Foundation/dh-create-offr-nostr
feat(offer): Trigger createOffer() from Nostr topic
2024-12-06 09:43:58 -08:00
Chris Troutner 03b7ed980a fix(IPFS): Updating IPFS gateway for getting token data 2024-12-06 09:35:33 -08:00
Chris Troutner bf74d50bb0 linting 2024-12-04 17:12:40 -08:00
Chris Troutner 003480bcb5 fix(offer): Refactored _this in rest-api controller 2024-12-04 17:12:26 -08:00
Daniel Gonzalez 9dbd18d0d6 Increased test coverage 2024-12-04 06:51:28 -04:00
Chris Troutner b3ba7f03e4 Commenting out POST /offer/ webhook endpoint 2024-12-03 09:28:52 -08:00
Daniel Gonzalez 0a8bb52241 feat(offer): Trigger createOffer() from Nostr topic 2024-12-02 22:19:54 -04:00
Chris Troutner 5eeafd7ead Merge pull request #79 from Permissionless-Software-Foundation/dh-create-order-fix
fix(order): Refactored createOrder() to use Nostr
2024-11-29 04:02:53 -08:00
Chris Troutner df146382cb Reducing info posted to Nostr on Order 2024-11-29 04:00:10 -08:00
Daniel Gonzalez af8a7c3ff3 fix(order): Refactored createOrder() to use Nostr 2024-11-28 12:37:47 -04:00
Chris Troutner ed6d2af23c Merge pull request #77 from Permissionless-Software-Foundation/dh-nostr-adapter
feat(nostr): Added Nostr adapter
2024-11-26 04:25:26 -08:00
Chris Troutner 4f0ad71fff Making slight tweeks to common config 2024-11-26 04:23:54 -08:00
Daniel Gonzalez 6871380288 feat(nostr): Added Nostr adapter 2024-11-25 14:11:21 -04:00
Chris Troutner 285456656c Merge pull request #75 from Permissionless-Software-Foundation/ct-unstable
Synced with ipfs-service-provider
2024-11-22 10:56:21 -08:00
Chris Troutner 179d8f1656 Merged with ipfs-service-provider. Mitigated several conflict errors 2024-11-22 10:54:21 -08:00
Chris Troutner 94241c08ce Updating to v3.0 2024-11-22 09:51:10 -08:00
Chris Troutner abd4e872bf Merge pull request #73 from Permissionless-Software-Foundation/ct-unstable
Merging changes before branching
2024-11-22 09:48:40 -08:00
Chris Troutner 81521151f4 Merge pull request #164 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs): Disabling webRTC again
2024-09-18 12:02:22 -07:00
Chris Troutner 5a819c1d9c linting 2024-09-18 12:01:10 -07:00
Chris Troutner f2f3208fda fix(ipfs): Disabling webRTC again 2024-09-18 12:00:56 -07:00
Chris Troutner 7fe888cdda Merge pull request #163 from Permissionless-Software-Foundation/ct-unstable
fix(webRTC): Renabling webRTC & circuit relays
2024-09-17 07:50:59 -07:00
Chris Troutner db3f1bf2cf fix(webRTC): Renabling webRTC & circuit relays 2024-09-17 07:49:43 -07:00
Chris Troutner 5f64257dca Merge pull request #162 from Permissionless-Software-Foundation/ct-unstable
fix(webrtc): Disabling webRTC due to bug
2024-09-13 10:07:53 -07:00
Chris Troutner 2fbdccb883 fix(webrtc): Disabling webRTC due to bug 2024-09-13 10:06:19 -07:00
Chris Troutner ecc10afac3 linting 2024-09-13 10:05:05 -07:00
Chris Troutner 4fc54aa9c5 Merge pull request #161 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.14
2024-09-12 16:59:23 -07:00
Chris Troutner 2bd7f0c45c Merge branch 'master' into ct-unstable 2024-09-12 16:57:00 -07:00
Chris Troutner 900423339e fix(helia-coord): Updating to v1.5.14 2024-09-12 16:56:44 -07:00
Chris Troutner d907afa6bb Merge pull request #160 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.13
2024-09-12 16:48:03 -07:00
Chris Troutner 9b116d82b7 fix(helia-coord): Updating to v1.5.13 2024-09-12 16:42:43 -07:00
Chris Troutner 1d1bf50ee3 Merge pull request #159 from Permissionless-Software-Foundation/ct-unstable
Updating dependencies
2024-09-09 18:15:03 -07:00
Chris Troutner 5002004697 fix(minimal-slp-wallet): Upgraded to v5.12.0 2024-09-09 18:13:36 -07:00
Chris Troutner f311a3b9c6 fix(webRTC): Upgrading to new package, adding required config 2024-08-29 11:00:07 -07:00
Chris Troutner fe91f4b875 Merge pull request #158 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.12
2024-08-23 10:30:27 -07:00
Chris Troutner 2d9a2b811c fix(helia-coord): Updating to v1.5.12 2024-08-23 10:24:21 -07:00
Chris Troutner 514b65ed24 Merge pull request #157 from Permissionless-Software-Foundation/ct-unstable
fix(minimal-slp-wallet): Updated to v5.11.3
2024-08-23 07:02:19 -07:00
Chris Troutner 0c7f3088a8 fix(minimal-slp-wallet): Updated to v5.11.3 2024-08-23 06:59:58 -07:00
Chris Troutner 12cab02300 Merge pull request #156 from Permissionless-Software-Foundation/ct-unstable
Upgrading helia to v4.2.5
2024-08-20 07:53:53 -07:00
Chris Troutner 04a908261a Updating requirements in README 2024-08-20 07:32:30 -07:00
Chris Troutner 83898742f1 fix(webRTC): Falling back from v4.1.7 to v4.0.34 2024-08-19 07:35:59 -07:00
Chris Troutner 1795918719 Using node.js v20 for docker container 2024-08-19 06:33:31 -07:00
Chris Troutner 6894217078 fix(production): Limiting docker to 1GB of memory 2024-08-19 06:29:16 -07:00
Chris Troutner 3ffbed27b5 fix(helia-coord): Got running with v1.5.11 2024-08-18 08:41:28 -07:00
Chris Troutner b372e3d260 Merge pull request #155 from Permissionless-Software-Foundation/ct-unstable
fix(Mongo): Skipping some tests when noMongo is true
2024-06-09 09:12:31 -07:00
Chris Troutner 707b385b8a linting 2024-06-09 09:11:09 -07:00
Chris Troutner 6cb140a491 fix(Mongo): Skipping some tests when noMongo is true 2024-06-09 09:10:55 -07:00
Chris Troutner c43ee8c25a Merge pull request #154 from Permissionless-Software-Foundation/ct-unstable
feat(Mongo): Config variable can disable MongoDB usage and dependency in Docker
2024-06-09 08:38:00 -07:00
Chris Troutner 0a7ec11510 feat(Mongo): Config variable can disable MongoDB usage and dependency in Docker 2024-06-09 08:28:40 -07:00
Chris Troutner f7aaba71d1 Merge pull request #153 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.8
2024-05-04 09:38:28 -07:00
Chris Troutner 418a2e2ad2 fix(helia-coord): Updating to v1.5.8 2024-05-04 09:37:10 -07:00
Chris Troutner a1023e8224 Merge pull request #152 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Setting memory limit at 4GB
2024-04-27 13:20:20 -07:00
Chris Troutner a32ed63d47 fix(Docker): Setting memory limit at 4GB 2024-04-27 13:17:57 -07:00
Chris Troutner 934fb46ffc Merge pull request #151 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Removing Volume statement
2024-04-26 14:40:41 -07:00
Chris Troutner b27136d03b fix(Docker): Removing Volume statement 2024-04-26 14:39:18 -07:00
Chris Troutner 064ac36dc7 linting 2024-04-26 14:38:17 -07:00
Chris Troutner 5e2cf86f4b Checking in code before switching branches 2024-04-26 14:38:03 -07:00
Chris Troutner 425d2ada65 Binding 'this' object to some subfunctions 2024-04-20 07:14:17 -07:00
Chris Troutner 60a3d67c0b Merge pull request #150 from Permissionless-Software-Foundation/ct-unstable
fix(config): web2Api can be set with env var
2024-04-13 07:11:20 -07:00
Chris Troutner a03c5d8c91 fix(config): web2Api can be set with env var 2024-04-13 07:09:45 -07:00
Chris Troutner bcc90da49b Merge pull request #149 from Permissionless-Software-Foundation/ct-unstable
fix(config): Adding web2Api property to jsonLd config
2024-04-13 07:04:02 -07:00
Chris Troutner b473a3803c fix(config): Adding web2Api property to jsonLd config 2024-04-13 07:02:32 -07:00
Chris Troutner 9614f186b4 Merge pull request #148 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.7
2024-04-12 08:32:13 -07:00
Chris Troutner 0390f2d956 fix(helia-coord): Updating to v1.5.7 2024-04-12 08:30:14 -07:00
Chris Troutner ba5ead998d Merge pull request #147 from Permissionless-Software-Foundation/ct-unstable
fix(docker): Tweaking some defaults
2024-04-03 06:02:17 -07:00
Chris Troutner 409a0f1049 fix(docker): Tweaking some defaults 2024-04-03 05:54:24 -07:00
Chris Troutner bd2f27bb64 Merge pull request #146 from Permissionless-Software-Foundation/ct-unstable
fix(incrementNextAddress): fixing bug
2024-03-29 12:44:43 -07:00
Chris Troutner 55d8d131cf Removing commented code 2024-03-29 12:43:15 -07:00
Chris Troutner 5a6e3247e8 fix(incrementNextAddress): fixing bug 2024-03-29 12:43:02 -07:00
Chris Troutner 2a522775eb Adding wallet.json to gitignore 2024-03-29 11:36:58 -07:00
Chris Troutner d677c83795 Removing wallet.json file 2024-03-29 11:36:35 -07:00
Chris Troutner 7cffd9f377 Merge pull request #145 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.6, auto-retry of bootstrap list
2024-03-24 07:44:46 -07:00
Chris Troutner 0a406cb9a4 fix(helia-coord): Updating to v1.5.6, auto-retry of bootstrap list 2024-03-24 07:43:34 -07:00
Chris Troutner f172a51016 Merge pull request #144 from Permissionless-Software-Foundation/ct-unstable
Integrating minimal-slp-wallet. Wallet creation at startup
2024-03-23 17:46:55 -07:00
Chris Troutner 00d53659b1 Updating README 2024-03-23 17:44:41 -07:00
Chris Troutner b0797d92f5 Increased test coverage to 100% 2024-03-23 17:42:59 -07:00
Chris Troutner 48484ed7af Fixing typo with web3 wallet 2024-03-23 17:23:07 -07:00
Chris Troutner 6a4aadde8a Refactoring config file 2024-03-23 17:14:26 -07:00
Chris Troutner 79e3aba26e Adding default startup wallet 2024-03-23 16:18:10 -07:00
Chris Troutner bf61359bc7 feat(wallet): Added adapter library for consuming minimal-slp-wallet 2024-03-23 11:16:45 -07:00
Chris Troutner a79d152993 Merge pull request #143 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.5
2024-03-12 08:32:05 -07:00
Chris Troutner ce7c3a0e2f fix(helia-coord): Updating to v1.5.5 2024-03-12 08:30:32 -07:00
Chris Troutner e6b0580209 Merge pull request #142 from Permissionless-Software-Foundation/ct-unstable
Back to 100% unit test coverage
2024-03-10 07:44:19 -07:00
Chris Troutner 1f3c639100 fix(tests): Back to 100% unit test coverage 2024-03-10 07:43:06 -07:00
Chris Troutner e6efc4f49b Prototyped /ipfs/download 2024-03-07 21:27:06 -08:00
Chris Troutner dbbd321029 fix(/ipfs/node): Added unit tests for API controller 2024-03-07 18:55:09 -08:00
Chris Troutner 439dbc54ae fix(ipfs): Increased adapter to 100% unit test coverage 2024-03-07 10:22:25 -08:00
Chris Troutner 547e59f52f fix(Docker): Adding .env file 2024-03-04 10:47:08 -08:00
Chris Troutner a0d385de73 Merge pull request #141 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.4, fixes minor bug
2024-03-03 06:46:38 -08:00
Chris Troutner bec136009f fix(helia-coord): Updating to v1.5.4, fixes minor bug 2024-03-03 06:44:24 -08:00
Chris Troutner 98b7fa7734 Merge pull request #140 from Permissionless-Software-Foundation/ct-unstable
Removing Circuit Relay for nodes configured for direct connection
2024-02-05 06:41:01 -08:00
Chris Troutner d7bb476858 fix(circuit-relay): Removing CR transport if node is configured for direct connection 2024-02-05 06:39:01 -08:00
Chris Troutner 18c71c383f fix(webrtc): Upgrading from v3.2.11 to v4.0.17 2024-02-05 06:26:01 -08:00
Chris Troutner de85a933c4 fix(webrtc): Adding @libp2p/webrtc 2024-02-04 20:43:34 -08:00
Chris Troutner 16247372f1 linting 2024-02-04 20:35:34 -08:00
Chris Troutner c6c8b6418c Prototyping download REST API 2024-02-04 20:35:22 -08:00
Chris Troutner 10c4e7380f fix(webrtc): Removing webrtc library 2024-01-31 09:38:18 -08:00
Chris Troutner 4933dce472 Merge pull request #139 from Permissionless-Software-Foundation/ct-unstable
fix(webRTC): Explicitly adding @libp2p/webrtc package
2024-01-31 08:50:43 -08:00
Chris Troutner 80c2822716 fix(webRTC): Explicitly adding @libp2p/webrtc package 2024-01-31 08:42:38 -08:00
Chris Troutner f48ff9a67e Merge pull request #138 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.5.3
2024-01-30 19:45:57 -08:00
Chris Troutner 4bd2385359 fix(helia-coord): Updating to v1.5.3 2024-01-30 19:44:29 -08:00
Chris Troutner be71492773 Merge pull request #137 from Permissionless-Software-Foundation/ct-unstable
fix(createNode()): Updating RSA to Ed25519 keys
2024-01-29 07:58:49 -08:00
Chris Troutner bcb70edf2b fix(createNode()): Updating RSA to Ed25519 keys 2024-01-29 07:57:32 -08:00
Chris Troutner 07071032bc Merge pull request #136 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Updated new node.js installation
2024-01-28 19:57:45 -08:00
Chris Troutner f476e119a2 fix(Docker): Updated new node.js installation 2024-01-28 19:54:21 -08:00
Chris Troutner 0fed232428 Merge pull request #135 from Permissionless-Software-Foundation/ct-unstable
fix(Docker): Switching to ct-unstable branch
2024-01-28 19:42:02 -08:00
Chris Troutner 0273510485 fix(Docker): Switching to ct-unstable branch 2024-01-28 19:38:42 -08:00
Chris Troutner a62dee37f4 Merge pull request #134 from Permissionless-Software-Foundation/v1.0.10
libp2p v1.2.1
2024-01-28 19:36:24 -08:00
Chris Troutner 8d6af3e1b7 fix(helia-coord): Updating to v1.4.3 2024-01-28 15:17:01 -08:00
Chris Troutner ee0f2da641 fix(docker): Building container rather than using image 2024-01-28 15:05:34 -08:00
Chris Troutner 44499d9652 Updating Dockerfile to use v1.0.10 branch 2024-01-28 15:00:30 -08:00
Chris Troutner 365a5dcd4b Updated to libp2p 1.2.1 2024-01-28 14:59:25 -08:00
Chris Troutner 7f5b456018 linting 2024-01-28 14:13:42 -08:00
Chris Troutner c7217486bd fix(getSeed()): Generates seed for IPFS private key 2024-01-28 12:49:18 -08:00
Chris Troutner 050141d31e Creating todo for seed 2024-01-28 08:22:55 -08:00
Chris Troutner b697f37199 fix(keys): Creating persistent keystore for libp2p v1.0.x 2024-01-28 08:19:38 -08:00
Chris Troutner ceb58caba1 linting 2024-01-28 08:04:02 -08:00
Chris Troutner 2724b500ee Got keychain working for first time 2024-01-28 08:03:41 -08:00
Chris Troutner 1b84e7beb8 Merge pull request #133 from Permissionless-Software-Foundation/v0.46.21
fix(pubsub): Removing chat handler causing screen spam
2024-01-27 17:48:29 -08:00
Chris Troutner 383900d8b5 fix(pubsub): Removing chat handler causing screen spam 2024-01-27 17:45:23 -08:00
Chris Troutner ba806d86b5 Merge pull request #132 from Permissionless-Software-Foundation/v0.46.21
Updated to helia-coord v1.4.1
2024-01-27 15:02:57 -08:00
Chris Troutner 4d7c7350a0 linting 2024-01-27 15:01:01 -08:00
Chris Troutner adc6500aed Adding config options for V2 Circuit Relay server 2024-01-27 13:17:34 -08:00
Chris Troutner eb9fce73eb Updated helia-coord to v1.4.1 2024-01-27 10:45:44 -08:00
Chris Troutner ec3ed165e4 feat(GET /ipfs/node): Retrieves thisNode object 2024-01-27 09:26:50 -08:00
Chris Troutner d41d20cc81 prototyping keychain example 2024-01-26 19:52:55 -08:00
Chris Troutner 5f965e7146 Switching back to libp2p v0.46.21 2024-01-11 12:26:16 -08:00
Chris Troutner f382bb4578 Merge pull request #131 from Permissionless-Software-Foundation/ct-unstable
Helia v1.2.24 - recommended peers from gist
2023-12-18 20:56:26 -08:00
Chris Troutner ba37f87f80 fix(helia-coord): updating to v1.2.24, adding recommended peers 2023-12-18 20:55:25 -08:00
Chris Troutner eaa9c23f55 Updating helia-coord ot v1.2.23 2023-12-16 06:42:12 -08:00
Chris Troutner ac3730dbd9 Merge pull request #130 from Permissionless-Software-Foundation/libp2p-v1
Libp2p v1
2023-12-13 19:46:43 -08:00
Chris Troutner 581b8eb626 fix(libp2p): Updating to v1.0.10 2023-12-13 19:44:58 -08:00
Chris Troutner cd4ec2711e Updated circuit-relay library 2023-12-06 10:32:59 -08:00
Chris Troutner e9e3dbad0d Updated to libp2p 1.0.9, added @libp2p/identify 2023-12-06 10:20:48 -08:00
Chris Troutner 295b361431 Merge pull request #129 from Permissionless-Software-Foundation/ct-unstable
Updating helia-coord to v1.2.21
2023-12-04 08:53:58 -08:00
Chris Troutner ad60df9d51 Updating helia-coord to v1.2.21 2023-12-04 08:52:16 -08:00
Chris Troutner 6ae635c21d Merge pull request #128 from Permissionless-Software-Foundation/ct-unstable
fix(bootstrap): Loading bootstrap nodes from gist only
2023-12-04 07:27:14 -08:00
Chris Troutner 61807a2f3d fix(bootstrap): Loading bootstrap nodes from gist only 2023-12-04 07:25:32 -08:00
Chris Troutner 16ba5a8980 Merge pull request #127 from Permissionless-Software-Foundation/ct-unstable
fix(deps): Updating helia-coord and helia
2023-12-03 16:43:34 -08:00
Chris Troutner 99b817549e fix(deps): Updating helia-coord and helia 2023-12-03 16:42:32 -08:00
Chris Troutner 60a8c335c8 Merge pull request #126 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updated to v1.2.18
2023-12-03 09:42:52 -08:00
Chris Troutner 1783768c32 fix(helia-coord): Updated to v1.2.18 2023-12-03 09:41:42 -08:00
Chris Troutner b3630eaa06 Merge pull request #125 from Permissionless-Software-Foundation/ct-unstable
fix(helia): Upgrade to v0.46.21
2023-12-03 09:04:01 -08:00
Chris Troutner d691016911 fix(helia): Upgrade to v0.46.21 2023-12-03 09:01:03 -08:00
Chris Troutner 6506aead8c Merge pull request #124 from Permissionless-Software-Foundation/ct-unstable
fix(webrtc): Adding webrtc as a transport
2023-12-03 07:37:11 -08:00
Chris Troutner ddfc83d026 fix(webrtc): Adding webrtc as a transport 2023-12-03 07:35:47 -08:00
Chris Troutner fe42c1e0e3 Merge pull request #123 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs): Autodetecting ip addr and adding to multiaddr
2023-12-03 07:30:15 -08:00
Chris Troutner 928a211a63 fix(ipfs): Autodetecting ip addr and adding to multiaddr 2023-12-03 07:28:31 -08:00
Chris Troutner 8bff69a4ca Merge pull request #122 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.2.17
2023-12-02 17:12:47 -08:00
Chris Troutner 5fced7d501 fix(helia-coord): Updating to v1.2.17 2023-12-02 17:11:05 -08:00
Chris Troutner 7cdf622ad6 Merge pull request #121 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating helida-coord to v1.2.16
2023-12-02 14:13:43 -08:00
Chris Troutner afd8ceac7b fix(helia-coord): Updating helida-coord to v1.2.16 2023-12-02 14:12:32 -08:00
Chris Troutner 5d63db3662 Merge pull request #120 from Permissionless-Software-Foundation/ct-unstable
fix(helia-coord): Updating to v1.2.5
2023-11-04 08:47:34 -07:00
Chris Troutner 9f6cd7dbf1 fix(helia-coord): Updating to v1.2.5 2023-11-04 08:45:46 -07:00
Chris Troutner 0725aa3ada Merge pull request #119 from Permissionless-Software-Foundation/ct-unstable
fix(peers): Adding connectionAddr to /ipfs/peers
2023-11-03 14:42:28 -07:00
Chris Troutner 3eb88d4076 fix(peers): Adding connectionAddr to /ipfs/peers 2023-11-03 14:33:58 -07:00
Chris Troutner 1c61f150b1 fix(v1 relays): Discerning between v1 and v2 circuit relays 2023-11-03 12:07:43 -07:00
Chris Troutner 4c74b287d8 Merge pull request #118 from Permissionless-Software-Foundation/ct-unstable
feat(/ipfs/connect): Adding REST API endpoint to connect to an IPFS peer
2023-11-01 14:18:51 -07:00
Chris Troutner 264d762f74 feat(/ipfs/connect): Adding REST API endpoint to connect to an IPFS peer 2023-11-01 14:17:24 -07:00
Chris Troutner 3c02461c6b Merge pull request #117 from Permissionless-Software-Foundation/ct-unstable
fix(circuit relay): Adding ability to creating and consume circuit re…
2023-10-29 16:41:52 -07:00
Chris Troutner a30f2be51f fix(circuit relay): Adding ability to creating and consume circuit relays 2023-10-29 16:39:35 -07:00
Chris Troutner 0690a9fb65 Merge pull request #116 from Permissionless-Software-Foundation/ct-unstable
fix(README): Adding link to video demo
2023-10-28 13:57:55 -07:00
Chris Troutner 4fa103765e Merge branch 'master' into ct-unstable 2023-10-28 13:51:35 -07:00
Chris Troutner b0d6ce40a9 fix(README): Adding link to video demo 2023-10-28 13:50:46 -07:00
Chris Troutner c57a3d935b Merge pull request #115 from Permissionless-Software-Foundation/ct-unstable
Updating Docker container for production
2023-10-28 11:32:24 -07:00
Chris Troutner 82b5943601 Adding collaborative coinjoin project to README 2023-10-28 11:30:57 -07:00
Chris Troutner 41b76967f5 Adding production section back to README 2023-10-28 11:27:06 -07:00
Chris Troutner e4c2ee44c1 fix(production): Updating docker container 2023-10-28 11:24:40 -07:00
Chris Troutner c05f022995 Bumping to v5.0.0 2023-10-28 10:32:45 -07:00
Chris Troutner 7723c3a201 Merge pull request #114 from Permissionless-Software-Foundation/helia
Helia refactor. v4.0.0
2023-10-28 10:24:20 -07:00
Chris Troutner 8e338818ca Updating READMe 2023-10-28 10:19:59 -07:00
Chris Troutner ea51aa420c Updating README 2023-10-28 10:14:42 -07:00
Chris Troutner bc410678b4 Updating README and package.json 2023-10-28 10:11:04 -07:00
Chris Troutner a42c15c969 Updating package.json 2023-10-28 09:55:41 -07:00
Chris Troutner a3102a0278 Cleaned up comments 2023-10-28 09:50:12 -07:00
Chris Troutner f4f142dc48 fix(tests): Increased test coverage to 100% 2023-10-28 09:17:48 -07:00
Chris Troutner e03a70c9a3 fix(get-peers): finalizing REST endpoint behavior 2023-10-27 17:39:24 -07:00
Chris Troutner d47b8797d4 fix(libp2p): Updated to v0.46.16 2023-10-25 22:13:35 -07:00
Chris Troutner 56ba0aad02 Adding IPFS REST API endpoints 2023-10-23 20:28:17 -07:00
Chris Troutner 825bc6d2de fix(ipfs): Creating block & data store directories if they don't exit 2023-10-23 09:16:06 -07:00
Chris Troutner 18fe63a5ad fix(tests): Fixed broken tests 2023-10-22 17:35:10 -07:00
Chris Troutner d6e0e7b903 got helia working 2023-10-22 07:51:45 -07:00
Chris Troutner 8759e1af5e Merge pull request #113 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord-esm): Updating to v9.1.13
2023-08-29 16:38:56 -07:00
Chris Troutner b78f3fb08b fix(ipfs-coord-esm): Updating to v9.1.13 2023-08-29 16:34:41 -07:00
Chris Troutner 5fa0b2e1c8 Merge pull request #112 from Permissionless-Software-Foundation/ct-unstable
fix(license): Updating license file
2023-06-25 06:15:14 -07:00
Chris Troutner 6a88d0559a fix(license): Updating license file 2023-06-25 06:13:22 -07:00
Chris Troutner 314fdc2bb0 fix(minimal-slp-wallet): Updating to v5.11.1 2023-06-25 06:11:45 -07:00
Chris Troutner acd7154d41 fix(docker): Updating p2wdb image to v4.1.3 2023-05-14 17:22:13 -07:00
Chris Troutner 63a1bea60d fix(docker): Updating P2WDB image to v4.1.1 2023-05-13 14:46:24 -07:00
Chris Troutner 077c60bd34 Merge pull request #111 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord-esm): Updating to v9.1.9
2023-05-13 07:06:26 -07:00
Chris Troutner 6485537207 Turning down debug logs 2023-05-13 07:03:49 -07:00
Chris Troutner 286f079679 fix(ipfs-coord-esm): Updating to v9.1.9 2023-05-13 06:59:25 -07:00
Chris Troutner 075818d551 Adding test coverage reporting for just unit tests 2023-05-12 15:25:58 -07:00
Chris Troutner b0f42d613b Adding Dia workflows 2023-05-12 14:34:55 -07:00
Chris Troutner 8e5b87f142 Merge pull request #71 from Permissionless-Software-Foundation/ct-unstable
fix(dependencies): Updating npm dependencies
2023-05-11 12:14:38 -07:00
Chris Troutner 92483ee5e1 fix(dependencies): Updating npm dependencies 2023-05-11 12:09:43 -07:00
Chris Troutner a80b82da7a Merge pull request #70 from Permissionless-Software-Foundation/ct-unstable
Updating P2WDB docker image
2023-05-11 07:12:38 -07:00
Chris Troutner 41d2aaa16d fix(production): Updating P2WDB docker image 2023-05-11 07:11:11 -07:00
Chris Troutner 9de2caba94 Merge pull request #110 from Permissionless-Software-Foundation/ct-unstable
fix(ipfs-coord-esm): Updating to 9.1.7
2023-05-09 09:00:04 -07:00
Chris Troutner a45fb3ad08 fix(ipfs-coord-esm): Updating to 9.1.7 2023-05-09 08:56:26 -07:00
Chris Troutner 666f954529 Merge pull request #109 from Permissionless-Software-Foundation/ct-unstable
Minor Fixes
2023-05-09 08:07:55 -07:00
Chris Troutner 7d2e02fe64 fix(timer-controller): Adding unit tests for startTimers() 2023-05-09 08:06:57 -07:00
Chris Troutner 9912ed7af7 fix(json-files): Rejecting promise rather than throwing an error 2023-05-09 08:00:30 -07:00
Chris Troutner 441d898ae7 fix(timer controller): Preventing duplicate timer controllers 2023-05-08 08:01:02 -07:00
Chris Troutner 553a7ff86a Merge pull request #108 from Permissionless-Software-Foundation/ct-unstable
feat(ipfs): Allowing disabling IPFS features with config flag
2023-04-10 09:22:11 -07:00
Chris Troutner 349d070273 feat(ipfs): Allowing disabling IPFS features with config flag 2023-04-10 09:19:51 -07:00
Chris Troutner b6dcb9c177 Updating link to clean architecture 2023-03-22 17:10:51 -07:00
Chris Troutner 8aff3a6568 Merge pull request #107 from Permissionless-Software-Foundation/ct-unstable
feat(timer-controllers): Added template for timer-based controllers
2023-02-25 06:10:53 -08:00
Chris Troutner 9e885b8bc3 feat(timer-controllers): Added template for timer-based controllers 2023-02-25 06:09:56 -08:00
Chris Troutner d6b924a5f4 Merge pull request #106 from Permissionless-Software-Foundation/ct-unstable
Updating ipfs-coord-esm for bch-coinjoin pubsub
2023-02-24 13:50:17 -08:00
Chris Troutner 97691a0dd9 fix(minimal-slp-wallet): Updated to v5.8.5 2023-02-24 13:47:22 -08:00
Chris Troutner 3781af6305 fix(ipfs-coord-esm): Updating to v9.1.5 2023-02-24 13:45:48 -08:00
Chris Troutner cb5d5a603d fixing typo in user REST controller 2023-02-23 08:36:47 -08:00
Chris Troutner 81713bfebe Updating README 2023-01-16 05:37:52 -08:00
Chris Troutner b9aeb8faa0 Updating list-addrs script 2022-12-24 21:07:30 -08:00
Chris Troutner a4e75cdccc fix(scripts): Adding script to list addresses in a wallet 2022-12-24 21:05:42 -08:00
Chris Troutner f74c94ef50 fix(sweep): Adding fullstack-sweep-wallet.js for faster wallet sweeping 2022-12-24 07:34:54 -08:00
Chris Troutner bfb51fdcc6 fix(docker): Bumping p2wdb to v3.3.5 2022-12-13 06:52:10 -08:00
Chris Troutner 25aaf4146f Merge pull request #69 from Permissionless-Software-Foundation/ct-unstable
Minor fixes. New docker image v2.2.4
2022-12-13 06:47:11 -08:00
Chris Troutner 8ee10f2f8c fix(docker): Increasing docker image to v2.2.4 2022-12-13 06:44:20 -08:00
Chris Troutner bf96821c2c fix(docker): Fixing issue with P2WDB 2022-12-13 06:09:02 -08:00
Chris Troutner 9787806427 fix(docker): Disabling BCH payments in local P2WDB (be default) 2022-12-10 12:36:02 -08:00
Chris Troutner 9043aab432 fix(docker): Switching to p2wdbv3.3.3 2022-12-10 12:19:53 -08:00
Chris Troutner 58989a81dc Merge pull request #68 from Permissionless-Software-Foundation/ct-unstable
fix(config): Using public P2WDB for writes by default
2022-12-10 10:54:07 -08:00
Chris Troutner 82acd623d8 fix(config): Using public P2WDB for writes by default 2022-12-10 10:43:55 -08:00
Chris Troutner e4fe8cc492 Adding minor debug log 2022-12-07 15:26:35 -08:00
Chris Troutner 0f8a6b9299 Merge pull request #103 from Permissionless-Software-Foundation/ct-unstable
fix(minimal-slp-wallet): Updating to v5.5.5
2022-12-05 13:53:19 -08:00
Chris Troutner 4f2c87bbd4 fix(minimal-slp-wallet): Updating to v5.5.5 2022-12-05 13:51:41 -08:00
Chris Troutner 66b8e6b263 Merge branch 'ct-unstable' 2022-12-05 10:36:25 -08:00
Chris Troutner cabff83222 Updating docker image to v2.2.3 2022-12-05 10:36:16 -08:00
Chris Troutner d94b527780 Merge pull request #67 from Permissionless-Software-Foundation/ct-unstable
fix(minimal-slp-wallet): Updating to v5.5.5
2022-12-05 10:32:38 -08:00
Chris Troutner f0e8a05f08 fix(minimal-slp-wallet): Updating to v5.5.5 2022-12-05 10:27:37 -08:00
Chris Troutner 278b4127e1 fix(docker): Bumping bch-dex container to v2.2.2 2022-12-05 09:51:54 -08:00
Chris Troutner 87b3d7519c Merge pull request #66 from Permissionless-Software-Foundation/ct-unstable
Optimize wallet on new orders. Use local P2WDB
2022-12-05 09:43:14 -08:00
Chris Troutner ed4e75c9ad fix(docker): Using local P2WDB rather than p2wdb.fullstack.cash 2022-12-05 09:39:52 -08:00
Chris Troutner b0cc5a6e0b fix(order): Optimizing wallet before creating new orders 2022-12-05 09:38:28 -08:00
Chris Troutner 6b4139a29e Merge pull request #65 from Permissionless-Software-Foundation/ct-unstable
fix(docker): Disabling P2WDB payment service by default
2022-12-03 13:35:13 -08:00
Chris Troutner 925242262a fix(docker): Disabling P2WDB payment service by default 2022-12-03 13:32:13 -08:00
Chris Troutner 74fe77009c fix(timer): Setting time controller to fire every 10 minutes 2022-12-03 13:27:47 -08:00
Chris Troutner 51f7905dad fix(p2wdb-esm): Upgrading to v2.2.9 2022-12-02 21:10:18 -08:00
Chris Troutner 0b1d3e63f6 Merge pull request #64 from Permissionless-Software-Foundation/ct-unstable
feat(order): Adding functions needed by AMM
2022-12-02 20:07:54 -08:00
Chris Troutner 549fd4fd5a feat(order): Adding functions needed by AMM 2022-12-02 07:44:35 -08:00
Chris Troutner 846ee21c48 Merge pull request #63 from Permissionless-Software-Foundation/ct-unstable
feat(Orders): Added new REST endpoint to get all Orders
2022-12-01 15:12:46 -08:00
Chris Troutner 6654836440 Merge branch 'master' into ct-unstable 2022-12-01 15:10:41 -08:00
Chris Troutner a25a13673e feat(Orders): Added new REST endpoint to get all Orders 2022-12-01 15:10:04 -08:00
Chris Troutner dd7db3814a Merge pull request #102 from Permissionless-Software-Foundation/ct-unstable
fix(minimal-slp-wallet): Updating to v5.5.3
2022-11-30 15:06:29 -08:00
Chris Troutner fdb3b9c3ac fix(minimal-slp-wallet): Updating to v5.5.3 2022-11-30 14:59:54 -08:00
Chris Troutner 30eed1ffb1 Merge pull request #62 from Permissionless-Software-Foundation/ct-unstable
fix(production): Updating docker images
2022-11-29 15:49:36 -08:00
Chris Troutner 7884cffdc6 fix(production): Updating docker images 2022-11-29 15:47:18 -08:00
Chris Troutner c1a8210fa7 Merge pull request #61 from Permissionless-Software-Foundation/ct-unstable
fix(p2wdb-esm): Bumping to v2.2.8
2022-11-03 05:58:54 -07:00
Chris Troutner 9f3b482f57 fix(p2wdb-esm): Bumping to v2.2.8 2022-11-03 05:56:48 -07:00
Chris Troutner 9487027852 Merge pull request #60 from Permissionless-Software-Foundation/ct-unstable
fix(garbage collection): Fixing bug that was deleting valid offers
2022-11-02 17:32:45 -07:00
Chris Troutner 9065900608 fix(garbage collection): Fixing bug that was deleting valid offers 2022-11-02 17:02:36 -07:00
Chris Troutner 2da319f896 fix(garbage): Debugging garbage collection 2022-11-02 15:07:47 -07:00
Chris Troutner 449a98d80c fix(offers): Rounding sats 2022-11-02 15:06:10 -07:00
Chris Troutner 2343120fcf Merge pull request #59 from Permissionless-Software-Foundation/ct-unstable
Updating p2wdb lib
2022-10-10 16:49:24 -07:00
Chris Troutner 2d4fa523cd fix(production): Updating startup script 2022-10-09 08:23:26 -07:00
Chris Troutner c8cc3adbc5 fix(p2wd-esm): Bumping to v2.2.7 2022-10-09 08:16:07 -07:00
Chris Troutner 169e0ad3d2 Updating p2wdb lib 2022-10-09 06:51:03 -07:00
Chris Troutner e20a0a5d27 Rebuilding containers 2022-10-09 06:41:41 -07:00
Chris Troutner 6dda29bb14 fixing create-wallet script 2022-10-09 06:39:42 -07:00
Chris Troutner dd662e9fe9 fix(deps): Updating minimal-slp-wallet 2022-10-09 06:37:12 -07:00
125 changed files with 48022 additions and 16838 deletions
+4
View File
@@ -69,5 +69,9 @@ ipfs-service-provider.sh
run-dev.sh
wallet.json
data/
run-dev.sh
*.zip
!README.md
wallet.json
+93
View File
@@ -0,0 +1,93 @@
# Permissionless Software Foundation Community Contributing Guide 1.0
This document describes a very simple process suitable for most projects under
the PSF umbrella. It is based on [this Medium article](https://medium.com/the-node-js-collection/healthy-open-source-967fa8be7951) and the [Node.js Community Contribution Guide](https://github.com/nodejs/TSC/blob/master/BasePolicies/CONTRIBUTING.md).
Projects are encouraged to adopt this whether they
are hosted under the PSF or not.
The goal of this document is to create a contribution process that:
* Encourages new contributions.
* Encourages contributors to remain involved.
* Avoids unnecessary processes and bureaucracy whenever possible.
* Creates a transparent decision making process which makes it clear how
contributors can be involved in decision making.
This document is based on much prior art in the Node.js community, io.js,
and the Node.js project.
Additional guidance can be found at the [Permissionless Software Foundation Telegram Channel](https://t.me/permissionless_software).
## Vocabulary
* A **Contributor** is any individual creating or commenting on an issue or pull request.
* A **Committer** is a subset of contributors who have been given write access to the repository.
* A **TC (Technical Committee)** is a group of committers representing the required technical
expertise to resolve rare disputes.
# Logging Issues
Log an issue for any question or problem you might have. When in doubt, log an issue,
any additional policies about what to include will be provided in the responses. The only
exception is security disclosures which should be sent privately.
Committers may direct you to another repository, ask for additional clarifications, and
add appropriate metadata before the issue is addressed.
Please be courteous, respectful, and every participant is expected to follow the
project's Code of Conduct.
# Contributions
Any change to resources in this repository must be through pull requests. This applies to all changes
to documentation, code, binary files, etc. Even long term committers and TC members must use
pull requests.
No pull request can be merged without being reviewed.
For non-trivial contributions, pull requests should sit for at least 36 hours to ensure that
contributors in other timezones have time to review. Consideration should also be given to
weekends and other holiday periods to ensure active committers all have reasonable time to
become involved in the discussion and review process if they wish.
The default for each contribution is that it is accepted once no committer has an objection.
During review committers may also request that a specific contributor who is most versed in a
particular area gives a "LGTM" before the PR can be merged. There is no additional "sign off"
process for contributions to land. Once all issues brought by committers are addressed it can
be landed by any committer.
In the case of an objection being raised in a pull request by another committer, all involved
committers should seek to arrive at a consensus by way of addressing concerns being expressed
by discussion, compromise on the proposed change, or withdrawal of the proposed change.
If a contribution is controversial and committers cannot agree about how to get it to land
or if it should land then it should be escalated to the TC. TC members should regularly
discuss pending contributions in order to find a resolution. It is expected that only a
small minority of issues be brought to the TC for resolution and that discussion and
compromise among committers be the default resolution mechanism.
# Becoming a Committer
All contributors who land a non-trivial contribution should be on-boarded in a timely manner,
and added as a committer, and be given write access to the repository.
Committers are expected to follow this policy and continue to send pull requests, go through
proper review, and have other committers merge their pull requests.
# TC Process
The TC uses a "consensus seeking" process for issues that are escalated to the TC.
The group tries to find a resolution that has no open objections among TC members.
If a consensus cannot be reached that has no objections then a majority wins vote
is called. It is also expected that the majority of decisions made by the TC are via
a consensus seeking process and that voting is only used as a last-resort.
Resolution may involve returning the issue to committers with suggestions on how to
move forward towards a consensus. It is not expected that a meeting of the TC
will resolve all issues on its agenda during that meeting and may prefer to continue
the discussion happening among the committers.
Members can be added to the TC at any time. Any committer can nominate another committer
to the TC and the TC uses its standard consensus seeking process to evaluate whether or
not to add this new member. Members who do not participate consistently at the level of
a majority of the other members are expected to resign.
+1 -1
View File
@@ -1,5 +1,5 @@
The MIT License (MIT)
Copyright (c) 2022 Permissionless Software Foundation
Copyright (c) 2021-2024 Permissionless Software Foundation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+3
View File
@@ -0,0 +1,3 @@
# Pedigree
This repository was forked from [koa-api-boilerplate](https://github.com/christroutner/koa-api-boilerplate). Code changes from that upstream repository is frequently pulled in and merged to this repository.
+27 -41
View File
@@ -1,14 +1,14 @@
# bch-dex
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) [![Join the chat at https://gitter.im/Permissionless-Software-Foundation/psf-dex-dev](https://badges.gitter.im/Permissionless-Software-Foundation/psf-dex-dev.svg)](https://gitter.im/Permissionless-Software-Foundation/psf-dex-dev?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release)
`bch-dex` is a decentralized exchange for permissionless trading of [SLP tokens](https://github.com/simpleledger/slp-specifications/blob/master/slp-token-type-1.md) and [NFTs](https://github.com/simpleledger/slp-specifications/blob/master/slp-nft-1.md). Trading fees are incredibly small, compared to other DEX platforms. The software is based entirely on open protocols and open source software. It's not a '.com' website that you visit, but software you run on your own computer, in the confort of your own home. So long as you have an internet connection and the technical knowledge to run this software, no one can stop you from trading tokens. `bch-dex` software is maintained by the [Permissionless Software Foundation](https://psfoundation.cash/).
`bch-dex` is a decentralized exchange *protocol* for permissionless trading of [SLP tokens](https://github.com/simpleledger/slp-specifications/blob/master/slp-token-type-1.md) and [NFTs](https://github.com/simpleledger/slp-specifications/blob/master/slp-nft-1.md). Trading fees are incredibly small, compared to other DEX platforms. The software is based entirely on open protocols and open source software. `bch-dex` software is maintained by the [Permissionless Software Foundation](https://psfoundation.info).
- [High-level Overview of bch-dex](https://youtu.be/LVX8CLi4sHw) (Video)
This is a prototype web service that monitors the [P2WDB](https://github.com/Permissionless-Software-Foundation/ipfs-p2wdb-service) for trading signals, to trade BCH and SLP tokens. It's inspired by the [SWaP Protocol](https://github.com/vinarmani/swap-protocol/blob/master/swap-protocol-spec.md).
This is a prototype web service that monitors one or more [Nostr relays](https://nostr.com) for trading signals, to trade BCH and SLP tokens. It's inspired by the [SWaP Protocol](https://github.com/vinarmani/swap-protocol/blob/master/swap-protocol-spec.md).
This repository contains the back end code. The user interface is contained in the [bch-dex-ui](https://github.com/Permissionless-Software-Foundation/bch-dex-ui) repository.
This repository contains the back end code. The user interface is contained in the [bch-dex-ui-v3](https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v3) repository.
**Warning**: This repository is under active development. Things will be constantly changing and breaking.
@@ -19,57 +19,43 @@ This repository contains the back end code. The user interface is contained in t
## Participate
This is an open source project, and we encourage other JavaScript developers to participate in its creation and maintenance. We have two chat rooms for the community:
- [Telegram Channel](https://t.me/psf_dex_dev)
- [Gitter Channel](https://gitter.im/Permissionless-Software-Foundation/psf-dex-dev)
- [Telegram Channel](https://t.me/bch_js_toolkit)
## Installation
Running the DEX requires composition of these different software packages:
- bch-dex - This repository is the back end software that tracks trade data on the network, generates [Offers and Counter Offers](https://github.com/Permissionless-Software-Foundation/bch-dex/tree/ct-unstable/dev-docs#definitions), and finalizes trades by accepting Counter Offers.
- [bch-dex-ui](https://github.com/Permissionless-Software-Foundation/bch-dex-ui) is a [Gatsby](https://www.gatsbyjs.com/) web app and user interface (UI) for bch-dex.
- [P2WDB](https://github.com/Permissionless-Software-Foundation/ipfs-p2wdb-service) is a censorship-resistant database used to communicate trade data between peers running bch-dex.
- [IPFS](https://ipfs.io/) is a censorship-resistant network for communicating data over the internet.
- [MongoDB](https://www.mongodb.com/) is a database used by both P2WDB and bch-dex to store and manage local data.
- [bch-dex-ui-v3](https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v3) web app and user interface (UI) for bch-dex focused on Sellers of tokens.
- [MongoDB](https://www.mongodb.com/) is a database used by bch-dex to store and manage local data, user accounts, etc.
The above software is orchestrated using [Docker](https://www.docker.com/) and Docker Compose. The target operating system is Ubuntu 20+, and the target hardware is amd64 (normal desktop PCs) and the arm64 (Raspberry Pi 4). Trying to operate this software on other operating systems or hardware is possible, but not supported.
The above software is orchestrated using [Docker](https://www.docker.com/) and Docker Compose. The target operating system is Ubuntu 20+, and the target hardware is amd64 (normal desktop PCs). Trying to operate this software on other operating systems or hardware is possible, but not supported.
The Raspberry Pi 4 is the primary hardware target, because this software functions best when it is always running. The Pi 4 makes a great appliance for monitoring trade signals and finalizing trades.
Setup instructions:
Instructions for setting up Node.js, Docker, and Docker Compose can be found in [this Gist](https://gist.github.com/christroutner/a39f656850dc022b60f25c9663dd1cdd). Walk-through videos can also be found on the [PSF Videos page](https://psfoundation.cash/video/).
Here are the steps involved in setting up bch-dex:
1. Follow the direction in [this Gist](https://gist.github.com/christroutner/a39f656850dc022b60f25c9663dd1cdd) to install Node.js, Docker, and Docker Compose.
1. Clone the repository with `git clone https://github.com/Permissionless-Software-Foundation/bch-dex` and enter it with `cd bch-dex`.
1. Install dependencies by running `npm install`
1. Create a wallet:
- Follow the direction in [this Gist](https://gist.github.com/christroutner/a39f656850dc022b60f25c9663dd1cdd) to install Node.js, Docker, and Docker Compose.
- Clone the repository with `git clone https://github.com/Permissionless-Software-Foundation/bch-dex` and enter it with `cd bch-dex`.
- Install dependencies by running `npm install`
- Create a wallet:
- `cd production/scripts`
- `node create-wallet.js`
1. Change directory to the `production/docker` or `production/rpi-docker` folder depending on your hardware target.
1. Pull the Docker images down from Docker Hub: `docker-compose pull`
1. Start the Docker containers with `docker-compose up -d`
1. Wait for the P2WDB to sync and populate bch-dex with trade data. You can monitor it with `docker logs --tail 20 -f p2wdb`.
1. Open a web browser and navigate the `http://localhost:4500`. You'll be able to see new Offers as they come in and are detected by bch-dex.
1. To take the other side of the trade, click the `Take` button in the UI.
1. You can add the 12-word mnemonic from the `wallet.json` file to the the web wallet, which will mirror your wallet in the UI, and allow you to perform basic wallet functions (send and receive BCH and tokens).
- Change directory to the `production/docker` folder.
- Pull the Docker images down from Docker Hub: `docker-compose pull`
- Build the back end: `docker-compose build --no-cache bch-dex`
- Build the front end: `docker-compose build --no-cache dex-ui`
- Start the Docker containers with `docker-compose up -d`
- It will take about 10 minutes for the bch-dex to sync with the network and display tokens for sale.
- Open a web browser and navigate to `http://localhost:4500`.
- You will be presented with a login screen. Click the sign-up tab, enter an email and password to create an account. This information is stored locally on bch-dex, it is not set to a third party.
- Once logged in, you can retrieve your BCH and SLP addresses, to fund your wallet.
## Blockchain Infrastructure
The P2WDB and bch-dex containers requires a 'Global Back End' (as described in [CashStack.info](https://cashstack.info)) in order to connect to the blockchain. By default, the Docker containers connect to [free-bch.fullstack.cash](https://free-bch.fullstack.cash/). Several community-provided Global Back End servers are provided and can be [viewed here](https://gist.github.com/christroutner/63c5513782181f8b8ea3eb89f7cadeb6). The back end can be changed by setting the `CONSUMER_URL` environment variable in the `docker-compose.yml` file.
bch-dex requires a [Cash Stack](https://cashstack.info) back end in order to connect to the blockchain. By default, the Docker containers connect to [free-bch.fullstack.cash](https://free-bch.fullstack.cash/). Several community-provided servers are provided and can be [viewed here](https://consumers.psfoundation.info/consumers.json). The back end can be changed by setting the `CONSUMER_URL` environment variable in the `docker-compose.yml` file.
### Applying Software Updates
As this is an active project, software updates will happen frequently. To apply a software update, perform these steps.
## Environment Variables
1. Enter the `docker` or `rpi-docker` folder, depending on your hardware target.
1. Bring down the Docker containers with `docker-compose down`.
1. Pull in the software updates with `git pull`
1. Update dependencies with `npm install`
1. Pull new Docker images with `docker-compose pull`
1. Clean up disk space by deleting old Docker images with `./cleanup-images.sh`
1. Start the Docker containers with `docker-compose up -d`
Sometimes it may be necessary to delete the databases before applying a software update. This can be done by stopping the Docker containers and deleting the `production/data` directory. When the Docker containers are restarted, they will recreate that directory. The P2WDB will re-sync and bch-dex will be populated with fresh trade data.
- `DISABLE_NEW_ACCOUNTS` - If set, only the admin user can create new accounts. The sign-up tab becomes non-functional. Useful for controlling access to your instance of bch-dex.
- `ADMIN_PASSWORD` - Sets the password for the admin user. This can be used with the [createUser.js script](/util/users/production/createUser.js) to add new users to the system, if the `DISABLE_NEW_ACCOUNTS` env var is set.
## License
+22 -14
View File
@@ -25,6 +25,7 @@ import config from '../config/index.js' // this first.
import AdminLib from '../src/adapters/admin.js'
import errorMiddleware from '../src/controllers/rest-api/middleware/error.js'
import { usageMiddleware } from '../src/use-cases/usage-use-cases.js'
// import wlogger from '../src/adapters/wlogger.js'
import Controllers from '../src/controllers/index.js'
import { applyPassportMods } from '../config/passport.js'
@@ -45,16 +46,18 @@ class Server {
const app = new Koa()
app.keys = [this.config.session]
// Connect to the Mongo Database.
this.mongoose.Promise = global.Promise
this.mongoose.set('useCreateIndex', true) // Stop deprecation warning.
console.log(
`Connecting to MongoDB with this connection string: ${this.config.database}`
)
await this.mongoose.connect(this.config.database, {
useUnifiedTopology: true,
useNewUrlParser: true
})
if (!this.config.noMongo) {
// Connect to the Mongo Database.
this.mongoose.Promise = global.Promise
this.mongoose.set('useCreateIndex', true) // Stop deprecation warning.
console.log(
`Connecting to MongoDB with this connection string: ${this.config.database}`
)
await this.mongoose.connect(this.config.database, {
useUnifiedTopology: true,
useNewUrlParser: true
})
}
console.log(`Starting environment: ${this.config.env}`)
console.log(`Debug level: ${this.config.debugLevel}\n`)
@@ -68,6 +71,7 @@ class Server {
app.use(bodyParser())
app.use(session())
app.use(errorMiddleware())
app.use(usageMiddleware())
// Used to generate the docs.
app.use(mount('/', serve(`${process.cwd()}/docs`)))
@@ -124,9 +128,11 @@ class Server {
this.server = await app.listen(config.port)
console.log(`Server started on ${config.port}`)
// Create the system admin user.
const success = await this.adminLib.createSystemUser()
if (success) console.log('System admin user created.')
if (!this.config.noMongo) {
// Create the system admin user.
const success = await this.adminLib.createSystemUser()
if (success) console.log('System admin user created.')
}
// Attach the other IPFS controllers.
// Skip if this is a test environment.
@@ -138,7 +144,9 @@ class Server {
console.log('\nConfiguration:')
console.log(`Circuit Relay: ${this.config.isCircuitRelay}`)
console.log(`IPFS TCP port: ${this.config.ipfsTcpPort}`)
console.log(`IPFS WS port: ${this.config.ipfsWsPort}\n`)
console.log(`IPFS WS port: ${this.config.ipfsWsPort}`)
console.log(`IPFS WebRTC port: ${this.config.ipfsWebRtcPort}`)
console.log(`Connection preference: ${this.config.connectPref}\n`)
return app
} catch (err) {
+88 -13
View File
@@ -1,6 +1,11 @@
/*
This file is used to store unsecure, application-specific data common to all
environments.
Additional Environent Variables:
- CONNECT_PREF: should have a value of 'cr' (default), or 'direct'. This is
used by helia-coord to select a connection preference between peers. Servers
with an ip4 or ip6 address should use 'direct'.
*/
/* eslint no-unneeded-ternary:0 */
@@ -42,28 +47,76 @@ export default {
useFullStackCash: process.env.USE_FULLSTACKCASH ? true : false,
consumerUrl: process.env.CONSUMER_URL
? process.env.CONSUMER_URL
: 'https://free-bch.fullstack.cash',
// : 'https://free-bch.fullstack.cash',
: 'https://dev-consumer.psfoundation.info',
// : 'https://wa-usa-bch-consumer.fullstackcash.nl',
// P2WDB URL that will accept API calls from the p2wdb npm library.
p2wdbUrl: process.env.P2WDB_URL ? process.env.P2WDB_URL : 'https://p2wdb.fullstack.cash',
// p2wdbUrl: process.env.P2WDB_URL ? process.env.P2WDB_URL : 'http://localhost:5010',
// Enable or Disable the usage of Mongo DB.
noMongo: process.env.NO_MONGO ? true : false,
// BEGIN WALLET CONFIGURATION
// BCH Mnemonic for generating encryption keys and payment address
mnemonic: process.env.MNEMONIC ? process.env.MNEMONIC : '',
// (Optional) use a wallet file generated by psf-bch-wallet.
walletFile: process.env.WALLET_FILE
? process.env.WALLET_FILE
: `${__dirname.toString()}/../../wallet.json`,
// Wallet Configuration
// Default value: web3 - connect to bch-api through web 3 JSON RPC over IPFS.
// Alternate value: web2 - connect to bch-api through a web 2 REST API over HTTP.
walletInterface: process.env.WALLET_INTERFACE ? process.env.WALLET_INTERFACE : 'web3',
// Wallet API Server
// Default web2 server: https://api.fullstack.cash/v5/
// Default web3 server: https://free-bch.fullstack.cash/
apiServer: process.env.WALLET_INTERFACE === 'web2'
? (
process.env.APISERVER
? process.env.APISERVER
: 'https://api.fullstack.cash/v5/'
)
: 'https://free-bch.fullstack.cash/',
// Basic Authentication Password for connecting to bch-api through a web 2
// REST API over HTTP.
authPass: process.env.WALLET_AUTH_PASS
? process.env.WALLET_AUTH_PASS
: '',
// FullStack.cash account information, used for automatic JWT handling.
getJwtAtStartup: process.env.GET_JWT_AT_STARTUP ? true : false,
authServer: process.env.AUTHSERVER
? process.env.AUTHSERVER
: 'https://auth.fullstack.cash',
apiServer: process.env.APISERVER
? process.env.APISERVER
: 'https://api.fullstack.cash/v5/',
fullstackLogin: process.env.FULLSTACKLOGIN
? process.env.FULLSTACKLOGIN
: 'demo@demo.com',
fullstackPassword: process.env.FULLSTACKPASS
? process.env.FULLSTACKPASS
: 'demo',
// authPass: process.env.FULLSTACK_AUTH_PASS
// ? process.env.FULLSTACK_AUTH_PASS
// : '',
// END WALLET CONFIGURATION
// BEGIN IPFS CONFIGURATION
// Debug verbosity level of helia-coord.
// 0 = no debug logs. 3 = maximum logs.
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2,
// Enable/Disable the IPFS node at startup. Enabled by default.
// useIpfs: process.env.DISABLE_IPFS ? false : true,
useIpfs: false,
// IPFS settings.
isCircuitRelay: process.env.ENABLE_CIRCUIT_RELAY ? true : false,
// SSL domain used for websocket connection via browsers.
crDomain: process.env.CR_DOMAIN ? process.env.CR_DOMAIN : '',
@@ -84,7 +137,10 @@ export default {
'@type': 'Organization',
name: 'Permissionless Software Foundation',
url: 'https://PSFoundation.cash'
}
},
// If this node has an IP4 address or domain name used to provide a REST API.
web2Api: process.env.WEB2_API ? process.env.WEB2_API : null
},
// P2WDB webhook endpoint
@@ -100,14 +156,11 @@ export default {
// IPFS Ports
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 4001,
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 4003,
// BCH Mnemonic for generating encryption keys and payment address
mnemonic: process.env.MNEMONIC ? process.env.MNEMONIC : '',
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 1,
ipfsWebRtcPort: process.env.IPFS_WEB_RTC_PORT ? process.env.IPFS_WEB_RTC_PORT : 4005,
connectPref: process.env.CONNECT_PREF, // Used in helia-coord to select connection preference.
// Settings for production, using external go-ipfs node.
isProduction: process.env.SVC_ENV === 'production' ? true : false,
isProduction: process.env.SVC_ENV === 'prod' ? true : false,
ipfsHost: process.env.IPFS_HOST ? process.env.IPFS_HOST : 'localhost',
ipfsApiPort: process.env.IPFS_API_PORT
? parseInt(process.env.IPFS_API_PORT)
@@ -116,5 +169,27 @@ export default {
chatPubSubChan: 'psf-ipfs-chat-001',
// IPFS gateway
ipfsGateway: process.env.IPFS_GATEWAY ? process.env.IPFS_GATEWAY : 'https://p2wdb-gateway.fullstack.cash/ipfs/'
ipfsGateway: process.env.IPFS_GATEWAY ? process.env.IPFS_GATEWAY : 'https://pin.fullstack.cash/ipfs/download/',
// This can add specific Circuit Relay v2 servers to connect to.
bootstrapRelays: [
// v2 Circuit Relay (Token Tiger)
// '/ip4/137.184.93.145/tcp/8001/p2p/12D3KooWGMEKkdJfyZbwdH9EafZbRTtMn7FnhWPrE4MhRty2763g',
// v2 Circuit Relay server (FullStack.cash)
// '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWFQ11GQ5NubsJGhYZ4X3wrAGimLevxfm6HPExCrMYhpSL'
],
// END IPFS CONFIGURATION
// Nostr
nostrRelay: process.env.NOSTR_RELAY ? process.env.NOSTR_RELAY : 'wss://nostr-relay.psfoundation.info',
nostrTopic: process.env.NOSTR_TOPIC ? process.env.NOSTR_TOPIC : 'bch-dex-test-topic-02',
nostrGlobalFeed: process.env.NOSTR_GLOBAL_FEED ? process.env.NOSTR_GLOBAL_FEED : 'slpdex-socialmedia',
// Account Configuration
disableNewAccounts: process.env.DISABLE_NEW_ACCOUNTS ? true : false,
// Admin password
adminPassword: process.env.ADMIN_PASSWORD
}
+2 -1
View File
@@ -8,5 +8,6 @@ export default {
session: 'secret-boilerplate-token',
token: 'secret-jwt-token',
database: 'mongodb://localhost:27017/bch-swap-service-test',
env: 'test'
env: 'test',
nostrRelay: 'wss://'
}
-4
View File
@@ -14,8 +14,4 @@ if (env === 'test') {
config = production
}
// const importStr = `./env/${env}.js`
// console.log('importStr: ', importStr)
// import config from importStr
export default Object.assign({}, common, config)
-3
View File
@@ -53,7 +53,4 @@ function applyPassportMods (passport) {
return true
}
// For testing
// export default { passport, passportCallback };
// export default applyPassportMods
export { applyPassportMods, passportCallback }
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+4
View File
@@ -1,4 +1,8 @@
import Server from './bin/server.js'
const server = new Server()
process.on('unhandledRejection', (reason, promise) => {
console.log(`Handling ${reason.code} error. stack: `, reason)
})
server.startServer()
+26701
View File
File diff suppressed because it is too large Load Diff
+13280 -14800
View File
File diff suppressed because it is too large Load Diff
+32 -10
View File
@@ -1,14 +1,14 @@
{
"name": "bch-dex",
"version": "2.0.2",
"version": "3.0.0",
"description": "A DEX on BCH for trading SLP tokens.",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node index.js",
"test": "npm run test:all",
"test": "npm run test:unit",
"test:all": "export BCH_DEX=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
"test:unit": "export BCH_DEX=test && mocha --exit --timeout 15000 --recursive test/unit/",
"test:unit": "export BCH_DEX=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit/",
"test:e2e:auto": "export BCH_DEX=test && mocha --exit --timeout 30000 test/e2e/automated/",
"test:integration": "export BCH_DEX=test && mocha --exit --timeout 45000 --recursive test/integration",
"test:temp": "export BCH_DEX=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
@@ -28,13 +28,34 @@
},
"repository": "Permissionless-Software-Foundation/bch-dex",
"dependencies": {
"@chainsafe/libp2p-gossipsub": "14.1.0",
"@chainsafe/libp2p-noise": "16.0.1",
"@chainsafe/libp2p-yamux": "7.0.1",
"@chris.troutner/retry-queue": "1.0.8",
"@helia/unixfs": "4.0.2",
"@libp2p/bootstrap": "11.0.26",
"@libp2p/circuit-relay-v2": "3.2.2",
"@libp2p/config": "1.1.0",
"@libp2p/identify": "3.0.22",
"@libp2p/keychain": "5.0.14",
"@libp2p/logger": "5.1.8",
"@libp2p/ping": "2.0.22",
"@libp2p/tcp": "10.1.2",
"@libp2p/webrtc": "5.2.2",
"@libp2p/websockets": "9.2.2",
"@multiformats/multiaddr": "12.3.5",
"axios": "0.27.2",
"bch-message-lib": "2.2.1",
"bch-nostr": "1.3.3",
"bch-token-sweep": "2.2.1",
"bcryptjs": "2.4.3",
"bitcoincashjs-lib": "3.3.3",
"bitcoincashjs-lib": "3.3.3",
"blockstore-fs": "2.0.2",
"datastore-fs": "10.0.2",
"glob": "7.1.6",
"ipfs-coord-esm": "9.1.3",
"helia": "5.2.1",
"helia-coord": "1.7.2",
"ipfs-coord-esm": "9.1.7",
"ipfs-http-client": "58.0.0",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
@@ -50,25 +71,26 @@
"koa-router": "10.0.0",
"koa-static": "5.0.0",
"koa2-ratelimit": "0.9.1",
"libp2p": "0.36.2",
"libp2p": "2.7.2",
"line-reader": "0.4.0",
"minimal-slp-wallet": "5.2.1",
"minimal-slp-wallet": "5.13.2",
"mongoose": "5.13.14",
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
"nodemailer": "6.7.5",
"p2wdb": "2.2.3",
"nostr-tools": "2.10.4",
"p2wdb-esm": "2.2.9",
"passport-local": "1.0.0",
"public-ip": "4.0.4",
"public-ip": "6.0.1",
"winston": "3.3.3",
"winston-daily-rotate-file": "4.5.0"
},
"devDependencies": {
"apidoc": "0.51.1",
"bch-token-sweep": "2.1.2",
"c8": "7.12.0",
"chai": "4.3.0",
"coveralls": "2.11.4",
"husky": "4.3.8",
"lodash.clonedeep": "4.5.0",
"mocha": "10.0.0",
"semantic-release": "19.0.3",
"sinon": "9.2.4",
+1
View File
@@ -0,0 +1 @@
COMPOSE_PROJECT_NAME=bch-dex
+9 -6
View File
@@ -12,7 +12,7 @@ RUN apt-get update
RUN apt-get install -y sudo git curl nano gnupg wget
#Install Node and NPM
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
RUN curl -sL https://deb.nodesource.com/setup_20.x -o nodesource_setup.sh
RUN bash nodesource_setup.sh
RUN apt-get install -y nodejs build-essential
@@ -34,7 +34,7 @@ WORKDIR /home/safeuser
# Update to the latest version of npm.
#RUN npm install -g npm@7.23.0
RUN npm install -g npm
#RUN npm install -g npm
# npm mirror to prevent direct dependency on npm.
#RUN npm set registry http://94.130.170.209:4873/
@@ -46,11 +46,11 @@ RUN npm install -g npm
# Clone the rest.bitcoin.com repository
WORKDIR /home/safeuser
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v2
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v3
# Switch to the desired branch. `master` is usually stable,
# and `stage` has the most up-to-date changes.
WORKDIR /home/safeuser/bch-dex-ui-v2
WORKDIR /home/safeuser/bch-dex-ui-v3
# For development: switch to unstable branch
RUN git checkout ct-unstable
@@ -61,13 +61,16 @@ RUN git checkout ct-unstable
RUN npm install
# Build the site
RUN CI=true npm run build
RUN npm run build
# Load the NGINX image.
FROM nginx
EXPOSE 80
# Copy the files built in the first container to the new NGINX container.
COPY --from=builder /home/safeuser/bch-dex-ui-v2/build /usr/share/nginx/html
COPY --from=builder /home/safeuser/bch-dex-ui-v3/build /usr/share/nginx/html
# Copy the NGINX configuration file.
COPY default.conf /etc/nginx/conf.d/default.conf
#USER safeuser
+23
View File
@@ -0,0 +1,23 @@
server {
listen 80;
listen [::]:80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
+6 -3
View File
@@ -12,7 +12,7 @@ RUN apt-get update
RUN apt-get install -y sudo git curl nano gnupg wget
#Install Node and NPM
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
RUN curl -sL https://deb.nodesource.com/setup_20.x -o nodesource_setup.sh
RUN bash nodesource_setup.sh
RUN apt-get install -y nodejs build-essential
@@ -34,7 +34,7 @@ WORKDIR /home/safeuser
# Update to the latest version of npm.
#RUN npm install -g npm@7.23.0
RUN npm install -g npm
#RUN npm install -g npm
# npm mirror to prevent direct dependency on npm.
RUN npm set registry http://94.130.170.209:4873/
@@ -72,7 +72,10 @@ RUN npm run docs
#EXPOSE 5010
# Start the application.
#COPY start-production.sh start-production.sh
CMD ["./start-production.sh"]
#CMD ["npm", "start"]
# Used for debugging
#COPY dummy-app.js dummy-app.js
#CMD ["node", "dummy-app.js"]
+4
View File
@@ -0,0 +1,4 @@
setInterval(function () {
const now = new Date()
console.log(`timestamp: ${now.toLocaleString()}`)
}, 5000)
@@ -24,9 +24,11 @@ export DBURL=mongodb://172.17.0.1:5666/bch-swap-service-prod
export PORT=5700
# bch-dex specific env vars
export BCH_DEX=production
export BCH_DEX=prod
export WEBHOOKSERVICE=http://172.17.0.1:5667/webhook
export WEBHOOKTARGET=http://172.17.0.1:5700/p2wdb
export APP_ID=bch-dex-001
#export P2WDB_URL=http://172.17.0.1:5667
npm start
+20 -71
View File
@@ -3,9 +3,9 @@
version: '3.9'
services:
mongo-p2wdb-service:
mongo-dex:
image: mongo:4.2.0
container_name: mongo-p2wdb-service
container_name: mongo-dex
ports:
- '5666:27017' # <host port>:<container port>
volumes:
@@ -13,67 +13,11 @@ services:
command: mongod --logpath=/dev/null # -- quiet
restart: always
ipfs:
# https://github.com/christroutner/trickle-ipfs
image: christroutner/trickle-ipfs:v1.0.1
#build:
# context: ./
# dockerfile: Dockerfile
container_name: ipfs
environment:
IPFS_DAEMON_ARGUMENTS: '--enable-pubsub-experiment --migrate=true --agent-version-suffix=docker --routing=dhtclient'
UPLOAD_KBPS: '1000'
DOWNLOAD_KBPS: '1000'
logging:
driver: 'json-file'
options:
max-size: '10m'
max-file: '10'
mem_limit: 2000mb
ports:
- 4001:4001
- 4003:4003
- 172.17.0.1:5001:5001
- 172.17.0.1:8080:8080
command: [
'./start-ipfs.sh'
]
volumes:
- ../data/go-ipfs/data:/root/.ipfs
restart: always
p2wdb:
#build:
# context: ./p2wdb/
# dockerfile: Dockerfile
image: christroutner/p2wdb:v3.1.8
container_name: p2wdb
environment:
CONSUMER_URL: 'https://free-bch.fullstack.cash'
DEBUG_LEVEL: 1
logging:
driver: 'json-file'
options:
max-size: '10m'
max-file: '10'
mem_limit: 1gb
links:
- mongo-p2wdb-service
ports:
# <host port>:<container port>
- 5667:5667 # REST API
#- 5668:5668 # IPFS TCP port
#- 5669:5669 # IPFS WS Port
volumes:
- ../data/ipfsdata/p2wdb:/home/safeuser/ipfs-p2wdb-service/.ipfsdata/p2wdb
- ./p2wdb/start-production.sh:/home/safeuser/ipfs-p2wdb-service/start-production.sh
restart: always
bch-dex:
#build:
# context: ./bch-dex/
# dockerfile: Dockerfile
image: christroutner/bch-dex:v2.0.8
build:
context: ./bch-dex/
dockerfile: Dockerfile
#image: christroutner/bch-dex:v2.2.4
container_name: bch-dex
environment:
CONSUMER_URL: 'https://free-bch.fullstack.cash'
@@ -84,29 +28,34 @@ services:
max-file: '10'
mem_limit: 1000mb
links:
- mongo-p2wdb-service
- p2wdb
- mongo-dex
#- p2wdb
ports:
- '5700:5700' # <host port>:<container port>
# <host port>:<container port>
- '5700:5700' # REST API
- '4001:4001' # TCP
- '4003:4003' # Websockets
- '4005:4005' # WebRTC
volumes:
- ../scripts/wallet.json:/home/safeuser/bch-dex/wallet.json
- ./bch-dex/start-production.sh:/home/safeuser/bch-dex/start-production.sh
restart: always
dex-ui:
#build:
# context: ./bch-dex-ui/
# dockerfile: Dockerfile
image: christroutner/bch-dex-ui:v1.2.4
build:
context: ./bch-dex-ui/
dockerfile: Dockerfile
#image: christroutner/bch-dex-ui:v1.2.4
container_name: dex-ui
environment:
SERVER: 'http://192.168.2.3'
SERVER: 'http://172.17.0.1'
logging:
driver: 'json-file'
options:
max-size: '10m'
max-file: '10'
mem_limit: 500mb
mem_limit: 1000mb
links:
- bch-dex
ports:
-71
View File
@@ -1,71 +0,0 @@
# Create a Docker container for the P2WDB
#
#IMAGE BUILD COMMANDS
FROM ubuntu:20.04
MAINTAINER Chris Troutner <chris.troutner@gmail.com>
#Update the OS and install any OS packages needed.
RUN apt-get update
RUN apt-get install -y sudo git curl nano gnupg wget
#Install Node and NPM
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
RUN bash nodesource_setup.sh
RUN apt-get install -y nodejs build-essential
#Create the user 'safeuser' and add them to the sudo group.
RUN useradd -ms /bin/bash safeuser
RUN adduser safeuser sudo
#Set password to 'abcd8765' change value below if you want a different password
RUN echo safeuser:abcd8765 | chpasswd
#Set the working directory to be the users home directory
WORKDIR /home/safeuser
#Setup NPM for non-root global install (like on a mac)
RUN mkdir /home/safeuser/.npm-global
RUN chown -R safeuser .npm-global
RUN echo "export PATH=~/.npm-global/bin:$PATH" >> /home/safeuser/.profile
RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'"
# Update to the latest version of npm.
#RUN npm install -g npm@7.23.0
RUN npm install -g npm
# npm mirror to prevent direct dependency on npm.
RUN npm set registry http://94.130.170.209:4873/
# Switch to user account.
#USER safeuser
# Prep 'sudo' commands.
#RUN echo 'abcd8765' | sudo -S pwd
# Clone the rest.bitcoin.com repository
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-p2wdb-service
WORKDIR /home/safeuser/ipfs-p2wdb-service
RUN mkdir .ipfsdata
RUN git checkout ct-unstable
#RUN npm install fsevents
# Install dependencies
#RUN mkdir .ipfsdata
RUN npm install
# Generate the API docs
RUN npm run docs
# Expose the port the API will be served on.
#EXPOSE 5010
# Start the application.
#COPY start-production.sh start-production.sh
CMD ["./start-production.sh"]
#CMD ["npm", "start"]
# Used for debugging
#COPY dummyapp.js dummyapp.js
#CMD ["node", "dummyapp.js"]
-4
View File
@@ -1,4 +0,0 @@
setInterval(function () {
const now = new Date()
console.log(`ping ${now.toLocaleString()}`)
}, 10000)
@@ -1,47 +0,0 @@
#!/bin/bash
# BEGIN: Optional configuration settings
# This mnemonic is used to set up persistent public key for e2ee
# Replace this with your own 12-word mnemonic.
export MNEMONIC="mass response fiscal world message exact series swallow forward confirm canoe festival"
# The human readable name this IPFS node identifies as.
export COORD_NAME=generic-p2wdb-production
# Allow this node to function as a circuit relay. It must not be behind a firewall.
#export ENABLE_CIRCUIT_RELAY=true
# For browsers to use your circuit realy, you must set up a domain, SSL certificate,
# and you must forward that subdomain to the IPFS_WS_PORT.
#export CR_DOMAIN=subdomain.yourdomain.com
# Debug level. 0 = minimal info. 2 = max info.
# This value set by docker-compose.yml
#export DEBUG_LEVEL=1
# Log-in information for retrieving a JWT token from FullStack.cash.
export FULLSTACKLOGIN=demo@demo.com
export FULLSTACKPASS=demo
# END: Optional configuration settings
# Production database connection string.
export DBURL=mongodb://172.17.0.1:5666/p2wdb-service-dev
# Configure IPFS ports
#export IPFS_TCP_PORT=5668
#export IPFS_WS_PORT=5669
# Configure REST API port
export PORT=5667
export P2W_ENV=production
export IPFS_HOST=172.17.0.1
export IPFS_API_PORT=5001
export IPFS_TCP_PORT=4001
# P2WDB specific env vars
#export ORBITDB_NAME=/orbitdb/zdpuAm4fPafpjs5mgbBUJV8Az7kd8Xj1ttD6M1JaAxF8RMRrw/psf-bch-p2wdb-keyvalue-v1.0.0-0004
npm start
+8 -13
View File
@@ -12,9 +12,12 @@ RUN apt-get update
RUN apt-get install -y sudo git curl nano gnupg wget
#Install Node and NPM
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
RUN bash nodesource_setup.sh
RUN apt-get install -y nodejs build-essential
RUN sudo apt-get install -y ca-certificates curl gnupg
RUN sudo mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
RUN sudo apt-get update
RUN sudo apt-get install -y nodejs build-essential
#Create the user 'safeuser' and add them to the sudo group.
#RUN useradd -ms /bin/bash safeuser
@@ -34,7 +37,7 @@ WORKDIR /home/safeuser
# Update to the latest version of npm.
#RUN npm install -g npm@7.23.0
RUN npm install -g npm
#RUN npm install -g npm
# npm mirror to prevent direct dependency on npm.
RUN npm set registry http://94.130.170.209:4873/
@@ -53,7 +56,7 @@ RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex
WORKDIR /home/safeuser/bch-dex
# For development: switch to unstable branch
RUN git checkout ct-unstable
RUN git checkout master
# Install dependencies
#RUN mkdir .ipfsdata
@@ -63,14 +66,6 @@ RUN npm install
# Generate the API docs
RUN npm run docs
# Copy the wallet files
#COPY wallet.json wallet.json
#VOLUME /home/safeuser/keys
# Expose the port the API will be served on.
#EXPOSE 5010
# Start the application.
#COPY start-production.sh start-production.sh
CMD ["./start-production.sh"]
@@ -41,8 +41,14 @@ export IPFS_HOST=172.17.0.1
export IPFS_API_PORT=5001
export IPFS_TCP_PORT=4001
export IPFS_WS_PORT=4003
export IPFS_WEB_RTC_PORT=4005
# Set the debug level for helia-coord. 0-3.
# 0 = no debug logs. 3 = maximum debug logs.
export DEBUG_LEVEL=0
# P2WDB specific env vars
#export ORBITDB_NAME=/orbitdb/zdpuAqNiwLiJBfbRK7uihV2hAbNSXj78ufzv5VyQb8GuvRwDh/psf-bch-p2wdb-keyvalue-v3.0.0-0001
# Use this if the IPFS node has a publically accessible IP address.
#export CONNECT_PREF=direct
# Use this if the IPFS node is behind a NAT or Firewall and cannot be accessed directly.
export CONNECT_PREF=cr
npm start
+153
View File
@@ -0,0 +1,153 @@
/*
This script is similar ot sweep-wallet.js, but it can do a much faster (bulk)
scanning of addresses. However, this requires a subscription to a FullStack.cash
or access to a web2 Cash Stack.
This script will scan 20 addresses at a time. If a balance is found in any
of the addresses, it will sweep the funds from that address.
*/
/*
// Public npm libraries
import BchTokenSweep from 'bch-token-sweep/index.js'
// Local libraries
import WalletAdapter from '../../src/adapters/wallet.js'
import BCHJS from '@psf/bch-js'
import JwtLib from 'jwt-bch-lib'
// Constants
// const EMTPY_ADDR_CUTOFF = 100
const LAST_ADDR_INDEX = 100
if (!process.env.BCHJSTOKEN) {
console.log('You will need a JWT token from FullStack.cash to execute this script. Export it to the BCHJSTOKEN environment variable and try again.')
exit(0)
}
async function sweepFunds () {
try {
// Configure the wallet library to use a FullStack.cash or a local Cash Stack
const walletLib = new WalletAdapter()
walletLib.config.useFullStackCash = true
walletLib.config.apiServer = 'http://192.168.2.129:3000/v5/'
// const walletInfo = await walletLib.openWallet()
const walletInfo = await openWallet(walletLib)
const wallet = await walletLib.instanceWallet(walletInfo)
console.log('walletInfo: ', walletInfo)
const rootAddr = walletInfo.cashAddress
const rootWif = walletInfo.privateKey
console.log(`Sweeping all funds into root address ${rootAddr}...`)
// Instantiate bch-js, and use the JWT token saved to environment variable.
// Generate an HD tree
// const bchjs = wallet.bchjs
const bchjs = new BCHJS()
const rootSeed = await bchjs.Mnemonic.toSeed(walletInfo.mnemonic)
const masterHDNode = bchjs.HDNode.fromSeed(rootSeed)
const emptyAddrCnt = 0
const hdIndex = 1
// Divide all the addresses by 20, since that's how many will be checked at once.
const numOfLoops = Math.ceil(LAST_ADDR_INDEX / 20)
for (let i = 1; i < numOfLoops; i++) {
const startIndex = i
const stopIndex = i * 20
console.log(`startIndex: ${startIndex}, stopIndex: ${stopIndex}`)
// Generate the next block of 20 key pairs to scan.
const addrsToScan = []
const wifsToScan = []
for (let j = startIndex; j < stopIndex; j++) {
// Generate a keypair from the HD wallet.
const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${j}`)
const cashAddress = bchjs.HDNode.toCashAddress(childNode)
const wifToSweep = bchjs.HDNode.toWIF(childNode)
addrsToScan.push(cashAddress)
wifsToScan.push(wifToSweep)
}
console.log(`Scanning these addresses for a balance: ${JSON.stringify(addrsToScan, null, 2)}`)
// Scan a block of 20 addresses for a balance.
const balances = await bchjs.Electrumx.balance(addrsToScan)
console.log(`balances: ${JSON.stringify(balances, null, 2)}`)
// Scan through the results and look for a balance.
for (let j = 0; j < balances.balances.length; j++) {
const thisBalance = balances.balances[j]
// const combinedBal = thisBalance.balance.confirmed + thisBalance.balance.
// if(thisBalance.)
}
}
// 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)
//
// console.log(`\nSweeping ${cashAddress} with private key ${wifToSweep}`)
//
// try {
// // Sweep tokens from address
// const sweeper = new BchTokenSweep(
// wifToSweep,
// rootWif,
// wallet,
// 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 with index ${hdIndex}: ${err.message}`)
// // console.log(err)
// emptyAddrCnt++
// }
//
// hdIndex++
// } while (emptyAddrCnt < EMTPY_ADDR_CUTOFF)
//
// console.log(`${EMTPY_ADDR_CUTOFF} empty addresses detected. Exiting.`)
//
// console.log('\n\nDo not forget to reset the nextAddress property in the wallet.json file!\n\n')
} catch (err) {
console.error('Error in sweepFunds(): ', err)
}
}
sweepFunds()
// Open the wallet file, or create one if the file doesn't exist.
// Does not instance the wallet. The output of this function is expected to
// be passed to instanceWallet().
async function openWallet (walletLib) {
try {
// console.log('this.WALLET_FILE: ', this.WALLET_FILE)
const walletData = await walletLib.jsonFiles.readJSON('./wallet.json')
return walletData
} catch (err) {
console.error('Error in openWallet()')
throw err
}
}
*/
+4 -4
View File
@@ -3,7 +3,7 @@
*/
// Public NPM libraries
import BchWallet from 'minimal-slp-wallet/index'
import BchWallet from 'minimal-slp-wallet'
import { promises as fs } from 'fs'
@@ -16,9 +16,9 @@ async function createWallet () {
}
// Wait for the wallet to be created.
this.bchWallet = new BchWallet(undefined, advancedConfig)
await this.bchWallet.walletInfoPromise
const walletData = this.bchWallet.walletInfo
const bchWallet = new BchWallet(undefined, advancedConfig)
await bchWallet.walletInfoPromise
const walletData = bchWallet.walletInfo
walletData.nextAddress = 1
// Save the wallet file to disk.
@@ -0,0 +1,111 @@
/*
This script is the same as sweep-wallet.js, but it requires a FullStack.cash
account, which makes the scanning much faster.
*/
// Public npm libraries
// const BCHJS = require('@psf/bch-js')
import BchTokenSweep from 'bch-token-sweep/index.js'
// Local libraries
import WalletAdapter from '../../src/adapters/wallet.js'
// Constants
// const EMTPY_ADDR_CUTOFF = 10
const START_INDEX = 1
const LAST_ADDR_INDEX = 100
if (!process.env.BCHJSTOKEN) {
console.log('You will need a JWT token from FullStack.cash to execute this script. Export it to the BCHJSTOKEN environment variable and try again.')
process.exit(0)
}
async function sweepFunds () {
try {
// Configure the wallet library to use a FullStack.cash or a local Cash Stack
const walletLib = new WalletAdapter()
walletLib.config.useFullStackCash = true
// walletLib.config.apiServer = 'http://192.168.2.129:3000/v5/'
walletLib.config.apiServer = 'https://api.fullstack.cash/v5/'
// Open the wallet files.
const walletInfo = await openWallet(walletLib)
const wallet = await walletLib.instanceWallet(walletInfo)
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 = wallet.bchjs
const rootSeed = await bchjs.Mnemonic.toSeed(walletInfo.mnemonic)
const masterHDNode = bchjs.HDNode.fromSeed(rootSeed)
let emptyAddrCnt = 0
let hdIndex = START_INDEX
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)
console.log(`\nSweeping ${cashAddress} with private key ${wifToSweep}`)
try {
// Sweep tokens from address
const sweeper = new BchTokenSweep(
wifToSweep,
rootWif,
wallet,
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 with index ${hdIndex}: ${err.message}`)
// console.log(err)
emptyAddrCnt++
}
hdIndex++
// } while (emptyAddrCnt < EMTPY_ADDR_CUTOFF)
// console.log(`${EMTPY_ADDR_CUTOFF} empty addresses detected. Exiting.`)
} while (hdIndex < LAST_ADDR_INDEX)
console.log(`Last index of ${LAST_ADDR_INDEX} reached. emptyAddrCnt: ${emptyAddrCnt}`)
console.log('\n\nDo not forget to reset the nextAddress property in the wallet.json file!\n\n')
} catch (err) {
console.error('Error in sweepFunds(): ', err)
}
}
sweepFunds()
// Open the wallet file, or create one if the file doesn't exist.
// Does not instance the wallet. The output of this function is expected to
// be passed to instanceWallet().
async function openWallet (walletLib) {
try {
// console.log('this.WALLET_FILE: ', this.WALLET_FILE)
const walletData = await walletLib.jsonFiles.readJSON('./wallet.json')
return walletData
} catch (err) {
console.error('Error in openWallet()')
throw err
}
}
+67
View File
@@ -0,0 +1,67 @@
/*
This script is the same as sweep-wallet.js, but instead of sweeping the keys,
it simply lists out the addresses in the HD keychain. This is hany when trying
to figure out which address a token was sent to.
*/
// Public npm libraries
// Local libraries
import WalletAdapter from '../../src/adapters/wallet.js'
// Constants
const START_INDEX = 1
const LAST_ADDR_INDEX = 100
async function listAddrs () {
try {
// Configure the wallet library to use a FullStack.cash or a local Cash Stack
const walletLib = new WalletAdapter()
// Open the wallet files.
const walletInfo = await openWallet(walletLib)
const wallet = await walletLib.instanceWallet(walletInfo)
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 = wallet.bchjs
const rootSeed = await bchjs.Mnemonic.toSeed(walletInfo.mnemonic)
const masterHDNode = bchjs.HDNode.fromSeed(rootSeed)
let hdIndex = START_INDEX
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)
console.log(`HD Index ${hdIndex}: ${cashAddress}`)
hdIndex++
} while (hdIndex < LAST_ADDR_INDEX)
console.log(`Last index of ${LAST_ADDR_INDEX} reached.`)
} catch (err) {
console.error('Error in sweepFunds(): ', err)
}
}
listAddrs()
// Open the wallet file, or create one if the file doesn't exist.
// Does not instance the wallet. The output of this function is expected to
// be passed to instanceWallet().
async function openWallet (walletLib) {
try {
// console.log('this.WALLET_FILE: ', this.WALLET_FILE)
const walletData = await walletLib.jsonFiles.readJSON('./wallet.json')
return walletData
} catch (err) {
console.error('Error in openWallet()')
throw err
}
}
+1 -1
View File
@@ -5,7 +5,7 @@
*/
// Global npm libraries
import BchWallet from 'minimal-slp-wallet/index.js'
import BchWallet from 'minimal-slp-wallet'
// Local libraries
import WalletAdapter from '../../src/adapters/wallet.js'
-2
View File
@@ -2,8 +2,6 @@
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.
This script will sweep BCH and fungible (Type 1) tokens, but not NFTs.
*/
// Public npm libraries
+1 -1
View File
@@ -13,7 +13,7 @@ export COORD_NAME=ipfs-service-provider-generic
export MNEMONIC="churn aisle shield silver ladder swear hunt slim pen demand spoil veteran"
# 0 = less verbose. 3 = most verbose
export DEBUG_LEVEL=2
export DEBUG_LEVEL=0
# Production settings that use external IPFS node.
# https://github.com/christroutner/docker-ipfs
+27 -58
View File
@@ -51,70 +51,38 @@ class Admin {
// used by the Listing Manager and test scripts, in order access private API
// functions.
async createSystemUser () {
// Create the system user.
try {
context.password = _this._randomString(20)
const options = {
method: 'POST',
url: `${LOCALHOST}/users`,
data: {
user: {
email: 'system@system.com',
password: context.password,
name: 'admin'
}
}
const context = {
email: 'system@system.com',
name: 'admin',
password: _this.config.adminPassword || _this._randomString(20),
type: 'admin',
mnemonic: 'spread ivory oval pioneer flash gap merit nerve simple image entire trouble'
}
const result = await _this.axios.request(options)
// console.log('admin.data: ', result.data)
// Check if the user already exists
let adminUser = await _this.User.findOne({ email: context.email })
context.email = result.data.user.email
context.id = result.data.user._id
context.token = result.data.token
if (adminUser) {
// Update the password
adminUser.password = context.password
} else {
// Create a new admin user
adminUser = new _this.User(context)
}
// Update context with the new user id and token
context.id = adminUser._id
context.token = await adminUser.generateToken()
// Get the mongoDB entry
const user = await _this.User.findById(context.id)
// Save the user
await adminUser.save()
// Change the user type to admin
user.type = 'admin'
// console.log(`user created: ${JSON.stringify(user, null, 2)}`)
// Save the user model.
await user.save()
// console.log(`admin user created: ${JSON.stringify(result.body, null, 2)}`)
// console.log(`with password: ${context.password}`)
// Write out the system user information to a JSON file that external
// applications like the Task Manager and the test scripts can access.
await jsonFiles.writeJSON(context, JSON_PATH)
// console.log('context: ', context)
// console.log('JSON_PATH: ', JSON_PATH)
// Write the user data to the JSON file
await _this.jsonFiles.writeJSON(context, JSON_PATH)
return context
} catch (err) {
// Handle existing system user.
if (err.response.status === 422) {
try {
// Delete the existing user
await _this.deleteExistingSystemUser()
// Call this function again.
return _this.createSystemUser()
} catch (err2) {
console.error(
'Error in admin.js/createSystemUser() while trying generate new system user.'
)
// process.end(1)
throw err2
}
} else {
console.log('Error in admin.js/createSystemUser: ')
// process.end(1)
throw err
}
} catch (error) {
console.log('Error in admin.js/createSystemUser()')
throw error
}
}
@@ -129,6 +97,7 @@ class Admin {
})
await _this.User.deleteOne({ email: 'system@system.com' })
return true
} catch (err) {
console.log('Error in admin.js/deleteExistingSystemUser()')
throw err
@@ -152,7 +121,7 @@ class Admin {
Accept: 'application/json'
},
data: {
email: 'system@system.com',
email: existingUser.email,
password: existingUser.password
}
}
+1 -1
View File
@@ -6,7 +6,7 @@
import BCHJS from '@psf/bch-js'
import MsgLib from 'bch-message-lib'
import BchWallet from 'minimal-slp-wallet/index.js'
import BchWallet from 'minimal-slp-wallet'
class Bch {
constructor () {
+24 -5
View File
@@ -9,22 +9,21 @@ import BCHJS from '@psf/bch-js'
// Load individual adapter libraries.
import IPFSAdapter from './ipfs/index.js'
import LocalDB from './localdb/index.js'
import LogsAPI from './logapi.js'
import Passport from './passport.js'
import Nodemailer from './nodemailer.js'
// const { wlogger } = require('./wlogger')
import JSONFiles from './json-files.js'
import FullStackJWT from './fullstack-jwt.js'
import config from '../../config/index.js'
// import Wallet from './wallet.adapter.js'
import BCHAdapter from './bch.js'
import WalletAdapter from './wallet.js'
import P2wdbAdapter from './p2wdb-adapter.js'
import Webhook from './webhook.js'
import NostrAdapter from './nostr.js'
class Adapters {
constructor (localConfig = {}) {
@@ -43,6 +42,9 @@ class Adapters {
localConfig.bchWallet = this.wallet.bchWallet
this.p2wdb = new P2wdbAdapter(localConfig)
this.webhook = new Webhook()
this.nostr = new NostrAdapter(this.config)
// this.wallet = new Wallet(localConfig)
// Get a valid JWT API key and instance bch-js.
this.fullStackJwt = new FullStackJWT(config)
@@ -50,14 +52,22 @@ class Adapters {
async start () {
try {
// let apiToken
if (this.config.getJwtAtStartup) {
// Get a JWT token and instantiate bch-js with it. Then pass that instance
// to all the rest of the apps controllers and adapters.
await this.fullStackJwt.getJWT()
// apiToken = await this.fullStackJwt.getJWT()
// Instantiate bch-js with the JWT token, and overwrite the placeholder for bch-js.
this.bchjs = await this.fullStackJwt.instanceBchjs()
}
// Create a default instance of minimal-slp-wallet without initializing it
// (without retrieving the wallets UTXOs). This instance will be overwritten
// if the operator has configured BCH payments.
console.log('\nCreating default startup wallet. This wallet may be overwritten.')
// await this.wallet.instanceWalletWithoutInitialization({}, { apiToken })
this.bchjs = this.wallet.bchWallet.bchjs
// Start the IPFS node.
// Do not start these adapters if this is an e2e test.
// if (this.config.env !== 'test') {
@@ -66,6 +76,8 @@ class Adapters {
// Open the wallet file
const walletData = await this.wallet.openWallet()
await this.nostr.start(walletData.privateKey)
// console.log('walletData: ', walletData)
if (this.config.env !== 'test') {
@@ -73,11 +85,18 @@ class Adapters {
await this.wallet.instanceWallet(walletData, this.bchjs)
// Wait until a webhook is established with the P2WDB
await this.webhook.waitUntilSuccess(this.config.webhookTarget)
// await this.webhook.waitUntilSuccess(this.config.webhookTarget)
// Overwrite instances of wallet used by P2WDB lib.
this.p2wdb.bchWallet = this.wallet.bchWallet
this.p2wdb.bchjs = this.wallet.bchWallet.bchjs
if (this.config.useIpfs) {
await this.ipfs.start()
}
} else {
// These lines are here to ensure code coverage hits 100%.
console.log('Not starting IPFS node since this is an e2e test.')
}
console.log('Async Adapters have been started.')
+134
View File
@@ -58,6 +58,140 @@ class IPFS {
throw err
}
}
// Get the status of this IPFS node.
// Create an object with different metrics about this IPFS node, then return
// the object.
getStatus () {
try {
const statusObj = {
ipfsId: this.ipfsCoordAdapter.ipfsCoord.thisNode.ipfsId,
multiAddrs: this.ipfsCoordAdapter.ipfsCoord.thisNode.ipfsMultiaddrs,
bchAddr: this.ipfsCoordAdapter.ipfsCoord.thisNode.bchAddr,
slpAddr: this.ipfsCoordAdapter.ipfsCoord.thisNode.slpAddr,
pubKey: this.ipfsCoordAdapter.ipfsCoord.thisNode.pubKey,
peers: this.ipfsCoordAdapter.ipfsCoord.thisNode.peerList.length,
relays: this.ipfsCoordAdapter.ipfsCoord.thisNode.relayData.length
}
return statusObj
} catch (err) {
console.error('Error in ipfs-coord.js/getStatus()')
throw err
}
}
// Get details on the other peers this node is connected to.
async getPeers (showAll) {
try {
const peerData = this.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
let ipfsPeers =
await this.ipfsCoordAdapter.ipfsCoord.adapters.ipfs.getPeers()
// console.log('ipfsPeers: ', ipfsPeers)
ipfsPeers = this._removeDuplicatePeers(ipfsPeers)
// console.log('filtered ipfsPeers: ', ipfsPeers)
const peerDataOut = []
// Loop through each IPFS peer and hydrate it with data from the peerData.
for (let i = 0; i < ipfsPeers.length; i++) {
const thisPeer = {
peer: ipfsPeers[i]
}
// if (!showAll) {
// // Delete properties that don't contain good info.
// delete thisPeer.muxer
// delete thisPeer.latency
// delete thisPeer.streams
// }
// Get the ipfs-coord peer data for this peer.
let thisPeerData = peerData.filter((x) =>
x.from.includes(thisPeer.peer)
)
thisPeerData = thisPeerData[0]
// console.log('thisPeerData: ', thisPeerData)
// Skip if peerData for this IPFS peer could not be found.
// if (!thisPeerData) continue
try {
// console.log('thisPeerData: ', thisPeerData)
// Add data to the IPFS peer data.
thisPeer.name = thisPeerData.data.jsonLd.name
thisPeer.protocol = thisPeerData.data.jsonLd.protocol
thisPeer.version = thisPeerData.data.jsonLd.version
thisPeer.connectionAddr = thisPeerData.data.connectionAddr
if (showAll) {
// Add all the peer data.
thisPeer.peerData = thisPeerData
}
} catch (err) {
console.warn(
`warning: ${thisPeer.peer}: ${err.message}`
)
}
peerDataOut.push(thisPeer)
}
return peerDataOut
} catch (err) {
console.error('Error in getPeers(): ', err)
throw err
}
}
// Expects an array of peers and returns an array of peers with duplicates
// removed.
// This function is used by getPeers()
_removeDuplicatePeers (arr) {
// https://stackoverflow.com/questions/2218999/how-to-remove-all-duplicates-from-an-array-of-objects
return arr.filter((v, i, a) => a.findIndex((t) => t === v) === i)
}
// Get data about the known Circuit Relays. Hydrate with data from peers list.
getRelays () {
try {
const relayData = this.ipfsCoordAdapter.ipfsCoord.thisNode.relayData
const peerData = this.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
// console.log(`relayData: ${JSON.stringify(relayData, null, 2)}`)
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
for (let i = 0; i < relayData.length; i++) {
const thisRelay = relayData[i]
// Find the peer that corresponds to this relay.
const thisPeer = peerData.filter((x) =>
x.from.includes(thisRelay.ipfsId)
)
// console.log('thisPeer: ', thisPeer)
// If the peer couldn't be found, skip.
if (!thisPeer.length) {
thisRelay.name = ''
thisRelay.description = ''
continue
}
thisRelay.name = thisPeer[0].data.jsonLd.name
thisRelay.description = thisPeer[0].data.jsonLd.description
}
const v1Relays = this.config.v1Relays
return { v2Relays: relayData, v1Relays }
} catch (err) {
console.error('Error in getRelays(): ', err)
throw err
}
}
}
export default IPFS
+20 -11
View File
@@ -5,11 +5,12 @@
*/
// Global npm libraries
import IpfsCoord from 'ipfs-coord-esm'
// import IpfsCoord from 'ipfs-coord-esm'
import IpfsCoord from 'helia-coord'
// import BCHJS from '@psf/bch-js';
import SlpWallet from 'minimal-slp-wallet/index.js'
import publicIp from 'public-ip'
import SlpWallet from 'minimal-slp-wallet'
import { publicIpv4 } from 'public-ip'
// Local libraries
import config from '../../../config/index.js'
@@ -34,7 +35,7 @@ class IpfsCoordAdapter {
// this.bchjs = new BCHJS()
this.wallet = new SlpWallet()
this.config = config
this.publicIp = publicIp
this.publicIp = publicIpv4
// Properties of this class instance.
this.isReady = false
@@ -51,7 +52,7 @@ class IpfsCoordAdapter {
// If configured as a Circuit Relay, get the public IP addresses for this node.
if (this.config.isCircuitRelay) {
try {
const ip4 = await this.publicIp.v4()
const ip4 = await this.publicIp()
// const ip6 = await publicIp.v6()
circuitRelayInfo.ip4 = ip4
@@ -64,23 +65,28 @@ class IpfsCoordAdapter {
}
}
const nullLog = () => {}
const ipfsCoordOptions = {
ipfs: this.ipfs,
type: 'node.js',
// type: 'browser',
wallet: this.wallet,
privateLog: console.log, // Default to console.log
// privateLog: console.log, // Default to console.log
privateLog: nullLog,
isCircuitRelay: this.config.isCircuitRelay,
circuitRelayInfo,
apiInfo: this.config.apiInfo,
announceJsonLd: this.config.announceJsonLd,
debugLevel: this.config.debugLevel
debugLevel: this.config.debugLevel,
v1Relays: this.config.v1Relays,
tcpPort: this.config.ipfsTcpPort
}
// Production env uses external go-ipfs node.
if (this.config.isProduction) {
ipfsCoordOptions.nodeType = 'external'
}
// if (this.config.isProduction) {
// ipfsCoordOptions.nodeType = 'external'
// }
this.ipfsCoord = new this.IpfsCoord(ipfsCoordOptions)
@@ -107,9 +113,12 @@ class IpfsCoordAdapter {
// Subscribe to the chat pubsub channel
async subscribeToChat () {
// TODO: Allow user to replace nullog with their own log handler at startup.
const nullLog = () => {}
await this.ipfsCoord.adapters.pubsub.subscribeToPubsubChannel(
this.config.chatPubSubChan,
console.log,
nullLog,
this.ipfsCoord.thisNode
)
}
+254 -79
View File
@@ -9,95 +9,98 @@
*/
// Global npm libraries
// const IPFS = require('ipfs')
// const IPFS = require('@chris.troutner/ipfs')
// import IPFSembedded from 'ipfs';
import { create } from 'ipfs-http-client'
import { createHelia } from 'helia'
import fs from 'fs'
import http from 'http'
import { FsBlockstore } from 'blockstore-fs'
import { FsDatastore } from 'datastore-fs'
import { createLibp2p } from 'libp2p'
import { tcp } from '@libp2p/tcp'
import { noise } from '@chainsafe/libp2p-noise'
import { yamux } from '@chainsafe/libp2p-yamux'
// import { bootstrap } from '@libp2p/bootstrap'
// import { identifyService } from 'libp2p/identify'
import { identify, identifyPush } from '@libp2p/identify'
import { circuitRelayServer, circuitRelayTransport } from '@libp2p/circuit-relay-v2'
// import { circuitRelayServer } from '@libp2p/circuit-relay-v2'
import { gossipsub } from '@chainsafe/libp2p-gossipsub'
import { webSockets } from '@libp2p/websockets'
import { publicIpv4 } from 'public-ip'
import { multiaddr } from '@multiformats/multiaddr'
import { webRTC, webRTCDirect } from '@libp2p/webrtc'
import { keychain } from '@libp2p/keychain'
import { unixfs } from '@helia/unixfs'
// import { generateKeyPairFromSeed } from '@libp2p/crypto/keys'
// import crypto from 'crypto'
import { ping } from '@libp2p/ping'
import { loadOrCreateSelfKey } from '@libp2p/config'
// Local libraries
import config from '../../../config/index.js'
import JsonFiles from '../json-files.js'
const IPFS_DIR = './.ipfsdata/ipfs'
// Hack to get __dirname back.
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
import * as url from 'url'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
// console.log('__dirname: ', __dirname)
const ROOT_DIR = `${__dirname}../../../`
const IPFS_DIR = `${__dirname}../../../.ipfsdata/ipfs`
class IpfsAdapter {
constructor (localConfig) {
// Encapsulate dependencies
this.config = config
this.fs = fs
this.create = create
// Choose the IPFS constructor based on the config settings.
// this.IPFS = IPFSembedded // default
// if (this.config.isProduction) {
// this.IPFS = IPFSexternal
// }
this.createLibp2p = createLibp2p
this.publicIp = publicIpv4
this.multiaddr = multiaddr
this.jsonFiles = new JsonFiles()
this.keychain = keychain
this.createHelia = createHelia
// Properties of this class instance.
this.isReady = false
// Bind 'this' object to all subfunctions
this.start = this.start.bind(this)
this.createNode = this.createNode.bind(this)
this.stop = this.stop.bind(this)
this.ensureBlocksDir = this.ensureBlocksDir.bind(this)
// this.getPrivateKey = this.getPrivateKey.bind(this)
this.getSeed = this.getSeed.bind(this)
}
// Start an IPFS node.
async start () {
try {
// Ipfs Options
const ipfsOptionsEmbedded = {
repo: IPFS_DIR,
start: true,
config: {
relay: {
enabled: true, // enable circuit relay dialer and listener
hop: {
enabled: config.isCircuitRelay // enable circuit relay HOP (make this node a relay)
}
},
pubsub: true, // enable pubsub
Swarm: {
ConnMgr: {
HighWater: 30,
LowWater: 10
}
},
Addresses: {
Swarm: [
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`
]
},
Datastore: {
StorageMax: '2GB',
StorageGCWatermark: 50,
GCPeriod: '15m'
}
}
}
// Ensure the directory structure exists that is needed by the IPFS node to store data.
this.ensureBlocksDir()
const ipfsOptionsExternal = {
host: this.config.ipfsHost,
port: this.config.ipfsApiPort,
agent: http.Agent({ keepAlive: true, maxSockets: 2000 })
}
// Create an IPFS node
const ipfs = await this.createNode()
// console.log('ipfs: ', ipfs)
let ipfsOptions = ipfsOptionsEmbedded
if (this.config.isProduction) {
ipfsOptions = ipfsOptionsExternal
}
this.id = ipfs.libp2p.peerId.toString()
console.log('IPFS ID: ', this.id)
// Create a new IPFS node.
this.ipfs = await this.create(ipfsOptions)
// Attempt to guess our ip4 IP address.
const ip4 = await this.publicIp()
let detectedMultiaddr = `/ip4/${ip4}/tcp/${this.config.ipfsTcpPort}/p2p/${this.id}`
detectedMultiaddr = this.multiaddr(detectedMultiaddr)
// Set the 'server' profile so the node does not scan private networks.
await this.ipfs.config.profiles.apply('server')
// Get the multiaddrs for the node.
const multiaddrs = ipfs.libp2p.getMultiaddrs()
multiaddrs.push(detectedMultiaddr)
console.log('Multiaddrs: ', multiaddrs)
// Debugging: Display IPFS config settings.
// const configSettings = await this.ipfs.config.getAll()
// console.log(`configSettings: ${JSON.stringify(configSettings, null, 2)}`)
this.multiaddrs = multiaddrs
// Signal that this adapter is ready.
this.isReady = true
this.ipfs = ipfs
return this.ipfs
} catch (err) {
console.error('Error in ipfs.js/start()')
@@ -111,30 +114,202 @@ class IpfsAdapter {
}
}
// This function creates an IPFS node using Helia.
// It returns the node as an object.
async createNode () {
try {
// Create block and data stores.
const blockstore = new FsBlockstore(`${IPFS_DIR}/blockstore`)
const datastore = new FsDatastore(`${IPFS_DIR}/datastore`)
// Get the private key.
// const privateKey = await this.getPrivateKey()
// console.log('privateKey: ', privateKey)
// Create an identity
const keychainInit = {
selfKey: 'myKey',
pass: await this.getSeed()
}
const privateKey = await loadOrCreateSelfKey(datastore, keychainInit)
// Configure services
const services = {
identify: identify(),
identifyPush: identifyPush(),
pubsub: gossipsub({ allowPublishToZeroTopicPeers: true }),
ping: ping(),
keychain: keychain(keychainInit)
}
if (this.config.isCircuitRelay) {
console.log('Helia (IPFS) node IS configured as Circuit Relay')
services.relay = circuitRelayServer({ // makes the node function as a relay server
hopTimeout: 30 * 1000, // incoming relay requests must be resolved within this time limit
reservations: {
maxReservations: 15, // how many peers are allowed to reserve relay slots on this server
reservationClearInterval: 300 * 1000, // how often to reclaim stale reservations
applyDefaultLimit: true, // whether to apply default data/duration limits to each relayed connection
defaultDurationLimit: 2 * 60 * 1000, // the default maximum amount of time a relayed connection can be open for
defaultDataLimit: BigInt(2 << 7) // the default maximum number of bytes that can be transferred over a relayed connection
},
maxInboundHopStreams: 32, // how many inbound HOP streams are allow simultaneously
maxOutboundHopStreams: 64 // how many outbound HOP streams are allow simultaneously
})
} else {
console.log('Helia (IPFS) node IS NOT configured as Circuit Relay')
}
const transports = [
tcp(),
webSockets(),
circuitRelayTransport({
discoverRelays: 3,
reservationConcurrency: 3
}),
webRTC(),
webRTCDirect()
]
// libp2p is the networking layer that underpins Helia
const libp2p = await this.createLibp2p({
// peerId,
privateKey,
datastore,
addresses: {
listen: [
'/ip4/127.0.0.1/tcp/0',
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`,
'/webrtc',
'/p2p-circuit'
]
},
transports,
connectionEncrypters: [
noise()
],
streamMuxers: [
yamux()
],
services
})
// create a Helia node
const helia = await this.createHelia({
blockstore,
datastore,
libp2p
})
// Attach IPFS file system.
const fs = unixfs(helia)
helia.fs = fs
return helia
} catch (err) {
console.error('Error creating Helia node: ', err)
throw err
}
}
async stop () {
await this.ipfs.stop()
return true
}
// Remove the '/blocks' directory that is used to store IPFS data.
// Dev Note: It's assumed this node is not pinning any data and that
// everything in this directory is transient. This folder will regularly
// fill up and prevent IPFS from starting.
// rmBlocksDir () {
// try {
// const dir = `${IPFS_DIR}/blocks`
// console.log(`Deleting ${dir} directory...`)
//
// this.fs.rmdirSync(dir, { recursive: true })
//
// console.log(`${dir} directory is deleted!`)
//
// return true // Signal successful execution.
// } catch (err) {
// console.log('Error in rmBlocksDir()')
// throw err
// }
// Ensure that the directories exist to store blocks from the IPFS network.
// This function is called at startup, before the IPFS node is started.
ensureBlocksDir () {
try {
!this.fs.existsSync(`${ROOT_DIR}.ipfsdata`) && this.fs.mkdirSync(`${ROOT_DIR}.ipfsdata`)
!this.fs.existsSync(`${IPFS_DIR}`) && this.fs.mkdirSync(`${IPFS_DIR}`)
!this.fs.existsSync(`${IPFS_DIR}/blockstore`) && this.fs.mkdirSync(`${IPFS_DIR}/blockstore`)
!this.fs.existsSync(`${IPFS_DIR}/datastore`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore`)
!this.fs.existsSync(`${IPFS_DIR}/datastore/pkcs8`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore/pkcs8`)
// !fs.existsSync(`${IPFS_DIR}/datastore/peers`) && fs.mkdirSync(`${IPFS_DIR}/datastore/peers`)
return true
} catch (err) {
console.error('Error in adapters/ipfs.js/ensureBlocksDir(): ', err)
throw err
}
}
// This function opens the seed used to generate the key for this IPFS peer.
// The seed is stored in a JSON file. If it doesn't exist, a new one is created.
async getSeed () {
try {
let seed
const filename = `${IPFS_DIR}/seed.json`
try {
// Try to read the JSON file containing the seed.
seed = await this.jsonFiles.readJSON(filename)
} catch (err) {
const seedNum = Math.floor(Math.random() * 1000000000000000000000)
seed = seedNum.toString()
// Save the newly generated seed
await this.jsonFiles.writeJSON(seed, filename)
}
// console.log('getSeed() seed: ', seed)
return seed
} catch (err) {
console.error('Error in adapters/ipfs/ipfs.js/getSeed()')
throw err
}
}
// Get the private key from disk, or generate a new one and save it,
// if it doesn't exist.
// async getPrivateKey () {
// try {
// const filename = `${IPFS_DIR}/privkey.json`
// let privKeyHex
// try {
// // Try to read the JSON file containing the seed.
// privKeyHex = await this.jsonFiles.readJSON(filename)
// // console.log('saved privKeyHex: ', privKeyHex)
// } catch (err) {
// // Generate a new private key and save it to disk.
// // Generate a new private key and save it to disk.
// const randomBuffer = crypto.randomBytes(32)
// // console.log('randomBuffer: ', randomBuffer)
// privKeyHex = randomBuffer.toString('hex')
// // console.log('new privKeyHex: ', privKeyHex)
// // Save the newly generated seed
// await this.jsonFiles.writeJSON(privKeyHex, filename)
// }
// // Convert the saved hex string to a buffer.
// const privKeyBuf = Buffer.from(privKeyHex, 'hex')
// // Convert the buffer to a Uint8Array 'seed'
// const seed = new Uint8Array(privKeyBuf.buffer, privKeyBuf.byteOffset, privKeyBuf.byteLength)
// // console.log('seed: ', seed)
// // Generate a ED25519 key pair.
// const privKey = await generateKeyPairFromSeed('Ed25519', seed)
// return privKey
// } catch (err) {
// console.error('Error in adapters/ipfs/ipfs.js/getPrivateKey(): ', err)
// throw err
// }
// }
}
+4
View File
@@ -7,6 +7,8 @@ import Users from './models/users.js'
import Entries from './models/entry.js'
import Order from './models/order.js'
import Offer from './models/offer.js'
import Usage from './models/usage.js'
import SmAccount from './models/smAccount.js'
class LocalDB {
constructor () {
@@ -15,6 +17,8 @@ class LocalDB {
this.Entry = Entries
this.Order = Order
this.Offer = Offer
this.Usage = Usage
this.SmAccount = SmAccount
}
}
+2 -1
View File
@@ -34,7 +34,8 @@ const Offer = new mongoose.Schema({
// SWaP Protocol Properties
lokadId: { type: String },
messageType: { type: Number },
messageClass: { type: Number }
messageClass: { type: Number },
nostrEventId: { type: String } // Nostr Event Id.
})
+17 -11
View File
@@ -10,20 +10,20 @@ import mongoose from 'mongoose'
const Order = new mongoose.Schema({
// Token data
tokenId: { type: String },
utxoTxid: { type: String },
utxoVout: { type: Number },
ticker: { type: String },
tokenType: { type: Number },
tokenId: { type: String, required: true },
utxoTxid: { type: String, required: true },
utxoVout: { type: Number, required: true },
ticker: { type: String, required: true },
tokenType: { type: Number, required: true },
// Trade data
buyOrSell: { type: String },
numTokens: { type: Number },
rateInBaseUnit: { type: String },
buyOrSell: { type: String, required: true },
numTokens: { type: Number, required: true },
rateInBaseUnit: { type: String, required: true },
minUnitsToExchange: { type: String },
p2wdbTxid: { type: String },
p2wdbHash: { type: String },
makerAddr: { type: String },
makerAddr: { type: String, required: true },
// Authentication data
signature: { type: String },
@@ -32,12 +32,18 @@ const Order = new mongoose.Schema({
offerPubKey: { type: String },
// Wallet Data
hdIndex: { type: Number }, // HD index address holding the UTXO for this offer.
hdIndex: { type: Number, required: true }, // HD index address holding the UTXO for this offer.
// SWaP Protocol Properties
lokadId: { type: String },
messageType: { type: Number },
messageClass: { type: Number }
messageClass: { type: Number },
nostrEventId: { type: String, required: true }, // Nostr Event Id.
// Additional properties found in createOrder
dataType: { type: String, required: true },
userId: { type: String, required: true }
})
export default mongoose.model('order', Order)
+25
View File
@@ -0,0 +1,25 @@
/*
Social Media Account Model.
This model is used to track content creators using the DEX
Nostr social media channel.
*/
import mongoose from 'mongoose'
const SmAccount = new mongoose.Schema({
npub: { type: String, required: true },
bchAddr: { type: String, required: true, default: '' },
pubkey: { type: String, required: true },
pfp: { type: String, default: '' },
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now },
followers: { type: Number, default: 0 },
following: { type: Number, default: 0 },
posts: { type: Number, default: 0 },
likes: { type: Number, default: 0 },
comments: { type: Number, default: 0 },
reposts: { type: Number, default: 0 }
})
export default mongoose.model('smAccount', SmAccount)
+15
View File
@@ -0,0 +1,15 @@
/*
Usage data model
*/
// Global npm libraries
import mongoose from 'mongoose'
const Usage = new mongoose.Schema({
ip: { type: String },
url: { type: String },
method: { type: String },
timestamp: { type: Date }
})
export default mongoose.model('usage', Usage)
+1
View File
@@ -11,6 +11,7 @@ const User = new mongoose.Schema({
name: { type: String },
username: { type: String },
password: { type: String, required: true },
mnemonic: { type: String, required: true },
email: {
type: String,
required: true,
+195
View File
@@ -0,0 +1,195 @@
/*
Adapter library for working with Nostr.
*/
// Global npm libraries
import BchNostr from 'bch-nostr'
import { RelayPool } from 'nostr'
import RetryQueue from '@chris.troutner/retry-queue'
import * as nip19 from 'nostr-tools/nip19'
class NostrAdapter {
constructor (localConfig = { nostrRelay: '', nostrTopic: '' }) {
this.relayWs = localConfig.nostrRelay //
this.topic = localConfig.nostrTopic
this.globalFeed = localConfig.nostrGlobalFeed
if (!this.relayWs) {
throw new Error(
'"nostrRelay" must be passed when instantiate NostrAdapter'
)
}
if (!this.topic) {
throw new Error(
'"nostrTopic" must be passed when instantiate NostrAdapter'
)
}
// Encapsulate dependencies
this.bchNostr = new BchNostr()
this.RelayPool = RelayPool
this.retryQueue = new RetryQueue({
concurrency: 1,
attempts: 5,
retryPeriod: 1000
})
// Bind the 'this' object
this.start = this.start.bind(this)
this.post = this.post.bind(this)
this.read = this.read.bind(this)
this.eventId2note = this.eventId2note.bind(this)
this.pubkey2npub = this.pubkey2npub.bind(this)
this.readGlobalFeed = this.readGlobalFeed.bind(this)
}
// Create nostr keys.
// NOTE: WIF must be provided in this function instead the constructor function
// we can provide it after app wallet creation.
async start (WIF) {
try {
if (!WIF || typeof WIF !== 'string') {
throw new Error('WIF must be a string!')
}
const { privKeyBuf, nostrPubKey } =
this.bchNostr.keys.createNostrPubKeyFromWif({ wif: WIF })
this.privKeyBuf = privKeyBuf
this.nostrPubKey = nostrPubKey
console.log(`Nostr Pub Key : ${nostrPubKey}`)
return true
} catch (error) {
console.log(`Error in nostr.js/start() ${error.message} `)
throw error
}
}
// Post a message over the instance-topic.
async post (msg = '') {
try {
if (!msg || typeof msg !== 'string') {
throw new Error('msg must be a string!')
}
const inObj = {
kind: 867,
privKeyBuf: this.privKeyBuf,
nostrPubKey: this.nostrPubKey,
relayWs: this.relayWs,
msg,
tags: [['t', this.topic]]
}
const eventId = await this.retryQueue.addToQueue(this.bchNostr.post.uploadToNostr, inObj)
return eventId
} catch (error) {
console.log(`Error in nostr.js/post() ${error.message} `)
throw error
}
}
// Read a message over the instance-topic.
async read (limit = 10) {
try {
if (typeof limit !== 'number' || limit < 1) {
throw new Error('Limit must be greater than 0')
}
const relays = [this.relayWs]
const pool = this.RelayPool(relays)
const nostrData = new Promise((resolve, reject) => {
const messages = []
pool.on('open', (relay) => {
// relay.subscribe('REQ', { ids: [eventId] })
relay.subscribe('REQ', { limit, kinds: [867], '#t': [this.topic] })
})
pool.on('eose', (relay) => {
relay.close()
resolve(messages)
})
pool.on('event', (relay, subId, ev) => {
// console.log('ev: ', ev)
messages.push({ content: ev.content, eventId: ev.id })
})
})
const messages = await nostrData
return messages
} catch (error) {
console.log(`Error in nostr.js/read() ${error.message} `)
throw error
}
}
// Convert an Event ID into a `noteabc..` syntax that Astral expects.
// This can be used to generate a link to Astral to display the post.
eventId2note (eventId) {
return nip19.noteEncode(eventId)
}
// Convert a pubkey into a `npubabc..` syntax that Astral expects.
pubkey2npub (pubkey) {
return nip19.npubEncode(pubkey)
}
// Read the global feed.
async readGlobalFeed (inObj = {}) {
try {
const { limit = 10 } = inObj
if (typeof limit !== 'number' || limit < 1) {
throw new Error('Limit must be greater than 0')
}
const relays = [this.relayWs]
const pool = this.RelayPool(relays)
const nostrData = new Promise((resolve, reject) => {
const messages = []
pool.on('open', (relay) => {
// relay.subscribe('REQ', { ids: [eventId] })
relay.subscribe('REQ', { limit, kinds: [1], '#t': [this.globalFeed] })
})
pool.on('eose', (relay) => {
relay.close()
resolve(messages)
})
pool.on('event', (relay, subId, ev) => {
// console.log('ev: ', ev)
const { content, id, tags, pubkey } = ev
const msg = {
content,
id,
tags,
npub: this.pubkey2npub(pubkey),
pubkey
}
messages.push(msg)
})
})
const messages = await nostrData
return messages
} catch (error) {
console.log(`Error in nostr.js/readGlobalFeed() ${error.message} `)
throw error
}
}
}
export default NostrAdapter
+2 -1
View File
@@ -5,10 +5,11 @@
// Public npm libraries.
import axios from 'axios'
import { Write, Read } from 'p2wdb/index.js'
import P2WDB from 'p2wdb-esm'
// Local libraries
import config from '../../config/index.js'
const { Write, Read } = P2WDB
class P2wdbAdapter {
constructor (localConfig = {}) {
+251
View File
@@ -0,0 +1,251 @@
/*
Wallet adapter. This library instantiates minimal-slp-wallet based on different
configuration settings. That single instance of minimal-slp-wallet is then
easily passed around the rest of this application.
*/
// Global npm libraries
import BchWallet from 'minimal-slp-wallet'
// Local libraries
import config from '../../config/index.js'
import JsonFiles from './json-files.js'
class Wallet {
constructor (localConfig = {}) {
// Encapsulate dependencies
this.config = config
this.BchWallet = BchWallet
this.jsonFiles = new JsonFiles()
// Bind 'this' object to all subfunctions
this.instanceWalletWithoutInitialization = this.instanceWalletWithoutInitialization.bind(this)
this._instanceWallet = this._instanceWallet.bind(this)
this.openWallet = this.openWallet.bind(this)
this.instanceWallet = this.instanceWallet.bind(this)
this.incrementNextAddress = this.incrementNextAddress.bind(this)
this.getKeyPair = this.getKeyPair.bind(this)
this.optimize = this.optimize.bind(this)
this.getBalance = this.getBalance.bind(this)
}
// This is used for initializing the wallet, without waiting to update the wallet
// UTXOs from the blockchain.
// This is useful when the wallet is simply needed to make calls to the blockchain,
// and there is no need to hydrate it with UTXO data.
async instanceWalletWithoutInitialization (walletData = {}, advancedConfig = {}) {
try {
// Use the apiToken from the config settings, if one is not passed-in at
// run-time.
if (!advancedConfig.apiToken) {
advancedConfig.apiToken = this.config.apiToken
}
// If an authentication password is passed in the config, add it.
if (this.config.authPass) {
advancedConfig.authPass = this.config.authPass
}
// Detect and configure different blockchain infrastructure settings.
if (this.config.walletInterface === 'web2') {
advancedConfig.interface = 'rest-api'
advancedConfig.restURL = this.config.apiServer
} else {
// By default use the web3 Cash Stack (https://cashstack.info)
advancedConfig.interface = 'consumer-api'
advancedConfig.restURL = this.config.apiServer
}
// console.log('advancedConfig setting when creating wallet: ', advancedConfig)
// Instantiate minimal-slp-wallet.
if (walletData.mnemonic) {
// Instance the wallet using the mnemonic passed in to this function.
console.log('Mnemonic provided to wallet library from calling function.')
this.bchWallet = await this._instanceWallet(walletData.mnemonic, advancedConfig)
} else if (this.config.mnemonic) {
// Otherwise use the mnemonic in the config setting or passed as an environment variable.
console.log('Mnemonic retrieved from config file or environment variable.')
this.bchWallet = await this._instanceWallet(this.config.mnemonic, advancedConfig)
} else {
// If no mnemonic is provided, then generate a new mnemonic to create the wallet.
console.log('New mnemonic generated.')
this.bchWallet = await this._instanceWallet(undefined, advancedConfig)
}
// Wait for wallet to initialize.
await this.bchWallet.walletInfoPromise
console.log('BCH wallet initialized.')
console.log(`Wallet address: ${this.bchWallet.walletInfo.cashAddress}`)
console.log(`Wallet mnemonic: ${this.bchWallet.walletInfo.mnemonic}`)
return this.bchWallet
} catch (err) {
console.error('Error in instanceWalletWithoutInitialization()')
throw err
}
}
// This function is used for easier mocking of unit tests.
async _instanceWallet (mnemonic, config) {
const wallet = new this.BchWallet(mnemonic, config)
await wallet.walletInfoPromise
return wallet
}
// Open the wallet file, or create one if the file doesn't exist.
// Does not instance the wallet. The output of this function is expected to
// be passed to instanceWallet().
async openWallet (advancedConfig = {}) {
try {
let walletData
// Try to open the wallet.json file.
try {
walletData = await this.jsonFiles.readJSON(this.config.walletFile)
walletData = walletData.wallet
} catch (err) {
// Create a new wallet file if one does not already exist.
console.log('Wallet file not found. Creating new wallet.json file.')
// Create a new wallet.
// No-Update flag creates wallet without making any network calls.
const walletInstance = await this.instanceWalletWithoutInitialization({}, advancedConfig)
walletData = walletInstance.walletInfo
// Add the nextAddress property
walletData.nextAddress = 1
// Write the wallet data to the JSON file.
await this.jsonFiles.writeJSON({ wallet: walletData }, this.config.walletFile)
}
// console.log('walletData: ', walletData)
return walletData
} catch (err) {
console.error('Error in openWallet()')
throw err
}
}
// Create an instance of minimal-slp-wallet. Same as
// instanceWalletWithoutInitialization(), but waits for the wallet to initialize
// its UTXOs (wallet balance and tokens).
async instanceWallet (walletData = {}, advancedConfig = {}) {
try {
// Instance the wallet without initialization.
await this.instanceWalletWithoutInitialization(walletData, advancedConfig)
// Initialize the wallet
await this.bchWallet.initialize()
return this.bchWallet
} catch (err) {
console.error('Error in wallet.js/instanceWallet()')
throw err
}
}
// Increments the 'nextAddress' property in the wallet file. This property
// indicates the HD index that should be used to generate a key pair for
// storing funds for Offers.
// This function opens the wallet file, increments the nextAddress property,
// then saves the change to the wallet file.
async incrementNextAddress () {
try {
const walletData = await this.openWallet()
await this.instanceWalletWithoutInitialization(walletData)
walletData.nextAddress++
// console.log('walletData finish: ', walletData)
await this.jsonFiles.writeJSON({ wallet: walletData }, this.config.walletFile)
// Update the working instance of the wallet.
this.bchWallet.walletInfo.nextAddress++
// console.log('this.bchWallet.walletInfo: ', this.bchWallet.walletInfo)
return walletData.nextAddress
} catch (err) {
console.error('Error in incrementNextAddress()')
throw err
}
}
// This method returns an object that contains a private key WIF, public address,
// and the index of the HD wallet that the key pair was generated from.
// TODO: Allow input integer. If input is used, use that as the index. If no
// input is provided, then call incrementNextAddress().
async getKeyPair (hdIndex = 0) {
try {
if (!hdIndex) {
// Increment the HD index and generate a new key pair.
hdIndex = await this.incrementNextAddress()
}
const mnemonic = this.bchWallet.walletInfo.mnemonic
// root seed buffer
const rootSeed = await this.bchWallet.bchjs.Mnemonic.toSeed(mnemonic)
const masterHDNode = this.bchWallet.bchjs.HDNode.fromSeed(rootSeed)
// HDNode of BIP44 account
// const account = this.bchWallet.bchjs.HDNode.derivePath(masterHDNode, "m/44'/245'/0'")
const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`)
const cashAddress = this.bchWallet.bchjs.HDNode.toCashAddress(childNode)
console.log('Generating a new key pair for cashAddress: ', cashAddress)
const wif = this.bchWallet.bchjs.HDNode.toWIF(childNode)
const outObj = {
cashAddress,
wif,
hdIndex
}
return outObj
} catch (err) {
console.error('Error in getKeyPair()')
throw err
}
}
// Optimize the wallet by consolidating the UTXOs.
async optimize () {
const UTXO_THREASHOLD = 7
// Do a dry-run first to see if there are enough UTXOs worth consolidating.
const dryRunOut = await this.bchWallet.optimize(true)
if (dryRunOut.bchUtxoCnt > UTXO_THREASHOLD) {
// Consolidate BCH UTXOs if the count is above the threashold.
const txids = await this.bchWallet.optimize()
console.log(`Wallet optimized with these return values: ${JSON.stringify(txids, null, 2)}`)
}
return true
}
// Get the balance of the wallet in sats and PSF tokens.
// This function is called by the GET /entry/balance controller.
async getBalance () {
const balance = await this.bchWallet.getBalance()
// console.log('balance: ', balance)
const tokens = await this.bchWallet.listTokens()
// console.log('tokens: ', tokens)
// Find the array entry for the PSF token
const psfTokens = tokens.find(x => x.tokenId === '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0')
// console.log('psfTokens: ', psfTokens)
let psfBalance = 0
if (psfTokens) {
psfBalance = psfTokens.qty
}
const outObj = {
satBalance: balance,
psfBalance,
success: true
}
return outObj
}
}
export default Wallet
+152 -12
View File
@@ -1,15 +1,17 @@
/*
Adapter library for working with a wallet.
This library is specific to bch-dex. The other wallet adapter library is
inherited from ipfs-service-provider.
*/
// Public npm libraries
import BchWallet from 'minimal-slp-wallet/index.js'
import BchWallet from 'minimal-slp-wallet'
import BchTokenSweep from 'bch-token-sweep/index.js'
import bitcoinJs from 'bitcoincashjs-lib'
import RetryQueue from '@chris.troutner/retry-queue'
// Local libraries
import JsonFiles from './json-files.js'
import config from '../../config/index.js'
// Hack to get __dirname back.
@@ -22,6 +24,8 @@ const WALLET_FILE = `${__dirname.toString()}/../../wallet.json`
// const P2WDB_TOKEN_ID =
// '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
const advancedConfig = {}
class WalletAdapter {
constructor (localConfig = {}) {
// Encapsulate dependencies
@@ -31,9 +35,26 @@ class WalletAdapter {
this.bitcoinJs = bitcoinJs
this.BchWallet = BchWallet
this.bchWallet = {} // Will be replaced when initialized.
// this.advancedConfig = localConfig.advancedConfig
this.retryQueue = new RetryQueue({
concurrency: 1,
attempts: 5,
retryPeriod: 5000
})
// Bind the 'this' object
this.openWallet = this.openWallet.bind(this)
this.instanceWallet = this.instanceWallet.bind(this)
this.incrementNextAddress = this.incrementNextAddress.bind(this)
this.getKeyPair = this.getKeyPair.bind(this)
this.generateSignature = this.generateSignature.bind(this)
this.generatePartialTx = this.generatePartialTx.bind(this)
this.moveTokens = this.moveTokens.bind(this)
this.moveBch = this.moveBch.bind(this)
this.deseralizeTx = this.deseralizeTx.bind(this)
this.completeTx = this.completeTx.bind(this)
this.reclaimTokens = this.reclaimTokens.bind(this)
this.moveTokensFromCustomWallet = this.moveTokensFromCustomWallet.bind(this)
}
// Open the wallet file, or create one if the file doesn't exist.
@@ -87,7 +108,7 @@ class WalletAdapter {
throw new Error('Wallet data is not formatted correctly. Can not read mnemonic in wallet file!')
}
const advancedConfig = {}
// const advancedConfig = {}
console.log(`Using FullStack.cash: ${this.config.useFullStackCash}`)
if (this.config.useFullStackCash) {
advancedConfig.interface = 'rest-api'
@@ -140,10 +161,9 @@ class WalletAdapter {
}
}
// Note: This method uses the system wallet, not the user or admin wallet.
// This method returns an object that contains a private key WIF, public address,
// and the index of the HD wallet that the key pair was generated from.
// TODO: Allow input integer. If input is used, use that as the index. If no
// input is provided, then call incrementNextAddress().
async getKeyPair (hdIndex = 0) {
try {
if (!hdIndex) {
@@ -152,6 +172,7 @@ class WalletAdapter {
}
const mnemonic = this.bchWallet.walletInfo.mnemonic
console.log(`getKeyPair() for HD index: ${hdIndex} called on mnemonic: ${mnemonic}`)
// root seed buffer
const rootSeed = await this.bchWallet.bchjs.Mnemonic.toSeed(mnemonic)
@@ -341,7 +362,7 @@ class WalletAdapter {
throw err
}
// return true
// return true
}
// Move tokens to an address controlled by the HD wallet, to generate a
@@ -437,18 +458,33 @@ class WalletAdapter {
return txObj2
} catch (err) {
console.error('Error in wallet.js/deserializePartialTx()')
console.error('Error in wallet.js/deserializeTx()')
throw err
}
}
// Complete the partially signed transaction by signing the first input,
// then broadcasting the transaction to the network.
async completeTx (hex, hdIndex) {
async completeTx (hex, hdIndex, mnemonic) {
try {
// Input validation
if (!hex || typeof hex !== 'string') {
throw new Error('hex must be a string!')
}
if (typeof hdIndex !== 'number' || hdIndex < 0) {
throw new Error('hdIndex must be a non-negative number!')
}
if (!mnemonic || typeof mnemonic !== 'string') {
throw new Error('mnemonic must be a string!')
}
// console.log('hex: ', hex)
console.log('completeTx() hdIndex: ', hdIndex)
console.log('completeTx() mnemonic: ', mnemonic)
const bchjs = this.bchWallet.bchjs
// Instantiate the user wallet
const userWallet = await this.instanceWallet({ mnemonic })
const bchjs = userWallet.bchjs
// instance of transaction builder
// const transactionBuilder = new bchjs.TransactionBuilder()
@@ -471,8 +507,10 @@ class WalletAdapter {
// 'mainnet'
// )
console.log('completeTx() userWallet.walletInfo: ', userWallet.walletInfo)
// Get the keypair for the address used in the Order
const keyPair = await this.getKeyPair(hdIndex)
const keyPair = await userWallet.getKeyPair(hdIndex)
console.log(`maker keyPair: ${JSON.stringify(keyPair, null, 2)}`)
const makerECPair = bchjs.ECPair.fromWIF(keyPair.wif)
@@ -504,7 +542,9 @@ class WalletAdapter {
// return csTxHex
// Broadcast transaction to the network
const txid = await this.bchWallet.ar.sendTx(csTxHex)
// const txid = await this.bchWallet.ar.sendTx(csTxHex)
const txid = await this.retryQueue.addToQueue(this.bchWallet.broadcast, { hex: csTxHex })
console.log('completeTx() txid: ', txid)
return txid
} catch (err) {
@@ -512,6 +552,106 @@ class WalletAdapter {
throw err
}
}
// Given an Order database object, this function sends the tokens from the
// HD index where they are stored, back to the root address of the wallet.
async reclaimTokens (orderData) {
try {
// Get the key pair from the Order.
const keyPair = await this.getKeyPair(orderData.hdIndex)
console.log('keyPair: ', keyPair)
// Instantiate the wallet using the private key of the Order.
// const tempWallet = new this.BchWallet(keyPair.wif, advancedConfig)
// await tempWallet.initialize()
// Send the tokens from the Order to the root address.
// const receiver = {
// address: this.bchWallet.walletInfo.address,
// tokenId: orderData.tokenId,
// qty: orderData.numTokens
// }
// console.log('receiver: ', receiver)
//
// const txid = await tempWallet.sendTokens(receiver)
// Sweep the tokens to the root address.
const sweeper = new BchTokenSweep(
keyPair.wif,
this.bchWallet.walletInfo.privateKey,
this.bchWallet,
550,
this.bchWallet.walletInfo.cashAddress
)
await sweeper.populateObjectFromNetwork()
const hex = await sweeper.sweepTo(this.bchWallet.walletInfo.cashAddress)
const txid = await this.bchWallet.ar.sendTx(hex)
return txid
} catch (err) {
console.error('Error in wallet.js/reclaimTokens()')
throw err
}
}
// This function is consumed by the use-cases/order.js/createOrder() function.
// Tokens are moved from the primary address to a holding address to protect the Order UTXO.
async moveTokensFromCustomWallet (inObj = {}) {
try {
const { tokenId, qty, wallet } = inObj
// Input validation
if (!tokenId || typeof tokenId !== 'string') {
throw new Error('tokenId must be a string!')
}
if (!qty) {
throw new Error('qty must be a number!')
}
if (!wallet) {
throw new Error('wallet is required!')
}
// Replace the system wallet with the user wallet.
// this.bchWallet = wallet
// DEV Note: Storing all token Offers in index 2 of the HD wallet.
// This makes them easy to sweep, and can be uniquely identified by their UTXO.
// Note: use walle.getKeyPair() to use the user wallet. this.getKeyPair uses the system wallet.
const keyPair = await wallet.getKeyPair(2)
console.log('keyPair: ', keyPair)
const receiver = {
address: keyPair.cashAddress,
tokenId,
qty
}
console.log('receiver: ', receiver)
// Update the UTXO store of the wallet.
await wallet.initialize()
// Get the token type of the token being moved.
// Combine Fungible and NFT token UTXOs.
let tokenUtxos = wallet.utxos.utxoStore.slpUtxos.type1.tokens.concat(
wallet.utxos.utxoStore.slpUtxos.nft.tokens)
// Get token UTXOs that match the token in the order.
tokenUtxos = tokenUtxos.filter(
x => x.tokenId === tokenId
)
const txid = await wallet.sendTokens(receiver, 3)
const utxoInfo = {
txid,
vout: 1,
hdIndex: keyPair.hdIndex,
tokenType: tokenUtxos[0].tokenType
}
return utxoInfo
} catch (err) {
console.error('Error in wallet.js/moveTokensFromCustomWallet()')
throw err
}
}
}
export default WalletAdapter
+17 -12
View File
@@ -6,28 +6,28 @@
// Public npm libraries.
// Load the Clean Architecture Adapters library
// Local libraries
import Adapters from '../adapters/index.js'
// Load the JSON RPC Controller.
import JSONRPC from './json-rpc/index.js'
// Load the Clean Architecture Use Case libraries.
import UseCases from '../use-cases/index.js'
// const useCases = new UseCases({ adapters })
// Load the REST API Controllers.
import RESTControllers from './rest-api/index.js'
import TimerControllers from './timer-controllers.js'
import config from '../../config/index.js'
class Controllers {
constructor (localConfig = {}) {
// Encapsulate dependencies
this.adapters = new Adapters()
this.useCases = new UseCases({ adapters: this.adapters })
this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases })
this.config = config
// Bind 'this' object to all subfunction
this.initAdapters = this.initAdapters.bind(this)
this.initUseCases = this.initUseCases.bind(this)
this.attachRESTControllers = this.attachRESTControllers.bind(this)
this.attachControllers = this.attachControllers.bind(this)
this.attachRPCControllers = this.attachRPCControllers.bind(this)
}
// Spin up any adapter libraries that have async startup needs.
@@ -54,8 +54,13 @@ class Controllers {
// Attach any other controllers other than REST API controllers.
async attachControllers (app) {
if (this.config.useIpfs) {
// Attach JSON RPC controllers
this.attachRPCControllers()
}
// this.attachRPCControllers()
// Attach and start the timer controllers
this.timerControllers.startTimers()
}
// Add the JSON RPC router to the ipfs-coord adapter.
+28 -7
View File
@@ -11,10 +11,14 @@ import AuthRESTController from './auth/index.js'
import UserRouter from './users/index.js'
import ContactRESTController from './contact/index.js'
import LogsRESTController from './logs/index.js'
import IpfsRESTController from './ipfs/index.js'
import config from '../../../config/index.js'
import EntryRouter from './entry/index.js'
import OfferRouter from './offer/index.js'
import OrderRouter from './order/index.js'
import P2WDBRouter from './p2wdb/index.js'
import UsageRESTController from './usage/index.js'
import SmAccountRouter from './smAccount/index.js'
class RESTControllers {
constructor (localConfig = {}) {
@@ -32,7 +36,11 @@ class RESTControllers {
)
}
// console.log('Controllers localConfig: ', localConfig)
// Bind 'this' object to all subfunctions.
this.attachRESTControllers = this.attachRESTControllers.bind(this)
// Encapsulate dependencies
this.config = config
}
attachRESTControllers (app) {
@@ -41,13 +49,15 @@ class RESTControllers {
useCases: this.useCases
}
// Attach the REST API Controllers associated with the /auth route
const authRESTController = new AuthRESTController(dependencies)
authRESTController.attach(app)
if (!this.config.noMongo) {
// Attach the REST API Controllers associated with the /auth route
const authRESTController = new AuthRESTController(dependencies)
authRESTController.attach(app)
// Attach the REST API Controllers associated with the /user route
const userRouter = new UserRouter(dependencies)
userRouter.attach(app)
// Attach the REST API Controllers associated with the /user route
const userRouter = new UserRouter(dependencies)
userRouter.attach(app)
}
// Attach the REST API Controllers associated with the /contact route
const contactRESTController = new ContactRESTController(dependencies)
@@ -57,6 +67,10 @@ class RESTControllers {
const logsRESTController = new LogsRESTController(dependencies)
logsRESTController.attach(app)
// Attach the REST API Controllers associated with the /ipfs route
const ipfsRESTController = new IpfsRESTController(dependencies)
ipfsRESTController.attach(app)
// Attach the REST API Controllers associated with the /entry route
const entryRouter = new EntryRouter(dependencies)
entryRouter.attach(app)
@@ -69,6 +83,13 @@ class RESTControllers {
const p2wdbRouter = new P2WDBRouter(dependencies)
p2wdbRouter.attach(app)
// Attach the REST API Controllers associated with the /usage route
const usageRESTController = new UsageRESTController(dependencies)
usageRESTController.attach(app)
const smAccountRouter = new SmAccountRouter(dependencies)
smAccountRouter.attach(app)
}
}
+145
View File
@@ -0,0 +1,145 @@
/*
REST API Controller library for the /ipfs route
*/
// Global npm libraries
// Local libraries
import wlogger from '../../../adapters/wlogger.js'
class IpfsRESTControllerLib {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating /ipfs REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating /ipfs REST Controller.'
)
}
// Encapsulate dependencies
// this.UserModel = this.adapters.localdb.Users
// this.userUseCases = this.useCases.user
// Bind 'this' object to all subfunctions
this.getStatus = this.getStatus.bind(this)
this.getPeers = this.getPeers.bind(this)
this.getRelays = this.getRelays.bind(this)
this.handleError = this.handleError.bind(this)
this.connect = this.connect.bind(this)
this.getThisNode = this.getThisNode.bind(this)
}
/**
* @api {get} /ipfs Get status on IPFS infrastructure
* @apiPermission public
* @apiName GetIpfsStatus
* @apiGroup REST BCH
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs
*
*/
async getStatus (ctx) {
try {
const status = await this.adapters.ipfs.getStatus()
ctx.body = { status }
} catch (err) {
wlogger.error('Error in ipfs/controller.js/getStatus(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
// Return information on IPFS peers this node is connected to.
async getPeers (ctx) {
try {
const showAll = ctx.request.body.showAll
const peers = await this.adapters.ipfs.getPeers(showAll)
ctx.body = { peers }
} catch (err) {
wlogger.error('Error in ipfs/controller.js/getPeers(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
// Get data about the known Circuit Relays. Hydrate with data from peers list.
async getRelays (ctx) {
try {
const relays = await this.adapters.ipfs.getRelays()
ctx.body = { relays }
} catch (err) {
wlogger.error('Error in ipfs/controller.js/getRelays(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
async connect (ctx) {
try {
const multiaddr = ctx.request.body.multiaddr
const getDetails = ctx.request.body.getDetails
// console.log('this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs: ', this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs)
const result = await this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs.connectToPeer({ multiaddr, getDetails })
// console.log('result: ', result)
ctx.body = result
} catch (err) {
wlogger.error('Error in ipfs/controller.js/connect():', err)
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
/**
* @api {get} /ipfs/node Get a copy of the thisNode object from helia-coord
* @apiPermission public
* @apiName GetThisNode
* @apiGroup REST BCH
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/node
*
*/
async getThisNode (ctx) {
try {
const thisNode = this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
ctx.body = { thisNode }
} catch (err) {
wlogger.error('Error in ipfs/controller.js/getThisNode(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// If an HTTP status is specified by the buisiness logic, use that.
if (err.status) {
if (err.message) {
ctx.throw(err.status, err.message)
} else {
ctx.throw(err.status)
}
} else {
// By default use a 422 error if the HTTP status is not specified.
ctx.throw(422, err.message)
}
}
}
// module.exports = IpfsRESTControllerLib
export default IpfsRESTControllerLib
+67
View File
@@ -0,0 +1,67 @@
/*
REST API library for the /ipfs route.
*/
// Public npm libraries.
import Router from 'koa-router'
// Local libraries.
import IPFSRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
// let _this
class IpfsRouter {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating IPFS REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating IPFS REST Controller.'
)
}
const dependencies = {
adapters: this.adapters,
useCases: this.useCases
}
// Encapsulate dependencies.
this.ipfsRESTController = new IPFSRESTControllerLib(dependencies)
this.validators = new Validators()
// Instantiate the router and set the base route.
const baseUrl = '/ipfs'
this.router = new Router({ prefix: baseUrl })
// _this = this
}
attach (app) {
if (!app) {
throw new Error(
'Must pass app object when attaching REST API controllers.'
)
}
// Define the routes and attach the controller.
this.router.get('/', this.ipfsRESTController.getStatus)
this.router.post('/peers', this.ipfsRESTController.getPeers)
this.router.post('/relays', this.ipfsRESTController.getRelays)
this.router.post('/connect', this.ipfsRESTController.connect)
this.router.get('/node', this.ipfsRESTController.getThisNode)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
app.use(this.router.allowedMethods())
}
}
// module.exports = BchRouter
export default IpfsRouter
+19 -15
View File
@@ -4,8 +4,6 @@
import wlogger from '../../../adapters/wlogger.js'
let _this
class OfferRESTControllerLib {
constructor (localConfig = {}) {
// Dependency Injection.
@@ -26,7 +24,13 @@ class OfferRESTControllerLib {
this.OfferModel = this.adapters.localdb.Offer
// this.userUseCases = this.useCases.user
_this = this
// Bind 'this' object to all subfunctions.
this.createOffer = this.createOffer.bind(this)
this.listOffers = this.listOffers.bind(this)
this.listNftOffers = this.listNftOffers.bind(this)
this.listFungibleOffers = this.listFungibleOffers.bind(this)
this.takeOffer = this.takeOffer.bind(this)
this.handleError = this.handleError.bind(this)
}
// No api-doc documentation because this wont be a public endpoint
@@ -36,7 +40,7 @@ class OfferRESTControllerLib {
const offerObj = ctx.request.body
await _this.useCases.offer.createOffer(offerObj)
await this.useCases.offer.createOffer(offerObj)
ctx.body = {
success: true
@@ -45,7 +49,7 @@ class OfferRESTControllerLib {
// console.log(`err.message: ${err.message}`)
// console.log('err: ', err)
// ctx.throw(422, err.message)
_this.handleError(ctx, err)
this.handleError(ctx, err)
}
}
@@ -55,12 +59,12 @@ class OfferRESTControllerLib {
let page = ctx.params.page
if (!page) page = 0
const offers = await _this.useCases.offer.listOffers(page)
const offers = await this.useCases.offer.listOffers(page)
ctx.body = offers
} catch (err) {
console.log('Error in listOffers REST API handler: ', err)
_this.handleError(ctx, err)
this.handleError(ctx, err)
}
}
@@ -70,12 +74,12 @@ class OfferRESTControllerLib {
let page = ctx.params.page
if (!page) page = 0
const offers = await _this.useCases.offer.listNftOffers(page)
const offers = await this.useCases.offer.listNftOffers(page)
ctx.body = offers
} catch (err) {
console.log('Error in listNftOffers REST API handler: ', err)
_this.handleError(ctx, err)
this.handleError(ctx, err)
}
}
@@ -85,12 +89,12 @@ class OfferRESTControllerLib {
let page = ctx.params.page
if (!page) page = 0
const offers = await _this.useCases.offer.listFungibleOffers(page)
const offers = await this.useCases.offer.listFungibleOffers(page)
ctx.body = offers
} catch (err) {
console.log('Error in Fungible REST API handler: ', err)
_this.handleError(ctx, err)
this.handleError(ctx, err)
}
}
@@ -100,14 +104,14 @@ class OfferRESTControllerLib {
try {
console.log('REST API controller, body: ', ctx.request.body)
const offerCid = ctx.request.body.offerCid
const nostrEventId = ctx.request.body.nostrEventId
const hash = await _this.useCases.offer.takeOffer(offerCid)
const { eventId, noteId } = await this.useCases.offer.takeOffer(nostrEventId)
ctx.body = { hash }
ctx.body = { eventId, noteId }
} catch (err) {
wlogger.error('Error in takeOffer() REST API handler.')
_this.handleError(ctx, err)
this.handleError(ctx, err)
}
}
+6 -1
View File
@@ -48,8 +48,13 @@ class OfferRouter {
)
}
// 12/3/24 CT:
// Note: The createOffer() path was used by a P2WDB webhook to generate an
// Offer from and Order. This has been deprecated and Offers are now created
// by a Timer Controller monitoring a Nostr topic.
// Define the routes and attach the controller.
this.router.post('/', _this.offerRESTController.createOffer)
// this.router.post('/', _this.offerRESTController.createOffer) // Deprecated.
this.router.post('/take', _this.offerRESTController.takeOffer)
this.router.get('/list/all/:page', _this.offerRESTController.listOffers)
this.router.get('/list/nft/:page', _this.offerRESTController.listNftOffers)
+40 -2
View File
@@ -35,10 +35,48 @@ class OrderRESTControllerLib {
// console.log('body: ', ctx.request.body)
const orderObj = ctx.request.body.order
const user = ctx.state.user
orderObj.userId = user._id
console.log('orderObj: ', orderObj)
const hash = await _this.useCases.order.createOrder(orderObj)
const { eventId, noteId } = await _this.useCases.order.createOrder(orderObj)
ctx.body = { hash }
ctx.body = { eventId, noteId }
} catch (err) {
// console.log(`err.message: ${err.message}`)
// console.log('err: ', err)
// ctx.throw(422, err.message)
_this.handleError(ctx, err)
}
}
// curl -X GET http://localhost:5700/order/list/all/0
async listOrders (ctx) {
try {
let page = ctx.params.page
if (!page) page = 0
const offers = await _this.useCases.order.listOrders(page)
ctx.body = offers
} catch (err) {
console.log('Error in listOrders REST API handler: ', err)
_this.handleError(ctx, err)
}
}
// Delete an existing order by returning the token to the root address of
// the DEX wallet.
async deleteOrder (ctx) {
try {
// console.log('body: ', ctx.request.body)
const nostrEventId = ctx.request.body.nostrEventId
// console.log('p2wdbHash: ', p2wdbHash)
const txid = await _this.useCases.order.deleteOrder(nostrEventId)
ctx.body = { txid }
} catch (err) {
// console.log(`err.message: ${err.message}`)
// console.log('err: ', err)
+11 -1
View File
@@ -7,6 +7,7 @@ import Router from 'koa-router'
// Local libraries.
import OrderRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
let _this
@@ -32,12 +33,14 @@ class OrderRouter {
}
// Encapsulate dependencies.
this.validators = new Validators()
this.orderRESTController = new OrderRESTControllerLib(dependencies)
// Instantiate the router and set the base route.
const baseUrl = '/order'
this.router = new Router({ prefix: baseUrl })
this.createOrder = this.createOrder.bind(this)
_this = this
}
@@ -49,12 +52,19 @@ class OrderRouter {
}
// Define the routes and attach the controller.
this.router.post('/', _this.orderRESTController.createOrder)
this.router.post('/', this.createOrder)
this.router.get('/list/all/:page', _this.orderRESTController.listOrders)
this.router.post('/delete', _this.orderRESTController.deleteOrder)
// Attach the Controller routes to the Koa app.
app.use(_this.router.routes())
app.use(_this.router.allowedMethods())
}
async createOrder (ctx, next) {
await _this.validators.ensureUser(ctx, next)
await _this.orderRESTController.createOrder(ctx, next)
}
}
export default OrderRouter
@@ -0,0 +1,65 @@
/*
REST API Controller library for the /order route
*/
// const { wlogger } = require('../../../adapters/wlogger')
let _this
class SmAccountRESTControllerLib {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating /order REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating /order REST Controller.'
)
}
// Encapsulate dependencies
this.SmAccountModel = this.adapters.localdb.SmAccount
// this.userUseCases = this.useCases.user
_this = this
}
// curl -X GET http://localhost:5700/sm/list/all/0
async listAccounts (ctx) {
try {
let page = ctx.params.page
if (!page) page = 0
const offers = await _this.useCases.smAccount.listAccounts(page)
ctx.body = offers
} catch (err) {
console.log('Error in listAccounts REST API handler: ', err)
_this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
console.log('err', err)
// If an HTTP status is specified by the buisiness logic, use that.
if (err.status) {
if (err.message) {
ctx.throw(err.status, err.message)
} else {
ctx.throw(err.status)
}
} else {
// By default use a 422 error if the HTTP status is not specified.
ctx.throw(422, err.message)
}
}
}
export default SmAccountRESTControllerLib
@@ -0,0 +1,68 @@
/*
REST API library for /order route.
*/
// Public npm libraries.
import Router from 'koa-router'
// Local libraries.
import SmAccountRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
let _this
class SmAccountRouter {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating /sm REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating /sm REST Controller.'
)
}
const dependencies = {
adapters: this.adapters,
useCases: this.useCases
}
// Encapsulate dependencies.
this.validators = new Validators()
this.smAccountRESTController = new SmAccountRESTControllerLib(dependencies)
// Instantiate the router and set the base route.
const baseUrl = '/sm'
this.router = new Router({ prefix: baseUrl })
this.listAccounts = this.listAccounts.bind(this)
_this = this
}
attach (app) {
if (!app) {
throw new Error(
'Must pass app object when attached REST API controllers.'
)
}
// Define the routes and attach the controller.
this.router.get('/list/all/:page', _this.smAccountRESTController.listAccounts)
// Attach the Controller routes to the Koa app.
app.use(_this.router.routes())
app.use(_this.router.allowedMethods())
}
async listAccounts (ctx, next) {
// await _this.validators.ensureUser(ctx, next)
await _this.smAccountRESTController.listAccounts(ctx, next)
}
}
export default SmAccountRouter
@@ -0,0 +1,119 @@
/*
REST API Controller library for the /usage route
*/
// Global npm libraries
// Local libraries
import wlogger from '../../../adapters/wlogger.js'
class UsageRESTControllerLib {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating /usage REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating /usage REST Controller.'
)
}
// Encapsulate dependencies
// Bind 'this' object to all subfunctions
this.getStatus = this.getStatus.bind(this)
this.getTopIps = this.getTopIps.bind(this)
this.getTopEndpoints = this.getTopEndpoints.bind(this)
this.handleError = this.handleError.bind(this)
}
/**
* @api {get} /usage Get status on IPFS infrastructure
* @apiPermission public
* @apiName GetUsageStatus
* @apiGroup REST Usage
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage
*
*/
getStatus (ctx) {
try {
// const status = await this.adapters.ipfs.getStatus()
const status = this.useCases.usage.getRestSummary()
ctx.body = { status }
} catch (err) {
wlogger.error('Error in usage/controller.js/getStatus(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
/**
* @api {get} /usage/ips Get top IP addresses consuming the REST API
* @apiPermission public
* @apiName GetUsageIPs
* @apiGroup REST Usage
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage/ips
*
*/
getTopIps (ctx) {
try {
const ips = this.useCases.usage.getTopIps()
ctx.body = { ips }
} catch (err) {
wlogger.error('Error in usage/controller.js/getTopIps(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
/**
* @api {get} /usage/endpoints Get top endpoints consumed from the REST API
* @apiPermission public
* @apiName GetUsageEndpoints
* @apiGroup REST Usage
*
* @apiExample Example usage:
* curl -H "Content-Type: application/json" -X GET localhost:5020/usage/endpoints
*
*/
getTopEndpoints (ctx) {
try {
const endpoints = this.useCases.usage.getTopEndpoints()
ctx.body = { endpoints }
} catch (err) {
wlogger.error('Error in usage/controller.js/getTopEndpoints(): ')
// ctx.throw(422, err.message)
this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
// If an HTTP status is specified by the buisiness logic, use that.
if (err.status) {
if (err.message) {
ctx.throw(err.status, err.message)
} else {
ctx.throw(err.status)
}
} else {
// By default use a 422 error if the HTTP status is not specified.
ctx.throw(422, err.message)
}
}
}
// module.exports = IpfsRESTControllerLib
export default UsageRESTControllerLib
+65
View File
@@ -0,0 +1,65 @@
/*
REST API library for the /usage route.
*/
// Public npm libraries.
import Router from 'koa-router'
// Local libraries.
import UsageRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
// let _this
class UsageRouter {
constructor (localConfig = {}) {
// Dependency Injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of Adapters library required when instantiating IPFS REST Controller.'
)
}
this.useCases = localConfig.useCases
if (!this.useCases) {
throw new Error(
'Instance of Use Cases library required when instantiating IPFS REST Controller.'
)
}
const dependencies = {
adapters: this.adapters,
useCases: this.useCases
}
// Encapsulate dependencies.
this.usageRESTController = new UsageRESTControllerLib(dependencies)
this.validators = new Validators()
// Instantiate the router and set the base route.
const baseUrl = '/usage'
this.router = new Router({ prefix: baseUrl })
// _this = this
}
attach (app) {
if (!app) {
throw new Error(
'Must pass app object when attaching REST API controllers.'
)
}
// Define the routes and attach the controller.
this.router.get('/', this.usageRESTController.getStatus)
this.router.get('/ips', this.usageRESTController.getTopIps)
this.router.get('/endpoints', this.usageRESTController.getTopEndpoints)
// Attach the Controller routes to the Koa app.
app.use(this.router.routes())
app.use(this.router.allowedMethods())
}
}
// module.exports = BchRouter
export default UsageRouter
+1 -1
View File
@@ -124,7 +124,7 @@ class UserRESTControllerLib {
ctx.body = { users }
} catch (err) {
wlogger.error('Error in users/controller.js/getUsers(): '.err)
wlogger.error('Error in users/controller.js/getUsers(): ', err)
ctx.throw(422, err.message)
}
}
+14 -3
View File
@@ -10,6 +10,8 @@ import UserRESTControllerLib from './controller.js'
import Validators from '../middleware/validators.js'
import config from '../../../../config/index.js'
let _this
class UserRouter {
@@ -34,6 +36,7 @@ class UserRouter {
}
// Encapsulate dependencies.
this.config = config
this.userRESTController = new UserRESTControllerLib(dependencies)
this.validators = new Validators()
@@ -52,7 +55,7 @@ class UserRouter {
}
// Define the routes and attach the controller.
this.router.post('/', this.userRESTController.createUser)
this.router.post('/', this.createUser)
this.router.get('/', this.getAll)
this.router.get('/:id', this.getById)
this.router.put('/:id', this.updateUser)
@@ -63,13 +66,21 @@ class UserRouter {
app.use(this.router.allowedMethods())
}
async createUser (ctx, next) {
if (process.env.DISABLE_NEW_ACCOUNTS) {
await _this.validators.ensureAdmin(ctx, next)
}
await _this.userRESTController.createUser(ctx, next)
return true
}
async getAll (ctx, next) {
await _this.validators.ensureUser(ctx, next)
await _this.validators.ensureAdmin(ctx, next)
await _this.userRESTController.getUsers(ctx, next)
}
async getById (ctx, next) {
await _this.validators.ensureUser(ctx, next)
await _this.validators.ensureTargetUserOrAdmin(ctx, next)
await _this.userRESTController.getUser(ctx, next)
}
+124 -11
View File
@@ -1,10 +1,9 @@
/*
This Controller library is concerned with timer-based functions that are
kicked off periodicially.
kicked off periodically.
*/
// Used to retain scope of 'this', when the scope is lost.
let _this
import config from '../../config/index.js'
class TimerControllers {
constructor (localConfig = {}) {
@@ -24,45 +23,159 @@ class TimerControllers {
this.debugLevel = localConfig.debugLevel
_this = this
// Constants
this.cleanUsageInterval = 60000 * 60 // 1 hour
this.backupUsageInterval = 60000 * 10 // 10 minutes
this.newSmAccountsInterval = 60000 * 60 // 60 minutes
this.startTimers()
// Encapsulate dependencies
this.config = config
// Bind 'this' object to all subfunctions
this.startTimers = this.startTimers.bind(this)
this.gcOrders = this.gcOrders.bind(this)
this.gcOffers = this.gcOffers.bind(this)
this.checkDupOffers = this.checkDupOffers.bind(this)
this.loadOffers = this.loadOffers.bind(this)
this.cleanUsage = this.cleanUsage.bind(this)
this.backupUsage = this.backupUsage.bind(this)
this.newSmAccounts = this.newSmAccounts.bind(this)
// State
this.gcOrdersInt = null
this.gcOffersInt = null
this.checkDupOffersInt = null
this.loadOffersInt = null
this.newSmAccountsInt = null
}
// Start all the time-based controllers.
startTimers () {
setInterval(this.gcOrders, 60000 * 5)
setInterval(this.gcOffers, 60000 * 5)
setInterval(this.checkDupOffers, 60000 * 4.5)
this.gcOrdersInt = setInterval(this.gcOrders, 60000 * 5)
this.gcOffersInt = setInterval(this.gcOffers, 60000 * 5)
// this.checkDupOffersInt = setInterval(this.checkDupOffers, 60000 * 4.5)
this.loadOffersInt = setInterval(this.loadOffers, 60000 * 2)
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
this.newSmAccountsInt = setInterval(this.newSmAccounts, this.newSmAccountsInterval)
return true
}
stopTimers () {
clearInterval(this.gcOrdersInt)
clearInterval(this.gcOffersInt)
// clearInterval(this.checkDupOffers)
clearInterval(this.optimizeWalletHandle)
clearInterval(this.cleanUsageHandle)
clearInterval(this.backupUsageHandle)
clearInterval(this.newSmAccountsInt)
}
// Garbage Collect the Orders.
gcOrders () {
try {
_this.useCases.order.removeStaleOrders()
this.useCases.order.removeStaleOrders()
return true
} catch (err) {
// Do not throw an error. This is a top-level function.
console.log('Error in timer-controllers.js/gcOrders(): ', err)
return false
}
}
// Clean the usage state so that stats reflect the last 24 hours.
cleanUsage () {
try {
clearInterval(this.cleanUsageHandle)
const now = new Date()
console.log(`cleanUsage() Timer Controller executing at ${now.toLocaleString()}`)
this.useCases.usage.cleanUsage()
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
return true
} catch (err) {
console.error('Error in time-controller.js/cleanUsage(): ', err)
this.cleanUsageHandle = setInterval(this.cleanUsage, this.cleanUsageInterval)
}
}
// Garbage Collect the Offers.
gcOffers () {
try {
_this.useCases.offer.removeStaleOffers()
this.useCases.offer.removeStaleOffers()
return true
} catch (err) {
// Do not throw an error. This is a top-level function.
console.log('Error in timer-controllers.js/gcOffers(): ', err)
return false
}
}
// Remove duplicate Offers
checkDupOffers () {
try {
_this.useCases.offer.removeDuplicateOffers()
this.useCases.offer.removeDuplicateOffers()
return true
} catch (err) {
// Do not throw an error. This is a top-level function.
console.log('Error in timer-controllers.js/checkDupOffers(): ', err)
return false
}
}
// Load offers From nostr .
async loadOffers () {
try {
await this.useCases.offer.loadOffers()
return true
} catch (err) {
// Do not throw an error. This is a top-level function.
console.log('Error in timer-controllers.js/loadOffers(): ', err)
return false
}
}
// Backup the usage stats to the database
async backupUsage () {
try {
clearInterval(this.backupUsageHandle)
console.log('backupUsage() Timer Controller executing at ', new Date().toLocaleString())
// Clear the database of old usage data.
await this.useCases.usage.clearUsage()
// Save the current usage snapshot to the database.
await this.useCases.usage.saveUsage()
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
return true
} catch (err) {
console.error('Error in time-controller.js/backupUsage(): ', err)
this.backupUsageHandle = setInterval(this.backupUsage, this.backupUsageInterval)
// Note: Do not throw an error. This is a top-level function.
return false
}
}
// Check for new Social Media Accounts.
async newSmAccounts () {
try {
await this.useCases.smAccount.checkForNewSmAccounts()
return true
} catch (err) {
console.error('Error in time-controller.js/newSmAccounts(): ', err)
// Note: Do not throw an error. This is a top-level function.
return false
}
}
}
+10 -2
View File
@@ -17,6 +17,8 @@ class OfferEntity {
)
}
// console.log('offer entity validate() offerData: ', offerData)
const {
messageType,
messageClass,
@@ -30,7 +32,8 @@ class OfferEntity {
offerStatus,
makerAddr,
ticker,
tokenType
tokenType,
nostrEventId
} = offerData.data
// Input Validation
@@ -73,6 +76,9 @@ class OfferEntity {
if (!tokenType || typeof tokenType !== 'number') {
throw new Error("Property 'tokenType' must be a number.")
}
if (!nostrEventId || typeof nostrEventId !== 'string') {
throw new Error("Property 'nostrEventId' must be a string.")
}
// Convert the timestamp to a number.
let timestamp = new Date(offerData.timestamp)
@@ -96,8 +102,10 @@ class OfferEntity {
offerStatus: offerStatus || this.offerStatus[0],
makerAddr,
ticker,
tokenType
tokenType,
nostrEventId
}
// console.log('offer entity validatedOfferData: ', validatedOfferData)
return validatedOfferData
}
+8 -6
View File
@@ -4,10 +4,13 @@
https://troutsblog.com/blog/clean-architecture
*/
// Local libraries
import UserUseCases from './user.js'
import EntryUseCases from './entry.js'
import OfferUseCases from './offer/index.js'
import OrderUseCases from './order.js'
import { UsageUseCases } from './usage-use-cases.js'
import SmAccountUseCases from './smAccount-use-cases.js'
class UseCases {
constructor (localConfig = {}) {
@@ -24,19 +27,18 @@ class UseCases {
this.order = new OrderUseCases(localConfig)
localConfig.order = this.order
this.offer = new OfferUseCases(localConfig)
this.usage = new UsageUseCases(localConfig)
this.smAccount = new SmAccountUseCases(localConfig)
}
// Run any startup Use Cases at the start of the app.
async start () {
// try {
// Load the usage stats from the database
await this.usage.loadUsage()
console.log('Async Use Cases have been started.')
return true
// } catch (err) {
// console.error('Error in use-cases/index.js/start()')
// // console.log(err)
// throw err
// }
}
}
+275 -93
View File
@@ -37,7 +37,7 @@ class OfferUseCases {
this.orderUseCase = localConfig.order
if (!this.orderUseCase) {
throw new Error(
'Instance of Offer Use Cases must be passed in when instantiating Offer Use Cases library.'
'Instance of Order Use Cases must be passed in when instantiating Offer Use Cases library.'
)
}
@@ -49,20 +49,37 @@ class OfferUseCases {
this.retryQueue = new RetryQueue({ retryPeriod: 1000, attempts: 3 })
// Bind 'this' object to functions
this.createOffer = this.createOffer.bind(this)
this.detectNsfw = this.detectNsfw.bind(this)
this.categorizeToken = this.categorizeToken.bind(this)
this.listOffers = this.listOffers.bind(this)
this.listNftOffers = this.listNftOffers.bind(this)
this.listFungibleOffers = this.listFungibleOffers.bind(this)
this.takeOffer = this.takeOffer.bind(this)
this.ensureFunds = this.ensureFunds.bind(this)
this.findOfferByEvent = this.findOfferByEvent.bind(this)
this.findOfferByTxid = this.findOfferByTxid.bind(this)
this.acceptCounterOffer = this.acceptCounterOffer.bind(this)
this.removeDuplicateOffers = this.removeDuplicateOffers.bind(this)
this.removeStaleOffers = this.removeStaleOffers.bind(this)
this.flagOffer = this.flagOffer.bind(this)
this.loadOffers = this.loadOffers.bind(this)
// State
this.seenOffers = []
this.seenCounterOffers = []
}
// This method is called by the POST /offer REST API controller, which is
// triggered by a P2WDB webhook.
// This method is called by timer controller to load offers from a Nostr topic.
async createOffer (offerObj) {
try {
console.log('Use Case createOffer(offerObj): ', offerObj)
// console.log('Use Case createOffer(offerObj): ', offerObj)
// Return if Offer already exists in database with the same P2WDB CID.
// Return if Offer already exists in database with the same utxo transaction id.
try {
await this.findOfferByHash(offerObj.hash)
await this.findOfferByTxid(offerObj.data.utxoTxid)
console.log('Offer already found in local database.')
// console.log('Offer already found in local database.')
return false
} catch (err) { /* exit quietly */ }
@@ -71,9 +88,17 @@ class OfferUseCases {
// Input Validation
// TODO: This is a hack. Find a better way to protect against the corner-
// case of counter-offers getting routed here.
if (offerObj.data.dataType === 'counter-offer') {
console.log('WARN: Counter Offer innappropriately routed to createOffer()')
// if (offerObj.data.dataType === 'counter-offer') {
// console.log('WARN: Counter Offer innappropriately routed to createOffer()')
// }
// Quickly skip over offers that have already been processed.
const eventId = offerObj.data.nostrEventId
if (this.seenOffers.includes(eventId)) {
// console.log(`Offer with event ID ${eventId} already processed. Skipping.`)
return false
}
this.seenOffers.push(eventId)
// Verify that UTXO in offer is unspent. If it is spent, then ignore the
// offer.
@@ -83,21 +108,26 @@ class OfferUseCases {
}
// const utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
const utxoStatus = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.utxoIsValid, utxo)
console.log('utxoStatus: ', utxoStatus)
// console.log('utxoStatus: ', utxoStatus)
// if (utxoStatus === null) return false
if (!utxoStatus) return false
// A new offer gets a status of 'posted'
offerObj.data.offerStatus = 'posted'
// Set timestamp
offerObj.timestamp = new Date().getTime()
const offerEntity = this.offerEntity.validate(offerObj)
console.log('offerEntity: ', offerEntity)
// console.log('offerEntity: ', offerEntity)
console.log(`New Offer for token ID ${offerEntity.tokenId} detected from Nostr post ${eventId}`)
// Get data about the token.
const tokenId = offerEntity.tokenId
// const tokenData = await this.adapters.wallet.bchWallet.getTokenData(tokenId)
const tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTokenData, tokenId)
console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
// Generate a 'display category' for the token. This will allow the
// front end UI to figure out how to display the token.
@@ -106,10 +136,9 @@ class OfferUseCases {
offerEntity.displayCategory = displayCategory
// Detect if user set the NSFW flag.
let nsfw = false
// nsfw = await this.detectNsfw(tokenData)
nsfw = await this.retryQueue.addToQueue(this.detectNsfw, tokenData)
offerEntity.nsfw = nsfw
// const nsfw = false
// nsfw = await this.retryQueue.addToQueue(this.detectNsfw, tokenData)
// offerEntity.nsfw = nsfw
// Add offer to the local database.
const offerModel = new this.OfferModel(offerEntity)
@@ -117,7 +146,7 @@ class OfferUseCases {
return true
} catch (err) {
console.error('Error in createOffer()')
console.error('Error in createOffer()', err.message)
throw err
}
}
@@ -139,16 +168,29 @@ class OfferUseCases {
// Retrieve the mutable data from Filecoin/IPFS.
// const url = `https://${cid}.ipfs.w3s.link/data.json`
const url = `${this.config.ipfsGateway}${cid}/data.json`
const result = await axios.get(url)
const mutableData = result.data
console.log(`mutableData: ${JSON.stringify(mutableData, null, 2)}`)
let mutableData = {}
try {
// Try the conventional data URL.
const url = `${this.config.ipfsGateway}${cid}/data.json`
const result = await this.axios.get(url)
mutableData = result.data
// console.log(`mutableData: ${JSON.stringify(mutableData, null, 2)}`)
} catch (err) {
// Try the newer data URL.
const url = `${this.config.ipfsGateway}${cid}`
const result = await this.axios.get(url)
mutableData = result.data
// console.log(`mutableData: ${JSON.stringify(mutableData, null, 2)}`)
}
console.log('mutableData: ', mutableData)
// Logical tests
const hasNsfw = !!mutableData.nsfw
const nsfwSetTrue = mutableData.nsfw === true
const nsfwStringTrue = mutableData.nsfw === 'true'
const nsfwDetected = hasNsfw && (nsfwSetTrue || nsfwStringTrue)
// console.log(`hasNsfw: ${hasNsfw}, nsfwSetTrue: ${nsfwSetTrue}, nsfwStringTrue: ${nsfwStringTrue}, nsfwDetected: ${nsfwDetected}`)
if (nsfwDetected) {
console.log('NSFW flag set as true')
@@ -207,11 +249,11 @@ class OfferUseCases {
async listOffers (page = 0) {
try {
const data = await this.OfferModel.find({})
// Sort entries so newest entries show first.
// Sort entries so newest entries show first.
.sort('-timestamp')
// Skip to the start of the selected page.
// Skip to the start of the selected page.
.skip(page * DEFAULT_ENTRIES_PER_PAGE)
// Only return 20 results.
// Only return 20 results.
.limit(DEFAULT_ENTRIES_PER_PAGE)
return data
@@ -227,11 +269,11 @@ class OfferUseCases {
displayCategory: { $ne: 'fungible' },
nsfw
})
// Sort entries so newest entries show first.
// Sort entries so newest entries show first.
.sort('-timestamp')
// Skip to the start of the selected page.
// Skip to the start of the selected page.
.skip(page * NFT_ENTRIES_PER_PAGE)
// Only return 20 results.
// Only return 20 results.
.limit(NFT_ENTRIES_PER_PAGE)
// console.log('listNftOffers() returning this data: ', data)
@@ -246,11 +288,11 @@ class OfferUseCases {
async listFungibleOffers (page = 0) {
try {
const data = await this.OfferModel.find({ displayCategory: 'fungible' })
// Sort entries so newest entries show first.
// Sort entries so newest entries show first.
.sort('-timestamp')
// Skip to the start of the selected page.
// Skip to the start of the selected page.
.skip(page * FUNGIBLE_ENTRIES_PER_PAGE)
// Only return 20 results.
// Only return 20 results.
.limit(FUNGIBLE_ENTRIES_PER_PAGE)
// console.log('listFungibleOffers() returning this data: ', data)
@@ -270,12 +312,12 @@ class OfferUseCases {
// provide functionality to take less than the total amount of tokens offered
// (offerInfo.numTokens). Taking less than the offered amount will be added
// in the future.
async takeOffer (offerCid) {
async takeOffer (eventId) {
try {
console.log('offerCid: ', offerCid)
if (!eventId || typeof eventId !== 'string') throw new Error('eventId must be a string')
// Get the Offer information
const offerInfo = await this.findOfferByHash(offerCid)
const offerInfo = await this.findOfferByEvent(eventId)
console.log(`offerInfo: ${JSON.stringify(offerInfo, null, 2)}`)
// Ensure the offer is in a 'posted' state and not already 'taken'
@@ -288,8 +330,10 @@ class OfferUseCases {
tx_hash: offerInfo.utxoTxid,
tx_pos: offerInfo.utxoVout
}
// Note : should be added to retry-queue?
const utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
console.log('utxoStatus: ', utxoStatus)
// console.log('utxoStatus: ', utxoStatus)
if (!utxoStatus) {
console.log(`utxo txid: ${offerInfo.utxoTxid}, vout: ${offerInfo.utxoVout}`)
@@ -302,11 +346,12 @@ class OfferUseCases {
await this.ensureFunds(offerInfo)
// Get UTXOs.
const utxos = this.adapters.wallet.bchWallet.utxos.utxoStore
console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
// const utxos = this.adapters.wallet.bchWallet.utxos.utxoStore
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
// Calculate amount of sats to generate a counter offer.
let satsToMove = offerInfo.numTokens * parseInt(offerInfo.rateInBaseUnit)
let satsToMove = Math.ceil(offerInfo.numTokens * parseInt(offerInfo.rateInBaseUnit))
console.log('satsToMove', satsToMove, offerInfo)
if (isNaN(satsToMove)) {
throw new Error('Could not calculate the amount of BCH to generate counter offer')
}
@@ -332,9 +377,9 @@ class OfferUseCases {
// Create valid Offer object
const takenOfferInfo = Object.assign({}, offerInfo)
takenOfferInfo.partialTxHex = partialTxHex
delete takenOfferInfo.p2wdbHash
delete takenOfferInfo.nostrEventId
delete takenOfferInfo._id
takenOfferInfo.offerHash = offerInfo.p2wdbHash
takenOfferInfo.offerHash = offerInfo.nostrEventId
// Add P2WDB specific flag for signaling that this is a new offer.
takenOfferInfo.dataType = 'counter-offer'
@@ -342,19 +387,21 @@ class OfferUseCases {
// Write offer info to the P2WDB
// TODO: This will trigger the webhook. Find some way of triggering the
// webhook on new offers, but not on counteroffers
const p2wdbObj = {
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
data: takenOfferInfo,
appId: this.config.p2wdbAppId
const nostrData = {
// wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
data: takenOfferInfo
// appId: this.config.p2wdbAppId
}
const hash = await this.adapters.p2wdb.write(p2wdbObj)
const resultEventId = await this.adapters.nostr.post(JSON.stringify(nostrData))
const noteId = this.adapters.nostr.eventId2note(resultEventId)
// Delete the Offer from the database, so that the user doesn't attempt
// to take the offer more than once.
offerInfo.remove()
// offerInfo.remove()
// Return the P2WDB CID
return hash
return { eventId: resultEventId, noteId }
// return 'fake-hash'
} catch (err) {
@@ -377,15 +424,16 @@ class OfferUseCases {
await this.adapters.wallet.bchWallet.initialize()
// 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.')
// Note : this validation should be deprecated for nostr functionality?
// 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 (offerEntity.buyOrSell.includes('sell')) {
// Sell Offer
// Calculate the sats needed
const satsNeeded = offerEntity.numTokens * parseInt(offerEntity.rateInBaseUnit)
const satsNeeded = Math.ceil(offerEntity.numTokens * parseInt(offerEntity.rateInBaseUnit))
if (isNaN(satsNeeded)) {
throw new Error('Could not calculate sats needed!')
}
@@ -398,7 +446,7 @@ class OfferUseCases {
throw new Error('App wallet does not control enough BCH to purchase the tokens.')
}
//
//
} else {
// Buy Offer
throw new Error('Buy offers are not supported yet.')
@@ -417,57 +465,130 @@ class OfferUseCases {
}
}
async findOfferByHash (p2wdbHash) {
// Retrieve an Order model from the database. Find it by its event Id.
async findOfferByEvent (nostrEventId) {
try {
if (typeof nostrEventId !== 'string' || !nostrEventId) {
throw new Error('nostrEventId must be a string')
}
const order = await this.OfferModel.findOne({ nostrEventId })
if (!order) {
throw new Error('offer not found')
}
const orderObject = order.toObject()
// return this.offerEntity.validateFromModel(offerObject)
return orderObject
} catch (err) {
console.error('Error in findOfferByEvent()')
throw err
}
}
async findOfferByTxid (utxoTxid) {
// try {
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
throw new Error('p2wdbHash must be a string')
// try {
if (typeof utxoTxid !== 'string' || !utxoTxid) {
throw new Error('utxoTxid must be a string')
}
const offer = await this.OfferModel.findOne({ p2wdbHash })
const offer = await this.OfferModel.findOne({ utxoTxid })
// TODO: Offer should be found by TXID, then if there is more than one
// result, they should be filtered by the vout property. That will leave
// one remaining UTXO.
if (!offer) {
throw new Error('offer not found')
}
return offer
// const offerObject = offer.toObject()
// return this.offerEntity.validateFromModel(offerObject)
// return offerObject
// } catch (err) {
// // console.error('Error in findOffer(): ', err)
// throw err
// } catch (error) {
// // console.error('Error in use-cases/offer/findOfferByTxid(): ', error.message)
// throw error
// }
}
// This function is called by the P2WDB webhook REST API handler. When a
// Counter Offer is passed to bch-dex by the P2WDB, the data is then passed
// to this function. It does due dilligence on the Counter Offer, then signs
// to this function. It does due diligence on the Counter Offer, then signs
// and broadcasts the transaction to accept the Counter Offer.
async acceptCounterOffer (p2wdbData) {
async acceptCounterOffer (offerData) {
try {
console.log(`acceptCounterOffer() p2wdbData: ${JSON.stringify(p2wdbData, null, 2)}`)
// console.log(`acceptCounterOffer() offerData: ${JSON.stringify(offerData, null, 2)}`)
// Quickly skip over offers that have already been processed.
const eventId = offerData.data.nostrEventId
if (this.seenOffers.includes(eventId)) {
// console.log(`Offer with event ID ${eventId} already processed. Skipping.`)
return false
}
// See if this instance of bch-dex is managing the Order associated with
// the incoming Counter Offer.
const orderHash = p2wdbData.data.offerHash
// Note : this should be handled by nostrEvent id or UtxoId?
// const orderHash = offerData.data.nostrEventId
let orderData = {}
try {
orderData = await this.orderUseCase.findOrderByHash(orderHash)
orderData = await this.orderUseCase.findOrderByUtxo(offerData)
console.log(`orderData: ${JSON.stringify(orderData, null, 2)}`)
} catch (err) {
console.log('Order matching this Counter Offer is not managed by this instance of bch-dex. Exiting.')
console.log('Order matching this Counter Offer is not managed by this instance of bch-dex. Skipping.\n')
// Add order to list of seen orders, so that we don't spent time trying to validate it again.
this.seenOffers.push(eventId)
return 'N/A'
}
// Verify that the UTXO for sale is valid.
let utxoStatus = null
try {
// Get the status of the UTXO associate with this Offer.
const utxo = {
tx_hash: offerData.data.utxoTxid,
tx_pos: offerData.data.utxoVout
}
// console.log(`Checking this UTXO: ${JSON.stringify(utxo, null, 2)}`)
// utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
utxoStatus = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.utxoIsValid, utxo)
console.log('utxoStatus: ', utxoStatus)
} catch (err) {
console.log('acceptCounterOffer() err validating UTXO: ', err)
// Handle corner case of bad-data in the Offer model.
if (err.isAxiosError) {
console.log('Error trying to contact wallet service: ', err)
return 'N/A'
} else {
return 'N/A'
}
}
// If the Offer UTXO is spent, exit.
if (utxoStatus === false) {
// Add order to list of seen orders, so that we don't spent time trying to validate it again.
this.seenOffers.push(eventId)
console.log(`Aborting Counter Offer from Event ID ${eventId}`)
console.log(`https://astral.psfoundation.info/${this.adapters.nostr.eventId2note(eventId)}`)
return 'N/A'
}
// Deserialize the partially signed transaction.
const txHex = p2wdbData.data.partialTxHex
const txHex = offerData.data.partialTxHex
const txObj = await this.adapters.wallet.deseralizeTx(txHex)
console.log(`txObj: ${JSON.stringify(txObj, null, 2)}`)
// Ensure the 3rd output (vout=2) contains the required amount of BCH.
const satsToReceive = orderData.numTokens * parseInt(orderData.rateInBaseUnit)
const satsToReceive = Math.ceil(orderData.numTokens * parseInt(orderData.rateInBaseUnit))
console.log('Ceil', satsToReceive)
if (isNaN(satsToReceive)) {
throw new Error('Could not calculate the amount of BCH offered in the Counter Offer')
}
@@ -486,13 +607,24 @@ class OfferUseCases {
throw new Error(`The Counter Offer has an output address of ${addrInCounterOffer}, which does not match the Maker address of ${makerAddr} in the Offer.`)
}
// Get the User ID from the Order model.
const userId = orderData.userId
// Get the User model from the database.
this.UserModel = this.adapters.localdb.Users
const user = await this.UserModel.findById(userId)
console.log('user: ', user)
// Get the HD index from the Order model.
const hdIndex = orderData.hdIndex
// Sign and broadcast the transaction.
const txid = await this.adapters.wallet.completeTx(txHex, orderData.hdIndex)
const txid = await this.adapters.wallet.completeTx(txHex, hdIndex, user.mnemonic)
console.log('txid: ', txid)
return txid
} catch (err) {
console.error('Error in acceptCounterOffer()')
console.error('Error in acceptCounterOffer(): ', err)
throw err
}
}
@@ -579,7 +711,8 @@ class OfferUseCases {
}
// If the Offer UTXO is spent, delete the Offer model.
if (!utxoStatus) {
if (utxoStatus === false) {
// console.log('utxoStatus: ', utxoStatus)
console.log(`Spent UTXO detected. Deleting this Offer: ${JSON.stringify(thisOffer, null, 2)}`)
await thisOffer.remove()
}
@@ -599,32 +732,81 @@ class OfferUseCases {
}
}
async flagOffer (flagData) {
console.log(`flagData: ${JSON.stringify(flagData, null, 2)}`)
// Flag offers as NSFW.
async flagOffer (flagData = {}) {
try {
if (!flagData.data) throw new Error('"data" property is required')
const p2wdbHash = flagData.data.p2wdbHash
console.log(`flagData: ${JSON.stringify(flagData, null, 2)}`)
// Get the offer from the database.
const offer = await this.findOfferByHash(p2wdbHash)
console.log(`Flagging this offer: ${JSON.stringify(offer, null, 2)}`)
const eventId = flagData.data.nostrEventId
if (!offer) {
throw new Error(`Offer ${p2wdbHash} not found in the database.`)
// Get the offer from the database.
const offer = await this.findOfferByEvent(eventId)
console.log(`Flagging this offer: ${JSON.stringify(offer, null, 2)}`)
if (!offer) {
throw new Error(`Offer ${eventId} not found in the database.`)
}
// Add the raw flag data to the database model.
offer.flags.push(flagData)
// If flag count is 3 or more, mark the Offer as NSFW
const flagCnt = offer.flags.length
if (flagCnt >= 3) {
offer.nsfw = true
}
// Save the updated offer data to the database.
await offer.save()
return true
} catch (error) {
console.error('Error in flagOffer(): ', error)
throw error
}
}
// Add the raw flag data to the database model.
offer.flags.push(flagData)
// Get offers data from nostr.
async loadOffers () {
try {
// Retrieve offers array.
const offers = await this.adapters.nostr.read()
// console.log('offers: ', offers)
// If flag count is 3 or more, mark the Offer as NSFW
const flagCnt = offer.flags.length
if (flagCnt >= 3) {
offer.nsfw = true
for (let i = 0; i < offers.length; i++) {
try {
const offer = offers[i]
// offer data
const offerObj = JSON.parse(offer.content)
// Append the Nostr Event ID to the offer object
offerObj.data.nostrEventId = offer.eventId
// console.log('loadOffers() offerObj: ', offerObj)
if (offerObj.data.dataType === 'offer') {
// Try to create new offer
await this.createOffer(offerObj)
}
if (offerObj.data.dataType === 'counter-offer') {
// console.log('Counter offer detected: ', offerObj)
// console.log('Counter offer detected: ', offerObj.data.nostrEventId)
console.log(`Counter offer detected: https://astral.psfoundation.info/${this.adapters.nostr.eventId2note(offerObj.data.nostrEventId)}`)
await this.acceptCounterOffer(offerObj)
}
// Ignore the post if it doesn't fit the above filters.
} catch (error) {
/* exit quietly */
}
}
} catch (error) {
console.error('Error in loadOffers(): ', error)
throw error
}
// Save the updated offer data to the database.
await offer.save()
return true
}
}
+136 -31
View File
@@ -10,6 +10,8 @@ import wlogger from '../adapters/wlogger.js'
import OrderEntity from '../entities/order.js'
import config from '../../config/index.js'
const DEFAULT_ENTRIES_PER_PAGE = 20
class OrderLib {
constructor (localConfig = {}) {
// console.log('User localConfig: ', localConfig)
@@ -23,54 +25,88 @@ class OrderLib {
// Encapsulate dependencies
this.orderEntity = new OrderEntity()
this.OrderModel = this.adapters.localdb.Order
this.UserModel = this.adapters.localdb.Users
this.bch = this.adapters.bch
this.config = config
this.retryQueue = new RetryQueue({ retryPeriod: 1000, attempts: 3 })
// Bind subfunctions to the 'this' object.
this.ensureFunds = this.ensureFunds.bind(this)
this.createOrder = this.createOrder.bind(this)
this.findOrderByUtxo = this.findOrderByUtxo.bind(this)
}
// Create a new order model and add it to the Mongo database.
async createOrder (entryObj) {
try {
console.log('createOrder(entryObj): ', entryObj)
// console.log('createOrder(entryObj): ', entryObj)
if (!entryObj) return false
if (!entryObj.tokenId) throw new Error('entry does not contain required properties')
const user = await this.UserModel.findById(entryObj.userId)
if (!user) throw new Error('user not found')
console.log(`Using FullStack.cash: ${this.config.useFullStackCash}`)
const advancedConfig = {}
if (this.config.useFullStackCash) {
advancedConfig.interface = 'rest-api'
advancedConfig.restURL = this.config.apiServer
advancedConfig.apiToken = this.config.apiToken
} else {
advancedConfig.interface = 'consumer-api'
advancedConfig.restURL = this.config.consumerUrl
}
// Instantiate minimal-slp-wallet with the user's mnemonic.
const BchWallet = this.adapters.wallet.BchWallet
const userWallet = new BchWallet(user.mnemonic, advancedConfig)
// Wait for wallet to initialize.
await userWallet.walletInfoPromise
await userWallet.initialize()
// Specify the address to send payment.
entryObj.makerAddr = this.adapters.wallet.bchWallet.walletInfo.cashAddress
console.log('userWallet.walletInfo: ', userWallet.walletInfo)
entryObj.makerAddr = userWallet.walletInfo.cashAddress
console.log('entryObj.makerAddr: ', entryObj.makerAddr)
// console.log('entryObj.makerAddr: ', entryObj.makerAddr)
// Input Validation
const orderEntity = this.orderEntity.inputValidate(entryObj)
console.log('orderEntity: ', orderEntity)
// console.log('orderEntity: ', orderEntity)
// Optimize the wallet to speed up working with it.
console.log('Optimizing wallet before creating new order.')
// await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.optimize, {})
await userWallet.optimize()
// Ensure sufficient tokens exist to create the order.
// await this.ensureFunds(orderEntity)
await this.retryQueue.addToQueue(this.ensureFunds, orderEntity)
// await this.retryQueue.addToQueue(this.ensureFunds, orderEntity)
// Get Ticker for token ID.
// const tokenData = await this.adapters.wallet.bchWallet.getTxData([entryObj.tokenId])
const tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTxData, [entryObj.tokenId])
// const tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTxData, [entryObj.tokenId])
const tokenData = await userWallet.getTxData([entryObj.tokenId])
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
orderEntity.ticker = tokenData[0].tokenTicker
// Move the tokens to holding address.
const moveObj = {
tokenId: orderEntity.tokenId,
qty: orderEntity.numTokens
qty: orderEntity.numTokens,
wallet: userWallet
}
// const utxoInfo = await this.adapters.wallet.moveTokens(moveObj)
const utxoInfo = await this.retryQueue.addToQueue(this.adapters.wallet.moveTokens, moveObj)
console.log('utxoInfo: ', utxoInfo)
const utxoInfo = await this.retryQueue.addToQueue(this.adapters.wallet.moveTokensFromCustomWallet, moveObj)
// console.log('utxoInfo: ', utxoInfo)
// Update the UTXO store for the wallet.
await this.adapters.wallet.bchWallet.bchjs.Util.sleep(3000)
await userWallet.bchjs.Util.sleep(3000)
// await this.adapters.wallet.bchWallet.getUtxos()
await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.initialize, {})
// await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.initialize, {})
await userWallet.initialize()
// Update the order with the new UTXO information.
orderEntity.utxoTxid = utxoInfo.txid
orderEntity.utxoVout = utxoInfo.vout
@@ -78,29 +114,31 @@ class OrderLib {
// Add P2WDB specific flag for signaling that this is a new offer.
orderEntity.dataType = 'offer'
orderEntity.userId = user._id
// Add order to P2WDB.
const p2wdbObj = {
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
data: orderEntity,
appId: this.config.p2wdbAppId
// Post the new Order information to Nostr under the topic set in the
// config file.
const postObj = {
data: orderEntity
}
// const hash = await this.adapters.p2wdb.write(p2wdbObj)
const hash = await this.retryQueue.addToQueue(this.adapters.p2wdb.write, p2wdbObj)
const postMsg = JSON.stringify(postObj)
const eventId = await this.adapters.nostr.post(postMsg)
// console.log('hash: ', hash)
// Create a MongoDB model to hold the Order
orderEntity.hdIndex = utxoInfo.hdIndex
orderEntity.p2wdbHash = hash
orderEntity.nostrEventId = eventId
console.log(`creating new order model: ${JSON.stringify(orderEntity, null, 2)}`)
const order = new this.OrderModel(orderEntity)
await order.save()
return hash
const noteId = this.adapters.nostr.eventId2note(eventId)
return { eventId, noteId }
} catch (err) {
// console.log("Error in use-cases/entry.js/createEntry()", err.message)
wlogger.error('Error in use-cases/order.js/createOrder())')
wlogger.error('Error in use-cases/order.js/createOrder(): ', err)
console.log('error entryObj: ', entryObj)
throw err
}
@@ -115,6 +153,7 @@ class OrderLib {
// Update the wallet balance
await this.adapters.wallet.bchWallet.getUtxos()
// console.log(`wallet UTXOs: ${JSON.stringify(this.adapters.wallet.bchWallet.utxos.utxoStore, null, 2)}`)
// Ensure the app wallet has enough funds to write to the P2WDB.
const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey
@@ -130,12 +169,10 @@ class OrderLib {
// Combine Fungible and NFT token UTXOs.
let tokenUtxos = utxos.slpUtxos.type1.tokens.concat(utxos.slpUtxos.nft.tokens)
// Get token UTXOs that match the token in the order.
tokenUtxos = tokenUtxos.filter(
x => x.tokenId === orderEntity.tokenId
)
console.log('tokenUtxos: ', tokenUtxos)
// Get the total amount of tokens in the wallet that match the token
// in the order.
@@ -151,7 +188,7 @@ class OrderLib {
)
}
//
//
} else {
// Buy Order
throw new Error('Buy orders are not supported yet.')
@@ -164,14 +201,14 @@ class OrderLib {
}
}
// Retrieve an Order model from the database. Find it by its P2WDB CID.
async findOrderByHash (p2wdbHash) {
// Retrieve an Order model from the database. Find it by its event Id.
async findOrderByEvent (nostrEventId) {
try {
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
throw new Error('p2wdbHash must be a string')
if (typeof nostrEventId !== 'string' || !nostrEventId) {
throw new Error('nostrEventId must be a string')
}
const order = await this.OrderModel.findOne({ p2wdbHash })
const order = await this.OrderModel.findOne({ nostrEventId })
if (!order) {
throw new Error('order not found')
@@ -182,7 +219,38 @@ class OrderLib {
return orderObject
} catch (err) {
console.error('Error in findOrder()')
console.error('Error in findOrderByEvent()')
throw err
}
}
// Retrieve an Order model from the database. Find it by its UTXO (TXID & Vout)
async findOrderByUtxo (offerData = {}) {
try {
// console.log('findOrderByUtxo() offerData: ', offerData)
// const order = await this.OrderModel.findOne({ nostrEventId })
const order = await this.OrderModel.findOne({ utxoTxid: offerData.data.utxoTxid, utxoVout: offerData.data.utxoVout })
// const order = await this.OrderModel.findOne({ utxoTxid: offerData.data.utxoTxid })
// const order = await this.OrderModel.findOne({ tokenId: offerData.data.tokenId })
console.log('\nfindOrderByUtxo() order: ', order)
if (!order) {
throw new Error('order not found')
}
if (!order.nostrEventId) {
throw new Error('order not found')
}
const orderObject = order.toObject()
// return this.offerEntity.validateFromModel(offerObject)
return orderObject
} catch (err) {
if (!err.message.toString().includes('order not found')) {
console.error('Error in findOrderByUtxo(): ', err)
}
throw err
}
}
@@ -234,7 +302,8 @@ class OrderLib {
}
// If the Order UTXO is spent, delete the Order model.
if (!utxoStatus) {
if (utxoStatus === false) {
// console.log('utxoStatus: ', utxoStatus)
console.log(`Spent UTXO detected. Deleting this Order: ${JSON.stringify(thisOrder, null, 2)}`)
await thisOrder.remove()
}
@@ -244,6 +313,42 @@ class OrderLib {
throw err
}
}
async listOrders (page = 0) {
try {
const data = await this.OrderModel.find({})
// Sort entries so newest entries show first.
.sort('-timestamp')
// Skip to the start of the selected page.
.skip(page * DEFAULT_ENTRIES_PER_PAGE)
// Only return 20 results.
.limit(DEFAULT_ENTRIES_PER_PAGE)
return data
} catch (error) {
console.error('Error in use-cases/order/listOrders()')
throw error
}
}
// Delete an Order model by sending the token back to the root address. This
// will allow the garbage collectors to delete the Order and the Offer.
async deleteOrder (nostrEventId) {
try {
// Find the order by the given hash.
const order = await this.findOrderByEvent(nostrEventId)
console.log('order: ', order)
// Reclaim the tokens
const txid = await this.adapters.wallet.reclaimTokens(order)
return txid
} catch (err) {
console.error('Error in use-cases/order/deleteOrder()')
throw err
}
}
}
export default OrderLib
+78
View File
@@ -0,0 +1,78 @@
/*
Social Media Account Use Cases.
*/
class smAccountUseCases {
constructor (localConfig = {}) {
// console.log('User localConfig: ', localConfig)
// Dependency injection.
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of adapters must be passed in when instantiating Order Use Cases library.'
)
}
// Bind 'this' object to all methods
this.checkForNewSmAccounts = this.checkForNewSmAccounts.bind(this)
this.listAccounts = this.listAccounts.bind(this)
}
// Check for new Social Media Accounts.
async checkForNewSmAccounts () {
try {
console.log('Checking for new Social Media Accounts...')
const messages = await this.adapters.nostr.readGlobalFeed()
for (let i = 0; i < messages.length; i++) {
const msg = messages[i]
// const { content, id, tags, npub, pubkey } = msg
const { tags, npub, pubkey } = msg
let bchAddr = null
// console.log('msg: ', JSON.stringify(msg, null, 2))
// Find the 'u' tag if it exists
const uTag = tags.find(tag => tag[0] === 'u')
// If 'u' tag exists and contains a BCH address
if (uTag && uTag[1].includes('bitcoincash:')) {
bchAddr = uTag[1]
// console.log('BCH address found:', bchAddr)
}
const SmAccount = await this.adapters.localdb.SmAccount
if (bchAddr) {
const smAccount = await SmAccount.findOne({ npub })
if (!smAccount) {
console.log(`Creating new Social Media Account with npub: ${npub} and bchAddr: ${bchAddr}`)
const newSmAccount = new SmAccount({ npub, bchAddr, pubkey })
await newSmAccount.save()
} else {
console.log(`Social Media Account with npub: ${npub} already exists`)
}
}
}
return true
} catch (err) {
console.error('Error in smAccount-use-cases.js/checkForNewSmAccounts(): ', err)
throw err
}
}
// List all Social Media Accounts.
async listAccounts (page = 0) {
try {
const SmAccount = await this.adapters.localdb.SmAccount
const accounts = await SmAccount.find({}).skip(page * 10).limit(10)
return accounts
} catch (err) {
console.error('Error in smAccount-use-cases.js/listAccounts(): ', err)
throw err
}
}
}
export default smAccountUseCases
+214
View File
@@ -0,0 +1,214 @@
/*
Use Case library for tracking usage. This library contains business logic
for tracking the usage of REST API and JSON RPC calls. This library is used
by admins to keep an eye on how many API calls were made in a 24-hour and
1-hour time period.
Usage stats are held in memory. But they are periodically backed up to the
Mongo database. On startup, the usage stats are loaded from the database.
This allows the usage stats to be persisted across restarts.
*/
// This global variable is used to share data between the REST middleware and
// the Usage Use Case class instance.
let restCalls = []
class UsageUseCases {
constructor (localConfig = {}) {
// console.log('User localConfig: ', localConfig)
this.adapters = localConfig.adapters
if (!this.adapters) {
throw new Error(
'Instance of adapters must be passed in when instantiating Usage Use Cases library.'
)
}
// Encapsulate dependencies
this.UsageModel = this.adapters.localdb.Usage
// Bind 'this' object to all subfunctions
this.cleanUsage = this.cleanUsage.bind(this)
this.getRestSummary = this.getRestSummary.bind(this)
this.getTopIps = this.getTopIps.bind(this)
this.getTopEndpoints = this.getTopEndpoints.bind(this)
this.clearUsage = this.clearUsage.bind(this)
this.saveUsage = this.saveUsage.bind(this)
this.loadUsage = this.loadUsage.bind(this)
// State
}
// Clean up the state by removing entries that are older than 24 hours. This
// ensures stats reflect only the last 24 hours.
// This function is called by a Timer Controller.
cleanUsage () {
try {
const now = new Date()
const twentyFourHoursAgo = now.getTime() - (60000 * 60 * 24)
console.log('cleanUsage() now: ', now)
console.log('cleanUsage() restCalls.length before filtering: ', restCalls.length)
restCalls = restCalls.filter(x => x.timestamp > twentyFourHoursAgo)
console.log('cleanUsage() restCalls.length after filtering: ', restCalls.length)
return restCalls
} catch (err) {
console.error('Error in usage-use-cases.js/cleanUsage()')
throw err
}
}
// Track the calls to a REST API
getRestSummary (inObj = {}) {
try {
console.log(`getRestSummary(): There have been ${restCalls.length} REST calls`)
return restCalls.length
} catch (err) {
console.error('Error in usage-use-cases.js/getRestSummary()')
throw err
}
}
// Get the top 20 IP addresses from the stats.
getTopIps () {
try {
const ips = restCalls.map(x => x.ip)
// Create a Map to count occurrences of each IP address string
const countMap = new Map()
ips.forEach(ip => {
countMap.set(ip, (countMap.get(ip) || 0) + 1)
})
// Convert the Map into an array of objects with `str` and `cnt` properties
const result = Array.from(countMap, ([ip, cnt]) => ({ ip, cnt }))
// Sort the results by the `cnt` property in descending order
result.sort((a, b) => b.cnt - a.cnt)
// Ensure the result has at most 20 elements
return result.slice(0, 20)
} catch (err) {
console.error('Error in usage-use-cases.js/getTopIps()')
throw err
}
}
// Get the top 20 most consumed endpoints.
getTopEndpoints () {
try {
const endpoints = restCalls.map(x => `${x.method} ${x.url}`)
// Create a Map to count occurrences of each IP address string
const countMap = new Map()
endpoints.forEach(endpoint => {
countMap.set(endpoint, (countMap.get(endpoint) || 0) + 1)
})
// Convert the Map into an array of objects with `str` and `cnt` properties
const result = Array.from(countMap, ([endpoint, cnt]) => ({ endpoint, cnt }))
// Sort the results by the `cnt` property in descending order
result.sort((a, b) => b.cnt - a.cnt)
// Ensure the result has at most 20 elements
return result.slice(0, 20)
} catch (err) {
console.error('Error in usage-use-cases.js/getTopEndpoints()')
throw err
}
}
// Clear the usage database data
async clearUsage () {
try {
await this.UsageModel.deleteMany({})
// Debugging: verify the database is empty
// Delete this code after debugging
const usage = await this.UsageModel.find({})
console.log('clearUsage() usage: ', usage)
return true
} catch (err) {
console.error('Error in usage-use-cases.js/clearUsage()')
throw err
}
}
// Save the usage data to the database
async saveUsage (inObj = {}) {
try {
for (let i = 0; i < restCalls.length; i++) {
const thisRestCall = restCalls[i]
// Debugging: delete this code after debugging
// if (i === 5) {
// console.log('saveUsage() thisRestCall: ', thisRestCall)
// }
const usageData = {
ip: thisRestCall.ip,
url: thisRestCall.url,
method: thisRestCall.method,
timestamp: thisRestCall.timestamp
}
const usage = new this.UsageModel(usageData)
await usage.save()
}
return true
} catch (err) {
console.error('Error in usage-use-cases.js/saveUsage()')
throw err
}
}
// Load usage data from the database
async loadUsage () {
try {
const usage = await this.UsageModel.find({})
// console.log('usage: ', usage)
// Debugging: delete this code after debugging
if (usage[5]) {
console.log('loadUsage() usage[5]: ', usage[5])
}
restCalls = usage
return usage
} catch (err) {
console.error('Error in usage-use-cases.js/loadUsage(): ', err)
return false
// throw err
}
}
}
// This Koa middleware is called any time there is a REST API. It logs the
// details from the request object.
function usageMiddleware () {
return async (ctx, next) => {
try {
await next()
// console.log('ctx.request: ', ctx.request)
const now = new Date()
const reqObj = {
ip: ctx.request.ip,
url: ctx.request.url,
method: ctx.request.method,
timestamp: now.getTime()
}
// console.log('reqObj: ', reqObj)
restCalls.push(reqObj)
} catch (err) {
ctx.status = err.status || 500
ctx.body = err.message
ctx.app.emit('error', err, ctx)
}
}
};
export { UsageUseCases, usageMiddleware, restCalls }
+10 -1
View File
@@ -16,7 +16,7 @@ class UserLib {
'Instance of adapters must be passed in when instantiating User Use Cases library.'
)
}
this.BchWallet = this.adapters.wallet.BchWallet
// Encapsulate dependencies
this.UserEntity = new UserEntity()
this.UserModel = this.adapters.localdb.Users
@@ -30,6 +30,11 @@ class UserLib {
const userEntity = this.UserEntity.validate(userObj)
const user = new this.UserModel(userEntity)
const wallet = new this.BchWallet()
const walletInfo = await wallet.walletInfoPromise
const mnemonic = walletInfo.mnemonic
user.mnemonic = mnemonic
// Enforce default value of 'user'
user.type = 'user'
// console.log('user: ', user)
@@ -102,6 +107,7 @@ class UserLib {
// Input Validation
// Optional inputs, but they must be strings if included.
if (newData.email && typeof newData.email !== 'string') {
throw new Error("Property 'email' must be a string!")
}
@@ -111,6 +117,9 @@ class UserLib {
if (newData.password && typeof newData.password !== 'string') {
throw new Error("Property 'password' must be a string!")
}
if (newData.mnemonic) {
throw new Error("Property 'mnemonic' cannot be updated!")
}
// Save a copy of the original user type.
const userType = existingUser.type
-3
View File
@@ -1,3 +0,0 @@
/key/swarm/psk/1.0.0/
/base16/
bbd935b70105b03ebd0c6a3c2d2730cd22fc0d18c490ccf689b6c8a22e1bed2a
@@ -0,0 +1,45 @@
/*
Liveness test runs before all other tests to ensure the server is running.
*/
// Public npm libraries
import { assert } from 'chai'
import axios from 'axios'
import testUtils from '../../utils/test-utils.js'
// const sinon = require('sinon')
// Local support libraries
import config from '../../../config/index.js'
// import Server from '../../../bin/server.js'
// import testUtils from '../../utils/test-utils.js'
// const adminLib = new AdminLib()
const LOCALHOST = `http://localhost:${config.port}`
describe('#Check Server Liveness', () => {
// before(async () => {
it('should confirm the server is running', async () => {
try {
const response = await axios.get(`${LOCALHOST}/`)
assert(response.status === 200, 'Server is running, continuing with E2E tests.')
} catch (err) {
console.log('\nServer is not running, exiting tests.')
console.log('Start the server with `npm run start:e2e:server` before running E2E tests.\n')
console.log('Ensure running npm run docs before running the test server')
process.exit(1)
}
})
it('should confirm the server is running over test enviroment', async () => {
try {
const res = await testUtils.loginAdminUser()
assert.property(res, 'user')
assert.property(res, 'token')
assert.property(res, 'id')
} catch (err) {
console.log('\nServer is not running over test enviroment, exiting tests.')
console.log('Start the server with `npm run start:e2e:server` before running E2E tests.\n')
process.exit(1)
}
})
})
+89 -91
View File
@@ -13,116 +13,114 @@ import axios from 'axios'
// Local support libraries
import config from '../../../config/index.js'
import Server from '../../../bin/server.js'
// import Server from '../../../bin/server.js'
import testUtils from '../../utils/test-utils.js'
import AdminLib from '../../../src/adapters/admin.js'
const adminLib = new AdminLib()
// const request = supertest.agent(app.listen())
const context = {}
const LOCALHOST = `http://localhost:${config.port}`
describe('Auth', () => {
before(async () => {
const app = new Server()
if (!config.noMongo) {
describe('Auth', () => {
before(async () => {
// const app = new Server()
// This should be the first instruction. It starts the REST API server.
await app.startServer()
// This should be the first instruction. It starts the REST API server.
// await app.startServer()
// Stop the IPFS node for the rest of the e2e tests.
// await app.controllers.adapters.ipfs.stop()
// TODO:
// Delete all previous users in the database.
await testUtils.deleteAllUsers()
// Stop the IPFS node for the rest of the e2e tests.
// await app.controllers.adapters.ipfs.stop()
// Create a new admin user.
await adminLib.createSystemUser()
// Delete all previous users in the database.
await testUtils.deleteAllUsers()
const userObj = {
email: 'test@test.com',
password: 'pass',
name: 'test'
}
const testUser = await testUtils.createUser(userObj)
// console.log('TestUser: ', testUser)
context.user = testUser.user
context.token = testUser.token
})
describe('POST /auth', () => {
it('should throw 401 if credentials are incorrect', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/auth`,
data: {
email: 'test@test.com',
password: 'wrongpassword'
}
}
const result = await axios(options)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
console.log(
`result stringified: ${JSON.stringify(result.data, null, 2)}`
)
assert(false, 'Unexpected result')
} catch (err) {
assert(err.response.status === 401, 'Error code 401 expected.')
const userObj = {
email: 'test@test.com',
password: 'pass',
name: 'test'
}
const testUser = await testUtils.createUser(userObj)
// console.log('TestUser: ', testUser)
context.user = testUser.user
context.token = testUser.token
})
it('should throw 401 if email is wrong format', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/auth`,
data: {
email: 'wrongEmail',
password: 'wrongpassword'
describe('POST /auth', () => {
it('should throw 401 if credentials are incorrect', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/auth`,
data: {
email: 'test@test.com',
password: 'wrongpassword'
}
}
const result = await axios(options)
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
console.log(
`result stringified: ${JSON.stringify(result.data, null, 2)}`
)
assert(false, 'Unexpected result')
} catch (err) {
assert(err.response.status === 401, 'Error code 401 expected.')
}
})
await axios(options)
assert(false, 'Unexpected result')
} catch (err) {
assert(err.response.status === 401, 'Error code 401 expected.')
}
})
it('should auth user', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/auth`,
data: {
email: 'test@test.com',
password: 'pass'
it('should throw 401 if email is wrong format', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/auth`,
data: {
email: 'wrongEmail',
password: 'wrongpassword'
}
}
}
const result = await axios(options)
// console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
assert(result.status === 200, 'Status Code 200 expected.')
assert(
result.data.user.email === 'test@test.com',
'Email of test expected'
)
assert(
result.data.user.password === undefined,
'Password expected to be omited'
)
} catch (err) {
console.log(
'Error authenticating test user: ' + JSON.stringify(err, null, 2)
)
throw err
}
await axios(options)
assert(false, 'Unexpected result')
} catch (err) {
assert(err.response.status === 401, 'Error code 401 expected.')
}
})
it('should auth user', async () => {
try {
const options = {
method: 'post',
url: `${LOCALHOST}/auth`,
data: {
email: 'test@test.com',
password: 'pass'
}
}
const result = await axios(options)
// console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
assert(result.status === 200, 'Status Code 200 expected.')
assert(
result.data.user.email === 'test@test.com',
'Email of test expected'
)
assert(
result.data.user.password === undefined,
'Password expected to be omited'
)
} catch (err) {
console.log(
'Error authenticating test user: ' + JSON.stringify(err, null, 2)
)
throw err
}
})
})
})
})
}
File diff suppressed because it is too large Load Diff
-115
View File
@@ -1,115 +0,0 @@
import { assert } from 'chai'
import Admin from '../../../src/adapters/admin.js'
import sinon from 'sinon'
import util from 'util'
util.inspect.defaultOptions = { depth: 1 }
let sandbox
let uut
describe('Admin', () => {
beforeEach(() => {
uut = new Admin()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('loginAdmin()', () => {
it('should logind admin', async () => {
try {
const error = new Error('test error')
error.response = {
status: 422
}
// sandbox.stub(uut.axios, 'request').onFirstCall().throws(error)
const result = await uut.loginAdmin()
const user = result.data.user
assert.property(user, '_id')
assert.property(user, 'email')
assert.property(user, 'type')
assert.isString(user._id)
assert.isString(user.email)
assert.isString(user.type)
assert.equal(user.type, 'admin')
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should handle axios error', async () => {
try {
// Returns an erroneous password to force
// an auth error
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'wrong' })
await uut.loginAdmin()
assert(false, 'Unexpected result')
} catch (err) {
assert.equal(err.response.status, 401)
assert.include(err.response.data, 'Unauthorized')
}
})
})
describe('createSystemUser()', () => {
it('should create admin', async () => {
try {
const result = await uut.createSystemUser()
assert.property(result, 'email')
assert.property(result, 'password')
assert.property(result, 'id')
assert.property(result, 'token')
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should handle axios error', async () => {
try {
const error1 = new Error('test error')
error1.response = {
status: 422
}
const error2 = new Error('test error')
error1.response = {
status: 500
}
// The loginAdmin() function in some use cases is recursive
// after handling the 422 error, it gets called again
sandbox
.stub(uut.axios, 'request')
.onFirstCall()
.throws(error1)
.onSecondCall()
.throws(error2)
await uut.createSystemUser()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle errors when remove user', async () => {
try {
const error1 = new Error('test error')
error1.response = {
status: 422
}
sandbox.stub(uut.axios, 'request').throws(error1)
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
await uut.createSystemUser()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
})
+74
View File
@@ -0,0 +1,74 @@
/*
End-to-end tests for /usage endpoints.
*/
import config from '../../../config/index.js'
import { assert } from 'chai'
import axios from 'axios'
import sinon from 'sinon'
import util from 'util'
util.inspect.defaultOptions = { depth: 1 }
const LOCALHOST = `http://localhost:${config.port}`
let sandbox
describe('Usage', () => {
beforeEach(() => {
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('GET /usage', () => {
it('should return usage status', async () => {
try {
const options = {
method: 'get',
url: `${LOCALHOST}/usage`
}
const result = await axios(options)
assert.property(result.data, 'status')
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
describe('GET /usage/ips', () => {
it('should return ips', async () => {
try {
const options = {
method: 'get',
url: `${LOCALHOST}/usage/ips`
}
const result = await axios(options)
assert.property(result.data, 'ips')
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
describe('GET /usage/endpoints', () => {
it('should return ips', async () => {
try {
const options = {
method: 'get',
url: `${LOCALHOST}/usage/endpoints`
}
const result = await axios(options)
assert.property(result.data, 'endpoints')
} catch (err) {
assert(false, 'Unexpected result')
}
})
})
})
+20 -2
View File
@@ -24,15 +24,33 @@ describe('#adapters', () => {
})
describe('#start', () => {
it('should start the async adapters', async () => {
// it('should start the async adapters', async () => {
// // Mock dependencies
// uut.config.getJwtAtStartup = true
// uut.config.useIpfs = true
// uut.config.env = 'not-a-test'
// sandbox.stub(uut.fullStackJwt, 'getJWT').resolves()
// sandbox.stub(uut.fullStackJwt, 'instanceBchjs').resolves()
// sandbox.stub(uut.ipfs, 'start').resolves()
// const result = await uut.start()
// assert.equal(result, true)
// })
it('should not start ipfs on test enviroment', async () => {
// Mock dependencies
uut.config.getJwtAtStartup = true
uut.config.useIpfs = true
uut.config.env = 'test'
sandbox.stub(uut.fullStackJwt, 'getJWT').resolves()
sandbox.stub(uut.fullStackJwt, 'instanceBchjs').resolves()
sandbox.stub(uut.ipfs, 'start').resolves()
const ipfsSpy = sandbox.stub(uut.ipfs, 'start').resolves(null)
const result = await uut.start()
assert.isTrue(ipfsSpy.notCalled)
assert.equal(result, true)
})
+122
View File
@@ -0,0 +1,122 @@
import { assert } from 'chai'
import Admin from '../../../src/adapters/admin.js'
import sinon from 'sinon'
import util from 'util'
import config from '../../../config/index.js'
util.inspect.defaultOptions = { depth: 1 }
let sandbox
let uut
describe('Admin', () => {
beforeEach(() => {
uut = new Admin()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
if (!config.noMongo) {
describe('loginAdmin()', () => {
it('should login admin', async () => {
try {
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'pass' })
sandbox.stub(uut.axios, 'request').resolves(true)
const result = await uut.loginAdmin()
assert.isTrue(result)
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should handle axios error', async () => {
try {
// Returns an erroneous password to force
// an auth error
sandbox.stub(uut.axios, 'request').throws(new Error('test error'))
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'wrong' })
await uut.loginAdmin()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
describe('createSystemUser()', () => {
it('should create admin', async () => {
try {
await uut.deleteExistingSystemUser()
const result = await uut.createSystemUser()
assert.property(result, 'email')
assert.property(result, 'password')
assert.property(result, 'id')
assert.property(result, 'token')
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should update admin password', async () => {
try {
uut.config.adminPassword = 'newpassword'
const fakeUser = {
password: 'oldpassword',
save: () => { return 'token' },
generateToken: () => { return 'token' }
}
sandbox.stub(uut.User, 'findOne').resolves(fakeUser)
sandbox.stub(uut.jsonFiles, 'writeJSON').resolves(true)
const result = await uut.createSystemUser()
assert.property(result, 'email')
assert.property(result, 'password')
assert.property(result, 'id')
assert.property(result, 'token')
assert.equal(fakeUser.password, 'newpassword', 'password should be updated')
} catch (err) {
console.log(err)
assert(false, 'Unexpected result')
}
})
it('should handle error', async () => {
try {
sandbox.stub(uut.User, 'findOne').throws(new Error('test error'))
await uut.createSystemUser()
assert.fail('Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
describe('deleteExistingSystemUser()', () => {
it('should delete admin', async () => {
try {
sandbox.stub(uut.User, 'deleteOne').resolves(true)
const result = await uut.deleteExistingSystemUser()
assert.isTrue(result)
} catch (err) {
assert(false, 'Unexpected result')
}
})
it('should handle error when deleting admin', async () => {
try {
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
await uut.deleteExistingSystemUser()
assert.fail('Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
}
})
@@ -52,7 +52,7 @@ describe('#IPFS', () => {
it('should get the public IP address if this node is a Circuit Relay', async () => {
// Mock dependencies.
uut.IpfsCoord = IPFSCoordMock
sandbox.stub(uut.publicIp, 'v4').resolves('123')
sandbox.stub(uut, 'publicIp').resolves('123')
// Force Circuit Relay
uut.config.isCircuitRelay = true
@@ -66,7 +66,7 @@ describe('#IPFS', () => {
it('should exit quietly if this node is a Circuit Relay and there is an issue getting the IP address', async () => {
// Mock dependencies.
uut.IpfsCoord = IPFSCoordMock
sandbox.stub(uut.publicIp, 'v4').rejects(new Error('test error'))
sandbox.stub(uut, 'publicIp').rejects(new Error('test error'))
// Force Circuit Relay
uut.config.isCircuitRelay = true
@@ -66,4 +66,155 @@ describe('#IPFS-adapter-index', () => {
}
})
})
describe('#getStatus', () => {
it('should return an object with node metrics', () => {
// Force uut to have the needed properties
uut.ipfsCoordAdapter = {
ipfsCoord: {
thisNode: {
ipfsId: 'fake-id',
ipfsMultiaddrs: [],
bchAddr: 'fake-bch-addr',
slpAddr: 'fake-slp-addr',
pubKey: 'fake-pubkey',
peerList: [],
relayData: []
}
}
}
const result = uut.getStatus()
// console.log('result: ', result)
assert.property(result, 'ipfsId')
assert.property(result, 'multiAddrs')
assert.property(result, 'bchAddr')
assert.property(result, 'slpAddr')
assert.property(result, 'pubKey')
assert.property(result, 'peers')
assert.property(result, 'relays')
})
it('should catch, report, and throw errors', () => {
try {
uut.getStatus()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err.message: ', err.message)
assert.include(err.message, 'Cannot read properties')
}
})
})
describe('#_removeDuplicatePeers', () => {
it('should return the same array when there are no duplicates', () => {
const inArray = [
'12D3KooWPpBXhhAeoCZCGuQ3KR4xwHzzvtP57f6zLmo8P7ZFBJFE',
'12D3KooWRBhwfeP2Y9CDkFRBAZ1pmxUadH36TKuk3KtKm5XXP8mA',
'12D3KooWGsgHWyDLKuV4ZSfRJfsxQJj77rxx3i8Px3qXKHsLN7a2',
'12D3KooWJyc54njjeZGbLew4D8u1ghrmZTTPyh3QpBF7dxtd3zGY',
'12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu'
]
const result = uut._removeDuplicatePeers(inArray)
// console.log('result: ', result)
assert.equal(result.length, inArray.length)
})
it('should remove duplicates from an array', () => {
const inArray = [
'12D3KooWPpBXhhAeoCZCGuQ3KR4xwHzzvtP57f6zLmo8P7ZFBJFE',
'12D3KooWRBhwfeP2Y9CDkFRBAZ1pmxUadH36TKuk3KtKm5XXP8mA',
'12D3KooWGsgHWyDLKuV4ZSfRJfsxQJj77rxx3i8Px3qXKHsLN7a2',
'12D3KooWJyc54njjeZGbLew4D8u1ghrmZTTPyh3QpBF7dxtd3zGY',
'12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu',
'12D3KooWPpBXhhAeoCZCGuQ3KR4xwHzzvtP57f6zLmo8P7ZFBJFE',
'12D3KooWRBhwfeP2Y9CDkFRBAZ1pmxUadH36TKuk3KtKm5XXP8mA'
]
const result = uut._removeDuplicatePeers(inArray)
// console.log('result: ', result)
assert.equal(result.length, inArray.length - 2)
})
})
describe('#getPeers', () => {
it('should return an array of current JSON data about each peer when showAll is true', async () => {
// Mock dependencies
uut.ipfsCoordAdapter.ipfsCoord = {
thisNode: {
peerData: [{ from: 'a', data: { jsonLd: { name: 'a', protocol: 'test', version: '1' } } }, { from: 'b' }]
},
adapters: {
ipfs: {
getPeers: async () => ['a', 'b']
}
}
}
const result = await uut.getPeers(true)
assert.isArray(result)
})
it('should catch, report, and throw errors', async () => {
try {
// Force an error
uut.ipfsCoordAdapter.ipfsCoord = {
thisNode: {},
adapters: {
ipfs: {
getPeers: async () => { throw new Error('test error') }
}
}
}
await uut.getPeers()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err.message: ', err.message)
assert.include(err.message, 'test error')
}
})
})
describe('#getRelays', () => {
it('should return known relays', () => {
uut.ipfsCoordAdapter.ipfsCoord = {
thisNode: {
peerData: [{ from: 'a', data: { jsonLd: { name: 'a', description: 'test' } } }, { from: 'b' }],
relayData: [{ ipfsId: 'c' }, { ipfsId: 'a' }]
}
}
const result = uut.getRelays()
// console.log('result: ', result)
assert.isArray(result.v2Relays)
assert.equal(result.v2Relays.length, 2)
assert.property(result.v2Relays[0], 'name')
assert.property(result.v2Relays[0], 'description')
assert.property(result.v2Relays[1], 'name')
assert.property(result.v2Relays[1], 'description')
})
it('should catch, report, and throw errors', () => {
try {
// Force an error
uut.ipfsCoordAdapter.ipfsCoord = { thisNode: {} }
uut.getRelays()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err.message: ', err.message)
assert.include(err.message, 'Cannot read')
}
})
})
})
+161 -38
View File
@@ -2,21 +2,29 @@
Unit tests for the IPFS Adapter.
*/
// Global npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
import cloneDeep from 'lodash.clonedeep'
// import { peerIdFromString } from '@libp2p/peer-id'
// Local libraries
import IPFSLib from '../../../src/adapters/ipfs/ipfs.js'
import create from '../mocks/ipfs-mock.js'
// import create from '../mocks/ipfs-mock.js'
import config from '../../../config/index.js'
import createHeliaLib from '../mocks/helia-mock.js'
// config.isProduction = true;
describe('#IPFS-adapter', () => {
let uut
let sandbox
let ipfs
beforeEach(() => {
uut = new IPFSLib()
ipfs = cloneDeep(createHeliaLib)
sandbox = sinon.createSandbox()
})
@@ -45,35 +53,28 @@ describe('#IPFS-adapter', () => {
describe('#start', () => {
it('should return a promise that resolves into an instance of IPFS.', async () => {
// Mock dependencies.
uut.create = create
sandbox.stub(uut, 'createNode').resolves(ipfs)
sandbox.stub(uut, 'publicIp').resolves('192.168.2.4')
sandbox.stub(uut, 'multiaddr').returns('/ip4/fake-multiaddr')
const result = await uut.start()
// console.log('result: ', result)
// Assert properties of the instance are set.
assert.equal(uut.isReady, true)
assert.property(uut, 'multiaddrs')
assert.property(uut, 'id')
assert.property(result, 'config')
})
it('should return a promise that resolves into an instance of IPFS in production mode.', async () => {
// Mock dependencies.
uut.create = create
uut.config.isProduction = true
const result = await uut.start()
// console.log('result: ', result)
assert.equal(uut.isReady, true)
assert.property(result, 'config')
// Output should be an instance of IPFS
assert.property(result, 'libp2p')
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox.stub(uut, 'create').rejects(new Error('test error'))
sandbox.stub(uut, 'createNode').rejects(new Error('test error'))
await uut.start()
assert.fail('Unexpected code path.')
} catch (err) {
// console.log(err)
@@ -96,24 +97,146 @@ describe('#IPFS-adapter', () => {
})
})
// describe('#rmBlocksDir', () => {
// it('should delete the /blocks directory', () => {
// const result = uut.rmBlocksDir()
//
// assert.equal(result, true)
// })
//
// it('should catch and throw an error', () => {
// try {
// // Force an error
// sandbox.stub(uut.fs, 'rmdirSync').throws(new Error('test error'))
//
// uut.rmBlocksDir()
//
// assert.fail('Unexpected code path')
// } catch (err) {
// assert.equal(err.message, 'test error')
// }
// })
// })
describe('#ensureBlocksDir', () => {
it('should create directory if it does not exist', () => {
// Force desired code path
sandbox.stub(uut.fs, 'existsSync').returns(false)
sandbox.stub(uut.fs, 'mkdirSync').returns(true)
const result = uut.ensureBlocksDir()
assert.equal(result, true)
})
it('should report and throw errors', () => {
// Force an error
sandbox.stub(uut.fs, 'existsSync').throws(new Error('test error'))
try {
uut.ensureBlocksDir()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
describe('#createNode', () => {
it('should report and throw errors', async () => {
// Force an error
sandbox.stub(uut, 'createLibp2p').rejects(new Error('test error'))
try {
await uut.createNode()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should create an IPFS node from Helia', async () => {
uut.config.isCircuitRelay = false
const result = await uut.createNode()
// console.log('result: ', result)
// Assert the returned IPFS node has expected properties
assert.property(result, 'libp2p')
assert.property(result, 'blockstore')
// Stop the IPFS node
await result.stop()
})
it('should create a Circuit Relay if configured', async () => {
uut.config.isCircuitRelay = true
const result = await uut.createNode()
// Assert the returned IPFS node has expected properties
assert.property(result, 'libp2p')
assert.property(result, 'blockstore')
// Stop the IPFS node
await result.stop()
})
it('should create a new private key on first run', async () => {
uut.config.isCircuitRelay = false
// Mock dependencies and force desired code path.
// const beenCalled = false
// sandbox.stub(uut, 'getKeychain').resolves({
// exportPeerId: async () => {
// if (!beenCalled) {
// beenCalled = true
// throw new Error('test error')
// }
// return peerIdFromString('12D3KooWSXF1PnEfiA8bCG8SJduCvzdwHtvhVPK4WC6zzDoto2XP')
// },
// createKey: async () => {}
// })
sandbox.stub(uut, 'createLibp2p').resolves()
sandbox.stub(uut, 'createHelia').resolves({})
const result = await uut.createNode()
// console.log('result: ', result)
assert.property(result, 'fs')
})
it('should not use circuit relay when CONNECT_PREF is set to direct', async () => {
process.env.CONNECT_PREF = 'direct'
uut.config.isCircuitRelay = false
const result = await uut.createNode()
// console.log('result: ', result)
delete process.env.CONNECT_PREF
// Assert the returned IPFS node has expected properties
assert.property(result, 'libp2p')
assert.property(result, 'blockstore')
// Stop the IPFS node
await result.stop()
})
})
describe('#getSeed', () => {
it('should read the seed from the JSON file', async () => {
// Mock dependencies and force desired code path
sandbox.stub(uut.jsonFiles, 'readJSON').resolves('12345678')
const result = await uut.getSeed()
// console.log('result: ', result)
assert.isString(result)
})
it('should generate a new seed if the JSON file is not found', async () => {
// Mock dependencies and force desired code path
sandbox.stub(uut.jsonFiles, 'readJSON').rejects(new Error('test error'))
sandbox.stub(uut.jsonFiles, 'writeJSON').resolves()
const result = await uut.getSeed()
// console.log('result: ', result)
assert.isString(result)
})
it('should catch, report, and throw errors', async () => {
try {
// Force an error
sandbox.stub(uut.jsonFiles, 'readJSON').rejects(new Error('test error'))
sandbox.stub(uut.jsonFiles, 'writeJSON').rejects(new Error('test error'))
await uut.getSeed()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
})
+131
View File
@@ -0,0 +1,131 @@
/*
Unit tests for the Nostr Adapter library.
*/
// Public npm libraries.
import { assert } from 'chai'
import sinon from 'sinon'
import NostrAdapter from '../../../src/adapters/nostr.js'
import NostrMock from '../mocks/nostr-mock.js'
describe('#nostr.js', () => {
let uut
let sandbox
before(() => {})
beforeEach(() => {
uut = new NostrAdapter({
nostrRelay: 'wss://nostr',
nostrTopic: 'uut-topic'
})
uut.RelayPool = NostrMock.RelayPoolMock
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
after(() => {})
describe('#contructor', () => {
it('should throw an error if nostr relay is not provided', async () => {
try {
const _uut = new NostrAdapter()
// Eslint
console.log(_uut)
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(
err.message,
'"nostrRelay" must be passed when instantiate NostrAdapter'
)
}
})
it('should throw an error if nostr topic is not provided', async () => {
try {
const _uut = new NostrAdapter({ nostrRelay: 'wss://' })
// Eslint
console.log(_uut)
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(
err.message,
'"nostrTopic" must be passed when instantiate NostrAdapter'
)
}
})
})
describe('#start', () => {
it('should throw an error if WIF is not provided', async () => {
try {
await uut.start()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'WIF must be a string!')
}
})
it('should start adapter', async () => {
// Stub bchNostr
sandbox
.stub(uut.bchNostr.keys, 'createNostrPubKeyFromWif')
.returns({ privKeyBuf: 'privKeyBuf', nostrPubKey: 'nostrPubKey' })
const result = await uut.start('WIF')
assert.isTrue(result)
})
})
describe('#post', () => {
it('should throw an error if msg is not provided', async () => {
try {
await uut.post()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'msg must be a string!')
}
})
it('should post message', async () => {
const evId = 'uut-eventId' // Mock
sandbox.stub(uut.bchNostr.post, 'uploadToNostr').resolves(evId)
const result = await uut.post('uut-message')
assert.isString(result)
assert.equal(result, evId)
})
})
describe('#read', () => {
it('should throw an error if provided limit is wrong', async () => {
try {
await uut.read(-5)
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'Limit must be greater than 0')
}
})
it('should read message', async () => {
const result = await uut.read()
assert.isArray(result)
assert.equal(result.length, 1)
})
it('should read message with custom limit', async () => {
const result = await uut.read(1)
assert.isArray(result)
assert.equal(result.length, 1)
})
})
})
+10 -1
View File
@@ -47,7 +47,16 @@ describe('#OrderPagination', () => {
timestamp: 'timestamp',
localTimestamp: 'localTimestamp',
txid: 'txid',
p2wdbHash: 'p2wdbHash'
p2wdbHash: 'p2wdbHash',
hdIndex: 0,
nostrEventId: 'nostrEventId',
dataType: 'dataType',
makerAddr: 'makerAddr',
rateInBaseUnit: 'rateInBaseUnit',
minUnitsToExchange: 'minUnitsToExchange',
ticker: 'ticker',
tokenType: 1,
userId: 'userId'
})
await order.save()
})
+1 -1
View File
@@ -8,7 +8,7 @@ import { assert } from 'chai'
import sinon from 'sinon'
// const BCHJS = require('@psf/bch-js')
import BchWallet from 'minimal-slp-wallet/index.js'
import BchWallet from 'minimal-slp-wallet'
// Local libraries.
import P2wdbAdapter from '../../../src/adapters/p2wdb-adapter.js'
@@ -41,5 +41,17 @@ describe('#passport.js', () => {
assert.include(err.message, 'cant auth user')
}
})
it('should authenticate user', async () => {
const ctx = {}
const errMock = null
const userMock = {
_id: '123',
email: 'test@test.com'
}
sandbox.stub(uut.passport, 'authenticate').yields(errMock, userMock)
const user = await uut.authUser(ctx)
assert.equal(user._id, userMock._id)
assert.equal(user.email, userMock.email)
})
})
})
+9 -1
View File
@@ -31,7 +31,8 @@ describe('#User-Adapter', () => {
testuser = new User({
email: 'test983@test.com',
name: 'test983',
password: 'password'
password: 'password',
mnemonic: '12 words mnemonic'
})
})
@@ -54,6 +55,13 @@ describe('#User-Adapter', () => {
assert.notEqual(testuser.password, 'password')
})
it('should ignore password encrption if password property is not provided', async () => {
const lastPassword = testuser.password
await testuser.save()
// console.log('testuser: ', testuser)
assert.equal(testuser.password, lastPassword)
})
})
describe('#validatePassword', () => {
+180 -160
View File
@@ -2,16 +2,14 @@
Unit tests for the Wallet Adapter library.
*/
// Public npm libraries.
// Global npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
import BchWallet from 'minimal-slp-wallet'
import fs from 'fs'
// const BCHJS = require('@psf/bch-js')
// Local libraries.
import WalletAdapter from '../../../src/adapters/wallet.js'
// Local libraries
import WalletAdapter from '../../../src/adapters/wallet.adapter.js'
import { MockBchWallet } from '../mocks/adapters/wallet.js'
// Hack to get __dirname back.
@@ -20,7 +18,7 @@ import * as url from 'url'
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
// Global constants
const testWalletFile = `${__dirname.toString()}/test-wallet.json`
const testWalletFile = `${__dirname.toString()}test-wallet.json`
describe('#wallet', () => {
let uut
@@ -30,7 +28,7 @@ describe('#wallet', () => {
// Delete the test file if it exists.
try {
deleteFile(testWalletFile)
} catch (err) {}
} catch (err) { }
})
beforeEach(() => {
@@ -44,20 +42,26 @@ describe('#wallet', () => {
// Delete the test file if it exists.
try {
deleteFile(testWalletFile)
} catch (err) {}
} catch (err) { }
})
describe('#_instanceWallet', () => {
it('should create a wallet given a mnemonic', async () => {
const mnemonic = 'wagon tray learn flat erase laugh lonely rug check captain jacket morning'
const result = await uut._instanceWallet(mnemonic)
// console.log('result: ', result)
assert.equal(result.walletInfo.mnemonic, mnemonic)
})
})
describe('#openWallet', () => {
it('should create a new wallet what wallet file does not exist', async () => {
it('should create a new wallet when wallet file does not exist', async () => {
// Mock dependencies
uut.BchWallet = MockBchWallet
// Ensure we open the test file, not the production wallet file.
uut.WALLET_FILE = testWalletFile
uut.config.walletFile = testWalletFile
const result = await uut.openWallet()
// console.log('result: ', result)
assert.property(result, 'mnemonic')
assert.property(result, 'privateKey')
assert.property(result, 'publicKey')
@@ -70,13 +74,10 @@ describe('#wallet', () => {
it('should open existing wallet file', async () => {
// This test case uses the file created in the previous test case.
// Ensure we open the test file, not the production wallet file.
uut.WALLET_FILE = testWalletFile
uut.config.walletFile = testWalletFile
const result = await uut.openWallet()
// console.log('result: ', result)
assert.property(result, 'mnemonic')
assert.property(result, 'privateKey')
assert.property(result, 'publicKey')
@@ -90,13 +91,11 @@ describe('#wallet', () => {
it('should catch and throw an error', async () => {
try {
// Force an error
uut.WALLET_FILE = ''
uut.config.walletFile = ''
uut.BchWallet = () => {
}
await uut.openWallet()
// console.log('result: ', result)
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
@@ -105,113 +104,158 @@ describe('#wallet', () => {
})
})
describe('#instanceWallet', () => {
// it('should create an instance of BchWallet', async () => {
// // Mock dependencies
// uut.BchWallet = MockBchWallet
//
// // Ensure we open the test file, not the production wallet file.
// uut.WALLET_FILE = testWalletFile
//
// const walletData = await uut.openWallet()
//
// const result = await uut.instanceWallet(walletData.mnemonic)
// console.log('result: ', result)
//
// assert.property(result, 'walletInfoPromise')
// })
describe('#instanceWalletWithoutInitialization', () => {
it('should create an instance of BchWallet', async () => {
// Create a mock wallet.
const mockWallet = new BchWallet()
await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves()
// Mock dependencies
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
uut.config.authPass = 'fake-auth-pass'
// Ensure we open the test file, not the production wallet file.
uut.config.walletFile = testWalletFile
const walletData = await uut.openWallet()
// console.log('walletData: ', walletData)
const result = await uut.instanceWalletWithoutInitialization(walletData)
// console.log('result: ', result)
assert.property(result, 'walletInfoPromise')
assert.property(result, 'walletInfo')
})
it('should catch and throw an error', async () => {
try {
await uut.instanceWallet()
// Force an error
sandbox.stub(uut, '_instanceWallet').rejects(new Error('test error'))
await uut.instanceWalletWithoutInitialization()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'Cannot read')
}
})
})
describe('#generateSignature', () => {
it('should return a signature', async () => {
// mock instance of minimal-slp-wallet
uut.bchWallet = new MockBchWallet()
const result = await uut.generateSignature('test')
// console.log('result: ', result)
assert.isString(result)
})
it('should catch and throw errors', async () => {
try {
// mock instance of minimal-slp-wallet
uut.bchWallet = new MockBchWallet()
// force an error
sandbox
.stub(uut.bchWallet.bchjs.BitcoinCash, 'signMessageWithPrivKey')
.throws(new Error('test error'))
await uut.generateSignature('test')
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should create an instance of BchWallet using web2 infra', async () => {
// Create a mock wallet.
const mockWallet = new BchWallet()
await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves()
// Mock dependencies
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
// Ensure we open the test file, not the production wallet file.
uut.config.walletFile = testWalletFile
const walletData = await uut.openWallet()
// console.log('walletData: ', walletData)
// Force desired code path
uut.config.walletInterface = 'web2'
const result = await uut.instanceWalletWithoutInitialization(walletData)
// console.log('result: ', result)
assert.property(result, 'walletInfoPromise')
assert.property(result, 'walletInfo')
})
it('should generate wallet from mnemonic in config', async () => {
// Create a mock wallet.
const mockWallet = new BchWallet()
await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves()
// Mock dependencies
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
// Ensure we open the test file, not the production wallet file.
uut.config.walletFile = testWalletFile
const walletData = await uut.openWallet()
// console.log('walletData: ', walletData)
const originalConfig = uut.config.mnemonic
uut.config.mnemonic = walletData.mnemonic
const result = await uut.instanceWalletWithoutInitialization({})
// console.log('result: ', result)
uut.config.mnemonic = originalConfig
assert.property(result, 'walletInfoPromise')
assert.property(result, 'walletInfo')
})
})
// describe('#burnPsf', () => {
// it('should burn PSF tokens and return the txid', async () => {
// // mock instance of minimal-slp-wallet
// uut.bchWallet = new MockBchWallet()
//
// const result = await uut.burnPsf()
// // console.log('result: ', result)
//
// assert.equal(result.success, true)
// assert.equal(result.txid, 'txid')
// })
//
// it('should throw error if no PSF tokens are found', async () => {
// try {
// // mock instance of minimal-slp-wallet
// uut.bchWallet = new MockBchWallet()
//
// // Remove the PSF token from the mock data.
// uut.bchWallet.utxos.utxoStore.slpUtxos.type1.tokens.pop()
//
// await uut.burnPsf()
//
// assert.fail('Unexpected code path')
// } catch (err) {
// assert.include(err.message, 'Token UTXO of with ID of')
// }
// })
//
// it('should catch and throw an error', async () => {
// try {
// await uut.burnPsf()
//
// assert.fail('Unexpected code path')
// } catch (err) {
// assert.include(err.message, 'Cannot read')
// }
// })
// })
describe('#instanceWallet', () => {
it('should create an instance of BchWallet', async () => {
// Create a mock wallet.
const mockWallet = new BchWallet()
await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves()
// Mock dependencies
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
// Ensure we open the test file, not the production wallet file.
uut.WALLET_FILE = testWalletFile
const walletData = await uut.openWallet()
// console.log('walletData: ', walletData)
const result = await uut.instanceWallet(walletData)
// console.log('result: ', result)
assert.property(result, 'walletInfoPromise')
assert.property(result, 'walletInfo')
})
it('should catch and throw an error', async () => {
try {
// Force an error
sandbox.stub(uut, 'instanceWalletWithoutInitialization').rejects(new Error('test error'))
await uut.instanceWallet()
assert.fail('Unexpected code path')
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'test error')
}
})
it('should create an instance of BchWallet using web2 infra', async () => {
// Create a mock wallet.
const mockWallet = new BchWallet()
await mockWallet.walletInfoPromise
sandbox.stub(mockWallet, 'initialize').resolves()
// Mock dependencies
sandbox.stub(uut, '_instanceWallet').resolves(mockWallet)
// Ensure we open the test file, not the production wallet file.
uut.WALLET_FILE = testWalletFile
const walletData = await uut.openWallet()
// console.log('walletData: ', walletData)
// Force desired code path
uut.config.useFullStackCash = true
const result = await uut.instanceWallet(walletData)
// console.log('result: ', result)
assert.property(result, 'walletInfoPromise')
assert.property(result, 'walletInfo')
})
})
describe('#incrementNextAddress', () => {
it('should increment the nextAddress property', async () => {
// Ensure we open the test file, not the production wallet file.
uut.WALLET_FILE = testWalletFile
// mock instance of minimal-slp-wallet
uut.bchWallet = new MockBchWallet()
const result = await uut.incrementNextAddress()
assert.equal(result, 2)
})
@@ -219,9 +263,7 @@ describe('#wallet', () => {
try {
// Force an error
sandbox.stub(uut, 'openWallet').rejects(new Error('test error'))
await uut.incrementNextAddress()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
@@ -232,10 +274,8 @@ describe('#wallet', () => {
describe('#getKeyPair', () => {
it('should return an object with a key pair', async () => {
// Ensure we open the test file, not the production wallet file.
uut.WALLET_FILE = testWalletFile
// mock instance of minimal-slp-wallet
uut.bchWallet = new MockBchWallet()
// uut.WALLET_FILE = testWalletFile
uut.config.walletFile = testWalletFile
const result = await uut.getKeyPair()
// console.log('result: ', result)
@@ -251,9 +291,7 @@ describe('#wallet', () => {
sandbox
.stub(uut, 'incrementNextAddress')
.rejects(new Error('test error'))
await uut.getKeyPair()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
@@ -261,55 +299,37 @@ describe('#wallet', () => {
})
})
describe('#moveTokens', () => {
it('should move tokens to a new address in the HD wallet', async () => {
// Mock dependencies
sandbox.stub(uut, 'getKeyPair').resolves({
cashAddress: 'bitcoincash:qqsj63493jk4p05zzdgqzc29k5unqtet9vv8l4x0yt',
wif: 'L4qKTMCwjH9jHnYtNh9Vsrxj7Hg6zmoN8E2v7N47UKvNVEjw7FU8',
hdIndex: 6
})
describe('#optimize', () => {
it('should call the wallet optimize function', async () => {
// mock instance of minimal-slp-wallet
uut.bchWallet = new MockBchWallet()
// uut.bchWallet = {
// sendTokens: async () => 'fake-txid',
// getUtxos: async () => {}
// }
const inObj = {
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
qty: 1
}
const result = await uut.moveTokens(inObj)
// console.log('result: ', result)
assert.property(result, 'txid')
assert.property(result, 'vout')
assert.property(result, 'hdIndex')
sandbox.stub(uut.bchWallet, 'optimize').resolves({ bchUtxoCnt: 10 })
const result = await uut.optimize()
assert.equal(result, true)
})
})
describe('#moveBch', () => {
it('should move BCH to a new address in the HD wallet', async () => {
// Mock dependencies
sandbox.stub(uut, 'getKeyPair').resolves({
cashAddress: 'bitcoincash:qqsj63493jk4p05zzdgqzc29k5unqtet9vv8l4x0yt',
wif: 'L4qKTMCwjH9jHnYtNh9Vsrxj7Hg6zmoN8E2v7N47UKvNVEjw7FU8',
hdIndex: 6
})
uut.bchWallet = {
send: async () => 'fake-txid',
getUtxos: async () => {}
}
const amountSat = 1000
const result = await uut.moveBch(amountSat)
describe('#getBalance', () => {
it('should get the balance for the wallet', async () => {
// mock instance of minimal-slp-wallet
uut.bchWallet = new MockBchWallet()
// Mock dependencies and force desired code path
sandbox.stub(uut.bchWallet, 'getBalance').resolves(41012)
sandbox.stub(uut.bchWallet, 'listTokens').resolves([{
tokenId: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
ticker: 'PSF',
name: 'Permissionless Software Foundation',
decimals: 8,
tokenType: 1,
url: 'psfoundation.cash',
qty: 2
}])
const result = await uut.getBalance()
// console.log('result: ', result)
assert.property(result, 'txid')
assert.property(result, 'vout')
assert.property(result, 'hdIndex')
// Assert the expected properties exist and have the expected values.
assert.equal(result.satBalance, 41012)
assert.equal(result.psfBalance, 2)
assert.equal(result.success, true)
})
})
})
+144
View File
@@ -0,0 +1,144 @@
/*
Unit tests for the adapter/wallet.js library.
*/
// Global npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
import WalletAdapter from '../../../src/adapters/wallet.js'
import { MockBchWallet } from '../mocks/adapters/wallet.js'
describe('#wallet', () => {
let uut
let sandbox
beforeEach(() => {
uut = new WalletAdapter()
uut.bchWallet = new MockBchWallet()
uut.bitcoinJs = {
Transaction: {
fromHex: () => { return { } },
SIGHASH_ALL: () => { return { } }
},
TransactionBuilder: {
fromTransaction: () => { return { sign: () => { return { } }, build: () => { return { toHex: () => { return 'hex' } } } } }
},
ECPair: {
fromWIF: () => { return { } }
}
}
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('#moveTokensFromCustomWallet', () => {
it('should move tokens from a custom wallet', async () => {
sandbox.stub(uut, 'getKeyPair').resolves({
cashAddress: 'cashAddress',
wif: 'wif',
hdIndex: 11
})
const customWallet = new MockBchWallet()
customWallet.getKeyPair = () => { return { cashAddress: 'cashAddress', wif: 'wif', hdIndex: 11 } }
customWallet.sendTokens = () => { return 'move token tx id result' }
customWallet.utxos.utxoStore.slpUtxos.type1.tokens = [
{
tokenId: 'tokenId',
tokenType: 1,
qty: 1
}
]
const inObj = {
qty: 1,
wallet: customWallet,
tokenId: 'tokenId'
}
const result = await uut.moveTokensFromCustomWallet(inObj)
assert.equal(result.txid, 'move token tx id result')
assert.equal(result.hdIndex, 11)
assert.equal(result.tokenType, 1)
assert.equal(result.vout, 1)
})
it('should throw an error if the wallet is not provided', async () => {
try {
const inObj = {
qty: 1,
tokenId: 'tokenId'
}
await uut.moveTokensFromCustomWallet(inObj)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'wallet is required!')
}
})
it('should throw an error if the tokenId is not provided', async () => {
try {
const inObj = {
qty: 1,
wallet: new MockBchWallet()
}
await uut.moveTokensFromCustomWallet(inObj)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'tokenId must be a string!')
}
})
it('should throw an error if the qty is not provided', async () => {
try {
const inObj = {
tokenId: 'tokenId',
wallet: new MockBchWallet()
}
await uut.moveTokensFromCustomWallet(inObj)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'qty must be a number!')
}
})
})
describe('#completeTx', () => {
// TODO: This test needs to be refactored to use the new instanceWallet() method for loading the user wallet.
// it('should complete a transaction', async () => {
// sandbox.stub(uut, 'getKeyPair').resolves({
// cashAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
// wif: 'L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ',
// hdIndex: 11
// })
// sandbox.stub(uut, 'deseralizeTx').resolves({
// txid: 'complete tx id result'
// })
// sandbox.stub(uut.retryQueue, 'addToQueue').resolves('complete tx id result')
// const hex = 'hex'
// const hdIndex = 11
// const mnemonic = 'course abstract aerobic deer try switch turtle diet fence affair butter top'
// const txid = await uut.completeTx(hex, hdIndex, mnemonic)
// assert.equal(txid, 'complete tx id result')
// })
it('should throw an error if the hex is not provided', async () => {
try {
const hdIndex = 11
await uut.completeTx(null, hdIndex)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'hex must be a string!')
}
})
it('should throw an error if the hdIndex is not provided', async () => {
try {
const hex = 'hex'
await uut.completeTx(hex, null)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(error.message, 'hdIndex must be a non-negative number!')
}
})
})
})
+24
View File
@@ -28,6 +28,9 @@ describe('#Controllers', () => {
attachRPCRouter: () => {}
}
// Mock the timer controllers
sandbox.stub(uut.timerControllers, 'startTimers').returns()
const app = {
use: () => {}
}
@@ -35,4 +38,25 @@ describe('#Controllers', () => {
await uut.attachControllers(app)
})
})
describe('#attachRESTControllers', () => {
it('should attach the controllers', async () => {
const app = {
use: () => {}
}
await uut.attachRESTControllers(app)
})
})
describe('#initAdapters', () => {
it('should attach the controllers', async () => {
sandbox.stub(uut.adapters, 'start').resolves({})
await uut.initAdapters()
})
})
describe('#initUseCases', () => {
it('should attach the controllers', async () => {
sandbox.stub(uut.useCases, 'start').resolves({})
await uut.initUseCases()
})
})
})
@@ -79,11 +79,22 @@ describe('#Auth-REST-Router', () => {
await uut.authUser(ctx)
})
it('should catch and throw an error', async () => {
it('should catch and throw passport error', async () => {
try {
// Force an error
sandbox.stub(uut.passport, 'authUser').rejects('test error')
await uut.authUser(ctx)
} catch (err) {
// console.log('err: ', err)
assert.include(err.message, 'Unauthorized')
}
})
it('should handle error if user is not found!', async () => {
try {
// Force an error
sandbox.stub(uut.passport, 'authUser').resolves(null)
await uut.authUser(ctx)
} catch (err) {
// console.log('err: ', err)
@@ -0,0 +1,251 @@
/*
Unit tests for the REST API handler for the /ipfs endpoints.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local libraries
import IpfsApiController from '../../../../../src/controllers/rest-api/ipfs/controller.js'
import adapters from '../../../mocks/adapters/index.js'
import UseCasesMock from '../../../mocks/use-cases/index.js'
import { context as mockContext } from '../../../mocks/ctx-mock.js'
let uut
let sandbox
let ctx
describe('#IPFS REST API', () => {
before(async () => {
})
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new IpfsApiController({ adapters, useCases })
sandbox = sinon.createSandbox()
// Mock the context object.
ctx = mockContext()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new IpfsApiController()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating /ipfs REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new IpfsApiController({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating /ipfs REST Controller.'
)
}
})
})
describe('#GET /status', () => {
it('should return 422 status on biz logic error', async () => {
try {
// Force an error
sandbox.stub(uut.adapters.ipfs, 'getStatus').rejects(new Error('test error'))
await uut.getStatus(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
// Mock dependencies
sandbox.stub(uut.adapters.ipfs, 'getStatus').resolves({ a: 'b' })
await uut.getStatus(ctx)
// console.log('ctx.body: ', ctx.body)
assert.property(ctx.body, 'status')
assert.equal(ctx.body.status.a, 'b')
})
})
describe('#POST /peers', () => {
it('should return 422 status on biz logic error', async () => {
try {
// Force an error
sandbox.stub(uut.adapters.ipfs, 'getPeers').rejects(new Error('test error'))
ctx.request.body = {
showAll: true
}
await uut.getPeers(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
// Mock dependencies
sandbox.stub(uut.adapters.ipfs, 'getPeers').resolves({ a: 'b' })
ctx.request.body = {
showAll: true
}
await uut.getPeers(ctx)
// console.log('ctx.body: ', ctx.body)
assert.property(ctx.body, 'peers')
assert.equal(ctx.body.peers.a, 'b')
})
})
describe('#POST /relays', () => {
it('should return 422 status on biz logic error', async () => {
try {
// Force an error
sandbox.stub(uut.adapters.ipfs, 'getRelays').rejects(new Error('test error'))
await uut.getRelays(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
// Mock dependencies
sandbox.stub(uut.adapters.ipfs, 'getRelays').resolves({ a: 'b' })
await uut.getRelays(ctx)
// console.log('ctx.body: ', ctx.body)
assert.property(ctx.body, 'relays')
assert.equal(ctx.body.relays.a, 'b')
})
})
describe('#POST /connect', () => {
it('should return 422 status on biz logic error', async () => {
try {
// Force an error
sandbox.stub(uut.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs, 'connectToPeer').rejects(new Error('test error'))
ctx.request.body = {
multiaddr: '/ip4/161.35.99.207/tcp/4001/p2p/12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu'
}
await uut.connect(ctx)
assert.fail('Unexpected result')
} catch (err) {
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
// Mock dependencies
sandbox.stub(uut.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs, 'connectToPeer').resolves({ success: true })
ctx.request.body = {
multiaddr: '/ip4/161.35.99.207/tcp/4001/p2p/12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu'
}
await uut.connect(ctx)
// console.log('ctx.body: ', ctx.body)
assert.property(ctx.body, 'success')
assert.equal(ctx.body.success, true)
})
})
describe('#handleError', () => {
it('should still throw error if there is no message', () => {
try {
const err = {
status: 404
}
uut.handleError(ctx, err)
} catch (err) {
assert.include(err.message, 'Not Found')
}
})
it('should throw error with message', () => {
try {
const err = {
status: 422,
message: 'test error'
}
uut.handleError(ctx, err)
} catch (err) {
assert.include(err.message, 'test error')
}
})
})
describe('#getThisNode', () => {
it('should return 422 status on biz logic error', async () => {
try {
// Force an error
// sandbox.stub(uut.adapters.ipfs.ipfsCoordAdapter.ipfsCoord, 'thisNode').rejects(new Error('test error'))
uut.adapters.ipfs.ipfsCoordAdapter = {}
ctx.request.body = {}
await uut.getThisNode(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log('err: ', err)
assert.equal(err.status, 422)
assert.include(err.message, 'Cannot read')
}
})
it('should return 200 status on success', async () => {
// Mock dependencies
// sandbox.stub(uut.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs, 'connectToPeer').resolves({ success: true })
uut.adapters.ipfs.ipfsCoordAdapter = {
ipfsCoord: {
thisNode: {}
}
}
ctx.request.body = {}
await uut.getThisNode(ctx)
assert.property(ctx.body, 'thisNode')
})
})
})
@@ -0,0 +1,78 @@
/*
Unit tests for the REST API handler for the /ipfs endpoints.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
import adapters from '../../../mocks/adapters/index.js'
import UseCasesMock from '../../../mocks/use-cases/index.js'
import IpfsRouter from '../../../../../src/controllers/rest-api/ipfs/index.js'
// const app = require('../../../mocks/app-mock')
let uut
let sandbox
// let ctx
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('#IPFS-REST-Router', () => {
// const testUser = {}
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new IpfsRouter({ adapters, useCases })
sandbox = sinon.createSandbox()
// Mock the context object.
// ctx = mockContext()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new IpfsRouter()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating IPFS REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new IpfsRouter({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating IPFS REST Controller.'
)
}
})
})
describe('#attach', () => {
it('should throw an error if app is not passed in.', () => {
try {
uut.attach()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Must pass app object when attaching REST API controllers.'
)
}
})
})
})
@@ -0,0 +1,66 @@
/*
Unit tests for the REST API middleware that handle response errors.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local libraries
import errorMiddleware from '../../../../../src/controllers/rest-api/middleware/error.js'
import { context as mockContext } from '../../../../unit/mocks/ctx-mock.js'
describe('#Validators', () => {
let ctx
let sandbox
beforeEach(() => {
// Mock the context object.
ctx = mockContext()
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('#errorMiddleware', () => {
it('should run next function', async () => {
// Spy on next
const next = sinon.spy(() => { })
errorMiddleware()(ctx, next)
assert.isTrue(next.calledOnce)
})
it('should handle unknown status error', async () => {
try {
const next = async () => {
const e = new Error('test error')
e.status = null
throw e
}
await errorMiddleware()(ctx, next)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(ctx.status, 500)
assert.equal(ctx.body, 'test error')
}
})
it('should handle known status error', async () => {
try {
const next = async () => {
const e = new Error('test error')
e.status = 422
throw e
}
await errorMiddleware()(ctx, next)
assert.fail('Unexpected code path')
} catch (error) {
assert.equal(ctx.status, 422)
assert.equal(ctx.body, 'test error')
}
})
})
})
@@ -65,13 +65,17 @@ describe('#Order-REST-Router', () => {
ctx.request.body = {
order: {}
}
ctx.state.user = {
id: 'testUserId'
}
// Mock dependencies
sandbox.stub(uut.useCases.order, 'createOrder').resolves('testHash')
sandbox.stub(uut.useCases.order, 'createOrder').resolves({ eventId: 'testEventId', noteId: 'testNoteId' })
await uut.createOrder(ctx)
assert.equal(ctx.body.hash, 'testHash')
assert.equal(ctx.body.eventId, 'testEventId')
assert.equal(ctx.body.noteId, 'testNoteId')
})
it('should catch and throw an error', async () => {
@@ -79,6 +83,9 @@ describe('#Order-REST-Router', () => {
ctx.request.body = {
order: {}
}
ctx.state.user = {
id: 'testUserId'
}
// Force an error
sandbox
@@ -20,7 +20,7 @@ describe('#RESTControllers', () => {
let sandbox
// let ctx
before(async () => {})
before(async () => { })
beforeEach(() => {
const useCases = new UseCasesMock()
@@ -64,4 +64,19 @@ describe('#RESTControllers', () => {
}
})
})
describe('#attachRESTControllers', () => {
it('should attach controllers without mongo service', () => {
uut.config.noMongo = true
const app = { use: () => {} }
uut.attachRESTControllers(app)
})
it('should attach controllers with mongo service', () => {
uut.config.noMongo = false
const app = { use: () => {} }
uut.attachRESTControllers(app)
})
})
})
@@ -0,0 +1,163 @@
/*
Unit tests for the REST API handler for the /usage endpoints.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
import adapters from '../../../mocks/adapters/index.js'
import UseCasesMock from '../../../mocks/use-cases/index.js'
import UsageController from '../../../../../src/controllers/rest-api/usage/controller.js'
import { context as mockContext } from '../../../mocks/ctx-mock.js'
let uut
let sandbox
let ctx
describe('#Usage-REST-Controller', () => {
// const testUser = {}
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new UsageController({ adapters, useCases })
sandbox = sinon.createSandbox()
// Mock the context object.
ctx = mockContext()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new UsageController()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating /usage REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new UsageController({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating /usage REST Controller.'
)
}
})
})
describe('#Get /usage', () => {
it('should return 422 status on biz logic error', async () => {
try {
sandbox.stub(uut.useCases.usage, 'getRestSummary').throws(new Error('test error'))
await uut.getStatus(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
await uut.getStatus(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
// Assert that expected properties exist in the returned data.
assert.property(ctx.response.body, 'status')
})
})
describe('#Get /ips', () => {
it('should return 422 status on biz logic error', async () => {
try {
sandbox.stub(uut.useCases.usage, 'getTopIps').throws(new Error('test error'))
await uut.getTopIps(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
await uut.getTopIps(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
// Assert that expected properties exist in the returned data.
assert.property(ctx.response.body, 'ips')
})
})
describe('#Get /endpoints', () => {
it('should return 422 status on biz logic error', async () => {
try {
sandbox.stub(uut.useCases.usage, 'getTopEndpoints').throws(new Error('test error'))
await uut.getTopEndpoints(ctx)
assert.fail('Unexpected result')
} catch (err) {
// console.log(err)
assert.equal(err.status, 422)
assert.include(err.message, 'test error')
}
})
it('should return 200 status on success', async () => {
await uut.getTopEndpoints(ctx)
// Assert the expected HTTP response
assert.equal(ctx.status, 200)
// Assert that expected properties exist in the returned data.
assert.property(ctx.response.body, 'endpoints')
})
})
describe('#handleError', () => {
it('should pass an error message', () => {
try {
const err = {
status: 422,
message: 'Unprocessable Entity'
}
uut.handleError(ctx, err)
} catch (err) {
assert.include(err.message, 'Unprocessable Entity')
}
})
it('should still throw error if there is no message', () => {
try {
const err = {
status: 404
}
uut.handleError(ctx, err)
} catch (err) {
assert.include(err.message, 'Not Found')
}
})
})
})
@@ -0,0 +1,75 @@
/*
Unit tests for the REST API handler for the /usage endpoints.
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local support libraries
import adapters from '../../../mocks/adapters/index.js'
import UseCasesMock from '../../../mocks/use-cases/index.js'
import UsageRouter from '../../../../../src/controllers/rest-api/usage/index.js'
let uut
let sandbox
// let ctx
// const mockContext = require('../../../../unit/mocks/ctx-mock').context
describe('#Usage-REST-Router', () => {
beforeEach(() => {
const useCases = new UseCasesMock()
uut = new UsageRouter({ adapters, useCases })
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new UsageRouter()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating IPFS REST Controller.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new UsageRouter({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating IPFS REST Controller.'
)
}
})
})
describe('#attach', () => {
it('should throw an error if app is not passed in.', () => {
try {
uut.attach()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Must pass app object when attaching REST API controllers.'
)
}
})
})
})

Some files were not shown because too many files have changed in this diff Show More