Compare commits

..
299 Commits
Author SHA1 Message Date
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 2a78145170 Debugging counter-offer 2024-12-07 20:33: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 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
Chris Troutner 9b775c5bf6 Merge pull request #58 from Permissionless-Software-Foundation/ct-unstable
Updating retry-queue to v1.0.8
2022-10-05 14:53:16 -07:00
Chris Troutner 2047673862 fix(timer controllers): Wrapper stale order/offer detection in retry-queue 2022-10-05 14:50:53 -07:00
Chris Troutner 5b545aebeb Updating RPi docker image 2022-10-03 06:46:49 -07:00
Chris Troutner 1e75816125 fix(RPi): Updating dex-ui docker image 2022-10-03 06:45:39 -07:00
Chris Troutner 455fd807a4 fix(docker): Updating dex-ui docker image 2022-10-03 05:58:33 -07:00
Chris Troutner 8b75ac0f32 Removing old comments 2022-10-02 16:50:54 -07:00
Chris Troutner f1a5ae70e2 fix(docker): Updating docker image 2022-10-02 15:43:16 -07:00
Chris Troutner 46bd532eeb Merge pull request #57 from Permissionless-Software-Foundation/ct-unstable
fixing missing ticker
2022-10-02 14:07:43 -07:00
Chris Troutner 6f99b215cd fixing missing ticker 2022-10-02 14:03:27 -07:00
Chris Troutner 3482540fc8 fix(order): fixing missing ticker 2022-10-02 13:46:19 -07:00
Chris Troutner fb999c7bd4 fix(RPi): Not specifying a P2WDB string' 2022-10-02 09:02:50 -07:00
Chris Troutner a09e5a5ba7 Merge pull request #56 from Permissionless-Software-Foundation/ct-unstable
Updating docker images
2022-10-02 08:47:10 -07:00
Chris Troutner ba14b97258 fix(RPi): Updating docker container to v2.0.6 2022-10-02 08:41:57 -07:00
Chris Troutner 79ac3729c9 fix(spam): Reducing screen spam 2022-10-02 08:36:55 -07:00
Chris Troutner bd77754686 fix(docker): Updating docker image for bch-dex 2022-10-02 08:27:09 -07:00
Chris Troutner b3b5322226 Merge pull request #55 from Permissionless-Software-Foundation/ct-unstable
fix(moveTokens): Trying to fix tx-mempool conflict error
2022-10-02 08:06:17 -07:00
Chris Troutner ec1f950ffb fix(moveTokens): Trying to fix tx-mempool conflict error 2022-10-02 08:02:40 -07:00
Chris Troutner b18bc51885 Merge pull request #54 from Permissionless-Software-Foundation/ct-unstable
Added timer controller to remove duplicate offers
2022-10-02 07:22:24 -07:00
Chris Troutner e8c35dd1f3 Merge branch 'master' into ct-unstable 2022-10-02 07:19:49 -07:00
Chris Troutner 8be0accdca Merge branch 'ct-unstable' of https://github.com/Permissionless-Software-Foundation/bch-dex into ct-unstable 2022-10-02 07:18:38 -07:00
Chris Troutner 94c56a06b7 fix(offers): Added timer controller to remove duplicate offers 2022-10-02 07:18:17 -07:00
Chris Troutner 42f64e3304 Merge branch 'ct-unstable' of https://github.com/Permissionless-Software-Foundation/bch-dex into ct-unstable 2022-10-02 00:14:24 -07:00
Chris Troutner cc9bbc3c5d Merge pull request #53 from Permissionless-Software-Foundation/ct-unstable
Refactoring Maker workflow to use retry queue
2022-10-02 00:12:43 -07:00
Chris Troutner 0c6696875b fix(offer): Refactored createOffer to use retry queue 2022-10-02 00:06:02 -07:00
Chris Troutner 37b2e426a5 fix(order): Retry-Queue used to wrap workflow 2022-10-01 23:45:49 -07:00
Chris Troutner f3713985ed fix(order): Refactored to completed some to-do items 2022-10-01 22:57:29 -07:00
Chris Troutner 4ed9994414 Merge pull request #52 from Permissionless-Software-Foundation/ct-unstable
several little tweeks
2022-10-01 22:44:58 -07:00
Chris Troutner 140707ce6b fix(RPi): Updating P2WDB image 2022-10-01 16:54:49 -07:00
Chris Troutner c289e82b0e fix(scripts): Coverting scripts to ES6 2022-10-01 15:58:36 -07:00
Chris Troutner 84210d07c4 Merge branch 'ct-unstable' of https://github.com/Permissionless-Software-Foundation/bch-dex into ct-unstable 2022-10-01 15:44:51 -07:00
Chris Troutner 6d285c77cd fix(RPi): Updating docker images 2022-10-01 15:43:49 -07:00
Chris Troutner 7cefbd71ef fix(ipfs): Adding gateway setting to config 2022-10-01 14:20:37 -07:00
Chris Troutner 85d6aa7d46 Updating package.json to reflect major release 2022-10-01 13:17:30 -07:00
91 changed files with 21061 additions and 16374 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.
+17 -13
View File
@@ -45,16 +45,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`)
@@ -124,9 +126,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.
+80 -12
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 */
@@ -47,23 +52,70 @@ export default {
// 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 +136,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
@@ -101,17 +156,30 @@ export default {
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,
// 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)
: 5001,
chatPubSubChan: 'psf-ipfs-chat-001'
chatPubSubChan: 'psf-ipfs-chat-001',
// IPFS gateway
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-01'
}
+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

+15604 -14470
View File
File diff suppressed because it is too large Load Diff
+29 -7
View File
@@ -1,6 +1,6 @@
{
"name": "bch-dex",
"version": "2.0.0",
"version": "3.0.0",
"description": "A DEX on BCH for trading SLP tokens.",
"main": "index.js",
"type": "module",
@@ -28,12 +28,33 @@
},
"repository": "Permissionless-Software-Foundation/bch-dex",
"dependencies": {
"@chainsafe/libp2p-gossipsub": "13.1.1",
"@chainsafe/libp2p-noise": "15.1.1",
"@chainsafe/libp2p-yamux": "6.0.2",
"@chris.troutner/retry-queue": "1.0.8",
"@helia/unixfs": "3.0.7",
"@libp2p/bootstrap": "10.1.5",
"@libp2p/circuit-relay-v2": "1.1.5",
"@libp2p/identify": "2.1.5",
"@libp2p/keychain": "4.1.5",
"@libp2p/logger": "4.0.20",
"@libp2p/tcp": "9.1.5",
"@libp2p/webrtc": "4.1.7",
"@libp2p/websockets": "8.2.0",
"@multiformats/multiaddr": "12.3.0",
"@psf/bch-js": "6.7.3",
"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",
"blockstore-fs": "2.0.0",
"datastore-fs": "9.1.7",
"glob": "7.1.6",
"ipfs-coord-esm": "9.1.3",
"helia": "4.2.5",
"helia-coord": "1.5.15",
"ipfs-coord-esm": "9.1.7",
"ipfs-http-client": "58.0.0",
"jsonrpc-lite": "2.2.0",
"jsonwebtoken": "8.5.1",
@@ -49,25 +70,26 @@
"koa-router": "10.0.0",
"koa-static": "5.0.0",
"koa2-ratelimit": "0.9.1",
"libp2p": "^0.36.2",
"libp2p": "1.9.1",
"line-reader": "0.4.0",
"minimal-slp-wallet": "5.1.0",
"minimal-slp-wallet": "5.12.1",
"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=ipfs-service-provider
+2 -2
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/
+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
+14 -70
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.0
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,8 +28,8 @@ services:
max-file: '10'
mem_limit: 1000mb
links:
- mongo-p2wdb-service
- p2wdb
- mongo-dex
#- p2wdb
ports:
- '5700:5700' # <host port>:<container port>
volumes:
@@ -94,19 +38,19 @@ services:
restart: always
dex-ui:
#build:
# context: ./bch-dex-ui/
# dockerfile: Dockerfile
image: christroutner/bch-dex-ui:v1.12.0
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
+7 -4
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/
@@ -24,7 +24,7 @@ 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
+3 -3
View File
@@ -43,7 +43,7 @@ services:
#build:
# context: ./p2wdb/
# dockerfile: Dockerfile
image: christroutner/p2wdb-rpi:v3.1.5
image: christroutner/p2wdb-rpi:v3.1.9
container_name: p2wdb
environment:
CONSUMER_URL: 'https://wa-usa-bch-consumer.fullstackcash.nl'
@@ -70,7 +70,7 @@ services:
#build:
# context: ./bch-dex/
# dockerfile: Dockerfile
image: christroutner/bch-dex-rpi:v1.13.0
image: christroutner/bch-dex-rpi:v2.0.8
container_name: bch-dex
environment:
#CONSUMER_URL: 'https://free-bch.fullstack.cash'
@@ -95,7 +95,7 @@ services:
#build:
# context: ./bch-dex-ui/
# dockerfile: Dockerfile
image: christroutner/bch-dex-ui-rpi:v1.2.2
image: christroutner/bch-dex-ui-rpi:v1.2.4
container_name: dex-ui
logging:
driver: 'json-file'
@@ -43,6 +43,6 @@ export IPFS_TCP_PORT=4001
export IPFS_WS_PORT=4003
# P2WDB specific env vars
export ORBITDB_NAME=/orbitdb/zdpuAqNiwLiJBfbRK7uihV2hAbNSXj78ufzv5VyQb8GuvRwDh/psf-bch-p2wdb-keyvalue-v3.0.0-0001
#export ORBITDB_NAME=/orbitdb/zdpuAqNiwLiJBfbRK7uihV2hAbNSXj78ufzv5VyQb8GuvRwDh/psf-bch-p2wdb-keyvalue-v3.0.0-0001
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
}
}
*/
+1 -1
View File
@@ -6,7 +6,7 @@
the DEX app wallet in order to be acceped.
*/
const axios = require('axios')
import axios from 'axios'
const LOCALHOST = 'http://localhost:5700'
// const LOCALHOST = 'http://172.17.0.1:5700'
+6 -5
View File
@@ -3,8 +3,9 @@
*/
// Public NPM libraries
const BchWallet = require('minimal-slp-wallet/index')
const fs = require('fs').promises
import BchWallet from 'minimal-slp-wallet'
import { promises as fs } from 'fs'
async function createWallet () {
try {
@@ -15,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
}
}
+2 -2
View File
@@ -5,10 +5,10 @@
*/
// Global npm libraries
const BchWallet = require('minimal-slp-wallet/index')
import BchWallet from 'minimal-slp-wallet'
// Local libraries
const WalletAdapter = require('../../src/adapters/wallet')
import WalletAdapter from '../../src/adapters/wallet.js'
// Constants
const EMTPY_ADDR_CUTOFF = 10
+2 -4
View File
@@ -2,16 +2,14 @@
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
// const BCHJS = require('@psf/bch-js')
const BchTokenSweep = require('bch-token-sweep/index')
import BchTokenSweep from 'bch-token-sweep/index.js'
// Local libraries
const WalletAdapter = require('../../src/adapters/wallet')
import WalletAdapter from '../../src/adapters/wallet.js'
// Constants
const EMTPY_ADDR_CUTOFF = 10
+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
+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
)
}
+224 -80
View File
@@ -9,95 +9,95 @@
*/
// 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 } 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 } from '@libp2p/webrtc'
import { keychain } from '@libp2p/keychain'
import { defaultLogger } from '@libp2p/logger'
import { unixfs } from '@helia/unixfs'
// 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.getSeed = this.getSeed.bind(this)
this.getKeychain = this.getKeychain.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,31 +111,175 @@ class IpfsAdapter {
}
}
async getKeychain (datastore) {
const keychainInit = {
pass: await this.getSeed()
}
const chain = this.keychain(keychainInit)({
datastore,
logger: defaultLogger()
})
return chain
}
// 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`)
// const keychainInit = {
// pass: await this.getSeed()
// }
// Create an identity
let peerId
// console.log('this.keychain: ', this.keychain)
// const chain = this.keychain(keychainInit)({
// datastore,
// logger: defaultLogger()
// })
const chain = await this.getKeychain(datastore)
try {
peerId = await chain.exportPeerId('myKey')
} catch (err) {
await chain.createKey('myKey', 'Ed25519', 4096)
peerId = await chain.exportPeerId('myKey')
}
// Configure services
const services = {
identify: identify(),
pubsub: gossipsub({ allowPublishToZeroTopicPeers: true })
}
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
advertise: true,
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()
]
// libp2p is the networking layer that underpins Helia
const libp2p = await this.createLibp2p({
peerId,
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'
]
},
transports,
connectionEncryption: [
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`)
// !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
}
}
}
export default IpfsAdapter
+3 -2
View File
@@ -6,7 +6,7 @@ const Offer = new mongoose.Schema({
tokenId: { type: String },
utxoTxid: { type: String },
utxoVout: { type: Number },
ticker: { type: String },
ticker: { type: String, default: '' },
tokenType: { type: Number },
// Trade data
@@ -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.
})
+2 -1
View File
@@ -37,7 +37,8 @@ const Order = new mongoose.Schema({
// SWaP Protocol Properties
lokadId: { type: String },
messageType: { type: Number },
messageClass: { type: Number }
messageClass: { type: Number },
nostrEventId: { type: String } // Nostr Event Id.
})
export default mongoose.model('order', Order)
+136
View File
@@ -0,0 +1,136 @@
/*
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
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)
}
// 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)
}
}
export default NostrAdapter
+6 -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 = {}) {
@@ -27,6 +28,10 @@ class P2wdbAdapter {
// Allow the localConfig to overwrite the config file values.
this.p2wdbURL = localConfig.p2wdbURL || config.p2wdbUrl
// Bind the 'this' object to all functions
this.write = this.write.bind(this)
this.checkForSufficientFunds = this.checkForSufficientFunds.bind(this)
}
// Write some data to the P2WDB
+244
View File
@@ -0,0 +1,244 @@
/*
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 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
+62 -7
View File
@@ -3,13 +3,13 @@
*/
// 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 +22,8 @@ const WALLET_FILE = `${__dirname.toString()}/../../wallet.json`
// const P2WDB_TOKEN_ID =
// '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
const advancedConfig = {}
class WalletAdapter {
constructor (localConfig = {}) {
// Encapsulate dependencies
@@ -31,6 +33,15 @@ 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.moveTokens = this.moveTokens.bind(this)
}
// Open the wallet file, or create one if the file doesn't exist.
@@ -84,7 +95,8 @@ 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'
advancedConfig.restURL = this.config.apiServer
@@ -356,7 +368,7 @@ class WalletAdapter {
}
// Update the UTXO store of the wallet.
await this.bchWallet.getUtxos()
await this.bchWallet.initialize()
// Get the token type of the token being moved.
// Combine Fungible and NFT token UTXOs.
@@ -433,7 +445,7 @@ class WalletAdapter {
return txObj2
} catch (err) {
console.error('Error in wallet.js/deserializePartialTx()')
console.error('Error in wallet.js/deserializeTx()')
throw err
}
}
@@ -500,7 +512,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) {
@@ -508,6 +522,47 @@ 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
}
}
}
export default WalletAdapter
+10 -12
View File
@@ -6,28 +6,21 @@
// 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
}
// Spin up any adapter libraries that have async startup needs.
@@ -54,8 +47,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.
+16 -7
View File
@@ -11,6 +11,8 @@ 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'
@@ -32,7 +34,8 @@ class RESTControllers {
)
}
// console.log('Controllers localConfig: ', localConfig)
// Encapsulate dependencies
this.config = config
}
attachRESTControllers (app) {
@@ -41,13 +44,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 +62,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)
+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)
+38 -2
View File
@@ -35,10 +35,46 @@ class OrderRESTControllerLib {
// console.log('body: ', ctx.request.body)
const orderObj = ctx.request.body.order
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)
+2
View File
@@ -50,6 +50,8 @@ class OrderRouter {
// Define the routes and attach the controller.
this.router.post('/', _this.orderRESTController.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())
+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)
}
}
+54 -8
View File
@@ -3,8 +3,7 @@
kicked off periodicially.
*/
// Used to retain scope of 'this', when the scope is lost.
let _this
import config from '../../config/index.js'
class TimerControllers {
constructor (localConfig = {}) {
@@ -23,35 +22,82 @@ class TimerControllers {
}
this.debugLevel = localConfig.debugLevel
this.config = config
_this = this
// 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.startTimers()
// State
this.gcOrdersInt = null
this.gcOffersInt = null
this.checkDupOffersInt = null
this.loadOffersInt = null
}
// Start all the time-based controllers.
startTimers () {
setInterval(this.gcOrders, 60000 * 5)
setInterval(this.gcOffers, 60000 * 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)
return true
}
stopTimers () {
clearInterval(this.gcOrdersInt)
clearInterval(this.gcOffersInt)
// clearInterval(this.checkDupOffers)
}
// 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
}
}
// 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()
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
}
}
}
+13 -5
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
@@ -67,12 +70,15 @@ class OfferEntity {
if (!makerAddr || typeof makerAddr !== 'string') {
throw new Error("Property 'makerAddr' must be a string.")
}
if (!ticker || typeof ticker !== 'string') {
throw new Error("Property 'ticker' must be a string.")
}
// if (!ticker || typeof ticker !== 'string') {
// throw new Error("Property 'ticker' must be a string.")
// }
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
}
+10 -8
View File
@@ -7,7 +7,10 @@
The Order tracks the hdIndex address used to hold tokens or BCH for sale.
*/
class Order {
validate (data) {
// TODO: Create a fullValidate() function that validates a fully-hydrated
// order model.
inputValidate (data) {
const {
messageType,
messageClass,
@@ -16,8 +19,7 @@ class Order {
rateInBaseUnit,
minUnitsToExchange,
numTokens,
makerAddr,
ticker
makerAddr
} = data
// Input Validation
@@ -45,9 +47,9 @@ class Order {
if (!makerAddr || typeof makerAddr !== 'string') {
throw new Error("Property 'makerAddr' must be a string.")
}
if (!ticker || typeof ticker !== 'string') {
throw new Error("Property 'ticker' must be a string.")
}
// if (!ticker || typeof ticker !== 'string') {
// throw new Error("Property 'ticker' must be a string.")
// }
const offerData = {
messageType,
@@ -57,8 +59,8 @@ class Order {
rateInBaseUnit,
minUnitsToExchange,
numTokens,
makerAddr,
ticker
makerAddr
// ticker
}
return offerData
-6
View File
@@ -28,15 +28,9 @@ class UseCases {
// Run any startup Use Cases at the start of the app.
async start () {
// try {
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
// }
}
}
+321 -115
View File
@@ -15,10 +15,10 @@
// Global npm libraries
import axios from 'axios'
import RetryQueue from '@chris.troutner/retry-queue'
// Local libraries
import OfferEntity from '../../entities/offer.js'
import config from '../../../config/index.js'
const DEFAULT_ENTRIES_PER_PAGE = 20
@@ -37,29 +37,49 @@ 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.'
)
}
// Encapsulate dependencies
this.config = config
this.axios = axios
this.offerEntity = new OfferEntity()
this.OfferModel = this.adapters.localdb.Offer
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 */ }
@@ -68,41 +88,57 @@ 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.
const txid = offerObj.data.utxoTxid
const vout = offerObj.data.utxoVout
const utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
txid,
vout
)
console.log('utxoStatus: ', utxoStatus)
if (utxoStatus === null) return false
const utxo = {
tx_hash: offerObj.data.utxoTxid,
tx_pos: offerObj.data.utxoVout
}
// 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)
// 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)
console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
// 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)}`)
// Generate a 'display category' for the token. This will allow the
// front end UI to figure out how to display the token.
const displayCategory = await this.categorizeToken(offerEntity, tokenData)
const displayCategory = this.categorizeToken(offerEntity, tokenData)
console.log('displayCategory: ', displayCategory)
offerEntity.displayCategory = displayCategory
// Detect if user set the NSFW flag.
let nsfw = false
nsfw = await this.detectNsfw(tokenData)
// nsfw = await this.detectNsfw(tokenData)
nsfw = await this.retryQueue.addToQueue(this.detectNsfw, tokenData)
offerEntity.nsfw = nsfw
// Add offer to the local database.
@@ -111,7 +147,7 @@ class OfferUseCases {
return true
} catch (err) {
console.error('Error in createOffer()')
console.error('Error in createOffer()', err.message)
throw err
}
}
@@ -132,16 +168,30 @@ class OfferUseCases {
const cid = mutableCid.substring(7)
// Retrieve the mutable data from Filecoin/IPFS.
const url = `https://${cid}.ipfs.w3s.link/data.json`
const result = await axios.get(url)
const mutableData = result.data
console.log(`mutableData: ${JSON.stringify(mutableData, null, 2)}`)
// const url = `https://${cid}.ipfs.w3s.link/data.json`
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')
@@ -165,7 +215,7 @@ class OfferUseCases {
// The first three are easy to categorize. The simple-nft is a fungible token
// with a quantity of 1, decimals of 0, and no minting baton. Categorizing this
// type of token is the main reason why this function exists.
async categorizeToken (offerData, tokenData) {
categorizeToken (offerData, tokenData) {
try {
// console.log(`categorizeToken(): ${JSON.stringify(offerData, null, 2)}`)
@@ -200,11 +250,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
@@ -220,11 +270,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)
@@ -239,11 +289,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)
@@ -263,12 +313,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'
@@ -277,19 +327,14 @@ class OfferUseCases {
}
// Verify that UTXO for sale is unspent. Abort if it's been spent.
// const txid = offerInfo.utxoTxid
// const vout = offerInfo.utxoVout
const utxo = {
tx_hash: offerInfo.utxoTxid,
tx_pos: offerInfo.utxoVout
}
// Note : should be added to retry-queue?
const utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
// const utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
// txid,
// vout
// )
console.log('utxoStatus: ', utxoStatus)
// console.log('utxoStatus: ', utxoStatus)
if (!utxoStatus) {
console.log(`utxo txid: ${offerInfo.utxoTxid}, vout: ${offerInfo.utxoVout}`)
@@ -302,11 +347,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 +378,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 +388,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 +425,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 +447,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,27 +466,50 @@ 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
// }
}
@@ -445,29 +517,79 @@ class OfferUseCases {
// 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
// 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)
console.log(`orderData: ${JSON.stringify(orderData, null, 2)}`)
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.')
// 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')
}
@@ -492,7 +614,46 @@ class OfferUseCases {
return txid
} catch (err) {
console.error('Error in acceptCounterOffer()')
console.error('Error in acceptCounterOffer(): ', err)
throw err
}
}
// TODO: Write unit tests for this function, then add it to the Timer Controllers
// Looks for duplicate offers and removes the duplicate.
// Duplicates are checked for when an offer is created, and so should not
// exist. But emperical testing shows that they do. This function is called
// periodically by a timer, to clean up any duplicates that slipped through
// the cracks.
async removeDuplicateOffers () {
try {
const now = new Date()
console.log(`Starting removeDuplicateOffers() at ${now.toLocaleString()}`)
let duplicateFound = false
// Get all Offers in the database.
const offers = await this.OfferModel.find({})
// console.log('offers: ', offers)
const offerZcids = []
for (let i = 0; i < offers.length; i++) {
const thisOffer = offers[i]
if (offerZcids.includes(thisOffer.p2wdbHash)) {
await thisOffer.remove()
duplicateFound = true
continue
}
// Add the zcid to the array.
offerZcids.push(thisOffer.p2wdbHash)
}
return duplicateFound
} catch (err) {
console.error('Error in removeDuplicateOffers()')
throw err
}
}
@@ -515,12 +676,6 @@ class OfferUseCases {
let utxoStatus = null
try {
// Get the status of the UTXO associate with this Offer.
// utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
// thisOffer.utxoTxid,
// thisOffer.utxoVout
// )
// Get the status of the UTXO associate with this Offer.
const utxo = {
tx_hash: thisOffer.utxoTxid,
@@ -528,7 +683,8 @@ class OfferUseCases {
}
// console.log(`Checking this UTXO: ${JSON.stringify(utxo, null, 2)}`)
utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
// 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) {
// Handle corner case of bad-data in the Offer model.
@@ -545,7 +701,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()
}
@@ -565,32 +722,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
}
}
+119 -38
View File
@@ -2,12 +2,16 @@
Order use-case library.
*/
// Global npm libraries
import RetryQueue from '@chris.troutner/retry-queue'
// Local libraries
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,47 +27,57 @@ class OrderLib {
this.OrderModel = this.adapters.localdb.Order
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')
// Specify the address to send payment.
entryObj.makerAddr = this.adapters.wallet.bchWallet.walletInfo.cashAddress
console.log('entryObj.makerAddr: ', entryObj.makerAddr)
// Get Ticker for token ID.
// TODO: Move this below the orderEntity.validate() call.
const tokenData = await this.adapters.wallet.bchWallet.getTxData([entryObj.tokenId])
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
entryObj.ticker = tokenData[0].tokenTicker
// console.log('entryObj.makerAddr: ', entryObj.makerAddr)
// Input Validation
// TODO: Remove ticker from validation.
// TODO: Rename validate() to inputValidate(). Create fullValidate() that
// validates a fully hydrated Order entity.
const orderEntity = this.orderEntity.validate(entryObj)
console.log('orderEntity: ', orderEntity)
const orderEntity = this.orderEntity.inputValidate(entryObj)
// 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, {})
// Ensure sufficient tokens exist to create the order.
await this.ensureFunds(orderEntity)
// await 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])
// 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
}
const utxoInfo = await this.adapters.wallet.moveTokens(moveObj)
console.log('utxoInfo: ', utxoInfo)
// const utxoInfo = await this.adapters.wallet.moveTokens(moveObj)
const utxoInfo = await this.retryQueue.addToQueue(this.adapters.wallet.moveTokens, moveObj)
// console.log('utxoInfo: ', utxoInfo)
// Update the UTXO store for the wallet.
await this.adapters.wallet.bchWallet.bchjs.Util.sleep(3000)
await this.adapters.wallet.bchWallet.getUtxos()
// await this.adapters.wallet.bchWallet.getUtxos()
await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.initialize, {})
// Update the order with the new UTXO information.
orderEntity.utxoTxid = utxoInfo.txid
@@ -73,27 +87,29 @@ class OrderLib {
// Add P2WDB specific flag for signaling that this is a new offer.
orderEntity.dataType = 'offer'
// 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 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
}
@@ -108,6 +124,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
@@ -116,19 +133,17 @@ class OrderLib {
// Get UTXOs.
const utxos = this.adapters.wallet.bchWallet.utxos.utxoStore
console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
if (orderEntity.buyOrSell.includes('sell')) {
// Sell Order
// 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.
@@ -157,14 +172,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')
@@ -175,7 +190,35 @@ 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('findOrderByUtxo() 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) {
console.error('Error in findOrderByUtxo(): ', err)
throw err
}
}
@@ -209,7 +252,8 @@ class OrderLib {
tx_hash: thisOrder.utxoTxid,
tx_pos: thisOrder.utxoVout
}
utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
// 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) {
// Handle corner case of bad-data in the Order model.
@@ -226,7 +270,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()
}
@@ -236,6 +281,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
+89 -88
View File
@@ -13,7 +13,6 @@ import axios from 'axios'
// Local support libraries
import config from '../../../config/index.js'
import Server from '../../../bin/server.js'
import testUtils from '../../utils/test-utils.js'
import AdminLib from '../../../src/adapters/admin.js'
@@ -24,105 +23,107 @@ 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()
// Stop the IPFS node for the rest of the e2e tests.
// await app.controllers.adapters.ipfs.stop()
// Delete all previous users in the database.
await testUtils.deleteAllUsers()
// Delete all previous users in the database.
await testUtils.deleteAllUsers()
// Create a new admin user.
await adminLib.createSystemUser()
// Create a new admin user.
await adminLib.createSystemUser()
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
+93 -89
View File
@@ -2,6 +2,8 @@ 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
@@ -15,101 +17,103 @@ describe('Admin', () => {
afterEach(() => sandbox.restore())
describe('loginAdmin()', () => {
it('should logind admin', async () => {
try {
const error = new Error('test error')
error.response = {
status: 422
if (!config.noMongo) {
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')
}
// sandbox.stub(uut.axios, 'request').onFirstCall().throws(error)
})
const result = await uut.loginAdmin()
const user = result.data.user
it('should handle axios error', async () => {
try {
// Returns an erroneous password to force
// an auth error
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'wrong' })
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
await uut.loginAdmin()
assert(false, 'Unexpected result')
} catch (err) {
assert.equal(err.response.status, 401)
assert.include(err.response.data, 'Unauthorized')
}
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'))
describe('createSystemUser()', () => {
it('should create admin', async () => {
try {
const result = await uut.createSystemUser()
await uut.createSystemUser()
assert(false, 'Unexpected result')
} catch (err) {
assert.include(err.message, 'test error')
}
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')
}
})
})
})
}
})
@@ -27,6 +27,8 @@ describe('#adapters', () => {
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()
@@ -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.
let 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)
})
})
})
+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'
+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)
})
})
})
@@ -28,6 +28,9 @@ describe('#Controllers', () => {
attachRPCRouter: () => {}
}
// Mock the timer controllers
sandbox.stub(uut.timerControllers, 'startTimers').returns()
const app = {
use: () => {}
}
@@ -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.'
)
}
})
})
})
@@ -67,11 +67,12 @@ describe('#Order-REST-Router', () => {
}
// 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 () => {
@@ -0,0 +1,125 @@
/*
Unit tests for the timer-controller.js Controller library
*/
// Public npm libraries
import { assert } from 'chai'
import sinon from 'sinon'
// Local libraries
import TimerControllers from '../../../src/controllers/timer-controllers.js'
import adapters from '../mocks/adapters/index.js'
import UseCasesMock from '../mocks/use-cases/index.js'
describe('#Timer-Controllers', () => {
let uut
let sandbox
beforeEach(() => {
sandbox = sinon.createSandbox()
const useCases = new UseCasesMock()
uut = new TimerControllers({ adapters, useCases })
})
afterEach(() => {
sandbox.restore()
uut.stopTimers()
})
describe('#constructor', () => {
it('should throw an error if adapters are not passed in', () => {
try {
uut = new TimerControllers()
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Adapters library required when instantiating Timer Controller libraries.'
)
}
})
it('should throw an error if useCases are not passed in', () => {
try {
uut = new TimerControllers({ adapters })
assert.fail('Unexpected code path')
} catch (err) {
assert.include(
err.message,
'Instance of Use Cases library required when instantiating Timer Controller libraries.'
)
}
})
})
describe('#startTimers', () => {
it('should start the timers', () => {
const result = uut.startTimers()
// uut.stopTimers()
assert.equal(result, true)
})
})
describe('#gcOrders', () => {
it('should kick off the Use Case', async () => {
const result = await uut.gcOrders()
assert.equal(result, true)
})
it('should return false on error', async () => {
sandbox.stub(uut.useCases.order, 'removeStaleOrders').throws(new Error('test error'))
const result = await uut.gcOrders()
assert.equal(result, false)
})
})
describe('#gcOffers', () => {
it('should kick off the Use Case', async () => {
const result = await uut.gcOffers()
assert.equal(result, true)
})
it('should return false on error', async () => {
sandbox.stub(uut.useCases.offer, 'removeStaleOffers').throws(new Error('test error'))
const result = await uut.gcOffers()
assert.equal(result, false)
})
})
describe('#checkDupOffers', () => {
it('should kick off the Use Case', async () => {
const result = await uut.checkDupOffers()
assert.equal(result, true)
})
it('should return false on error', async () => {
sandbox.stub(uut.useCases.offer, 'removeDuplicateOffers').throws(new Error('test error'))
const result = await uut.checkDupOffers()
assert.equal(result, false)
})
})
describe('#loadOffers', () => {
it('should kick off the Use Case', async () => {
const result = await uut.loadOffers()
assert.equal(result, true)
})
it('should return false on error', async () => {
sandbox.stub(uut.useCases.offer, 'loadOffers').throws(new Error('test error'))
const result = await uut.loadOffers()
assert.equal(result, false)
})
})
})
+32 -1
View File
@@ -237,6 +237,36 @@ describe('#Offer-Entity', () => {
}
})
it('should throw an error if nostrEventId is not included', () => {
try {
const offerData = {
data: {
messageType: 1,
messageClass: 1,
tokenId: 'fakeId',
buyOrSell: 'buy',
rateInBaseUnit: 1000,
minUnitsToExchange: 350,
numTokens: 1,
utxoTxid: 'fakeTxid',
utxoVout: 0,
offerStatus: 'posted',
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
tokenType: 1
}
}
uut.validate(offerData)
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.include(
err.message,
"Property 'nostrEventId' must be a string."
)
}
})
it('should validate a new offer', () => {
const offerObj = {
appId: 'swapTest555',
@@ -255,7 +285,8 @@ describe('#Offer-Entity', () => {
offerStatus: 'posted',
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
ticker: 'TROUT',
tokenType: 1
tokenType: 1,
nostrEventId: 'test'
},
timestamp: '2021-09-20T17:54:26.395Z',
localTimeStamp: '9/20/2021, 10:54:26 AM',
+9 -9
View File
@@ -21,10 +21,10 @@ describe('#Order-Entity', () => {
afterEach(() => sandbox.restore())
describe('#validate', () => {
describe('#inputValidate', () => {
it('should throw an error if data is not provided', () => {
try {
uut.validate()
uut.inputValidate()
} catch (err) {
// console.log(err)
assert.include(err.message, 'Cannot destructure property')
@@ -34,7 +34,7 @@ describe('#Order-Entity', () => {
it('should throw an error if messageType is not included', () => {
try {
const data = {}
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(
@@ -47,7 +47,7 @@ describe('#Order-Entity', () => {
it('should throw an error if messageClass is not included', () => {
try {
const data = { messageType: 1 }
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(
@@ -60,7 +60,7 @@ describe('#Order-Entity', () => {
it('should throw an error if tokenId is not included', () => {
try {
const data = { messageType: 1, messageClass: 1 }
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(err.message, "Property 'tokenId' must be a string.")
@@ -70,7 +70,7 @@ describe('#Order-Entity', () => {
it('should throw an error if buyOrSell is not included', () => {
try {
const data = { messageType: 1, messageClass: 1, tokenId: 'fakeId' }
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(err.message, "Property 'buyOrSell' must be a string.")
@@ -85,7 +85,7 @@ describe('#Order-Entity', () => {
tokenId: 'fakeId',
buyOrSell: 'buy'
}
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(
@@ -104,7 +104,7 @@ describe('#Order-Entity', () => {
buyOrSell: 'buy',
rateInBaseUnit: 1000
}
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(
@@ -124,7 +124,7 @@ describe('#Order-Entity', () => {
rateInBaseUnit: 1000,
minUnitsToExchange: 350
}
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(err.message, "Property 'numTokens' must be a number.")
+5 -1
View File
@@ -40,9 +40,13 @@ describe('#config', () => {
assert.equal(config.env, 'test')
})
it('Should return test environment config', async () => {
it('Should return prod environment config', async () => {
process.env.BCH_DEX = 'prod'
process.env.WALLET_INTERFACE = 'web2'
process.env.APISERVER = 'https://api.fullstack.cash/v5/'
await import('../../../config/env/common.js?foo=bar2')
const importedConfig3 = await import('../../../config/index.js?foo=bar2')
const config = importedConfig3.default
// console.log('config: ', config)
+24 -4
View File
@@ -18,19 +18,28 @@ class IpfsAdapter {
class IpfsCoordAdapter {
constructor () {
this.ipfsCoord = {
adapters: {
ipfs: {
connectToPeer: async () => {}
}
},
useCases: {
peer: {
sendPrivateMessage: () => {
}
}
}
},
thisNode: {}
}
}
}
const ipfs = {
ipfsAdapter: new IpfsAdapter(),
ipfsCoordAdapter: new IpfsCoordAdapter()
ipfsCoordAdapter: new IpfsCoordAdapter(),
getStatus: async () => {},
getPeers: async () => {},
getRelays: async () => {}
}
ipfs.ipfs = ipfs.ipfsAdapter.ipfs
@@ -141,7 +150,12 @@ const wallet = {
return { cashAddress: 'fakeAddr', wif: 'fakeWif', hdIndex: 1 }
},
bchWallet: new MockBchWallet(),
moveTokens: async () => {}
moveTokens: async () => {},
moveBch: async () => {},
reclaimTokens: async ()=>{},
generatePartialTx: async ()=>{},
deseralizeTx:async ()=>{},
completeTx:async ()=>{ return ''},
}
const p2wdb = {
@@ -150,4 +164,10 @@ const p2wdb = {
checkForSufficientFunds: async () => true
}
export default { ipfs, localdb, bch, wallet, p2wdb, bchjs}
const nostr = {
post: async () => {return true },
read: async () => { return true },
eventId2note: () => { return 'testNoteId' }
}
export default { ipfs, localdb, bch, wallet, p2wdb, bchjs, nostr}
+142 -136
View File
@@ -1,144 +1,150 @@
/*
Mock data for the wallet.adapter.unit.js test file.
*/
import BCHJS from '@psf/bch-js';
import BCHJS from "@psf/bch-js";
const mockWallet = {
mnemonic: 'course abstract aerobic deer try switch turtle diet fence affair butter top',
privateKey: 'L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ',
publicKey: '0379433ffc401483ade310469953c1cba77c71af904f07c15bde330d7198b4d6dc',
cashAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
address: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
slpAddress: 'simpleledger:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acq9zm7d33',
legacyAddress: '1JQj1KcQL7GPKzc1D2PvdUSgw3MbDtrHzi',
hdPath: "m/44'/245'/0'/0/0",
nextAddress: 1
}
mnemonic: 'course abstract aerobic deer try switch turtle diet fence affair butter top',
privateKey: 'L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ',
publicKey: '0379433ffc401483ade310469953c1cba77c71af904f07c15bde330d7198b4d6dc',
cashAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
address: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
slpAddress: 'simpleledger:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acq9zm7d33',
legacyAddress: '1JQj1KcQL7GPKzc1D2PvdUSgw3MbDtrHzi',
hdPath: "m/44'/245'/0'/0/0",
nextAddress: 1
};
class MockBchWallet {
constructor () {
this.walletInfoPromise = true
this.walletInfo = mockWallet
this.bchjs = new BCHJS()
this.burnTokens = async () => {
return { success: true, txid: 'txid' }
}
this.sendTokens = async () => {
return 'fakeTxid'
}
this.getUtxos = async () => {
}
this.getTxData = async () => {
return [{
tokenTicker: 'TROUT'
}]
}
this.getTokenData = async () => {}
// Environment variable is used by wallet-balance.unit.js to force an error.
if (process.env.NO_UTXO) {
this.utxos = {}
} else {
this.utxos = {
utxoStore: {
address: 'bitcoincash:qqetvdnlt0p8g27dr44cx7h057kpzly9xse9huc97z',
bchUtxos: [
{
height: 700685,
tx_hash: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
tx_pos: 0,
value: 1000,
txid: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
vout: 0,
isValid: false
},
{
height: 700685,
tx_hash: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
tx_pos: 2,
value: 19406,
txid: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
vout: 2,
isValid: false
}
],
nullUtxos: [],
slpUtxos: {
type1: {
mintBatons: [],
tokens: [
{
'height': 717331,
'tx_hash': '74889580bb1a5f8c026aa2f55118ac9917df3332f7abae72a70343daa1c29621',
'tx_pos': 1,
'value': 546,
'txid': '74889580bb1a5f8c026aa2f55118ac9917df3332f7abae72a70343daa1c29621',
'vout': 1,
'isSlp': true,
'type': 'token',
'qty': '10',
'tokenId': '600ee24d0f208aebc2bdd2c4ee1b9acb6d57343561442e8676b5bbea311d5a0f',
'address': 'bitcoincash:qqraj35x6l2qyqhjm5l7qlt7z2245ez8l5z3dwkeq5',
'ticker': 'FLIPS',
'name': 'FLIPS',
'documentUri': '',
'documentHash': '',
'decimals': 1,
'qtyStr': '1'
},
{
'height': 730597,
'tx_hash': '52520faddfafc46b8f8c9548b097f3a3b82a5bf363b5095047b9c5f83247fe36',
'tx_pos': 1,
'value': 546,
'txid': '52520faddfafc46b8f8c9548b097f3a3b82a5bf363b5095047b9c5f83247fe36',
'vout': 1,
'isSlp': true,
'type': 'token',
'qty': '34999991',
'tokenId': '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
'address': 'bitcoincash:qqraj35x6l2qyqhjm5l7qlt7z2245ez8l5z3dwkeq5',
'ticker': 'PSF',
'name': 'Permissionless Software Foundation',
'documentUri': 'psfoundation.cash',
'documentHash': '',
'decimals': 8,
'qtyStr': '0.34999991'
},
{
'height': 730597,
'tx_hash': '5dc7e7c91382aed1666a51212dfb74050261e12c3c4f62b6b1e57f42d6c51ee1',
'tx_pos': 2,
'value': 546,
'txid': '5dc7e7c91382aed1666a51212dfb74050261e12c3c4f62b6b1e57f42d6c51ee1',
'vout': 2,
'isSlp': true,
'type': 'token',
'qty': '18898',
'tokenId': 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
'address': 'bitcoincash:qqraj35x6l2qyqhjm5l7qlt7z2245ez8l5z3dwkeq5',
'ticker': 'TROUT',
'name': "Trout's test token",
'documentUri': 'troutsblog.com',
'documentHash': '',
'decimals': 2,
'qtyStr': '188.98'
}
]
},
nft: {
tokens: []
},
group: {
tokens: [],
mintBatons: []
}
}
constructor() {
this.walletInfoPromise = true;
this.walletInfo = mockWallet;
this.initialize = async () => {}
this.bchjs = new BCHJS();
this.burnTokens = async () => {
return { success: true, txid: 'txid' };
};
this.sendTokens = async () => {
return 'fakeTxid';
};
this.utxoIsValid =async ()=>{}
this.getUtxos = async () => { };
this.getBalance = async () => { };
this.listTokens = async () => { };
this.getTxData = async () => {
return [{
tokenTicker: 'TROUT'
}];
};
this.optimize = async () => { };
// Environment variable is used by wallet-balance.unit.js to force an error.
if (process.env.NO_UTXO) {
this.utxos = {};
}
else {
this.utxos = {
utxoStore: {
address: 'bitcoincash:qqetvdnlt0p8g27dr44cx7h057kpzly9xse9huc97z',
bchUtxos: [
{
height: 700685,
tx_hash: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
tx_pos: 0,
value: 1000,
txid: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
vout: 0,
isValid: false
},
{
height: 700685,
tx_hash: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
tx_pos: 2,
value: 19406,
txid: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
vout: 2,
isValid: false
}
],
nullUtxos: [],
slpUtxos: {
type1: {
mintBatons: [],
tokens: [
{
'height': 717331,
'tx_hash': '74889580bb1a5f8c026aa2f55118ac9917df3332f7abae72a70343daa1c29621',
'tx_pos': 1,
'value': 546,
'txid': '74889580bb1a5f8c026aa2f55118ac9917df3332f7abae72a70343daa1c29621',
'vout': 1,
'isSlp': true,
'type': 'token',
'qty': '10',
'tokenId': '600ee24d0f208aebc2bdd2c4ee1b9acb6d57343561442e8676b5bbea311d5a0f',
'address': 'bitcoincash:qqraj35x6l2qyqhjm5l7qlt7z2245ez8l5z3dwkeq5',
'ticker': 'FLIPS',
'name': 'FLIPS',
'documentUri': '',
'documentHash': '',
'decimals': 1,
'qtyStr': '1'
},
{
'height': 730597,
'tx_hash': '52520faddfafc46b8f8c9548b097f3a3b82a5bf363b5095047b9c5f83247fe36',
'tx_pos': 1,
'value': 546,
'txid': '52520faddfafc46b8f8c9548b097f3a3b82a5bf363b5095047b9c5f83247fe36',
'vout': 1,
'isSlp': true,
'type': 'token',
'qty': '34999991',
'tokenId': '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
'address': 'bitcoincash:qqraj35x6l2qyqhjm5l7qlt7z2245ez8l5z3dwkeq5',
'ticker': 'PSF',
'name': 'Permissionless Software Foundation',
'documentUri': 'psfoundation.cash',
'documentHash': '',
'decimals': 8,
'qtyStr': '0.34999991'
},
{
'height': 730597,
'tx_hash': '5dc7e7c91382aed1666a51212dfb74050261e12c3c4f62b6b1e57f42d6c51ee1',
'tx_pos': 2,
'value': 546,
'txid': '5dc7e7c91382aed1666a51212dfb74050261e12c3c4f62b6b1e57f42d6c51ee1',
'vout': 2,
'isSlp': true,
'type': 'token',
'qty': '18898',
'tokenId': 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
'address': 'bitcoincash:qqraj35x6l2qyqhjm5l7qlt7z2245ez8l5z3dwkeq5',
'ticker': 'TROUT',
'name': "Trout's test token",
'documentUri': 'troutsblog.com',
'documentHash': '',
'decimals': 2,
'qtyStr': '188.98'
}
]
},
nft: {
tokens: []
},
group: {
tokens: [],
mintBatons: []
}
}
}
};
}
}
}
}
}
export { MockBchWallet, mockWallet};
export { MockBchWallet };
export { mockWallet };
export default {
MockBchWallet,
mockWallet
};
+13
View File
@@ -0,0 +1,13 @@
/*
Mocking library for helia.
This is used to replace the helia library when running unit tests.
*/
const ipfs = {
libp2p: {
getMultiaddrs: () => [],
peerId: 'fake-id'
}
}
export default ipfs
+15
View File
@@ -0,0 +1,15 @@
class RelayPoolClassMock {
constructor() {
this.relay = { close: () => { } , subscribe: () => { } }
this.event = { content: 'content' }
}
on(ev, callback) {
callback(this.relay, null, this.event)
}
}
const RelayPoolMock =()=>{
return new RelayPoolClassMock()
}
export default { RelayPoolMock }
+12
View File
@@ -41,12 +41,24 @@ class Offer {
async createOffer() {
return {}
}
async removeStaleOffers(){
}
async removeDuplicateOffers(){
}
async loadOffers(){
}
}
class Order {
async createOrder() {
return {}
}
async removeStaleOrders(){
}
}
class UseCasesMock {
+83 -37
View File
@@ -3,45 +3,45 @@
*/
const nftOffer01 = {
"messageType": 1,
"messageClass": 1,
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
"buyOrSell": "sell",
"rateInBaseUnit": 7672536,
"minUnitsToExchange": 7672536,
"numTokens": 1,
"utxoTxid": "c736d73e274df20e9b069b4990d1a264fb80aa98d67cc6c7a39e42bff48e7c04",
"utxoVout": 1,
"timestamp": 1662930309998,
"globaltimestamp": "2022-09-11T21:05:09.998Z",
"localTimestamp": "9/11/2022, 9:05:09 PM",
"txid": "4266862b8358664996038d8c29d49dc0f3d3058fd1ef3d567b084e6b16ceb5b2",
"p2wdbHash": "zdpuArq7rCuVCGPyTnWpLjy9AYsh8yrbwjGdKwg2GDDALkM8t",
"offerStatus": "posted",
"makerAddr": "bitcoincash:qrqlz63cwmu0hcmsrfnd8jemn3atkpaqds6tf4ksrr",
"ticker": "TV001",
"tokenType": 65
"messageType": 1,
"messageClass": 1,
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
"buyOrSell": "sell",
"rateInBaseUnit": 7672536,
"minUnitsToExchange": 7672536,
"numTokens": 1,
"utxoTxid": "c736d73e274df20e9b069b4990d1a264fb80aa98d67cc6c7a39e42bff48e7c04",
"utxoVout": 1,
"timestamp": 1662930309998,
"globaltimestamp": "2022-09-11T21:05:09.998Z",
"localTimestamp": "9/11/2022, 9:05:09 PM",
"txid": "4266862b8358664996038d8c29d49dc0f3d3058fd1ef3d567b084e6b16ceb5b2",
"p2wdbHash": "zdpuArq7rCuVCGPyTnWpLjy9AYsh8yrbwjGdKwg2GDDALkM8t",
"offerStatus": "posted",
"makerAddr": "bitcoincash:qrqlz63cwmu0hcmsrfnd8jemn3atkpaqds6tf4ksrr",
"ticker": "TV001",
"tokenType": 65
}
const nftTokenData01 = {
"genesisData": {
"type": 65,
"ticker": "TV001",
"name": "Introduction to NFTs on BCH",
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
"documentUri": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
"documentHash": "c1731268f4873f1928438abdaf6ffc546d86a1817dc6f3c6bc73fbdfb4664f10",
"decimals": 0,
"mintBatonIsActive": false,
"tokensInCirculationBN": "1",
"tokensInCirculationStr": "1",
"blockCreated": 740395,
"totalBurned": "0",
"totalMinted": "1",
"parentGroupId": "030563ddd65772d8e9b79b825529ed53c7d27037507b57c528788612b4911107"
},
"immutableData": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
"mutableData": "ipfs://bafybeifzhunfpodsztsj5x4ypopngkxuapbxwaxaxkahaybdktk6joqtlq"
"genesisData": {
"type": 65,
"ticker": "TV001",
"name": "Introduction to NFTs on BCH",
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
"documentUri": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
"documentHash": "c1731268f4873f1928438abdaf6ffc546d86a1817dc6f3c6bc73fbdfb4664f10",
"decimals": 0,
"mintBatonIsActive": false,
"tokensInCirculationBN": "1",
"tokensInCirculationStr": "1",
"blockCreated": 740395,
"totalBurned": "0",
"totalMinted": "1",
"parentGroupId": "030563ddd65772d8e9b79b825529ed53c7d27037507b57c528788612b4911107"
},
"immutableData": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
"mutableData": "ipfs://bafybeifzhunfpodsztsj5x4ypopngkxuapbxwaxaxkahaybdktk6joqtlq"
}
const simpleNftOffer01 = {
@@ -126,11 +126,57 @@ const fungibleTokenData01 = {
"mutableData": ""
}
const offerMockData = {
data: {
messageType: 1,
messageClass: 1,
tokenId:
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
buyOrSell: 'sell',
rateInBaseUnit: 1000,
minUnitsToExchange: 10,
numTokens: 0.02,
utxoTxid:
'241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
utxoVout: 0,
makerAddr: 'address',
tokenType: 1,
nostrEventId: 'test'
}
}
const deserealizeTxMock = {
//...
vout: [
{
value: 0,
scriptPubKey: {
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
}
},
{
value: 0,
scriptPubKey: {
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
}
},
{
value: 0,
scriptPubKey: {
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
}
},
//...
]
}
export default {
nftOffer01,
nftTokenData01,
simpleNftOffer01,
simpleNftTokenData01,
fungibleOffer01,
fungibleTokenData01
fungibleTokenData01,
offerMockData,
deserealizeTxMock
};
+687 -72
View File
@@ -48,89 +48,69 @@ describe('#offer-use-case', () => {
)
}
})
it('should throw an error if order use cases are not passed in', () => {
try {
uut = new OfferLib({ adapters })
assert.fail('Unexpected code path')
console.log(uut) // linter
} catch (err) {
assert.include(
err.message,
'Instance of Order Use Cases must be passed in when instantiating Offer Use Cases library.'
)
}
})
})
describe('#createOffer', () => {
it('should ignore an offer if utxo has been spent', async () => {
const offerObj = {
appId: 'swapTest555',
data: {
messageType: 1,
messageClass: 1,
tokenId:
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
buyOrSell: 'sell',
rateInSats: 1000,
minSatsToExchange: 10,
numTokens: 0.02,
utxoTxid:
'241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
utxoVout: 0
},
timestamp: '2021-09-20T17:54:26.395Z',
localTimeStamp: '9/20/2021, 10:54:26 AM',
txid: '46f50f2a0cf44e3ed70dfb0618ef3ebfee57aabcf229b5d2d17c07322b54a8d7',
hash: 'zdpuB2X25AZCKo3wpr4sSbw44vqPWJRqcxWQRHZccK5BdtoGD'
it('should handle error', async () => {
try {
await uut.createOffer()
assert.fail('unexpected code path')
} catch (error) {
assert.include(error.message, 'Cannot read properties of undefined')
}
// Mock dependencies
sandbox.stub(uut.adapters.bchjs.Blockchain, 'getTxOut').resolves(null)
sandbox.stub(uut.adapters.wallet.bchWallet, 'getTokenData').resolves({})
sandbox.stub(uut, 'categorizeToken').resolves('nft')
sandbox.stub(uut, 'detectNsfw').resolves(false)
const result = await uut.createOffer(offerObj)
// console.log('result: ', result)
assert.equal(result, false)
})
it('should create an offer and return the hash', async () => {
const offerObj = {
appId: 'swapTest555',
data: {
messageType: 1,
messageClass: 1,
tokenId:
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
buyOrSell: 'sell',
rateInBaseUnit: 1000,
minUnitsToExchange: 10,
numTokens: 0.02,
utxoTxid:
'241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
utxoVout: 0,
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
ticker: 'TROUT',
tokenType: 1
},
timestamp: '2021-09-20T17:54:26.395Z',
localTimeStamp: '9/20/2021, 10:54:26 AM',
txid: '46f50f2a0cf44e3ed70dfb0618ef3ebfee57aabcf229b5d2d17c07322b54a8d7',
hash: 'zdpuB2X25AZCKo3wpr4sSbw44vqPWJRqcxWQRHZccK5BdtoGD'
}
it('should return false if offer already exist', async () => {
const offerObj = mockData.offerMockData
// Mock dependencies
sandbox.stub(uut.adapters.bchjs.Blockchain, 'getTxOut').resolves({
bestblock:
'000000000000000000d2060b83f90f8187b92fcccb4a42aaa19ce5a305fe0ae3',
confirmations: 0,
value: 0,
scriptPubKey: {
asm: 'OP_RETURN 5262419 1 1145980243 38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0 00000000001e8480 0000000009a7ec80',
hex: '6a04534c500001010453454e442038e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b00800000000001e8480080000000009a7ec80',
type: 'nulldata'
},
coinbase: false
})
sandbox.stub(uut, 'categorizeToken').resolves('fungible')
sandbox.stub(uut.adapters.wallet.bchWallet, 'getTokenData').resolves({})
sandbox.stub(uut, 'detectNsfw').resolves(false)
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
sandbox.stub(uut, 'findOfferByTxid').resolves({})
const result = await uut.createOffer(offerObj)
// console.log('result: ', result)
assert.isFalse(result)
})
assert.equal(result, true)
it('should return false for invalid utxo', async () => {
const offerObj = mockData.offerMockData
// Mock dependencies
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found'))
sandbox.stub(uut.retryQueue, 'addToQueue').resolves(null)
const result = await uut.createOffer(offerObj)
assert.isFalse(result)
})
it('should create offer', async () => {
const tokenDataMock = mockData.simpleNftTokenData01
const offerObj = mockData.offerMockData
// Mock dependencies
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found'))
sandbox.stub(uut.retryQueue, 'addToQueue')
.onCall(0).resolves({}) // Utxo Status call
.onCall(1).resolves(tokenDataMock) // Token Data call
.onCall(2).resolves(false) // detectNsfw call
const result = await uut.createOffer(offerObj)
assert.isTrue(result)
})
})
@@ -170,5 +150,640 @@ describe('#offer-use-case', () => {
assert.equal(result, 'fungible')
})
it('should unknow type', async () => {
try {
// Mock dependencies
const offerData = mockData.fungibleOffer01
const tokenData = { genesisData: {} }
await uut.categorizeToken(offerData, tokenData)
assert.fail('unexpected code path')
} catch (error) {
assert.include(error.message, 'Unknown token type:')
}
})
})
describe('#removeDuplicateOffers', () => {
it('should remove duplicate entries and return true', async () => {
// Mock dependencies and force desired code path.
sandbox.stub(uut.OfferModel, 'find').resolves([
{ p2wdbHash: 'a', remove: async () => { } },
{ p2wdbHash: 'a', remove: async () => { } },
{ p2wdbHash: 'b', remove: async () => { } }
])
// sandbox.stub(uut.OfferModel, 'remove').resolves()
const result = await uut.removeDuplicateOffers()
console.log('result: ', result)
assert.equal(result, true)
})
it('should return false if there are no duplicate entries', async () => {
// Mock dependencies and force desired code path.
sandbox.stub(uut.OfferModel, 'find').resolves([
{ p2wdbHash: 'a', remove: async () => { } },
{ p2wdbHash: 'b', remove: async () => { } }
])
// sandbox.stub(uut.OfferModel, 'remove').resolves()
const result = await uut.removeDuplicateOffers()
console.log('result: ', result)
assert.equal(result, false)
})
it('should handle error', async () => {
try {
// Mock dependencies and force desired code path.
sandbox.stub(uut.OfferModel, 'find').throws(new Error('test error'))
// sandbox.stub(uut.OfferModel, 'remove').resolves()
await uut.removeDuplicateOffers()
assert.fail('unexpected code path')
} catch (error) {
assert.equal(error.message, 'test error')
}
})
})
describe('#removeStaleOffers', () => {
it('remove offer with wrong utxoState', async () => {
// Mock dependencies and force desired code path.
sandbox.stub(uut.OfferModel, 'find').resolves([{ remove: async () => { } }])
sandbox.stub(uut.retryQueue, 'addToQueue').resolves(false)
await uut.removeStaleOffers()
})
it('remove offer with wrong txid', async () => {
// Mock dependencies and force desired code path.
sandbox.stub(uut.OfferModel, 'find').resolves([{ remove: async () => { } }])
sandbox.stub(uut.retryQueue, 'addToQueue').throws(new Error('txid needs to be a proper transaction ID'))
await uut.removeStaleOffers()
})
it('remove expired offer ', async () => {
const tsMock = new Date()
tsMock.setMonth(tsMock.getMonth() - 3)
const timestamp = tsMock.getTime()
// Mock dependencies and force desired code path.
sandbox.stub(uut.OfferModel, 'find').resolves([{ timestamp, remove: async () => { } }])
sandbox.stub(uut.retryQueue, 'addToQueue').resolves(true)
await uut.removeStaleOffers()
})
it('should handle axios error ', async () => {
const testErr = new Error()
testErr.isAxiosError = true
// Mock dependencies and force desired code path.
sandbox.stub(uut.OfferModel, 'find').resolves([{ remove: async () => { } }])
sandbox.stub(uut.retryQueue, 'addToQueue').throws(testErr)
await uut.removeStaleOffers()
})
it('should handle error ', async () => {
try {
const testErr = new Error('unknow error')
// Mock dependencies and force desired code path.
sandbox.stub(uut.OfferModel, 'find').resolves([{ remove: async () => { } }])
sandbox.stub(uut.retryQueue, 'addToQueue').throws(testErr)
await uut.removeStaleOffers()
assert.fail('unexpected code path')
} catch (error) {
assert.equal(error.message, 'unknow error')
}
})
})
describe('#findOfferByTxid', () => {
it('should throw an error if input is not provided', async () => {
try {
await uut.findOfferByTxid()
assert.fail('unexpected code path')
} catch (error) {
assert.equal(error.message, 'utxoTxid must be a string')
}
})
it('should throw an error if offer is not found', async () => {
try {
// Mock dependencies and force desired code path.
sandbox.stub(uut.OfferModel, 'findOne').resolves(null)
await uut.findOfferByTxid('241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87')
assert.fail('unexpected code path')
} catch (error) {
assert.equal(error.message, 'offer not found')
}
})
it('should return offer', async () => {
// Mock dependencies and force desired code path.
sandbox.stub(uut.OfferModel, 'findOne').resolves(mockData.nftOffer01)
const result = await uut.findOfferByTxid('241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87')
assert.isObject(result)
})
})
describe('#detectNsfw', () => {
it('should return false for wrong cid format', async () => {
const result = await uut.detectNsfw({ mutableData: '' })
assert.isFalse(result)
})
it('should return true if nft boolean detected', async () => {
// Mock dependencies and force desired code path.
sandbox.stub(uut.axios, 'get').resolves({ data: { nsfw: true } })
const result = await uut.detectNsfw({ mutableData: 'ipfs://bafybeibqnsmmh6bkf2wwextetki4tly65z4r4qkrrpl5xwgvzdzjley6wm' })
assert.isTrue(result)
})
it('should return true if nft string detected', async () => {
// Mock dependencies and force desired code path.
sandbox.stub(uut.axios, 'get').resolves({ data: { nsfw: 'true' } })
const result = await uut.detectNsfw({ mutableData: 'ipfs://bafybeibqnsmmh6bkf2wwextetki4tly65z4r4qkrrpl5xwgvzdzjley6wm' })
assert.isTrue(result)
})
it('should return false if nfsw property does not exist', async () => {
// Mock dependencies and force desired code path.
sandbox.stub(uut.axios, 'get').resolves({ data: {} })
const result = await uut.detectNsfw({ mutableData: 'ipfs://bafybeibqnsmmh6bkf2wwextetki4tly65z4r4qkrrpl5xwgvzdzjley6wm' })
assert.isFalse(result)
})
it('should return false on error', async () => {
// Mock dependencies and force desired code path.
sandbox.stub(uut.axios, 'get').throws(new Error('test error'))
const result = await uut.detectNsfw({ mutableData: 'ipfs://bafybeibqnsmmh6bkf2wwextetki4tly65z4r4qkrrpl5xwgvzdzjley6wm' })
assert.isFalse(result)
})
})
describe('#listOffers', () => {
it('should handle error', async () => {
try {
// Mock dependencies
sandbox.stub(uut.OfferModel, 'find').throws(new Error('test error'))
await uut.listOffers()
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should list orders', async () => {
const queryMock = {
sort () {
return this
},
skip () {
return this
},
limit () { return [] }
}
// Mock dependencies
sandbox.stub(uut.OfferModel, 'find').returns(queryMock)
const result = await uut.listOffers()
assert.isArray(result)
})
})
describe('#listNftOffers', () => {
it('should handle error', async () => {
try {
// Mock dependencies
sandbox.stub(uut.OfferModel, 'find').throws(new Error('test error'))
await uut.listNftOffers()
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should list orders', async () => {
const queryMock = {
sort () {
return this
},
skip () {
return this
},
limit () { return [] }
}
// Mock dependencies
sandbox.stub(uut.OfferModel, 'find').returns(queryMock)
const result = await uut.listNftOffers(1, true)
assert.isArray(result)
})
})
describe('#listFungibleOffers', () => {
it('should handle error', async () => {
try {
// Mock dependencies
sandbox.stub(uut.OfferModel, 'find').throws(new Error('test error'))
await uut.listFungibleOffers()
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should list orders', async () => {
const queryMock = {
sort () {
return this
},
skip () {
return this
},
limit () { return [] }
}
// Mock dependencies
sandbox.stub(uut.OfferModel, 'find').returns(queryMock)
const result = await uut.listFungibleOffers()
assert.isArray(result)
})
})
describe('#takeOffer', () => {
it('should handle error if input is not provided', async () => {
try {
await uut.takeOffer()
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'eventId must be a string')
}
})
it('should handle error for wrong offer status', async () => {
try {
// Mock dependencies
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'completed' })
await uut.takeOffer('eventId')
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'offer status is not "posted", so offer is dead and can not be countered.')
}
})
it('should handle error for invalid utxo', async () => {
try {
// Mock dependencies
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'posted' })
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(false)
await uut.takeOffer('eventId')
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'UTXO does not exist. Aborting.')
}
})
it('should handle insufficient funds', async () => {
try {
// Mock dependencies
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'posted' })
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(true)
sandbox.stub(uut, 'ensureFunds').throws(new Error('test error'))
await uut.takeOffer('eventId')
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should handle error if counter offer cant be calculated', async () => {
try {
// Mock dependencies
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'posted' })
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(true)
sandbox.stub(uut, 'ensureFunds').resolves(true)
await uut.takeOffer('eventId')
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'Could not calculate the amount of BCH to generate counter offer')
}
})
it('should handle error if counter offer cant be calculated', async () => {
try {
// Mock dependencies
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'posted' })
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(true)
sandbox.stub(uut, 'ensureFunds').resolves(true)
await uut.takeOffer('eventId')
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'Could not calculate the amount of BCH to generate counter offer')
}
})
it('should take offer', async () => {
// Mock data
const offerMock = Object.assign({}, mockData.offerMockData.data)
offerMock.remove = async () => { }
offerMock.offerStatus = 'posted'
// Mock dependencies
sandbox.stub(uut, 'findOfferByEvent').returns(offerMock)
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(true)
sandbox.stub(uut, 'ensureFunds').resolves(true)
sandbox.stub(uut.adapters.wallet, 'moveBch').resolves({ sats: 1 })
await uut.takeOffer('eventId')
})
})
describe('#ensureFunds', () => {
// it('should handle insufficient funds to use p2wdb', async () => {
// try {
// // Mock dependencies
// sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(false)
//
// await uut.ensureFunds(mockData.offerMockData.data)
//
// assert.fail('Unexpected code path')
// } catch (err) {
// assert.include(err.message, 'App wallet does not have funds for writing to the P2WDB')
// }
// })
it('should throw error if sats needed could no be able to calculated', async () => {
try {
// Mock dependencies
sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(true)
// Mock Input
const mock = Object.assign({}, mockData.offerMockData.data)
mock.rateInBaseUnit = null
await uut.ensureFunds(mock)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'Could not calculate sats needed!')
}
})
it('should throw error if app wallet does not have enough bch', async () => {
try {
// Mock dependencies
sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(true)
sandbox.stub(uut.adapters.wallet.bchWallet, 'getBalance').resolves(0)
await uut.ensureFunds(mockData.offerMockData.data)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'App wallet does not control enough BCH to purchase the tokens.')
}
})
it('should handle BUY offer', async () => {
try {
const mock = Object.assign({}, mockData.offerMockData.data)
mock.buyOrSell = 'buy'
await uut.ensureFunds(mock)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'Buy offers are not supported yet.')
}
})
it('should return true', async () => {
// Mock dependencies
sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(true)
sandbox.stub(uut.adapters.wallet.bchWallet, 'getBalance').resolves(10 * 10 ** 6)
const result = await uut.ensureFunds(mockData.offerMockData.data)
assert.isTrue(result)
})
})
describe('#findOrderByEvent', () => {
it('should throw an error if hash is not provided', async () => {
try {
await uut.findOfferByEvent()
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'nostrEventId must be a string')
}
})
it('should throw an error if order is not found!', async () => {
try {
// Mock dependencies
sandbox.stub(uut.OfferModel, 'findOne').resolves(null)
await uut.findOfferByEvent('eventId')
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'offer not found')
}
})
it('should return offer by eventId', async () => {
// Mock dependencies
sandbox.stub(uut.OfferModel, 'findOne').resolves({ toObject: () => { return { hash: 'hash' } } })
const result = await uut.findOfferByEvent('eventId')
assert.isObject(result)
})
})
describe('#flagOffer', () => {
it('should throw an error if input is not provided', async () => {
try {
await uut.flagOffer()
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, '"data" property is required')
}
})
it('should throw an error if offer is not found!', async () => {
try {
// Mock dependencies
sandbox.stub(uut, 'findOfferByEvent').resolves(null)
const input = {
data:
{
nostrEventId: 'eventId'
}
}
await uut.flagOffer(input)
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'not found in the database')
}
})
it('should flag offer', async () => {
// Mock dependencies
sandbox.stub(uut, 'findOfferByEvent').resolves({ flags: ['a', 'b', 'c'], save: () => { } })
const input = {
data:
{
nostrEventId: 'eventId'
}
}
const result = await uut.flagOffer(input)
assert.isTrue(result)
})
})
describe('#loadOffers', () => {
it('should handle nostr error', async () => {
try {
// Mock dependencies
sandbox.stub(uut.adapters.nostr, 'read').throws(new Error('test error'))
await uut.loadOffers()
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should skip internal function errors ', async () => {
// Mock dependencies
sandbox.stub(uut.adapters.nostr, 'read').resolves([mockData.offerMockData])
await uut.loadOffers()
})
it('should review and load offers', async () => {
// Mock dependencies
sandbox.stub(uut.adapters.nostr, 'read').resolves([JSON.stringify(mockData.offerMockData)])
await uut.loadOffers()
})
})
describe('#acceptCounterOffer', () => {
it('should return if order is not found!', async () => {
// Mock dependencies
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').throws(new Error('test error'))
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
assert.equal(result, 'N/A')
})
it('should handle error if counter offer cant be calculated', async () => {
try {
// Mock Data
const mock = Object.assign({}, mockData.offerMockData.data)
mock.rateInBaseUnit = null
// Mock dependencies
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
console.log('result: ', result)
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'Could not calculate the amount of BCH offered in the Counter Offer')
}
})
it('should handle error for wrong transaction output', async () => {
try {
// Mock Data
const mock = Object.assign({}, mockData.offerMockData.data)
// Mock dependencies
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mockData.offerMockData.data)
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').returns(0)
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
await uut.acceptCounterOffer({ data: mock })
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'The Counter Offer has an output of ')
assert.include(err.message, 'which does not match the required')
}
})
it('should handle error for wrong transaction output address', async () => {
try {
// Mock data
const mock = Object.assign({}, mockData.offerMockData.data)
mock.makerAddr = 'bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7477' // Unknow Adress
mock.rateInBaseUnit = 0
mock.numTokens = 0
// Mock dependencies
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
sandbox.stub(uut.orderUseCase, 'findOrderByUtxo').resolves(mock)
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').returns(0)
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
await uut.acceptCounterOffer({ data: { /** .... */ } })
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'The Counter Offer has an output address of')
assert.include(err.message, 'which does not match the Maker address')
}
})
it('should return tx id', async () => {
// Mock data
const mock = Object.assign({}, mockData.offerMockData.data)
mock.makerAddr = mockData.deserealizeTxMock.vout[2].scriptPubKey.addresses[0] // Maker Address
mock.rateInBaseUnit = 0
mock.numTokens = 0
// Mock dependencies
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').returns(0)
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
//
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
assert.isString(result)
})
})
})
+204 -8
View File
@@ -62,6 +62,69 @@ describe('#order-use-case', () => {
assert.equal(result, true)
})
it('should handle insufficient funds', async () => {
try {
sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(false)
const orderEntity = {
lokadId: 'SWP',
messageType: 1,
messageClass: 1,
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
buyOrSell: 'sell',
rateInSats: 1000,
minSatsToExchange: 0,
numTokens: 1
}
await uut.ensureFunds(orderEntity)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'App wallet does not have funds for writing to the P2WDB')
}
})
it('should handle insufficient tokens to satisfy the order', async () => {
try {
const orderEntity = {
lokadId: 'SWP',
messageType: 1,
messageClass: 1,
tokenId: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
buyOrSell: 'sell',
rateInSats: 1000,
minSatsToExchange: 0,
numTokens: 1000
}
await uut.ensureFunds(orderEntity)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'App wallet does not have enough tokens to satisfy the SELL order.')
}
})
it('should handle BUY order', async () => {
try {
const orderEntity = {
lokadId: 'SWP',
messageType: 1,
messageClass: 1,
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
buyOrSell: 'buy',
rateInSats: 1000,
minSatsToExchange: 0,
numTokens: 1
}
await uut.ensureFunds(orderEntity)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'Buy orders are not supported yet')
}
})
})
// describe('#moveTokens', () => {
@@ -103,23 +166,23 @@ describe('#order-use-case', () => {
buyOrSell: 'sell',
rateInBaseUnit: 1000,
minUnitsToExchange: 1250,
numTokens: 1
numTokens: 1,
ticker: 'TEST'
}
// Mock dependencies
// sandbox.stub(uut.adapters.wallet, 'burnPsf').resolves('fakeTxid')
// sandbox.stub(uut.adapters.wallet.bchWallet, 'getTxData').resolves({ tokenTicker: 'TROUT' })
sandbox.stub(uut.orderEntity, 'validate').returns(entryObj)
// Mock dependencies and force expected code path
sandbox.stub(uut.orderEntity, 'inputValidate').returns(entryObj)
sandbox.stub(uut, 'ensureFunds').resolves()
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.Util, 'sleep').resolves()
sandbox.stub(uut.adapters.wallet, 'moveTokens').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 })
sandbox.stub(uut.adapters.wallet.bchWallet, 'getUtxos').resolves()
sandbox.stub(uut.adapters.p2wdb, 'write').resolves('fakeHash')
sandbox.stub(uut.adapters.wallet.bchWallet, 'initialize').resolves()
sandbox.stub(uut.adapters.nostr, 'post').resolves('fakeEvenetId')
const result = await uut.createOrder(entryObj)
console.log('result: ', result)
assert.isString(result)
assert.property(result, 'eventId')
assert.property(result, 'noteId')
})
it('should catch and throw an error', async () => {
@@ -138,4 +201,137 @@ describe('#order-use-case', () => {
assert.equal(result, false)
})
})
describe('#findOrderByEvent', () => {
it('should throw an error if hash is not provided', async () => {
try {
await uut.findOrderByEvent()
} catch (err) {
assert.include(err.message, 'nostrEventId must be a string')
}
})
it('should throw an error if order is not found!', async () => {
try {
sandbox.stub(uut.OrderModel, 'findOne').resolves(null)
await uut.findOrderByEvent('hash')
} catch (err) {
assert.include(err.message, 'order not found')
}
})
it('should return order by hash', async () => {
sandbox.stub(uut.OrderModel, 'findOne').resolves({ toObject: () => { return { hash: 'hash' } } })
const result = await uut.findOrderByEvent('hash')
assert.isObject(result)
})
})
describe('#removeStaleOrders', () => {
it('should remove stale orders', async () => {
const spy = sinon.spy() // Spy the function
// Create 2 orders mock .
const ordersMock = new Array(2)
ordersMock.fill({ remove: () => { spy.call() } })
sandbox.stub(uut.OrderModel, 'find').resolves(ordersMock)
sandbox.stub(uut.retryQueue, 'addToQueue').resolves(false)
await uut.removeStaleOrders()
assert.equal(spy.callCount, 2, 'Expected to be called twice.')
})
it('should handle wrong txid', async () => {
const spy = sinon.spy() // Spy the function
// Create 2 orders mock .
const ordersMock = new Array(2)
ordersMock.fill({ remove: () => { spy.call() } })
sandbox.stub(uut.OrderModel, 'find').resolves(ordersMock)
sandbox.stub(uut.retryQueue, 'addToQueue').throws(new Error('txid needs to be a proper transaction ID'))
await uut.removeStaleOrders()
assert.equal(spy.callCount, 2, 'Expected to be called twice.')
})
it('should skip order on axios error', async () => {
const spy = sinon.spy() // Spy the function
// Create 2 orders mock .
const ordersMock = new Array(2)
ordersMock.fill({ remove: () => { spy.call() } })
const axiosError = new Error()
axiosError.isAxiosError = true
sandbox.stub(uut.OrderModel, 'find').resolves(ordersMock)
sandbox.stub(uut.retryQueue, 'addToQueue').throws(axiosError)
await uut.removeStaleOrders()
assert.equal(spy.callCount, 0, 'Expected to no be called.')
})
it('should handle unknow error', async () => {
try {
// Create 2 orders mock .
const ordersMock = new Array(2)
ordersMock.fill({ remove: () => { } })
sandbox.stub(uut.OrderModel, 'find').resolves(ordersMock)
sandbox.stub(uut.retryQueue, 'addToQueue').throws(new Error('test error'))
await uut.removeStaleOrders()
assert.fail('unexpected code path')
} catch (error) {
assert.include(error.message, 'test error')
}
})
})
describe('#listOrders', () => {
it('should handle error', async () => {
try {
sandbox.stub(uut.OrderModel, 'find').throws(new Error('test error'))
await uut.listOrders()
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should list orders', async () => {
const queryMock = {
sort () {
return this
},
skip () {
return this
},
limit () { return [] }
}
sandbox.stub(uut.OrderModel, 'find').returns(queryMock)
const result = await uut.listOrders()
assert.isArray(result)
})
})
describe('#deleteOrder', () => {
it('should handle error', async () => {
try {
sandbox.stub(uut, 'findOrderByEvent').throws(new Error('test error'))
await uut.deleteOrder()
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should delete orders', async () => {
sandbox.stub(uut, 'findOrderByEvent').resolves([])
sandbox.stub(uut.adapters.wallet, 'reclaimTokens').resolves('txid')
const result = await uut.deleteOrder()
assert.isString(result)
})
})
})
+3 -3
View File
@@ -2,15 +2,15 @@
This script has not been customized for offers yet.
*/
const mongoose = require('mongoose')
import mongoose from 'mongoose'
// Force test environment
// make sure environment variable is set before this file gets called.
// see test script in package.json.
// process.env.KOA_ENV = 'test'
const config = require('../../config')
import config from '../../config'
const User = require('../../src/models/users')
import User from '../../src/models/users'
async function deleteUsers () {
// Connect to the Mongo Database.
+3 -4
View File
@@ -2,11 +2,10 @@
Get all Offers in the database.
*/
const mongoose = require('mongoose')
import mongoose from 'mongoose'
const config = require('../../config')
const Offer = require('../../src/adapters/localdb/models/offer')
import config from '../../config/index.js'
import Offer from '../../src/adapters/localdb/models/offer.js'
async function getOffers () {
// Connect to the Mongo Database.
+3 -3
View File
@@ -1,12 +1,12 @@
const mongoose = require('mongoose')
import mongoose from 'mongoose'
// Force test environment
// make sure environment variable is set before this file gets called.
// see test script in package.json.
// process.env.KOA_ENV = 'test'
const config = require('../../config')
import config from '../../config'
const User = require('../../src/models/users')
import User from '../../src/models/users'
async function deleteUsers () {
// Connect to the Mongo Database.
+3 -5
View File
@@ -1,8 +1,6 @@
const mongoose = require('mongoose')
const config = require('../../config')
const Order = require('../../src/adapters/localdb/models/order')
import mongoose from 'mongoose'
import config from '../../config/index.js'
import Order from '../../src/adapters/localdb/models/order.js'
async function getOrder () {
// Connect to the Mongo Database.
+7 -5
View File
@@ -7,11 +7,12 @@
*/
// Public npm libraries
const BCHJS = require('@psf/bch-js')
const BchTokenSweep = require('bch-token-sweep/index')
import BCHJS from '@psf/bch-js'
import BchTokenSweep from 'bch-token-sweep'
// Local libraries
const WalletAdapter = require('../../src/adapters/wallet')
import WalletAdapter from '../../src/adapters/wallet.js'
// Constants
const EMTPY_ADDR_CUTOFF = 15
@@ -21,6 +22,7 @@ async function sweepFunds () {
// Open the wallet files.
const wallet = new WalletAdapter()
const walletInfo = await wallet.openWallet()
const bchWallet = await wallet.instanceWallet(walletInfo)
console.log('walletInfo: ', walletInfo)
const rootAddr = walletInfo.cashAddress
@@ -48,7 +50,7 @@ async function sweepFunds () {
const sweeper = new BchTokenSweep(
wifToSweep,
rootWif,
bchjs,
bchWallet,
550,
rootAddr
)
@@ -67,7 +69,7 @@ async function sweepFunds () {
// Wait between loop iterations.
await bchjs.Util.sleep(3000)
} catch (err) {
console.log(`error message with index ${hdIndex}: ${err.message}`)
console.log(`error message with index ${hdIndex}: ${err}`)
emptyAddrCnt++
}