diff --git a/jest.config.ts b/jest.config.ts index 4e90b248..d17e1ee6 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -3,6 +3,7 @@ * https://jestjs.io/docs/en/configuration.html */ import type { Config } from '@jest/types' +import { Dates } from 'cafe-utility' import { getPssAddress } from './test/utility/address' import { getOrBuyStamp } from './test/utility/stamp' @@ -52,6 +53,6 @@ export default async (): Promise => { testPathIgnorePatterns: ['/node_modules/'], // Increase timeout since we have long running cryptographic functions - testTimeout: 4 * 60 * 1000, + testTimeout: Dates.minutes(5), } } diff --git a/package-lock.json b/package-lock.json index dbe883a0..36a2dd1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,18 +6,20 @@ "packages": { "": { "name": "@ethersphere/swarm-cli", - "version": "2.17.0", + "version": "2.18.0", "license": "BSD-3-Clause", "dependencies": { "@ethersphere/bee-js": "^8.3.0", "@fairdatasociety/bmt-js": "^2.1.0", - "cafe-utility": "^22.0.0", + "cafe-utility": "^26.2.1", "chalk": "^2.4.2", "cli-progress": "^3.11.2", "ethereumjs-wallet": "^1.0.2", + "ethers": "^5.7.2", "furious-commander": "^1.7.1", "inquirer": "^8.2.5", "mantaray-js": "^1.0.3", + "node-fetch": "^2.7.0", "ora": "^5.3.0", "ws": "^8.11.0" }, @@ -31,6 +33,7 @@ "@types/inquirer": "^8.2.5", "@types/jest": "^29.2.3", "@types/node": "^18.11.9", + "@types/node-fetch": "^2.6.12", "@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/parser": "^5.43.0", "eslint": "^8.27.0", @@ -1105,12 +1108,763 @@ "beeApiVersion": "7.1.0" } }, + "node_modules/@ethersphere/bee-js/node_modules/axios": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.28.1.tgz", + "integrity": "sha512-iUcGA5a7p0mVb4Gm/sy+FSECNkPFT4y7wt6OM/CDpO/OnNCvSs3PoMG8ibrC9jRoGYU0gUK5pXVC4NPXq6lHRQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/@ethersphere/bee-js/node_modules/cafe-utility": { "version": "23.10.0", "resolved": "https://registry.npmjs.org/cafe-utility/-/cafe-utility-23.10.0.tgz", "integrity": "sha512-e+jz+KE5tndsAiG3kSMqkF3PxiWKx6rpgMfhKkHQMwAaGGf4EVSm9y9gH6dEF8UGWbNXRtmyq2bPF/PXwNTrqA==", "license": "MIT" }, + "node_modules/@ethersproject/abi": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.7.0.tgz", + "integrity": "sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-provider": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.7.0.tgz", + "integrity": "sha512-R41c9UkchKCpAqStMYUpdunjo3pkEvZC3FAwZn5S5MGbXoMQOHIdHItezTETxAO5bevtMApSyEhn9+CHcDsWBw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0" + } + }, + "node_modules/@ethersproject/abstract-signer": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.7.0.tgz", + "integrity": "sha512-a16V8bq1/Cz+TGCkE2OPMTOUDLS3grCpdjoJCYNnVBbdYEMSgKrU0+B90s8b6H+ByYTBZN7a3g76jdIJi7UfKQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/address": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.7.0.tgz", + "integrity": "sha512-9wYhYt7aghVGo758POM5nqcOMaE168Q6aRLJZwUmiqSrAungkG74gSSeKEIR7ukixesdRZGPgVqme6vmxs1fkA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/rlp": "^5.7.0" + } + }, + "node_modules/@ethersproject/base64": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.7.0.tgz", + "integrity": "sha512-Dr8tcHt2mEbsZr/mwTPIQAf3Ai0Bks/7gTw9dSqk1mQvhW3XvRlmDJr/4n+wg1JmCl16NZue17CDh8xb/vZ0sQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0" + } + }, + "node_modules/@ethersproject/basex": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.7.0.tgz", + "integrity": "sha512-ywlh43GwZLv2Voc2gQVTKBoVQ1mti3d8HK5aMxsfu/nRDnMmNqaSJ3r3n85HBByT8OpoY96SXM1FogC533T4zw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/properties": "^5.7.0" + } + }, + "node_modules/@ethersproject/bignumber": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.7.0.tgz", + "integrity": "sha512-n1CAdIHRWjSucQO3MC1zPSVgV/6dy/fjL9pMrPP9peL+QxEg9wOsVqwD4+818B6LUEtaXzVHQiuivzRoxPxUGw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "bn.js": "^5.2.1" + } + }, + "node_modules/@ethersproject/bignumber/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "license": "MIT" + }, + "node_modules/@ethersproject/bytes": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/constants": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.7.0.tgz", + "integrity": "sha512-DHI+y5dBNvkpYUMiRQyxRBYBefZkJfo70VUkUAsRjcPs47muV9evftfZ0PJVCXYbAiCgght0DtcF9srFQmIgWA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0" + } + }, + "node_modules/@ethersproject/contracts": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.7.0.tgz", + "integrity": "sha512-5GJbzEU3X+d33CdfPhcyS+z8MzsTrBGk/sc+G+59+tPa9yFkl6HQ9D6L0QMgNTA9q8dT0XKxxkyp883XsQvbbg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "^5.7.0", + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/transactions": "^5.7.0" + } + }, + "node_modules/@ethersproject/hash": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/hdnode": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.7.0.tgz", + "integrity": "sha512-OmyYo9EENBPPf4ERhR7oj6uAtUAhYGqOnIS+jE5pTXvdKBS99ikzq1E7Iv0ZQZ5V36Lqx1qZLeak0Ra16qpeOg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/json-wallets": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.7.0.tgz", + "integrity": "sha512-8oee5Xgu6+RKgJTkvEMl2wDgSPSAQ9MB/3JYjFV9jlKvcYHUXZC+cQp0njgmxdHkYWn8s6/IqIZYm0YWCjO/0g==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/pbkdf2": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "aes-js": "3.0.0", + "scrypt-js": "3.0.1" + } + }, + "node_modules/@ethersproject/json-wallets/node_modules/aes-js": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", + "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", + "license": "MIT" + }, + "node_modules/@ethersproject/keccak256": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.7.0.tgz", + "integrity": "sha512-2UcPboeL/iW+pSg6vZ6ydF8tCnv3Iu/8tUmLLzWWGzxWKFFqOBQFLo6uLUv6BDrLgCDfN28RJ/wtByx+jZ4KBg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "js-sha3": "0.8.0" + } + }, + "node_modules/@ethersproject/logger": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.7.0.tgz", + "integrity": "sha512-0odtFdXu/XHtjQXJYA3u9G0G8btm0ND5Cu8M7i5vhEcE8/HmF4Lbdqanwyv4uQTr2tx6b7fQRmgLrsnpQlmnig==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT" + }, + "node_modules/@ethersproject/networks": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.7.1.tgz", + "integrity": "sha512-n/MufjFYv3yFcUyfhnXotyDlNdFb7onmkSy8aQERi2PjNcnWQ66xXxa3XlS8nCcA8aJKJjIIMNJTC7tu80GwpQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/pbkdf2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.7.0.tgz", + "integrity": "sha512-oR/dBRZR6GTyaofd86DehG72hY6NpAjhabkhxgr3X2FpJtJuodEl2auADWBZfhDHgVCbu3/H/Ocq2uC6dpNjjw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/sha2": "^5.7.0" + } + }, + "node_modules/@ethersproject/properties": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.7.0.tgz", + "integrity": "sha512-J87jy8suntrAkIZtecpxEPxY//szqr1mlBaYlQ0r4RCaiD2hjheqF9s1LVE8vVuJCXisjIP+JgtK/Do54ej4Sw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/providers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.7.2.tgz", + "integrity": "sha512-g34EWZ1WWAVgr4aptGlVBF8mhl3VWjv+8hoAnzStu8Ah22VHBsuGzP17eb6xDVRzw895G4W7vvx60lFFur/1Rg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/base64": "^5.7.0", + "@ethersproject/basex": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/networks": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/web": "^5.7.0", + "bech32": "1.1.4", + "ws": "7.4.6" + } + }, + "node_modules/@ethersproject/providers/node_modules/ws": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", + "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@ethersproject/random": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.7.0.tgz", + "integrity": "sha512-19WjScqRA8IIeWclFme75VMXSBvi4e6InrUNuaR4s5pTF2qNhcGdCUwdxUVGtDDqC00sDLCO93jPQoDUH4HVmQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/rlp": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.7.0.tgz", + "integrity": "sha512-rBxzX2vK8mVF7b0Tol44t5Tb8gomOHkj5guL+HhzQ1yBh/ydjGnpw6at+X6Iw0Kp3OzzzkcKp8N9r0W4kYSs9w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/sha2": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.7.0.tgz", + "integrity": "sha512-gKlH42riwb3KYp0reLsFTokByAKoJdgFCwI+CCiX/k+Jm2mbNs6oOaCjYQSlI1+XBVejwH2KrmCbMAT/GnRDQw==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.7.0.tgz", + "integrity": "sha512-MZdy2nL3wO0u7gkB4nA/pEf8lu1TlFswPNmy8AiYkfKTdO6eXBJyUdmHO/ehm/htHw9K/qF8ujnTyUAD+Ry54Q==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "bn.js": "^5.2.1", + "elliptic": "6.5.4", + "hash.js": "1.1.7" + } + }, + "node_modules/@ethersproject/signing-key/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "license": "MIT" + }, + "node_modules/@ethersproject/solidity": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.7.0.tgz", + "integrity": "sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/sha2": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/strings": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.7.0.tgz", + "integrity": "sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/transactions": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.7.0.tgz", + "integrity": "sha512-kmcNicCp1lp8qanMTC3RIikGgoJ80ztTyvtsFvCYpSCfkjhD0jZ2LOrnbcuxuToLIUYYf+4XwD1rP+B/erDIhQ==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/rlp": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0" + } + }, + "node_modules/@ethersproject/units": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.7.0.tgz", + "integrity": "sha512-pD3xLMy3SJu9kG5xDGI7+xhTEmGXlEqXU4OfNapmfnxLVY4EMSSRp7j1k7eezutBPH7RBN/7QPnwR7hzNlEFeg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/constants": "^5.7.0", + "@ethersproject/logger": "^5.7.0" + } + }, + "node_modules/@ethersproject/wallet": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.7.0.tgz", + "integrity": "sha512-MhmXlJXEJFBFVKrDLB4ZdDzxcBxQ3rLyCkhNqVu3CDYvR97E+8r01UgrI+TI99Le+aYm/in/0vp86guJuM7FCA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abstract-provider": "^5.7.0", + "@ethersproject/abstract-signer": "^5.7.0", + "@ethersproject/address": "^5.7.0", + "@ethersproject/bignumber": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/hdnode": "^5.7.0", + "@ethersproject/json-wallets": "^5.7.0", + "@ethersproject/keccak256": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/random": "^5.7.0", + "@ethersproject/signing-key": "^5.7.0", + "@ethersproject/transactions": "^5.7.0", + "@ethersproject/wordlists": "^5.7.0" + } + }, + "node_modules/@ethersproject/web": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.7.1.tgz", + "integrity": "sha512-Gueu8lSvyjBWL4cYsWsjh6MtMwM0+H4HvqFPZfB6dV8ctbP9zFAO73VG1cMWae0FLPCtz0peKPpZY8/ugJJX2w==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/base64": "^5.7.0", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, + "node_modules/@ethersproject/wordlists": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.7.0.tgz", + "integrity": "sha512-S2TFNJNfHWVHNE6cNDjbVlZ6MgE17MIxMbMg2zv3wn+3XSJGosL1m9ZVv3GXCf/2ymSsQ+hRI5IzoMJTG6aoVA==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@ethersproject/logger": "^5.7.0", + "@ethersproject/properties": "^5.7.0", + "@ethersproject/strings": "^5.7.0" + } + }, "node_modules/@fairdatasociety/bmt-js": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@fairdatasociety/bmt-js/-/bmt-js-2.1.0.tgz", @@ -2142,6 +2896,17 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", @@ -2555,16 +3320,6 @@ "node": ">= 4.0.0" } }, - "node_modules/axios": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.28.1.tgz", - "integrity": "sha512-iUcGA5a7p0mVb4Gm/sy+FSECNkPFT4y7wt6OM/CDpO/OnNCvSs3PoMG8ibrC9jRoGYU0gUK5pXVC4NPXq6lHRQ==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/babel-jest": { "version": "29.3.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.3.1.tgz", @@ -2759,6 +3514,12 @@ } ] }, + "node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "license": "MIT" + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -2935,9 +3696,10 @@ } }, "node_modules/cafe-utility": { - "version": "22.0.0", - "resolved": "https://registry.npmjs.org/cafe-utility/-/cafe-utility-22.0.0.tgz", - "integrity": "sha512-1nlKbWXznDPV+B7eQKm6VHgbDG80snNODRix54RTyv+Y/CJ9VPxlfrFiG/VCprR6mL4c9RK+KADGE1biVGVLSw==" + "version": "26.2.1", + "resolved": "https://registry.npmjs.org/cafe-utility/-/cafe-utility-26.2.1.tgz", + "integrity": "sha512-drjWI2urh0EJMnf/GRxhipm48tU8gmvwpcU2RR8fPzIp4vPY3VddyQgFMsWqcUC/+LWXCExZrj6jOIjQx9rwEQ==", + "license": "MIT" }, "node_modules/callsites": { "version": "3.1.0", @@ -3963,6 +4725,54 @@ "uuid": "^8.3.2" } }, + "node_modules/ethers": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.7.2.tgz", + "integrity": "sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==", + "funding": [ + { + "type": "individual", + "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2" + }, + { + "type": "individual", + "url": "https://www.buymeacoffee.com/ricmoo" + } + ], + "license": "MIT", + "dependencies": { + "@ethersproject/abi": "5.7.0", + "@ethersproject/abstract-provider": "5.7.0", + "@ethersproject/abstract-signer": "5.7.0", + "@ethersproject/address": "5.7.0", + "@ethersproject/base64": "5.7.0", + "@ethersproject/basex": "5.7.0", + "@ethersproject/bignumber": "5.7.0", + "@ethersproject/bytes": "5.7.0", + "@ethersproject/constants": "5.7.0", + "@ethersproject/contracts": "5.7.0", + "@ethersproject/hash": "5.7.0", + "@ethersproject/hdnode": "5.7.0", + "@ethersproject/json-wallets": "5.7.0", + "@ethersproject/keccak256": "5.7.0", + "@ethersproject/logger": "5.7.0", + "@ethersproject/networks": "5.7.1", + "@ethersproject/pbkdf2": "5.7.0", + "@ethersproject/properties": "5.7.0", + "@ethersproject/providers": "5.7.2", + "@ethersproject/random": "5.7.0", + "@ethersproject/rlp": "5.7.0", + "@ethersproject/sha2": "5.7.0", + "@ethersproject/signing-key": "5.7.0", + "@ethersproject/solidity": "5.7.0", + "@ethersproject/strings": "5.7.0", + "@ethersproject/transactions": "5.7.0", + "@ethersproject/units": "5.7.0", + "@ethersproject/wallet": "5.7.0", + "@ethersproject/web": "5.7.1", + "@ethersproject/wordlists": "5.7.0" + } + }, "node_modules/evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", @@ -7316,6 +8126,26 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-gyp-build": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz", @@ -8652,6 +9482,12 @@ "node": ">=8.0" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/trim-newlines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz", @@ -8960,6 +9796,22 @@ "defaults": "^1.0.3" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9063,15 +9915,16 @@ } }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/package.json b/package.json index 77fc3a2f..b7b50142 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@types/inquirer": "^8.2.5", "@types/jest": "^29.2.3", "@types/node": "^18.11.9", + "@types/node-fetch": "^2.6.12", "@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/parser": "^5.43.0", "eslint": "^8.27.0", @@ -63,13 +64,15 @@ "dependencies": { "@ethersphere/bee-js": "^8.3.0", "@fairdatasociety/bmt-js": "^2.1.0", - "cafe-utility": "^22.0.0", + "cafe-utility": "^26.2.1", "chalk": "^2.4.2", "cli-progress": "^3.11.2", "ethereumjs-wallet": "^1.0.2", + "ethers": "^5.7.2", "furious-commander": "^1.7.1", "inquirer": "^8.2.5", "mantaray-js": "^1.0.3", + "node-fetch": "^2.7.0", "ora": "^5.3.0", "ws": "^8.11.0" } diff --git a/src/command/feed/print.ts b/src/command/feed/print.ts index 5b682140..28211c2c 100644 --- a/src/command/feed/print.ts +++ b/src/command/feed/print.ts @@ -92,7 +92,7 @@ export class Print extends FeedCommand implements LeafCommand { if (this.list) { for (let i = 0; i < numberOfUpdates; i++) { - const indexBytes = Binary.numberToUint64BE(i) + const indexBytes = Binary.numberToUint64(BigInt(i), 'BE') const identifier = Utils.keccak256Hash(Binary.hexToUint8Array(topic), indexBytes) const owner = Binary.hexToUint8Array(this.address) const soc = Binary.uint8ArrayToHex(Utils.keccak256Hash(identifier, owner)) diff --git a/src/command/stamp/buy.ts b/src/command/stamp/buy.ts index f0586dcb..c35cb7bc 100644 --- a/src/command/stamp/buy.ts +++ b/src/command/stamp/buy.ts @@ -1,8 +1,8 @@ import { Utils } from '@ethersphere/bee-js' -import { Dates } from 'cafe-utility' +import { Dates, Numbers } from 'cafe-utility' +import { BigNumber } from 'ethers' import { LeafCommand, Option } from 'furious-commander' import { createSpinner } from '../../utils/spinner' -import { Storage } from '../../utils/storage' import { createKeyValue } from '../../utils/text' import { VerbosityLevel } from '../root-command/command-log' import { StampCommand } from './stamp-command' @@ -10,7 +10,7 @@ import { StampCommand } from './stamp-command' export class Buy extends StampCommand implements LeafCommand { public readonly name = 'buy' - public readonly description = 'Buy postage stamp' + public readonly description = 'Buy postage stamp based on depth and amount' @Option({ key: 'depth', @@ -57,25 +57,30 @@ export class Buy extends StampCommand implements LeafCommand { public postageBatchId!: string public async run(): Promise { - await super.init() + super.init() + + const chainState = await this.bee.getChainState() + const minimumAmount = BigNumber.from(chainState.currentPrice).mul(17280) + + if (minimumAmount.gte(BigNumber.from(this.amount))) { + this.console.error('The amount is too low. The minimum amount is', minimumAmount.add(1).toString()) + + return + } const estimatedCost = Utils.getStampCostInBzz(this.depth, Number(this.amount)) - const estimatedCapacity = new Storage(Utils.getStampMaximumCapacityBytes(this.depth)) - const estimatedTtl = Utils.getStampTtlSeconds(Number(this.amount)) + const estimatedCapacity = Numbers.convertBytes(Utils.getStampMaximumCapacityBytes(this.depth)) + const estimatedTtl = Utils.getStampTtlSeconds(Number(this.amount), Number(chainState.currentPrice), 5) this.console.log(createKeyValue('Estimated cost', `${estimatedCost.toFixed(3)} xBZZ`)) - this.console.log(createKeyValue('Estimated capacity', estimatedCapacity.toString())) + this.console.log(createKeyValue('Estimated capacity', estimatedCapacity)) this.console.log(createKeyValue('Estimated TTL', Dates.secondsToHumanTime(estimatedTtl))) this.console.log(createKeyValue('Type', this.immutable ? 'Immutable' : 'Mutable')) if (this.immutable) { - this.console.info( - 'Once an immutable stamp is maxed out, it disallows further content uploads, thereby safeguarding your previously uploaded content from unintentional overwriting.', - ) + this.console.info('At full capacity, an immutable stamp no longer allows new content uploads.') } else { - this.console.info( - 'When a mutable stamp reaches full capacity, it still permits new content uploads. However, this comes with the caveat of overwriting previously uploaded content associated with the same stamp.', - ) + this.console.info('At full capacity, a mutable stamp allows new content uploads, but overwrites old content.') } if (!this.quiet && !this.yes) { diff --git a/src/command/stamp/create.ts b/src/command/stamp/create.ts new file mode 100644 index 00000000..1f132b30 --- /dev/null +++ b/src/command/stamp/create.ts @@ -0,0 +1,128 @@ +import { Utils } from '@ethersphere/bee-js' +import { Dates, Numbers } from 'cafe-utility' +import { BigNumber } from 'ethers' +import { LeafCommand, Option } from 'furious-commander' +import { createSpinner } from '../../utils/spinner' +import { createKeyValue } from '../../utils/text' +import { VerbosityLevel } from '../root-command/command-log' +import { StampCommand } from './stamp-command' + +export class Create extends StampCommand implements LeafCommand { + public readonly name = 'create' + + public readonly description = 'Create postage stamp in a simple way' + + @Option({ + key: 'capacity', + description: 'Size of data, e.g. 100MB, 1GB', + type: 'string', + required: false, + }) + public capacity!: string + + @Option({ + key: 'ttl', + description: 'Time to live of the postage stamp, e.g. 1d, 4w, "6 months", 1y', + type: 'string', + required: false, + }) + public ttl!: string + + @Option({ key: 'immutable', description: 'Disable stamp reuse', type: 'boolean', default: true }) + public immutable!: boolean + + @Option({ key: 'label', description: 'Label of the postage stamp' }) + public label!: string + + public postageBatchId!: string + + public async run(): Promise { + super.init() + + let capacityInBytes = 0 + let ttlInMillis = 0 + + if (!this.capacity) { + this.console.log('Please provide the capacity of the postage stamp') + this.console.log('This is the size of the data that can be uploaded with this stamp') + this.console.log('Example: 100MB, 1GB') + this.capacity = await this.console.askForValue('Capacity') + this.console.log('') + } + + capacityInBytes = Numbers.makeStorage(this.capacity) + + if (!this.ttl) { + this.console.log('Please provide the time to live of the postage stamp') + this.console.log('This is the time after which the stamp will expire') + this.console.log('Example: 1h, 1d, 1w') + this.ttl = await this.console.askForValue('TTL') + this.console.log('') + } + + ttlInMillis = Dates.make(this.ttl) + const chainState = await this.bee.getChainState() + const minimumAmount = BigNumber.from(chainState.currentPrice).mul(17280) + + const depth = Utils.getDepthForCapacity(capacityInBytes / 1024 ** 3) + const amount = BigNumber.from(Math.ceil(ttlInMillis / 5_000) + 1).mul(chainState.currentPrice) + + if (minimumAmount.gt(amount)) { + this.console.error('The minimum amount for the TTL is 1 day') + + return + } + + this.console.log('You have provided the following parameters:') + this.console.log(createKeyValue('Capacity', Numbers.convertBytes(capacityInBytes))) + this.console.log(createKeyValue('TTL', Dates.secondsToHumanTime(ttlInMillis / 1000))) + this.console.log('') + this.console.log(`Your parameters are now converted to Swarm's internal parameters:`) + this.console.log(createKeyValue('Depth (capacity)', depth)) + this.console.log(createKeyValue('Amount (TTL)', amount.toString())) + + const estimatedCost = Utils.getStampCostInBzz(depth, Number(amount)) + const estimatedCapacity = Numbers.convertBytes(Utils.getStampMaximumCapacityBytes(depth)) + const estimatedTtl = Utils.getStampTtlSeconds(Number(amount), Number(chainState.currentPrice), 5) + + this.console.log('') + this.console.log(createKeyValue('Estimated cost', `${estimatedCost.toFixed(3)} xBZZ`)) + this.console.log(createKeyValue('Estimated capacity', estimatedCapacity)) + this.console.log(createKeyValue('Estimated TTL', Dates.secondsToHumanTime(estimatedTtl))) + this.console.log(createKeyValue('Type', this.immutable ? 'Immutable' : 'Mutable')) + + if (this.immutable) { + this.console.info('At full capacity, an immutable stamp no longer allows new content uploads.') + } else { + this.console.info('At full capacity, a mutable stamp allows new content uploads, but overwrites old content.') + } + + if (!this.quiet && !this.yes) { + this.yes = await this.console.confirm('Confirm the purchase') + } + + if (!this.yes && !this.quiet) { + return + } + + const spinner = createSpinner('Creating postage batch. This may take up to 5 minutes.') + + if (this.verbosity !== VerbosityLevel.Quiet && !this.curl) { + spinner.start() + } + + try { + const batchId = await this.bee.createPostageBatch(amount.toString(), depth, { + label: this.label, + immutableFlag: this.immutable, + waitForUsable: true, + }) + spinner.stop() + this.console.quiet(batchId) + this.console.log(createKeyValue('Stamp ID', batchId)) + this.postageBatchId = batchId + } finally { + spinner.stop() + } + } +} diff --git a/src/command/stamp/index.ts b/src/command/stamp/index.ts index 0b0fb6ca..b58e2607 100644 --- a/src/command/stamp/index.ts +++ b/src/command/stamp/index.ts @@ -1,5 +1,6 @@ import { GroupCommand } from 'furious-commander' import { Buy } from './buy' +import { Create } from './create' import { Dilute } from './dilute' import { List } from './list' import { Show } from './show' @@ -10,5 +11,5 @@ export class Stamp implements GroupCommand { public readonly description = 'Buy, list and show postage stamps' - public subCommandClasses = [List, Buy, Show, Dilute, Topup] + public subCommandClasses = [List, Create, Buy, Show, Dilute, Topup] } diff --git a/src/command/upload.ts b/src/command/upload.ts index b9a47f67..3a36a065 100644 --- a/src/command/upload.ts +++ b/src/command/upload.ts @@ -1,5 +1,5 @@ import { RedundancyLevel, Tag, Utils } from '@ethersphere/bee-js' -import { System } from 'cafe-utility' +import { Numbers, System } from 'cafe-utility' import { Presets, SingleBar } from 'cli-progress' import * as FS from 'fs' import { Argument, LeafCommand, Option } from 'furious-commander' @@ -12,13 +12,10 @@ import { CommandLineError } from '../utils/error' import { getMime } from '../utils/mime' import { stampProperties } from '../utils/option' import { createSpinner } from '../utils/spinner' -import { Storage } from '../utils/storage' import { createKeyValue, warningSymbol, warningText } from '../utils/text' import { RootCommand } from './root-command' import { VerbosityLevel } from './root-command/command-log' -const MAX_UPLOAD_SIZE = new Storage(parseInt(process.env.MAX_UPLOAD_SIZE || '', 10) || 100 * 1000 * 1000) // 100 megabytes - export class Upload extends RootCommand implements LeafCommand { public readonly name = 'upload' public readonly alias = 'up' @@ -335,19 +332,21 @@ export class Upload extends RootCommand implements LeafCommand { const currentSetting = Utils.getRedundancyStat(this.redundancy) const originalSize = await this.getUploadSize() - const originalChunks = Math.ceil(originalSize.getBytes() / 4e3) + const originalChunks = Math.ceil(originalSize / 4e3) const sizeMultiplier = Utils.approximateOverheadForRedundancyLevel( originalChunks, currentSetting.value, this.encrypt, ) - const newSize = new Storage(originalChunks * 4e3 * (1 + sizeMultiplier)) - const extraSize = new Storage(newSize.getBytes() - originalSize.getBytes()) + const newSize = originalChunks * 4e3 * (1 + sizeMultiplier) + const extraSize = newSize - originalSize this.console.log(createKeyValue('Redundancy setting', currentSetting.label)) this.console.log(`This setting will provide ${Math.round(currentSetting.errorTolerance * 100)}% error tolerance.`) - this.console.log(`An additional ${extraSize.toString()} of data will be uploaded approximately.`) - this.console.log(`${originalSize.toString()} → ${newSize.toString()} (+${extraSize.toString()})`) + this.console.log(`An additional ${Numbers.convertBytes(extraSize)} of data will be uploaded approximately.`) + this.console.log( + `${Numbers.convertBytes(originalSize)} → ${Numbers.convertBytes(newSize)} (+${Numbers.convertBytes(extraSize)})`, + ) if (!this.yes && !this.quiet) { const confirmation = await this.console.confirm('Do you want to proceed?') @@ -358,7 +357,7 @@ export class Upload extends RootCommand implements LeafCommand { } } - private async getUploadSize(): Promise { + private async getUploadSize(): Promise { let size = -1 if (this.stdin) { @@ -368,10 +367,9 @@ export class Upload extends RootCommand implements LeafCommand { size = stats.isDirectory() ? await Utils.getFolderSize(this.path) : stats.size } - const storage = new Storage(size) - this.console.verbose(`Upload size is approximately ${storage}`) + this.console.verbose(`Upload size is approximately ${Numbers.convertBytes(size)}`) - return storage + return size } private hasUnsupportedGatewayOptions(): boolean { diff --git a/src/command/utility/get-bee.ts b/src/command/utility/get-bee.ts new file mode 100644 index 00000000..cdd60fc1 --- /dev/null +++ b/src/command/utility/get-bee.ts @@ -0,0 +1,47 @@ +import { execSync } from 'child_process' +import { writeFileSync } from 'fs' +import { LeafCommand } from 'furious-commander' +import fetch from 'node-fetch' +import { RootCommand } from '../root-command' + +const archTable = { + arm64: 'arm64', + x64: 'amd64', +} + +const platformTable = { + win32: 'windows', + darwin: 'darwin', + linux: 'linux', +} + +export class GetBee extends RootCommand implements LeafCommand { + public readonly name = 'get-bee' + + public readonly description = 'Downloads the Bee binary for the current platform' + + public async run(): Promise { + super.init() + const archString = Reflect.get(archTable, process.arch) + const platformString = Reflect.get(platformTable, process.platform) + const suffixString = process.platform === 'win32' ? '.exe' : '' + + if (!archString || !platformString) { + throw Error(`Unsupported system: arch=${process.arch} platform=${process.platform}`) + } + const url = `https://github.com/ethersphere/bee/releases/download/v2.3.0/bee-${platformString}-${archString}${suffixString}` + this.console.info(`Downloading Bee from ${url}`) + await fetch(url) + .then(x => x.arrayBuffer()) + .then(x => writeFileSync(`bee${suffixString}`, Buffer.from(x))) + this.console.info('Bee downloaded successfully') + + if (process.platform !== 'win32') { + this.console.info(`Running chmod +x bee`) + execSync('chmod +x bee') + } + this.console.log('Verify the version of the downloaded Bee binary by running:') + this.console.log('') + this.console.log('./bee version') + } +} diff --git a/src/command/utility/index.ts b/src/command/utility/index.ts index 831ea134..dcfbc0af 100644 --- a/src/command/utility/index.ts +++ b/src/command/utility/index.ts @@ -3,15 +3,17 @@ import { readFile } from 'fs/promises' import { GroupCommand } from 'furious-commander' import { fileExists } from '../../utils' import { CommandLog } from '../root-command/command-log' +import { GetBee } from './get-bee' import { Lock } from './lock' +import { Redeem } from './redeem' import { Unlock } from './unlock' export class Utility implements GroupCommand { public readonly name = 'utility' - public readonly description = 'Utility commands for managing wallets' + public readonly description = 'Utility commands related to Swarm and wallets' - public subCommandClasses = [Lock, Unlock] + public subCommandClasses = [Lock, Unlock, GetBee, Redeem] } export async function createWallet(pathOrPrivateKey: string, console: CommandLog): Promise { diff --git a/src/command/utility/redeem.ts b/src/command/utility/redeem.ts new file mode 100644 index 00000000..5ca9439a --- /dev/null +++ b/src/command/utility/redeem.ts @@ -0,0 +1,103 @@ +import { Dates, System } from 'cafe-utility' +import { BigNumber, providers, Wallet } from 'ethers' +import { Argument, LeafCommand, Option } from 'furious-commander' +import { NETWORK_ID } from '../../utils/bzz-abi' +import { estimateNativeTransferTransactionCost, Rpc } from '../../utils/rpc' +import { RootCommand } from '../root-command' + +export class Redeem extends RootCommand implements LeafCommand { + public readonly name = 'redeem' + + public readonly description = 'Transfer xBZZ and xDAI from a private key to the Bee wallet' + + @Argument({ + key: 'wallet', + type: 'hex-string', + description: 'Redeemable wallet address (private key)', + required: true, + }) + public wallet!: string + + @Option({ + key: 'json-rpc-url', + type: 'string', + description: 'Ethereum JSON-RPC URL', + default: 'https://xdai.fairdatasociety.org', + }) + public jsonRpcUrl!: string + + @Option({ + key: 'target', + type: 'hex-string', + description: 'Target wallet address', + defaultDescription: 'Bee wallet address', + }) + public target!: string + + public async run(): Promise { + super.init() + + if (!this.target) { + this.console.log('Fetching Bee wallet address...') + const { ethereum } = await this.bee.getNodeAddresses() + this.target = ethereum + } + + this.console.log(`Target wallet address: ${this.target}`) + const provider = new providers.JsonRpcProvider(this.jsonRpcUrl, NETWORK_ID) + this.console.log('Creating wallet...') + const wallet = new Wallet(this.wallet, provider) + this.console.log('Fetching xBZZ balance...') + const xBZZ = await Rpc._eth_getBalanceERC20(wallet.address, provider) + this.console.log(`xBZZ balance: ${xBZZ}`) + this.console.log('Fetching xDAI balance...') + let xDAI = await Rpc.eth_getBalance(wallet.address, provider) + this.console.log(`xDAI balance: ${xDAI}`) + + if (!this.quiet && !this.yes) { + this.yes = await this.console.confirm('Do you want to continue?') + } + + if (!this.yes && !this.quiet) { + return + } + + const firstKnownxDAI = xDAI + + if (xBZZ !== '0') { + this.console.log('Transferring xBZZ to Bee wallet...') + await Rpc.sendBzzTransaction(this.wallet, this.target, xBZZ, this.jsonRpcUrl) + + for (let i = 0; i < 10; i++) { + this.console.log(`Refreshing xDAI balance #${i + 1}...`) + xDAI = await Rpc.eth_getBalance(wallet.address, provider) + + if (xDAI !== firstKnownxDAI) { + this.console.log(`xDAI balance: ${xDAI}`) + break + } + await System.sleepMillis(Dates.seconds(3)) + } + + if (xDAI === firstKnownxDAI) { + this.console.log('xDAI balance did not change, skipping transfer') + + return + } + } + const { gasPrice, totalCost } = await estimateNativeTransferTransactionCost(this.wallet, this.jsonRpcUrl) + const xDAIValue = BigNumber.from(xDAI) + + if (xDAIValue.gt(totalCost)) { + this.console.log('Transferring xDAI to Bee wallet...') + await Rpc.sendNativeTransaction( + this.wallet, + this.target, + xDAIValue.sub(totalCost).toString(), + this.jsonRpcUrl, + gasPrice, + ) + } + this.console.log('Redeem complete') + } +} diff --git a/src/service/stamp/index.ts b/src/service/stamp/index.ts index 73fea408..2c634b1b 100644 --- a/src/service/stamp/index.ts +++ b/src/service/stamp/index.ts @@ -1,9 +1,8 @@ import { Bee, PostageBatch, Utils } from '@ethersphere/bee-js' -import { Dates } from 'cafe-utility' +import { Dates, Numbers } from 'cafe-utility' import { exit } from 'process' import { CommandLog } from '../../command/root-command/command-log' import { getFieldOrNull } from '../../utils' -import { Storage } from '../../utils/storage' import { createKeyValue } from '../../utils/text' import { EnrichedStamp } from './types/stamp' @@ -22,7 +21,12 @@ export async function pickStamp(bee: Bee, console: CommandLog): Promise const choices = stamps .filter(stamp => stamp.usable || stamp.batchTTL > 0) - .map(stamp => `${stamp.batchID} ${stamp.remainingCapacity} expires in ${Dates.secondsToHumanTime(stamp.batchTTL)}`) + .map( + stamp => + `${stamp.batchID} ${Numbers.convertBytes(stamp.remainingCapacity)} remaining, TTL ${Dates.secondsToHumanTime( + stamp.batchTTL, + )}`, + ) if (!choices.length) { console.error('You need to have at least one stamp for this action.') @@ -39,8 +43,8 @@ export function enrichStamp(stamp: PostageBatch): EnrichedStamp { const usage = Utils.getStampUsage(stamp.utilization, stamp.depth, stamp.bucketDepth) const usageNormal = Math.ceil(usage * 100) const usageText = usageNormal + '%' - const capacity = new Storage(Utils.getStampMaximumCapacityBytes(stamp.depth)) - const remainingCapacity = new Storage(capacity.getBytes() * (1 - usage)) + const capacity = Utils.getStampMaximumCapacityBytes(stamp.depth) + const remainingCapacity = capacity * (1 - usage) return { ...stamp, @@ -74,7 +78,9 @@ export function printStamp( console.log( createKeyValue( richStamp.immutableFlag ? 'Capacity (immutable)' : 'Capacity (mutable)', - `${richStamp.remainingCapacity.toString()} remaining out of ${richStamp.capacity.toString()}`, + `${Numbers.convertBytes(richStamp.remainingCapacity)} remaining out of ${Numbers.convertBytes( + richStamp.capacity, + )}`, ), ) diff --git a/src/service/stamp/types/stamp.ts b/src/service/stamp/types/stamp.ts index a9d3ad65..e3c836a7 100644 --- a/src/service/stamp/types/stamp.ts +++ b/src/service/stamp/types/stamp.ts @@ -1,10 +1,9 @@ import { PostageBatch } from '@ethersphere/bee-js' -import { Storage } from '../../../utils/storage' export interface EnrichedStamp extends PostageBatch { usage: number usageNormal: number usageText: string - capacity: Storage - remainingCapacity: Storage + capacity: number + remainingCapacity: number } diff --git a/src/utils/bzz-abi.ts b/src/utils/bzz-abi.ts new file mode 100644 index 00000000..953a732f --- /dev/null +++ b/src/utils/bzz-abi.ts @@ -0,0 +1,46 @@ +export const NETWORK_ID = 100 +export const BZZ_TOKEN_ADDRESS = '0xdBF3Ea6F5beE45c02255B2c26a16F300502F68da' +export const bzzABI = [ + { + type: 'function', + stateMutability: 'view', + payable: false, + outputs: [ + { + type: 'uint256', + name: '', + }, + ], + name: 'balanceOf', + inputs: [ + { + type: 'address', + name: '_owner', + }, + ], + constant: true, + }, + { + type: 'function', + stateMutability: 'nonpayable', + payable: false, + outputs: [ + { + type: 'bool', + name: '', + }, + ], + name: 'transfer', + inputs: [ + { + type: 'address', + name: '_to', + }, + { + type: 'uint256', + name: '_value', + }, + ], + constant: false, + }, +] diff --git a/src/utils/rpc.ts b/src/utils/rpc.ts new file mode 100644 index 00000000..63bfcaac --- /dev/null +++ b/src/utils/rpc.ts @@ -0,0 +1,117 @@ +import { BigNumber as BN, Contract, providers, Wallet } from 'ethers' +import { BZZ_TOKEN_ADDRESS, bzzABI } from './bzz-abi' + +const NETWORK_ID = 100 + +async function getNetworkChainId(url: string): Promise { + const provider = new providers.JsonRpcProvider(url, NETWORK_ID) + await provider.ready + const network = await provider.getNetwork() + + return network.chainId +} + +async function eth_getBalance(address: string, provider: providers.JsonRpcProvider): Promise { + if (!address.startsWith('0x')) { + address = `0x${address}` + } + const balance = await provider.getBalance(address) + + return balance.toString() +} + +async function eth_getBlockByNumber(provider: providers.JsonRpcProvider): Promise { + const blockNumber = await provider.getBlockNumber() + + return blockNumber.toString() +} + +async function eth_getBalanceERC20( + address: string, + provider: providers.JsonRpcProvider, + tokenAddress = BZZ_TOKEN_ADDRESS, +): Promise { + if (!address.startsWith('0x')) { + address = `0x${address}` + } + const contract = new Contract(tokenAddress, bzzABI, provider) + const balance = await contract.balanceOf(address) + + return balance.toString() +} + +interface TransferResponse { + transaction: providers.TransactionResponse + receipt: providers.TransactionReceipt +} + +interface TransferCost { + gasPrice: BN + totalCost: BN +} + +export async function estimateNativeTransferTransactionCost( + privateKey: string, + jsonRpcProvider: string, +): Promise { + const signer = await makeReadySigner(privateKey, jsonRpcProvider) + const gasLimit = '21000' + const gasPrice = await signer.getGasPrice() + + return { gasPrice, totalCost: gasPrice.mul(gasLimit) } +} + +export async function sendNativeTransaction( + privateKey: string, + to: string, + value: string, + jsonRpcProvider: string, + externalGasPrice?: BN, +): Promise { + const signer = await makeReadySigner(privateKey, jsonRpcProvider) + const gasPrice = externalGasPrice ?? (await signer.getGasPrice()) + const transaction = await signer.sendTransaction({ + to, + value: BN.from(value), + gasPrice, + gasLimit: BN.from(21000), + type: 0, + }) + const receipt = await transaction.wait(1) + + return { transaction, receipt } +} + +export async function sendBzzTransaction( + privateKey: string, + to: string, + value: string, + jsonRpcProvider: string, +): Promise { + const signer = await makeReadySigner(privateKey, jsonRpcProvider) + const gasPrice = await signer.getGasPrice() + const bzz = new Contract(BZZ_TOKEN_ADDRESS, bzzABI, signer) + const transaction = await bzz.transfer(to, value, { gasPrice }) + const receipt = await transaction.wait(1) + + return { transaction, receipt } +} + +async function makeReadySigner(privateKey: string, jsonRpcProvider: string) { + const provider = new providers.JsonRpcProvider(jsonRpcProvider, NETWORK_ID) + await provider.ready + const signer = new Wallet(privateKey, provider) + + return signer +} + +export const Rpc = { + getNetworkChainId, + sendNativeTransaction, + sendBzzTransaction, + _eth_getBalance: eth_getBalance, + _eth_getBalanceERC20: eth_getBalanceERC20, + eth_getBalance, + eth_getBalanceERC20, + eth_getBlockByNumber, +} diff --git a/src/utils/storage.ts b/src/utils/storage.ts deleted file mode 100644 index 4ef29b43..00000000 --- a/src/utils/storage.ts +++ /dev/null @@ -1,47 +0,0 @@ -export class Storage { - private bytes: number - - constructor(bytes: number) { - this.bytes = bytes - } - - public getBytes(): number { - return this.bytes - } - - public getKilobytes(): number { - return this.bytes / 1024 - } - - public getMegabytes(): number { - return this.bytes / 1024 / 1024 - } - - public getGigabytes(): number { - return this.bytes / 1024 / 1024 / 1024 - } - - public getTerabytes(): number { - return this.bytes / 1024 / 1024 / 1024 / 1024 - } - - public toString(): string { - if (this.bytes < 1024) { - return `${this.bytes} B` - } - - if (this.bytes < 1024 * 1024) { - return `${this.getKilobytes().toFixed(2)} KB` - } - - if (this.bytes < 1024 * 1024 * 1024) { - return `${this.getMegabytes().toFixed(2)} MB` - } - - if (this.bytes < 1024 * 1024 * 1024 * 1024) { - return `${this.getGigabytes().toFixed(2)} GB` - } - - return `${this.getTerabytes().toFixed(2)} TB` - } -} diff --git a/test/prompt/stamp-prompt.spec.ts b/test/prompt/stamp-prompt.spec.ts index 0fe5f079..347bdf31 100644 --- a/test/prompt/stamp-prompt.spec.ts +++ b/test/prompt/stamp-prompt.spec.ts @@ -7,8 +7,8 @@ describeCommand('Postage stamp price estimation prompt', ({ consoleMessages }) = jest.spyOn(inquirer, 'prompt').mockResolvedValueOnce({ value: 'n' }) await invokeTestCli(['stamp', 'buy', '--depth', '24', '--amount', '596046400']) expect(consoleMessages[0]).toBe('Estimated cost: 1.000 xBZZ') - expect(consoleMessages[1]).toBe('Estimated capacity: 64.00 GB') - expect(consoleMessages[2]).toBe('Estimated TTL: 34 hours') + expect(consoleMessages[1]).toBe('Estimated capacity: 64.000 GB') + expect(consoleMessages[2]).toBe('Estimated TTL: Infinity weeks') expect(inquirer.prompt).toHaveBeenCalledWith({ message: 'Confirm the purchase', name: 'value', diff --git a/test/utils/storage.spec.ts b/test/utils/storage.spec.ts deleted file mode 100644 index 63fa3f10..00000000 --- a/test/utils/storage.spec.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Storage } from '../../src/utils/storage' - -describe('Storage', () => { - test('should return bytes', () => { - const storage = new Storage(1024) - expect(storage.getBytes()).toBe(1024) - }) - - test('should return kilobytes', () => { - const storage = new Storage(1024) - expect(storage.getKilobytes()).toBe(1) - }) - - test('should return megabytes', () => { - const storage = new Storage(1024 * 1024) - expect(storage.getMegabytes()).toBe(1) - }) - - test('should return gigabytes', () => { - const storage = new Storage(1024 * 1024 * 1024) - expect(storage.getGigabytes()).toBe(1) - }) - - test('should return terabytes', () => { - const storage = new Storage(1024 * 1024 * 1024 * 1024) - expect(storage.getTerabytes()).toBe(1) - }) - - test('toString should return bytes', () => { - const storage = new Storage(500) - expect(storage.toString()).toBe('500 B') - }) - - test('toString should return kilobytes', () => { - const storage = new Storage(500 * 1024) - expect(storage.toString()).toBe('500.00 KB') - }) - - test('toString should return megabytes', () => { - const storage = new Storage(500 * 1024 * 1024) - expect(storage.toString()).toBe('500.00 MB') - }) - - test('toString should return gigabytes', () => { - const storage = new Storage(500 * 1024 * 1024 * 1024) - expect(storage.toString()).toBe('500.00 GB') - }) - - test('toString should return terabytes', () => { - const storage = new Storage(500 * 1024 * 1024 * 1024 * 1024) - expect(storage.toString()).toBe('500.00 TB') - }) -})