diff --git a/README.md b/README.md index 36b363dd..d98ce1bf 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ Just install the chainjs library to get started ``` To run ts files (and examples), use ts-node (with the --files option to include local customTypes) +Important: Node version 13 or greater is required to run polkadot examples - Hint: Use nvm to run run node on your local machine ```bash $ ts-node --files mycode.ts ``` diff --git a/package-lock.json b/package-lock.json index 02a900cf..bccb9c34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@open-rights-exchange/chainjs", - "version": "0.27.8", + "version": "0.28.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -864,6 +864,680 @@ "@types/yargs": "^13.0.0" } }, + "@polkadot/api": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@polkadot/api/-/api-4.0.3.tgz", + "integrity": "sha512-jZf/NBkj6Ao7hG3I0ay7zOyDZm21tdqNRqglagBI+9Nw3wPvPL2Dz/mnGQCaeSq/fv/frY6YZQvouj4gRQzGwQ==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/api-derive": "4.0.3", + "@polkadot/keyring": "^6.0.5", + "@polkadot/metadata": "4.0.3", + "@polkadot/rpc-core": "4.0.3", + "@polkadot/rpc-provider": "4.0.3", + "@polkadot/types": "4.0.3", + "@polkadot/types-known": "4.0.3", + "@polkadot/util": "^6.0.5", + "@polkadot/util-crypto": "^6.0.5", + "@polkadot/x-rxjs": "^6.0.5", + "bn.js": "^4.11.9", + "eventemitter3": "^4.0.7" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/api-derive": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@polkadot/api-derive/-/api-derive-4.0.3.tgz", + "integrity": "sha512-ADHrIoYumHJBQuIdtDEX6LPiJVZmLGBlFvlkRGYsKL7qJzRZtkzfuNgd8i3cZVDKk9mlcpldmj1DTiN3KBjH0Q==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/api": "4.0.3", + "@polkadot/rpc-core": "4.0.3", + "@polkadot/types": "4.0.3", + "@polkadot/util": "^6.0.5", + "@polkadot/util-crypto": "^6.0.5", + "@polkadot/x-rxjs": "^6.0.5", + "bn.js": "^4.11.9" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/keyring": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@polkadot/keyring/-/keyring-6.0.5.tgz", + "integrity": "sha512-v9Tmu+eGZnWpLzxHUj5AIvmfX0uCHWShtF90zvaLvMXLFRWfeuaFnZwdZ+fNkXsrbI0R/w1gRtpFqzsT7QUbVw==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/util": "6.0.5", + "@polkadot/util-crypto": "6.0.5" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/metadata": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@polkadot/metadata/-/metadata-4.0.3.tgz", + "integrity": "sha512-w4QRpIendx0LWINS3o93weqrNenI4X5T2iOdiPYd+DkIj1k3GI9An5BWnta9e953xEtGstwW169PF/itWMKyTw==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/types": "4.0.3", + "@polkadot/types-known": "4.0.3", + "@polkadot/util": "^6.0.5", + "@polkadot/util-crypto": "^6.0.5", + "bn.js": "^4.11.9" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/networks": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@polkadot/networks/-/networks-6.0.5.tgz", + "integrity": "sha512-QSa5RdK43yD4kLsZ6tXIB652bZircaVPOpsZ5JFzxCM4gdxHCSCUeXNVBeh3uqeB3FOZZYzSYeoZHLaXuT3yJw==", + "requires": { + "@babel/runtime": "^7.13.9" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/rpc-core": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-core/-/rpc-core-4.0.3.tgz", + "integrity": "sha512-BJD5OS9uYlNMNPwRSFB0oT7az9NXBapapcafi6g1O6d4rvDwmsiptKr4+hkoLhzpuZcx6rfYSsVf7oz1v1J9/g==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/metadata": "4.0.3", + "@polkadot/rpc-provider": "4.0.3", + "@polkadot/types": "4.0.3", + "@polkadot/util": "^6.0.5", + "@polkadot/x-rxjs": "^6.0.5" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/rpc-provider": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@polkadot/rpc-provider/-/rpc-provider-4.0.3.tgz", + "integrity": "sha512-xddbODw+uMQrrdWWtKb39OwFqs6VFxvBHDjKmnB8IEUzKq2CIEDJG4qe3y2FfTeVCLWWxSmtxyOj0xo3jok3uw==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/types": "4.0.3", + "@polkadot/util": "^6.0.5", + "@polkadot/util-crypto": "^6.0.5", + "@polkadot/x-fetch": "^6.0.5", + "@polkadot/x-global": "^6.0.5", + "@polkadot/x-ws": "^6.0.5", + "bn.js": "^4.11.9", + "eventemitter3": "^4.0.7" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/types": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@polkadot/types/-/types-4.0.3.tgz", + "integrity": "sha512-aLNugf0Zyde8gAkHtPh8Pp2Rw6XJUUIDe9v/Lc3siJji6aPJuzwHW9XoJYBw8A8pl0MbmrJk3js/o3hEKqmFqg==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/metadata": "4.0.3", + "@polkadot/util": "^6.0.5", + "@polkadot/util-crypto": "^6.0.5", + "@polkadot/x-rxjs": "^6.0.5", + "@types/bn.js": "^4.11.6", + "bn.js": "^4.11.9" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/types-known": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@polkadot/types-known/-/types-known-4.0.3.tgz", + "integrity": "sha512-XF6Ft2L3zU0E294SpySFi0fv9JIrL0YM0ftOrvqagdXopchc9Sg9XTm3uoukrT8yVu5IVWjQHyk2NwqeAlNV4A==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/networks": "^6.0.5", + "@polkadot/types": "4.0.3", + "@polkadot/util": "^6.0.5", + "bn.js": "^4.11.9" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/util": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@polkadot/util/-/util-6.0.5.tgz", + "integrity": "sha512-0EnYdGAXx/Y2MLgCKtlfdKVcURV+Twx+M+auljTeMK8226pR7xMblYuVuO5bxhPWBa1W7+iQloEZ0VRQrIoMDw==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/x-textdecoder": "6.0.5", + "@polkadot/x-textencoder": "6.0.5", + "@types/bn.js": "^4.11.6", + "bn.js": "^4.11.9", + "camelcase": "^5.3.1", + "ip-regex": "^4.3.0" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/util-crypto": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@polkadot/util-crypto/-/util-crypto-6.0.5.tgz", + "integrity": "sha512-NlzmZzJ1vq2bjnQUU0MUocaT9vuIBGTlB/XCrCw94MyYqX19EllkOKLVMgu6o89xhYeP5rmASRQvTx9ZL9EzRw==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/networks": "6.0.5", + "@polkadot/util": "6.0.5", + "@polkadot/wasm-crypto": "^4.0.2", + "@polkadot/x-randomvalues": "6.0.5", + "base-x": "^3.0.8", + "base64-js": "^1.5.1", + "blakejs": "^1.1.0", + "bn.js": "^4.11.9", + "create-hash": "^1.2.0", + "elliptic": "^6.5.4", + "hash.js": "^1.1.7", + "js-sha3": "^0.8.0", + "scryptsy": "^2.1.0", + "tweetnacl": "^1.0.3", + "xxhashjs": "^0.2.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "requires": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "requires": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + }, + "scryptsy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scryptsy/-/scryptsy-2.1.0.tgz", + "integrity": "sha512-1CdSqHQowJBnMAFyPEBRfqag/YP9OF394FV+4YREIJX4ljD7OxvQRDayyoyyCk+senRjSkP6VnUNQmVQqB6g7w==" + } + } + }, + "@polkadot/wasm-crypto": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto/-/wasm-crypto-4.0.2.tgz", + "integrity": "sha512-2h9FuQFkBc+B3TwSapt6LtyPvgtd0Hq9QsHW8g8FrmKBFRiiFKYRpfJKHCk0aCZzuRf9h95bQl/X6IXAIWF2ng==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/wasm-crypto-asmjs": "^4.0.2", + "@polkadot/wasm-crypto-wasm": "^4.0.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/wasm-crypto-asmjs": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-asmjs/-/wasm-crypto-asmjs-4.0.2.tgz", + "integrity": "sha512-hlebqtGvfjg2ZNm4scwBGVHwOwfUhy2yw5RBHmPwkccUif3sIy4SAzstpcVBIVMdAEvo746bPWEInA8zJRcgJA==", + "requires": { + "@babel/runtime": "^7.13.9" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/wasm-crypto-wasm": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@polkadot/wasm-crypto-wasm/-/wasm-crypto-wasm-4.0.2.tgz", + "integrity": "sha512-de/AfNPZ0uDKFWzOZ1rJCtaUbakGN29ks6IRYu6HZTRg7+RtqvE1rIkxabBvYgQVHIesmNwvEA9DlIkS6hYRFQ==", + "requires": { + "@babel/runtime": "^7.13.9" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/x-fetch": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@polkadot/x-fetch/-/x-fetch-6.0.5.tgz", + "integrity": "sha512-LuyIxot8pLnYaYsR1xok7Bjm+s7wxYe27Y66THea6bDL3CrBPQdj74F9i0OIxD1GB+qJqh4mDApiGX3COqssvg==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/x-global": "6.0.5", + "@types/node-fetch": "^2.5.8", + "node-fetch": "^2.6.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@types/node-fetch": { + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.8.tgz", + "integrity": "sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==", + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/x-global": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@polkadot/x-global/-/x-global-6.0.5.tgz", + "integrity": "sha512-KjQvICngNdB2Gno0TYJlgjKI0Ia0NPhN1BG6YzcKLO/5ZNzNHkLmowdNb5gprE8uCBnOFXXHwgZAE/nTYya2dg==", + "requires": { + "@babel/runtime": "^7.13.9", + "@types/node-fetch": "^2.5.8", + "node-fetch": "^2.6.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@types/node-fetch": { + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.8.tgz", + "integrity": "sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==", + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/x-randomvalues": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@polkadot/x-randomvalues/-/x-randomvalues-6.0.5.tgz", + "integrity": "sha512-MZK6+35vk7hnLW+Jciu5pNwMOkaCRNdsTVfNimzaJpIi6hN27y1X2oD82SRln0X4mKh370eLbvP8i3ylOzWnww==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/x-global": "6.0.5" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/x-rxjs": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@polkadot/x-rxjs/-/x-rxjs-6.0.5.tgz", + "integrity": "sha512-nwMaP69/RzdXbPn8XypIRagMpW46waSraQq4/tGb4h+/Qob+RHxCT68UHKz1gp7kzxhrf85LanE9410A6EYjRw==", + "requires": { + "@babel/runtime": "^7.13.9", + "rxjs": "^6.6.6" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + }, + "rxjs": { + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.6.tgz", + "integrity": "sha512-/oTwee4N4iWzAMAL9xdGKjkEHmIwupR3oXbQjCKywF1BeFohswF3vZdogbmEF6pZkOsXTzWkrZszrWpQTByYVg==", + "requires": { + "tslib": "^1.9.0" + } + } + } + }, + "@polkadot/x-textdecoder": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@polkadot/x-textdecoder/-/x-textdecoder-6.0.5.tgz", + "integrity": "sha512-Vd2OftcEYxg2jG37lJw5NcZotnOidinN84m1HJszLIQT9vZDnFfN60gobHsuzHaGjEDexe4wqe0PfbgA4MfWIQ==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/x-global": "6.0.5" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/x-textencoder": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@polkadot/x-textencoder/-/x-textencoder-6.0.5.tgz", + "integrity": "sha512-wAheP9/kzpfBw5uU/jCnHtd9uN9XzUPYH81aPbx3X026dXNMa4xpOoroCfEuNu2RtFXm0ONuYfpHxvHUsst9lA==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/x-global": "6.0.5" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, + "@polkadot/x-ws": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@polkadot/x-ws/-/x-ws-6.0.5.tgz", + "integrity": "sha512-J2vUfDjWIwEB/FSIXKZxER2flWqRvWcxvAaN+w6dhxJw8BKl+NYKN8QRrlhcDvbk/PWEEtdjthwV3lyOoeGDLA==", + "requires": { + "@babel/runtime": "^7.13.9", + "@polkadot/x-global": "6.0.5", + "@types/websocket": "^1.0.2", + "websocket": "^1.0.33" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" + } + } + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -1012,8 +1686,7 @@ "@types/node": { "version": "13.13.38", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.38.tgz", - "integrity": "sha512-oxo8j9doh7ab9NwDA9bCeFfjHRF/uzk+fTljCy8lMjZ3YzZGAXNDKhTE3Byso/oy32UTUQIXB3HCVHu3d2T3xg==", - "dev": true + "integrity": "sha512-oxo8j9doh7ab9NwDA9bCeFfjHRF/uzk+fTljCy8lMjZ3YzZGAXNDKhTE3Byso/oy32UTUQIXB3HCVHu3d2T3xg==" }, "@types/node-fetch": { "version": "2.5.7", @@ -1077,6 +1750,14 @@ "resolved": "https://registry.npmjs.org/@types/text-encoding/-/text-encoding-0.0.35.tgz", "integrity": "sha512-jfo/A88XIiAweUa8np+1mPbm3h2w0s425YrI8t3wk5QxhH6UI7w517MboNVnGDeMSuoFwA8Rwmklno+FicvV4g==" }, + "@types/websocket": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/websocket/-/websocket-1.0.2.tgz", + "integrity": "sha512-B5m9aq7cbbD/5/jThEr33nUY8WEfVi6A2YKCTOvw5Ldy7mtsOkqRvGjnzy6g7iMMDsgu7xREuCzqATLDLQVKcQ==", + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "13.0.11", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.11.tgz", @@ -2019,8 +2700,7 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" }, "capture-exit": { "version": "2.0.0", @@ -2450,6 +3130,11 @@ "cssom": "0.3.x" } }, + "cuint": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz", + "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=" + }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", @@ -4586,6 +5271,11 @@ "loose-envify": "^1.0.0" } }, + "ip-regex": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==" + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -8195,8 +8885,7 @@ "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "tsutils": { "version": "3.17.1", @@ -9069,6 +9758,14 @@ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, + "xxhashjs": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz", + "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==", + "requires": { + "cuint": "^0.2.2" + } + }, "y18n": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", diff --git a/package.json b/package.json index cf59ee06..d08b620c 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,11 @@ }, "dependencies": { "@aikon/sjcl": "^0.1.8", + "@polkadot/api": "^4.0.3", + "@polkadot/keyring": "^6.0.5", + "@polkadot/types": "^4.0.3", + "@polkadot/util": "^6.0.5", + "@polkadot/util-crypto": "^6.0.5", "@types/bignumber.js": "^5.0.0", "@types/bn.js": "^4.11.6", "@types/bs58": "^4.0.1", diff --git a/parachains b/parachains new file mode 100644 index 00000000..66966940 --- /dev/null +++ b/parachains @@ -0,0 +1,50 @@ + + + +ParachainA Manifest +- DPOS +- curve type +- Account palate Type 1 +- Currency ABC +- Fee palate type 1 + +ParachainB +- POW +- curve type +- Account palate Type 2 +- Currency XYZ +- No Fees + +ParachainC +- POW +- curve type +- No Accounts +- No Currency + + +Developer Code + +- CreateAccount on chain +- Transfer Currency on chain +- ... +- compose transaction type X on chain +- sign transaction +- send transaction to chain +- wait for transaction confirmation + +chainJS != Poladot chain standard (parachains are diff) + + +para + +new ChainFactory(PolkaDotV1, options: {parachainManifest}) + + + +Developer App - Game + +(TX GAME123) -> Parachain X - general purpose contract chain (game contracts go here) + (tx 123) -> Parachain A - Marketplace for NFTs + (tx 456) -> Parachain B - Bridge to ETH (send 721) + Parachain C - service XYC + diff --git a/src/chainFactory.ts b/src/chainFactory.ts index e0850a57..e5a315ed 100644 --- a/src/chainFactory.ts +++ b/src/chainFactory.ts @@ -1,6 +1,7 @@ import { ChainEosV2 } from './chains/eos_2/ChainEosV2' import { ChainEthereumV1 } from './chains/ethereum_1/ChainEthereumV1' import { ChainAlgorandV1 } from './chains/algorand_1/ChainAlgorandV1' +import { ChainPolkadotV1 } from './chains/polkadot_1/ChainPolkadotV1' import { Chain } from './interfaces' import { ChainType, ChainEndpoint } from './models' import { throwNewError } from './errors' @@ -16,6 +17,8 @@ export class ChainFactory { return new ChainEthereumV1(endpoints, settings) case ChainType.AlgorandV1: return new ChainAlgorandV1(endpoints, settings) + case ChainType.PolkadotV1: + return new ChainPolkadotV1(endpoints, settings) default: throwNewError(`Chain type ${chainType} is not supported`) } diff --git a/src/chains/algorand_1/algoAction.ts b/src/chains/algorand_1/algoAction.ts index f3ed2877..80437160 100644 --- a/src/chains/algorand_1/algoAction.ts +++ b/src/chains/algorand_1/algoAction.ts @@ -2,13 +2,18 @@ import * as algosdk from 'algosdk' import { isNullOrEmpty, toBuffer, bufferToString, isAUint8Array, isAString, isAUint8ArrayArray } from '../../helpers' import { throwNewError } from '../../errors' import { + AlgorandTransactionOptions, AlgorandTxAction, AlgorandTxActionRaw, AlgorandTxActionSdkEncoded, AlgorandTxActionSdkEncodedFields, } from './models/transactionModels' import { AlgorandTxHeaderParams, AlgorandChainTransactionParamsStruct } from './models' -import { ALGORAND_TRX_COMFIRMATION_ROUNDS, ALGORAND_EMPTY_CONTRACT_NAME } from './algoConstants' +import { + ALGORAND_CHAIN_BLOCK_FREQUENCY, + ALGORAND_DEFAULT_TRANSACTION_VALID_BLOCKS, + ALGORAND_EMPTY_CONTRACT_NAME, +} from './algoConstants' import { toAlgorandAddressFromRaw, toRawAddressFromAlgoAddr } from './helpers' /** Helper class to ensure transaction actions properties are set correctly @@ -212,12 +217,20 @@ export class AlgorandActionHelper { /** Adds the latest transaction header fields (firstRound, etc.) from chain * Applies any that are not already provided in the action */ - applyCurrentTxHeaderParamsWhereNeeded(chainTxParams: AlgorandChainTransactionParamsStruct) { + applyCurrentTxHeaderParamsWhereNeeded( + chainTxParams: AlgorandChainTransactionParamsStruct, + transactionOptions?: AlgorandTransactionOptions, + ) { const rawAction = this.raw + // calculate last block + const numberOfBlockValidFor = transactionOptions?.expireSeconds + ? transactionOptions?.expireSeconds * ALGORAND_CHAIN_BLOCK_FREQUENCY + : ALGORAND_DEFAULT_TRANSACTION_VALID_BLOCKS + const lastValidBlock = rawAction.firstRound + numberOfBlockValidFor rawAction.genesisID = rawAction.genesisID || chainTxParams.genesisID rawAction.genesisHash = rawAction.genesisHash || toBuffer(chainTxParams.genesisHash, 'base64') rawAction.firstRound = rawAction.firstRound || chainTxParams.firstRound - rawAction.lastRound = rawAction.lastRound || rawAction.firstRound + ALGORAND_TRX_COMFIRMATION_ROUNDS + rawAction.lastRound = rawAction.lastRound || lastValidBlock rawAction.fee = rawAction.fee || chainTxParams.minFee rawAction.flatFee = true // since we're setting a fee, this will always be true - flatFee is just a hint to the AlgoSDK.Tx object which will set its own fee if this is not true } diff --git a/src/chains/algorand_1/algoConstants.ts b/src/chains/algorand_1/algoConstants.ts index c6e68ac7..3d07f104 100644 --- a/src/chains/algorand_1/algoConstants.ts +++ b/src/chains/algorand_1/algoConstants.ts @@ -9,7 +9,7 @@ export const PUBLIC_KEY_LENGTH = nacl.sign.publicKeyLength export const ALGORAND_EMPTY_CONTRACT_NAME = 'none' export const ALGORAND_POST_CONTENT_TYPE = { 'content-type': 'application/x-binary' } /** Number of rounds to wait after the first round for the transaction confirmation */ -export const ALGORAND_TRX_COMFIRMATION_ROUNDS = 1000 +export const ALGORAND_DEFAULT_TRANSACTION_VALID_BLOCKS = 1000 export const DEFAULT_TIMEOUT_FOR_TRX_CONFIRM = 500 export const DEFAULT_ALGO_UNIT = AlgorandUnit.Microalgo @@ -17,6 +17,8 @@ export const DEFAULT_ALGO_UNIT = AlgorandUnit.Microalgo export const DEFAULT_BLOCKS_TO_CHECK = 20 /** time to wait (in ms) between checking chain for a new block (to see if transaction appears within it) */ export const DEFAULT_CHECK_INTERVAL = 500 +/** time in seconds between blocks */ +export const ALGORAND_CHAIN_BLOCK_FREQUENCY = 2 /** number of times to attempt to read a chain endpoint before failing the read */ export const DEFAULT_GET_BLOCK_ATTEMPTS = 10 diff --git a/src/chains/algorand_1/algoTransaction.ts b/src/chains/algorand_1/algoTransaction.ts index e2775992..53a0cdcb 100644 --- a/src/chains/algorand_1/algoTransaction.ts +++ b/src/chains/algorand_1/algoTransaction.ts @@ -8,6 +8,7 @@ import { throwNewError } from '../../errors' import { byteArrayToHexString, hexStringToByteArray, + isANumber, isArrayLengthOne, isNullOrEmpty, notImplemented, @@ -79,7 +80,7 @@ export class AlgorandTransaction implements Transaction { constructor(chainState: AlgorandChainState, options?: AlgorandTransactionOptions) { this._chainState = chainState this.assertValidOptions(options) - this._options = options || {} + this.applyOptions(options) } /** Chain-specific values included in the transaction sent to the chain */ @@ -188,7 +189,7 @@ export class AlgorandTransaction implements Transaction { this._algoSdkTransaction = null } else { const chainTxHeaderParams = this._chainState.chainInfo.nativeInfo.transactionHeaderParams - this._actionHelper.applyCurrentTxHeaderParamsWhereNeeded(chainTxHeaderParams) + this._actionHelper.applyCurrentTxHeaderParamsWhereNeeded(chainTxHeaderParams, this.options) this._algoSdkTransaction = new AlgoTransactionClass(this._actionHelper.actionEncodedForSdk) } } @@ -579,6 +580,9 @@ export class AlgorandTransaction implements Transaction { /** Throws if from is not null or empty algorand argument */ private assertValidOptions(options: AlgorandTransactionOptions): void { + if (options?.expireSeconds && !isANumber(options.expireSeconds)) { + throwNewError('Invalid transaction options: ExpireSeconds is not a number') + } if (options?.multiSigOptions && options?.signerPublicKey) { throwNewError( 'Invalid transaction options: Provide multiSigOptions OR signerPublicKey - not both. The signerPublicKey is for non-multisig transasctions only', @@ -586,6 +590,16 @@ export class AlgorandTransaction implements Transaction { } } + /** apply options and/or use defaults */ + private applyOptions(options: AlgorandTransactionOptions) { + const { multiSigOptions, signerPublicKey } = options || {} + let { expireSeconds, fee, flatFee } = options || {} + expireSeconds = expireSeconds ?? this._chainState?.chainSettings?.defaultTransactionSettings?.expireSeconds + fee = fee ?? this._chainState?.chainSettings?.defaultTransactionSettings?.fee + flatFee = flatFee ?? this._chainState?.chainSettings?.defaultTransactionSettings?.flatFee + this._options = { expireSeconds, fee, flatFee, multiSigOptions, signerPublicKey } + } + /** Whether the transaction signature is valid for this transaction body and publicKey provided */ private isValidTxSignatureForPublicKey(signature: AlgorandSignature, publicKey: AlgorandPublicKey): boolean { if (!this.rawTransaction) return false diff --git a/src/chains/algorand_1/models/transactionModels.ts b/src/chains/algorand_1/models/transactionModels.ts index 3b6549e6..79c012a2 100644 --- a/src/chains/algorand_1/models/transactionModels.ts +++ b/src/chains/algorand_1/models/transactionModels.ts @@ -56,6 +56,8 @@ export type AlgorandSuggestedParams = { /** Transaction 'header' options set to chain along with the content type */ export type AlgorandTransactionOptions = { + /** Number of seconds after which transaction expires - must be submitted to the chain before then */ + expireSeconds?: number fee?: AlgorandValue flatFee?: boolean multiSigOptions?: AlgorandMultiSigOptions diff --git a/src/chains/ethereum_1/models/cryptoModels.ts b/src/chains/ethereum_1/models/cryptoModels.ts index 02cb38fb..b2c4e4f5 100644 --- a/src/chains/ethereum_1/models/cryptoModels.ts +++ b/src/chains/ethereum_1/models/cryptoModels.ts @@ -16,7 +16,7 @@ export type EthereumPublicKey = string & PublicKeyBrand /** a signature string - formatted correcly for ethereum */ export type EthereumSignature = ECDSASignature & SignatureBrand -/** key pair - in the format returned from algosdk */ +/** key pair - in the format returned from ethereum */ export type EthereumKeyPair = { publicKey: EthereumPublicKey privateKey: EthereumPrivateKey diff --git a/src/chains/polkadot_1/ChainPolkadotV1.ts b/src/chains/polkadot_1/ChainPolkadotV1.ts new file mode 100644 index 00000000..611b77a7 --- /dev/null +++ b/src/chains/polkadot_1/ChainPolkadotV1.ts @@ -0,0 +1,230 @@ +import { ChainActionType, ChainInfo, ChainType, CryptoCurve, ChainEntityName, ChainDate } from '../../models' +import { ChainError } from '../../errors' +import { Chain } from '../../interfaces' +import { + PolkadotAddress, + PolkadotChainEndpoint, + PolkadotChainInfo, + PolkadotChainSettings, + PolkadotDecomposeReturn, + PolkadotPublicKey, + PolkadotSymbol, +} from './models' +import { PolkadotChainState } from './polkadotChainState' +import { notImplemented } from '../../helpers' +import { PolkadotChainActionType } from './models/chainActionType' +import { PolkadotTransactionAction } from './models/transactionModels' +import { PolkadotAccount } from './polkadotAccount' +import { PolkadotTransaction } from './polkadotTransaction' + +import * as polkadotCrypto from './polkadotCrypto' +import * as ethcrypto from '../ethereum_1/ethCrypto' +import { Asymmetric } from '../../crypto' +import { + isValidEthereumPrivateKey, + isValidEthereumPublicKey, + isValidEthereumDateString, + toEthereumEntityName, + toEthereumDate, + toEthereumPublicKey, + toEthereumPrivateKey, + toEthereumSignature, +} from '../ethereum_1/helpers' +import { PolkadotCreateAccount } from './polkadotCreateAccount' +import { PolkadotCreateAccountOptions } from './models/accountModels' +import { composeAction } from './polkadotCompose' +import { decomposeAction } from './polkadotDecompose' + +class ChainPolkadotV1 implements Chain { + private _endpoints: PolkadotChainEndpoint[] + + private _settings: PolkadotChainSettings + + private _chainState: PolkadotChainState + + constructor(endpoints: PolkadotChainEndpoint[], settings?: PolkadotChainSettings) { + this._endpoints = endpoints + this._settings = settings + this._chainState = new PolkadotChainState(endpoints, settings) + } + + public get isConnected(): boolean { + return this._chainState?.isConnected + } + + /** Connect to chain endpoint to verify that it is operational and to get latest block info */ + public connect(): Promise { + return this._chainState.connect() + } + + /** Returns chain type enum - resolves to chain family as a string e.g. 'polkadot' */ + public get chainType(): ChainType { + return ChainType.PolkadotV1 + } + + public get chainId(): string { + return this._chainState.chain + } + + public get chainInfo(): PolkadotChainInfo { + return this._chainState.chainInfo + } + + public composeAction = async ( + actionType: ChainActionType | PolkadotChainActionType, + args: any, + ): Promise => { + return composeAction(this._chainState, actionType, args) + } + + public decomposeAction = async (action: PolkadotTransactionAction): Promise => { + return decomposeAction(action) + } + + public get description(): string { + return 'Polkadot Chain' + } + + public get nativeToken(): { defaultUnit: string; symbol: PolkadotSymbol; tokenAddress: any } { + return null + } + + public async fetchBalance( + account: PolkadotAddress, + symbol: PolkadotSymbol, + tokenAddress?: PolkadotAddress, + ): Promise<{ balance: string }> { + return this._chainState.fetchBalance(account, symbol, tokenAddress) + } + + public fetchContractData = ( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + contract: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + table: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + owner: string, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + indexNumber: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + lowerRow: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + upperRow: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + limit: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + reverseOrder: boolean, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + showPayer: boolean, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + keyType: string, + ): Promise => { + return null + } + + private newAccount = async (address?: PolkadotAddress): Promise => { + this.assertIsConnected() + const account = new PolkadotAccount(this._chainState) + if (address) { + await account.load(address) + } + return account + } + + private newCreateAccount = (options?: PolkadotCreateAccountOptions): any => { + this.assertIsConnected() + return new PolkadotCreateAccount(this._chainState, options) + } + + private newTransaction = (options?: any): PolkadotTransaction => { + this.assertIsConnected() + return new PolkadotTransaction(this._chainState, options) + } + + public new = { + Account: this.newAccount, + CreateAccount: this.newCreateAccount, + Transaction: this.newTransaction, + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public isValidEntityName = (value: string): boolean => { + notImplemented() + return false + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public isValidDate = (value: string): boolean => { + notImplemented() + return false + } + + public toEntityName = (value: string): ChainEntityName => { + return toEthereumEntityName(value) as ChainEntityName + } + + public toDate = (value: string | Date): ChainDate => { + return toEthereumDate(value) as ChainDate + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public setPublicKey = (publicKey: PolkadotPublicKey) => { + notImplemented() + return '' + } + + public mapChainError = (error: Error): ChainError => { + notImplemented() + return new ChainError(null, null, null, error) + } + + public assertIsConnected(): void { + if (!this._chainState?.isConnected) { + throw new Error('Not connected to chain') + } + } + + cryptoCurve: CryptoCurve.Ed25519 + + decryptWithPassword = polkadotCrypto.decryptWithPassword + + encryptWithPassword = polkadotCrypto.encryptWithPassword + + decryptWithPrivateKey = polkadotCrypto.decryptWithPrivateKey + + encryptWithPublicKey = polkadotCrypto.encryptWithPublicKey + + generateKeyPair = polkadotCrypto.generateKeyPair + + decryptWithPrivateKeys = ethcrypto.decryptWithPrivateKeys + + encryptWithPublicKeys = ethcrypto.encryptWithPublicKeys + + getPublicKeyFromSignature = ethcrypto.getEthereumPublicKeyFromSignature + + isSymEncryptedDataString = ethcrypto.isSymEncryptedDataString + + isAsymEncryptedDataString = Asymmetric.isAsymEncryptedDataString + + toAsymEncryptedDataString = Asymmetric.toAsymEncryptedDataString + + toSymEncryptedDataString = ethcrypto.toSymEncryptedDataString + + toPublicKey = toEthereumPublicKey + + toPrivateKey = toEthereumPrivateKey + + toSignature = toEthereumSignature + + sign = ethcrypto.sign + + verifySignedWithPublicKey = ethcrypto.verifySignedWithPublicKey + + isValidPrivateKey = isValidEthereumPrivateKey + + isValidPublicKey = isValidEthereumPublicKey + + isValidEthereumDate = isValidEthereumDateString +} + +export { ChainPolkadotV1 } diff --git a/src/chains/polkadot_1/examples/account.ts b/src/chains/polkadot_1/examples/account.ts new file mode 100644 index 00000000..ebb0bf94 --- /dev/null +++ b/src/chains/polkadot_1/examples/account.ts @@ -0,0 +1,51 @@ +import { Chain } from '../../../interfaces' +import { ChainFactory, ChainType } from '../../../index' +import { PolkadotChainEndpoint } from '../models' + +require('dotenv').config() + +const westendEndpoints: PolkadotChainEndpoint[] = [ + { + url: 'wss://westend-rpc.polkadot.io', + }, +] + +const createAccountOptions = { + // ... +} + +async function createAccount(paraChain: Chain) { + try { + await paraChain.connect() + const createdAccount = paraChain.new.CreateAccount(createAccountOptions) + await createdAccount.generateKeysIfNeeded() + console.log('generatedKeys:', createdAccount.generatedKeys) + console.log('address:', createdAccount.accountName) + const account = await paraChain.new.Account('5FkJuxShVBRJRirM3t3k5Y8XyDaxMi1c8hLdBsH2qeAYqAzF') + console.log('account', account) + } catch (error) { + console.log(error) + } +} + +async function run() { + try { + const paraChainA = new ChainFactory().create(ChainType.PolkadotV1, westendEndpoints) + const paraChainB = new ChainFactory().create(ChainType.PolkadotV1, westendEndpoints) + const accountA = createAccount(paraChainA) + console.log('account', accountA) + const accountB = createAccount(paraChainB) + console.log('account', accountB) + } catch (error) { + console.log(error) + } +} + +;(async () => { + try { + await run() + } catch (error) { + console.log('Error:', error) + } + process.exit() +})() diff --git a/src/chains/polkadot_1/examples/crypto.ts b/src/chains/polkadot_1/examples/crypto.ts new file mode 100644 index 00000000..e40edacc --- /dev/null +++ b/src/chains/polkadot_1/examples/crypto.ts @@ -0,0 +1,60 @@ +/* eslint-disable prettier/prettier */ +/* eslint-disable max-len */ +/* eslint-disable import/no-unresolved */ +/* eslint-disable @typescript-eslint/camelcase */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable no-console */ +import { ChainFactory, ChainType } from '../../../index' +import { ChainPolkadotV1 } from '../ChainPolkadotV1' +import { toPolkadotPrivateKey, toPolkadotPublicKey } from '../helpers' + +require('dotenv').config() + +const { env } = process + +const algoApiKey = env.AGLORAND_API_KEY || 'missing api key' +const polkadotMainnetEndpoints = [{ + url: 'wss://rpc.polkadot.io', +}] + +async function run() { + /** Create Algorand chain instance */ + const para = (new ChainFactory().create(ChainType.PolkadotV1, polkadotMainnetEndpoints) as ChainPolkadotV1) + await para.connect() + if (para.isConnected) { + console.log('Connected to %o', para.chainId) + } + + console.log('keyPair:', await para.generateKeyPair()) + + const toEncrypt = 'text to encrypt' + const encryptedBlob = await para.encryptWithPassword(toEncrypt, 'mypassword', { + salt: 'mysalt', + }) + console.log('encrypted blob:', encryptedBlob) + + const decryptedPayload = await para.decryptWithPassword(encryptedBlob, 'mypassword', { + salt: 'mysalt', + }) + console.log('decrypted payload:', decryptedPayload) + + const publicKey1 = toPolkadotPublicKey( + '0x2e438c99bd7ded27ed921919e1d5ee1d9b1528bb8a2f6c974362ad1a9ba7a6f59a452a0e4dfbc178ab5c5c090506bd7f0a6659fd3cf0cc769d6c17216d414163', + ) + + const privateKey1 = toPolkadotPrivateKey('0x7b0c4bdbc24fd7b6045e9001dbe93f1e46478dedcfcefbc42180ac79fd08ce28') + + const encrypted2 = await para.encryptWithPublicKey('text to encrypt 2', publicKey1) + console.log('encrypted text 2:', encrypted2) + const decrypted2 = await para.decryptWithPrivateKey(encrypted2, privateKey1) + console.log('decrypted text 2:', decrypted2) +} + +;(async () => { + try { + await run() + } catch (error) { + console.log('Error:', error) + } + process.exit() +})() diff --git a/src/chains/polkadot_1/examples/misc.ts b/src/chains/polkadot_1/examples/misc.ts new file mode 100644 index 00000000..0aa6fe2d --- /dev/null +++ b/src/chains/polkadot_1/examples/misc.ts @@ -0,0 +1,35 @@ +/* eslint-disable prettier/prettier */ +/* eslint-disable max-len */ +/* eslint-disable import/no-unresolved */ +/* eslint-disable @typescript-eslint/camelcase */ +/* eslint-disable @typescript-eslint/no-unused-vars */ + +import { ChainFactory, ChainType } from '../../../index' +import { PolkadotChainEndpoint } from '../models' + +/* eslint-disable no-console */ +require('dotenv').config() + +export const { env } = process + +export const serverEndpoints: PolkadotChainEndpoint[] = [ + { + // url: 'https://rpc.polkadot.io', + // options: { + // headers: { + // 'Content-Type': 'application/json', + // } + // } + url: 'wss://rpc.polkadot.io' + }, +]; + +(async () => { + try { + const polka = new ChainFactory().create(ChainType.PolkadotV1, serverEndpoints) + await polka.connect() + // console.debug(polka.chainInfo) + } catch (error) { + console.log(error) + } +})() diff --git a/src/chains/polkadot_1/examples/parachain.ts b/src/chains/polkadot_1/examples/parachain.ts new file mode 100644 index 00000000..75f2bb05 --- /dev/null +++ b/src/chains/polkadot_1/examples/parachain.ts @@ -0,0 +1,49 @@ +import { ChainFactory, ChainType } from '../../../index' +import { PolkadotChainEndpoint, PolkadotChainSettings } from '../models' + +require('dotenv').config() + +// const { env } = process + +/** + * Rococo relay-chain in which parachain features are availalbe. One of Polkadot testnet + */ +const rococoEndpoints: PolkadotChainEndpoint = { + url: 'wss://rococo-rpc.polkadot.io', +} + +/** + * One of parachains connected to Rococo network + */ +const tickEndpoints: PolkadotChainEndpoint = { + url: 'wss://tick-rpc.polkadot.io', +} + +const tickChainSettings: PolkadotChainSettings = { + relayEndpoint: rococoEndpoints, + otherParachains: [], +} + +const createAccountOptions = {} +;(async () => { + try { + const chain = new ChainFactory().create(ChainType.PolkadotV1, [tickEndpoints], tickChainSettings) + await chain.connect() + console.debug(chain.chainInfo) + + const createAccount = chain.new.CreateAccount(createAccountOptions) + await createAccount.generateKeysIfNeeded() + // console.log('generatedKeys:', createAccount.generatedKeys) + // console.log('address:', createAccount.accountName) + // const account = await chain.new.Account('5FkJuxShVBRJRirM3t3k5Y8XyDaxMi1c8hLdBsH2qeAYqAzF') + // console.log('account', account) + + // const { password, salt } = createAccountOptions.newKeysOptions + // const decryptedPrivateKey = ropsten.decryptWithPassword(createAccount.generatedKeys.privateKey, password, { salt }) + // console.log('decrypted privateKey: ', decryptedPrivateKey) + + process.exit() + } catch (error) { + console.log(error) + } +})() diff --git a/src/chains/polkadot_1/examples/transaction.ts b/src/chains/polkadot_1/examples/transaction.ts new file mode 100644 index 00000000..cb7c9296 --- /dev/null +++ b/src/chains/polkadot_1/examples/transaction.ts @@ -0,0 +1,190 @@ +/* eslint-disable max-len */ +/* eslint-disable import/no-unresolved */ +/* eslint-disable @typescript-eslint/camelcase */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable no-console */ +import { ChainFactory, ChainType } from '../../../index' +import { PolkadotChainEndpoint, PolkadotChainSettings } from '../models' + +require('dotenv').config() + +const { env } = process +;(async () => { + try { + // const westendEndpoints: PolkadotChainEndpoint[] = [ + // { + // url: 'wss://westend-rpc.polkadot.io', + // }, + // ] + + // const westendChainOptions: PolkadotChainSettings = {} + + // const ropsten = new ChainFactory().create(ChainType.PolkadotV1, westendEndpoints, westendChainOptions) + // await ropsten.connect() + + // const ropstenEndpoints: EthereumChainEndpoint[] = [ + // { + // url: 'https://ropsten.infura.io/v3/fc379c787fde4363b91a61a345e3620a', + // // Web3 HttpProvider options - https://github.com/ethereum/web3.js/tree/1.x/packages/web3-providers-http#usage + // // options: { + // // timeout: 20000, + // // headers: [{ header_name: 'header-value' }], + // // }, + // }, + // ] + + // const ropstenChainOptions: EthereumChainSettings = { + // chainForkType: { + // chainName: 'ropsten', + // hardFork: 'istanbul', + // }, + // defaultTransactionSettings: { + // maxFeeIncreasePercentage: 20, + // executionPriority: TxExecutionPriority.Fast, + // }, + // } + + // EthereumRawTransaction type input for setFromRaw() + // Defaults all optional properties, so you can set from raw just with to & value OR data + const sampleSetFromRawTrx = { + to: '5CrMhng7eMrJqSQrnw4s7By1hspxxKiAjGifTmLtieAzc3U3', + value: '0x01', + data: '0x00', + } + + // const composeValueTransferParams: ValueTransferParams = { + // toAccountName: toChainEntityName('0x27105356F6C1ede0e92020e6225E46DC1F496b81'), + // amount: '0.000000000000000001', + // symbol: toEthereumSymbol(EthUnit.Ether), + // } + + // const composeEthTransferParams: EthTransferParams = { + // to: toChainEntityName('0x27105356F6C1ede0e92020e6225E46DC1F496b81'), + // value: '0.000000000000000001', + // } + + // const composeTokenTransferParams: TokenTransferParams = { + // contractName: toChainEntityName('0x04825941Ad80A6a869e85606b29c9D25144E91e6'), + // toAccountName: toChainEntityName('0x27105356F6C1ede0e92020e6225E46DC1F496b81'), + // symbol: toEthereumSymbol(EthUnit.Wei), + // // precision: 18, // precision should be provided if possible + // amount: '20.000000000000000000', // if precision isn't specified, token precision is infered from the number of digits after the decimal place + // } + + // const composeERC20TransferParams: Erc20TransferParams = { + // contractAddress: '0x27105356f6c1ede0e92020e6225e46dc1f496b81', + // from: '0x27105356F6C1ede0e92020e6225E46DC1F496b81', + // to: '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', + // precision: 18, // precision should be provided if possible + // value: '20', + // } + + // const composeERC20IssueParams: Erc20IssueParams = { + // contractAddress: '0x27105356f6c1ede0e92020e6225e46dc1f496b81', + // precision: 18, // precision should be provided if possible + // value: '20', + // } + + // const composeERC721TransferFromParams: Erc721TransferFromParams = { + // contractAddress: '0x27105356f6c1ede0e92020e6225e46dc1f496b81', + // transferFrom: '0x27105356F6C1ede0e92020e6225E46DC1F496b81', + // to: '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', + // tokenId: 1, + // } + + // const defaultEthTxOptions: EthereumTransactionOptions = { + // chain: 'ropsten', + // hardfork: 'istanbul', + // } + + // // ---> Sign and send ethereum transfer with compose Action - using generic (cross-chain) native chain transfer action + // const transaction = ropsten.new.Transaction(defaultEthTxOptions) + // transaction.actions = [await ropsten.composeAction(ChainActionType.ValueTransfer, composeValueTransferParams)] + // console.log('transaction.actions[0]:', JSON.stringify(transaction.actions[0])) + // const decomposed = await ropsten.decomposeAction(transaction.actions[0]) + // console.log(JSON.stringify(decomposed)) + // const fee = await transaction.getSuggestedFee(TxExecutionPriority.Fast) + // await transaction.setDesiredFee(fee) + // await transaction.prepareToBeSigned() + // await transaction.validate() + // await transaction.sign([toEthereumPrivateKey(env.ROPSTEN_erc20acc_PRIVATE_KEY)]) + // console.log('raw transaction: ', transaction.raw) + // console.log('missing signatures: ', transaction.missingSignatures) + // console.log('transaction ID: ', transaction.transactionId) + // console.log('send response:', JSON.stringify(await transaction.send(ConfirmType.None))) + // console.log('send response:', JSON.stringify(await transaction.send(ConfirmType.After001))) // wait for transaction to complete on-chain before returning + // console.log(`actual cost of tx in ETH - available once tx is processed: ${await transaction.getActualCost()}`) // getActualCost will throw if called before tx is commited to chain + + // ---> Sign and send default transfer Transaction - using generic (cross-chain) token transfer action + // const transaction = ropsten.new.Transaction(defaultEthTxOptions) + // transaction.actions = [await ropsten.composeAction(ChainActionType.TokenTransfer, composeTokenTransferParams)] + // console.log(transaction.actions[0]) + // const decomposed = await ropsten.decomposeAction(transaction.actions[0]) + // console.log(decomposed) + // console.log( + // 'token value converted back using precision:', + // fromTokenValueString(decomposed[0]?.args?.amount, 10, composeTokenTransferParams?.precision), + // ) + // await transaction.prepareToBeSigned() + // await transaction.validate() + // await transaction.sign([toEthereumPrivateKey(env.ROPSTEN_erc20acc_PRIVATE_KEY)]) + // console.log('missing signatures: ', transaction.missingSignatures) + // console.log('send response:', JSON.stringify(await transaction.send())) + + // ---> Sign and send erc20 transfer Transaction + // const transaction = ropsten.new.Transaction(ropstenChainOptions) + // transaction.actions = [ await ropsten.composeAction(EthereumChainActionType.ERC20Transfer, composeERC20TransferParams) ] + // console.log(transaction.actions[0]) + // const decomposed = await ropsten.decomposeAction(transaction.actions[0]) + // console.log(decomposed) + // console.log( + // 'token value converted back using precision:', + // fromTokenValueString(decomposed[0]?.args?.amount, 10, composeERC20TransferParams?.precision), + // ) + // // const fee = await transaction.getSuggestedFee(TxExecutionPriority.Fast) + // // await transaction.setDesiredFee(fee) + // await transaction.prepareToBeSigned() + // await transaction.validate() + // await transaction.sign([toEthereumPrivateKey(env.ROPSTEN_erc20acc_PRIVATE_KEY)]) + // console.log('missing signatures: ', transaction.missingSignatures) + // console.log('send response:', JSON.stringify(await transaction.send())) + + // ---> Sign and send erc20 issue Transaction + // const transaction = ropsten.new.Transaction(defaultEthTxOptions) + // transaction.actions = [await ropsten.composeAction(EthereumChainActionType.ERC20Issue, composeERC20IssueParams)] + // console.log(transaction.actions[0]) + // const decomposed = await ropsten.decomposeAction(transaction.actions[0]) + // console.log(decomposed) + // await transaction.prepareToBeSigned() + // await transaction.validate() + // await transaction.sign([toEthereumPrivateKey(env.ROPSTEN_erc20acc_PRIVATE_KEY)]) + // console.log('missing signatures: ', transaction.missingSignatures) + // console.log('send response:', JSON.stringify(await transaction.send())) + + // // ---> Sign and send ethereum transfer with setFromRaw() + // const transaction = ropsten.new.Transaction(defaultEthTxOptions) + // await transaction.setFromRaw(sampleSetFromRawTrx) + // await transaction.prepareToBeSigned() + // const decomposed = await ropsten.decomposeAction(transaction.actions[0]) + // console.log('decomposd action:', decomposed) + // await transaction.validate() + // await transaction.sign([toEthereumPrivateKey(env.ROPSTEN_erc20acc_PRIVATE_KEY)]) + // console.log('raw transaction: ', JSON.stringify(transaction.raw)) + // console.log('missing signatures: ', transaction.missingSignatures) + // console.log('send response:', JSON.stringify(await transaction.send())) + + // ---> Compose & Decompose erc721 transferFrom Transaction + // const transaction = ropsten.new.Transaction(defaultEthTxOptions) + // transaction.actions = [ + // await ropsten.composeAction(EthereumChainActionType.ERC721TransferFrom, composeERC721TransferFromParams), + // ] + // console.log(transaction.actions[0]) + // const decomposed = await ropsten.decomposeAction(transaction.actions[0]) + // console.log(decomposed) + // await transaction.prepareToBeSigned() + // await transaction.validate() + } catch (error) { + console.log(error) + } + process.exit() +})() diff --git a/src/chains/polkadot_1/helpers/cryptoModelHelpers.ts b/src/chains/polkadot_1/helpers/cryptoModelHelpers.ts new file mode 100644 index 00000000..9d6d1c35 --- /dev/null +++ b/src/chains/polkadot_1/helpers/cryptoModelHelpers.ts @@ -0,0 +1,161 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { decodeAddress, encodeAddress } from '@polkadot/keyring' +import { signatureVerify } from '@polkadot/util-crypto' +import { + ensureHexPrefix, + isNullOrEmpty, + isABuffer, + isAString, + isHexString, + hexStringToByteArray, + byteArrayToHexString, + notSupported, +} from '../../../helpers' +import { + PolkadotPublicKey, + PolkadotPrivateKey, + PolkadotSignature, + PolkadotAddress, + PolkadotKeyPairType, +} from '../models' +import { CryptoCurve } from '../../../models' +import { AesCrypto, Ed25519Crypto } from '../../../crypto' + +/** determine crypto curve from key type */ +export function getCurveFromKeyType(keyPairType: PolkadotKeyPairType): CryptoCurve { + switch (keyPairType) { + case PolkadotKeyPairType.Ecdsa: + return CryptoCurve.Secp256k1 + case PolkadotKeyPairType.Ethereum: + return CryptoCurve.Secp256k1 + case PolkadotKeyPairType.Ed25519: + return CryptoCurve.Ed25519 + case PolkadotKeyPairType.Sr25519: + return CryptoCurve.Sr25519 + default: + notSupported(`Keytype ${keyPairType}`) + return null + } +} + +// todo eth - this should not have copied code - is the bug worked-around? if not, we should consider using a diff library +// Reimplemented from ethereumjs-util module to workaround a current bug +/** Checks if a valid signature with ECDSASignature */ +export function isValidSignature(value: string | PolkadotSignature, keyPairType?: PolkadotKeyPairType): boolean { + // TODO + return true +} + +export function isValidPolkadotPublicKey( + value: string | PolkadotPublicKey, + keyPairType?: PolkadotKeyPairType, +): value is PolkadotPublicKey { + // TODO + return true + // if (!value) return false + // return isValidPublic(toEthBuffer(ensureHexPrefix(value))) +} + +export function isValidPolkadotPrivateKey( + value: PolkadotPrivateKey | string, + keyPairType?: PolkadotKeyPairType, +): value is PolkadotPrivateKey { + // TODO, if curve param provided, check is valid for that type of curve + // if no curve param, check each curve until valid match - or return false + // check is valid for any of the supported curves + return true + // if (!value) return false + // return isValidPrivate(toEthBuffer(ensureHexPrefix(value))) +} + +export function isValidPolkadotSignature(value: PolkadotSignature | string): value is PolkadotSignature { + // TODO + return true +} + +/** Whether value is well-formatted address */ +export function isValidPolkadotAddress(value: string | Buffer | PolkadotAddress): boolean { + // TODO: confirm this works with different curves + if (!value) return false + try { + encodeAddress(isHexString(value) ? hexStringToByteArray(value as string) : decodeAddress(value)) + } catch (error) { + return false + } + return true +} + +/** Accepts hex string checks if a valid polkadot public key + * Returns PolkadotPublicKey with prefix + */ +export function toPolkadotPublicKey(value: string): PolkadotPublicKey { + if (isValidPolkadotPublicKey(value)) { + return value as PolkadotPublicKey + } + throw new Error(`Not a valid polkadot public key:${value}.`) +} + +/** Accepts hex string checks if a valid polkadot private key + * Returns PolkadotPrivateKey with prefix + */ +export function toPolkadotPrivateKey(value: string): PolkadotPrivateKey { + if (isValidPolkadotPrivateKey(value)) { + return value as PolkadotPrivateKey + } + throw new Error(`Not a valid polkadot private key:${value}.`) +} + +/** Verifies that the value is a valid, stringified JSON Encrypted object */ +export function isSymEncryptedDataString( + value: string, + keyPairType?: PolkadotKeyPairType, +): value is AesCrypto.AesEncryptedDataString | Ed25519Crypto.Ed25519EncryptedDataString { + const curve = getCurveFromKeyType(keyPairType) + if (curve === CryptoCurve.Secp256k1) return AesCrypto.isAesEncryptedDataString(value) + if (curve === CryptoCurve.Ed25519) return Ed25519Crypto.isEd25519EncryptedDataString(value) + // if no curve param, check all possible options + return AesCrypto.isAesEncryptedDataString(value) || Ed25519Crypto.isEd25519EncryptedDataString(value) +} + +/** Ensures that the value comforms to a well-formed, stringified JSON Encrypted Object */ +export function toSymEncryptedDataString( + value: any, + keyPairType?: PolkadotKeyPairType, +): AesCrypto.AesEncryptedDataString | Ed25519Crypto.Ed25519EncryptedDataString { + const curve = getCurveFromKeyType(keyPairType) + if (curve === CryptoCurve.Secp256k1) return AesCrypto.toAesEncryptedDataString(value) + if (curve === CryptoCurve.Ed25519) return Ed25519Crypto.toEd25519EncryptedDataString(value) + throw new Error(`Curve not supported ${curve}`) +} + +/** + * Returns PolkadotSignature + */ +export function toPolkadotSignature(value: string | PolkadotSignature): PolkadotSignature { + if (isValidPolkadotSignature(value)) { + return value + } + throw new Error(`Not a valid polkadot signature:${JSON.stringify(value)}.`) +} + +/** Accepts hex string checks if a valid address + * Returns PolkadotAddress with prefix + */ +export function toPolkadotAddress(value: string): PolkadotAddress { + if (isValidPolkadotAddress(value)) { + return value + } + throw new Error(`Not a valid polkadot address:${value}.`) +} + +/** verifies a signature is valid for a message body and address */ +export function verifySignatureWithAddress( + signedMessage: string, + signature: string, + address: PolkadotPublicKey, +): boolean { + const publicKey = decodeAddress(address) + const hexPublicKey = byteArrayToHexString(publicKey) + + return signatureVerify(signedMessage, signature, hexPublicKey).isValid +} diff --git a/src/chains/polkadot_1/helpers/generalModelHelpers.ts b/src/chains/polkadot_1/helpers/generalModelHelpers.ts new file mode 100644 index 00000000..45d2fe60 --- /dev/null +++ b/src/chains/polkadot_1/helpers/generalModelHelpers.ts @@ -0,0 +1,19 @@ +import { isNullOrEmpty } from '../../../helpers' +import { PolkadotAddress } from '../models' +import { isValidPolkadotAddress } from './cryptoModelHelpers' + +export function toPolkadotEntityName(name: string): PolkadotAddress { + if (isValidPolkadotAddress(name)) { + return name + } + + if (isNullOrEmpty(name)) { + return null + } + + throw new Error( + `Not a valid Ethereum entity :${name}. Ethereum entity can valid address, public key, private key or transaction data.`, + ) +} + +// TODO - use other generalModelHelpers.ts as starting point for this file diff --git a/src/chains/polkadot_1/helpers/index.ts b/src/chains/polkadot_1/helpers/index.ts new file mode 100644 index 00000000..ffc135c9 --- /dev/null +++ b/src/chains/polkadot_1/helpers/index.ts @@ -0,0 +1,4 @@ +export * from './cryptoModelHelpers' +// export * from './generalHelpers' +// export * from './generalModelHelpers' +// export * from './transactionHelpers' diff --git a/src/chains/polkadot_1/index.ts b/src/chains/polkadot_1/index.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/chains/polkadot_1/models/accountModels.ts b/src/chains/polkadot_1/models/accountModels.ts new file mode 100644 index 00000000..8bd72fa5 --- /dev/null +++ b/src/chains/polkadot_1/models/accountModels.ts @@ -0,0 +1,12 @@ +import { PolkadotPublicKey, PolkadotNewKeysOptions } from '.' + +export type PolkadotCreateAccountOptions = { + publicKey?: PolkadotPublicKey + newKeysOptions?: PolkadotNewKeysOptions +} + +/** Type of account to create */ +export enum EthereumNewAccountType { + /** Native account for chain type (Ethereum, etc.) */ + Native = 'Native', +} diff --git a/src/chains/polkadot_1/models/chainActionType.ts b/src/chains/polkadot_1/models/chainActionType.ts new file mode 100644 index 00000000..a14e8d9c --- /dev/null +++ b/src/chains/polkadot_1/models/chainActionType.ts @@ -0,0 +1 @@ +export enum PolkadotChainActionType {} diff --git a/src/chains/polkadot_1/models/cryptoModels.ts b/src/chains/polkadot_1/models/cryptoModels.ts new file mode 100644 index 00000000..36054975 --- /dev/null +++ b/src/chains/polkadot_1/models/cryptoModels.ts @@ -0,0 +1,46 @@ +import { Ed25519Crypto } from '../../../crypto' +import { PublicKeyBrand, PrivateKeyBrand, SignatureBrand, ModelsCryptoAes, ChainEntityNameBrand } from '../../../models' + +export enum PolkadotKeyPairType { + Ed25519 = 'ed25519', + Sr25519 = 'sr25519', + Ecdsa = 'ecdsa', + Ethereum = 'ethereum', +} + +export type PolkadotNewKeysOptions = { + keyPairType?: PolkadotKeyPairType + phrase?: string + derivationPath?: string +} + +/** an address string - formatted correctly for polkadot */ +export type PolkadotAddress = string + +/** will be used as accountName */ +export type PolkadotEntityName = string & ChainEntityNameBrand + +/** a private key string - formatted correctly for polkadot */ +export type PolkadotPrivateKey = string & PrivateKeyBrand + +/** a public key string - formatted correctly for polkadot */ +export type PolkadotPublicKey = string & PublicKeyBrand + +/** a signature string - formatted correcly for polkadot */ +export type PolkadotSignature = string & SignatureBrand + +/** key pair - in the format returned from algosdk */ +export type PolkadotKeypair = { + type: PolkadotKeyPairType + publicKey: PolkadotPublicKey + privateKey: PolkadotPrivateKey + privateKeyEncrypted?: ModelsCryptoAes.AesEncryptedDataString | Ed25519Crypto.Ed25519EncryptedDataString +} + +/** options used to convert a password and salt into a passowrd key */ +export type PolkadotEncryptionOptions = + | ModelsCryptoAes.AesEncryptionOptions + | Ed25519Crypto.Ed25519PasswordEncryptionOptions + +/** Additional parameters for encryption/decryption */ +export type EncryptionOptions = PolkadotEncryptionOptions diff --git a/src/chains/polkadot_1/models/generalModels.ts b/src/chains/polkadot_1/models/generalModels.ts new file mode 100644 index 00000000..1b2b401c --- /dev/null +++ b/src/chains/polkadot_1/models/generalModels.ts @@ -0,0 +1,78 @@ +import BN from 'bn.js' +import { AnyJson, AnyNumber } from '@polkadot/types/types' +import { AccountData } from '@polkadot/types/interfaces/balances' +import { Compact } from '@polkadot/types' +import { KeyringPair } from '@polkadot/keyring/types' +import { BlockNumber, Balance, Weight, Hash } from '@polkadot/types/interfaces' +import { ChainSymbolBrand } from '../../../models' + +export type PolkadotChainEndpoint = { + /** + * The rpc endpoint url of chain (parachain | relay-chain) + */ + url: string +} + +export type PolkadotChainManifest = { + /** + * In case, the chain is parachain + * Identifer referring to a specific parachain. + * The relay-chain runtime guarantees that this id is unique + * for the duration of any session. + * NOTE: https://w3f.github.io/parachain-implementers-guide/types/candidate.html#para-id + * + * If the id is -1, then the chain is relay-chain. + */ + id: number + endpoint: PolkadotChainEndpoint +} + +export type PolkadotChainSettings = { + /** + * In case the chain that we connect is a relay-chain, then the relayEndpoint is useless + */ + relayEndpoint?: PolkadotChainEndpoint + otherParachains: PolkadotChainManifest[] +} + +export type PolkadotChainInfo = { + headBlockNumber: number + headBlockTime: Date + version: string + nativeInfo: PolkadotNativeInfo +} + +export type PolkadotNativeInfo = { + chain: string + name: string + SS58: number + tokenDecimals: number[] + tokenSymbols: string[] + transacitonByteFee: number + metadata: any +} + +export type PolkadotSymbol = string & ChainSymbolBrand + +export type PolkadotChainSettingsCommunicationSettings = { + blocksToCheck: number + checkInterval: number + getBlockAttempts: number +} + +export type PolkadotBlockNumber = BlockNumber | BN | BigInt | Uint8Array | number | string + +export type PolkadotBlock = Record + +export type PolkadotKeyringPair = KeyringPair + +export type DotAmount = Compact | AnyNumber | Uint8Array + +export type PolkadotPaymentInfo = { + weight: Weight + partialFee: Balance +} + +export type PolkadotAccountBalance = AccountData + +export type PolkadotHash = Hash diff --git a/src/chains/polkadot_1/models/index.ts b/src/chains/polkadot_1/models/index.ts new file mode 100644 index 00000000..370845d8 --- /dev/null +++ b/src/chains/polkadot_1/models/index.ts @@ -0,0 +1,3 @@ +export * from './generalModels' +export * from './cryptoModels' +export * from './polkadotStructures' diff --git a/src/chains/polkadot_1/models/polkadotStructures.ts b/src/chains/polkadot_1/models/polkadotStructures.ts new file mode 100644 index 00000000..2b1f08b4 --- /dev/null +++ b/src/chains/polkadot_1/models/polkadotStructures.ts @@ -0,0 +1,10 @@ +import { ChainActionType } from '../../../models' +import { PolkadotChainActionType } from './chainActionType' + +export type PolkadotDecomposeReturn = { + chainActionType: ChainActionType | PolkadotChainActionType + args: any + partial: boolean +} + +// TODO: define types for PolkadotMetadata diff --git a/src/chains/polkadot_1/models/transactionModels.ts b/src/chains/polkadot_1/models/transactionModels.ts new file mode 100644 index 00000000..701da5a3 --- /dev/null +++ b/src/chains/polkadot_1/models/transactionModels.ts @@ -0,0 +1,17 @@ +export type PolkadotTransactionAction = {} + +export type PolkadotActionHelperInput = {} + +export type PolkadotTransactionOptions = {} + +export type PolkadotRawTransaction = {} + +export type PolkadotTransactionHeader = {} + +export type PolkadotAddressBuffer = Buffer + +export type PolkadotTransactionCost = {} + +export type PolkadotSetDesiredFeeOptions = {} + +export type PolkadotTransactionResources = {} \ No newline at end of file diff --git a/src/chains/polkadot_1/polkadotAccount.ts b/src/chains/polkadot_1/polkadotAccount.ts new file mode 100644 index 00000000..159451a1 --- /dev/null +++ b/src/chains/polkadot_1/polkadotAccount.ts @@ -0,0 +1,95 @@ +import { notSupported } from '../../helpers' +import { throwNewError } from '../../errors' +import { Account } from '../../interfaces' +import { PolkadotChainState } from './polkadotChainState' +import { PolkadotAddress, PolkadotPublicKey } from './models' +import { isValidPolkadotAddress, isValidPolkadotPublicKey } from './helpers' + +export class PolkadotAccount implements Account { + private _address: PolkadotAddress + + private _publicKey: PolkadotPublicKey + + private _chainState: PolkadotChainState + + constructor(chainState: PolkadotChainState) { + this._chainState = chainState + } + + /** Whether the account is currently unused and can be reused - not supported in Polkadot */ + get canBeRecycled(): boolean { + return notSupported('PolkadotAccount.canBeRecycled') + } + + /** Polkadot address */ + get name(): any { + this.assertHasAddress() + return this._address + } + + /** Public Key(s) associated with the account */ + get publicKeys(): any { + this.assertHasAddress() + return this._publicKey + } + + /** Weather the account name can be used for new account */ + isValidNewAccountName = async (accountName: PolkadotAddress): Promise => { + return isValidPolkadotAddress(accountName) + } + + /** Sets the polkadot address + * Public key can only be obtained from a transaction signed by an polkadot address + * Only polkadot address is not enough to get public key + */ + load = async (address?: PolkadotAddress): Promise => { + this.assertValidPolkadotAddress(address) + this._address = address + } + + /** Sets the polkadot public key and address */ + setPublicKey = async (publicKey: PolkadotPublicKey) => { + this.assertValidPolkadotPublickey(publicKey) + this._publicKey = publicKey + // this._address = getPolkadotAddressFromPublicKey(publicKey) + } + + /** Polkadot has no account structure/registry on the chain */ + get supportsOnChainAccountRegistry(): boolean { + return false + } + + /** Polkadot accounts cannot be recycled as the private keys cannot be replaced */ + get supportsRecycling(): boolean { + return false + } + + /** JSON representation of address */ + toJson() { + this.assertHasAddress() + return { address: this._address } + } + + /** Returns the address */ + get value(): PolkadotAddress { + return this._address + } + + private assertHasAddress(): void { + if (!this._address) { + throwNewError('Polkadot address or Public key not provided') + } + } + + private assertValidPolkadotAddress(address: PolkadotAddress): void { + if (!isValidPolkadotAddress(address)) { + throwNewError('Not a valid polkadot address') + } + } + + private assertValidPolkadotPublickey(publicKey: PolkadotPublicKey): void { + if (!isValidPolkadotPublicKey(publicKey)) { + throwNewError('Not a valid polkadot public key') + } + } +} diff --git a/src/chains/polkadot_1/polkadotAction.ts b/src/chains/polkadot_1/polkadotAction.ts new file mode 100644 index 00000000..9dea0c08 --- /dev/null +++ b/src/chains/polkadot_1/polkadotAction.ts @@ -0,0 +1,6 @@ +export type ActionChainOptions = { + chain: string + hardfork: string +} + +export class PolkadotActionHelper {} diff --git a/src/chains/polkadot_1/polkadotChainState.ts b/src/chains/polkadot_1/polkadotChainState.ts new file mode 100644 index 00000000..9f17fa24 --- /dev/null +++ b/src/chains/polkadot_1/polkadotChainState.ts @@ -0,0 +1,285 @@ +import { ApiPromise, WsProvider } from '@polkadot/api' +// import { isU8a, u8aToString } from '@polkadot/util' +import { SubmittableExtrinsic } from '@polkadot/api/submittable/types' +import { ChainFeature } from '../../models' +import { + PolkadotBlock, + PolkadotBlockNumber, + PolkadotChainEndpoint, + PolkadotChainInfo, + PolkadotChainSettings, + PolkadotAddress, + PolkadotPaymentInfo, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + PolkadotAccountBalance, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + PolkadotHash, + PolkadotKeyringPair, + PolkadotSymbol, +} from './models' +import { notImplemented, trimTrailingChars } from '../../helpers' +import { throwAndLogError, throwNewError } from '../../errors' + +export class PolkadotChainState { + private _chainInfo: PolkadotChainInfo + + private _chainSettings: PolkadotChainSettings + + private _endpoints: PolkadotChainEndpoint[] + + private _activeEndpoint: PolkadotChainEndpoint + + private _isConnected: boolean = false + + private _api: ApiPromise + + private _provider: WsProvider + + constructor(endpoints: PolkadotChainEndpoint[], settings?: PolkadotChainSettings) { + this._endpoints = endpoints + this._chainSettings = settings + } + + public get activeEndpoint(): PolkadotChainEndpoint { + return this._activeEndpoint + } + + public get chain(): string { + this.assertIsConnected() + return this._chainInfo?.nativeInfo?.chain.toString() + } + + public get chainInfo(): PolkadotChainInfo { + this.assertIsConnected() + return this._chainInfo + } + + public get isConnected(): boolean { + return this._isConnected + } + + public get endpoints(): PolkadotChainEndpoint[] { + return this._endpoints + } + + public async connect(): Promise { + try { + if (!this._provider) { + const { url, endpoint } = this.selectEndpoint() + this._activeEndpoint = endpoint + this._provider = new WsProvider(url) + } + if (!this._api) { + this._api = await ApiPromise.create({ provider: this._provider }) + } + + await this.getChainInfo() + this._isConnected = true + } catch (error) { + throwAndLogError('Problem connection to api', 'apiConnectFailed', error) + } + } + + /** map our chain features names to polkadot metadata module name */ + mapFeatureToMetadataModuleName(feature: ChainFeature) { + switch (feature) { + case ChainFeature.Account: + return 'Account' + case ChainFeature.AccountCreationTransaction: + return 'Account' + case ChainFeature.Multisig: + return 'Multisig' + case ChainFeature.TransactionFees: + return 'TransactionPayment' + default: + return null + } + } + + /** get the module metadata for a named module */ + getMetadataModule(moduleName: string) { + // TODO: update to use types for PolkadotMetadata + return this.metadata?.V12?.modules.find((m: any) => m.name.toLowercase() === moduleName) + } + + /** Whether the current parachain has a specific feautre/module installed */ + public hasFeature(feature: ChainFeature): boolean { + const moduleName = this.mapFeatureToMetadataModuleName(feature) + return !!this.getMetadataModule(moduleName) + } + + get metadata() { + return this.chainInfo?.nativeInfo?.metadata + } + + public async getChainInfo(): Promise { + try { + const [headBlockTime, chain, name, version, lastBlockHead, metadata] = await Promise.all([ + this._api.query.timestamp.now(), + this._api.rpc.system.chain(), + this._api.rpc.system.name(), + this._api.rpc.system.version(), + this._api.rpc.chain.getHeader(), + this._api.rpc.state.getMetadata(), + ]) + this._chainInfo = { + headBlockNumber: lastBlockHead.number.toNumber(), + headBlockTime: new Date(headBlockTime.toNumber()), + version: version.toString(), + nativeInfo: { + chain: chain.toString(), + name: name.toString(), + transacitonByteFee: this._api.consts.transactionPayment.transactionByteFee.toNumber(), + tokenDecimals: this._api.registry.chainDecimals, + SS58: this._api.registry.chainSS58, + tokenSymbols: this._api.registry.chainTokens, + metadata: metadata.toHuman(), + }, + } + } catch (error) { + throwAndLogError('error', 'error', error) + } + return this._chainInfo + } + + private selectEndpoint(): { url: string; endpoint: PolkadotChainEndpoint } { + const selectedEndpoint = this.endpoints[0] + const endpointUrl = new URL(selectedEndpoint.url) + return { url: trimTrailingChars(endpointUrl?.href, '/'), endpoint: selectedEndpoint } + } + + public async getBlock(blockNumber: PolkadotBlockNumber): Promise { + const blockHash = await this._api.rpc.chain.getBlockHash(blockNumber) + const block = await this._api.rpc.chain.getBlock(blockHash) + return block.toHuman() + } + + public async fetchBalance( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + address: PolkadotAddress, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + symbol: PolkadotSymbol, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + tokenAddress?: PolkadotAddress, + ): Promise<{ balance: string }> { + notImplemented() + return null + // const balanceInfo = await this.getBalance(isU8a(address) ? u8aToString(address) : address) + // const balance = balanceInfo.free.sub(balanceInfo.miscFrozen) + // return { balance: balance.toString() } + } + + // public async getBalance(address: string): Promise { + // const { data } = await this._api.query.system.account(address) + + // return data + // } + + public async estimateTxFee( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + sender: PolkadotKeyringPair, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + extrinsics: SubmittableExtrinsic<'promise'>, + ): Promise { + notImplemented() + return null + // const info = await extrinsics.paymentInfo(sender) + + // const paymentInfo: PolkadotPaymentInfo = { + // weight: info.weight, + // partialFee: info.partialFee, + // } + + // return paymentInfo + } + // public async estimateTxFee(sender: PolkadotKeyringPair, receiver: PolkadotAddress, amount: DotAmount): Promise { + // try { + // const info = await this._api.tx.balances.transfer(receiver, amount) + // .paymentInfo(sender) + + // const paymentInfo: PolkadotPaymentInfo = { + // weight: info.weight, + // partialFee: info.partialFee + // } + + // return paymentInfo + // } catch (error) { + // throw error + // } + // } + + // public async sendTransaction( + // sender: PolkadotKeyringPair, + // extrinsics: SubmittableExtrinsic<'promise'>, + // ): Promise { + // const txHash = await extrinsics.signAndSend(sender) + // return txHash + // } + + // public async sendTransaction(sender: PolkadotKeyringPair, receiver: PolkadotAddress, amount: DotAmount): Promise { + // try{ + // const tx = await this._api.tx.balances.transfer(receiver, amount) + // const paymentInfo = await tx.paymentInfo(sender) + // const fee = paymentInfo.partialFee.toString() + // const balanceInfo = await this.getBalance(sender.address) + // const balance = balanceInfo.free.sub(balanceInfo.miscFrozen) + + // const bnFee = new BigNumber(fee, this._chainInfo.nativeInfo.tokenDecimals[0]) + // const bnAmount = new BigNumber(amount.toString(), this._chainInfo.nativeInfo.tokenDecimals[0]) + // const bnBalance = new BigNumber(balance.toString(), this._chainInfo.nativeInfo.tokenDecimals[0]) + + // if (bnFee.plus(bnAmount).isGreaterThan(bnBalance) ) { + // throw new Error('Insufficient balance') + // } + + // const txHash = await tx.signAndSend(sender) + // return txHash + // } catch (error) { + // throw error + // } + // } + + // public async submitExtrinsics() { + // // Submittable createSubmittable() + // } + + // public buildExtrinsics(section: string, method: string) { + // const sectionOptions = this.extrinsicSectionOptions() + // if (!sectionOptions.filter(option => option === section).length) { + // throwNewError('Not available Extrinsic Section Option') + // } + // const methodOptions = this.extrinsicMethodOptions(section) + // if (!methodOptions.filter(option => option === method).length) { + // throwNewError('Not available Extrinsic Method Option') + // } + // const fn: SubmittableExtrinsicFunction<'promise'> = this._api.tx[section][method] + // console.log(this.getParams(fn)) + // } + + // public extrinsicSectionOptions(): string[] { + // return Object.keys(this._api.tx) + // .sort() + // .filter((name): number => Object.keys(this._api.tx[name]).length) + // .map((name): string => name) + // } + + // public extrinsicMethodOptions(sectionName: string): string[] { + // const section = this._api.tx[sectionName] + // return Object.keys(section) + // .sort() + // .map((name): string => name) + // } + + // public getParams({ meta }: SubmittableExtrinsicFunction<'promise'>): { name: string; type: TypeDef }[] { + // return GenericCall.filterOrigin(meta).map((arg): { name: string; type: TypeDef } => ({ + // name: arg.name.toString(), + // type: getTypeDef(arg.type.toString()), + // })) + // } + + public assertIsConnected(): void { + if (!this._isConnected) { + throwNewError('Not connected to chain') + } + } +} diff --git a/src/chains/polkadot_1/polkadotCompose.ts b/src/chains/polkadot_1/polkadotCompose.ts new file mode 100644 index 00000000..4f18ae90 --- /dev/null +++ b/src/chains/polkadot_1/polkadotCompose.ts @@ -0,0 +1,80 @@ +import { ChainActionType } from '../../models' +import { notImplemented } from '../../helpers' +import { PolkadotChainState } from './polkadotChainState' +import { PolkadotChainActionType } from './models/chainActionType' +import { PolkadotTransactionAction } from './models/transactionModels' +// import { composeAction as TokenTransferTemplate } from './templates/chainActions/standard/token_transfer' +// import { composeAction as ValueTransferTemplate } from './templates/chainActions/standard/value_transfer' +// import { composeAction as ApplicationClearTemplate } from './templates/chainActions/chainSpecific/application_clear' +// import { composeAction as ApplicationCloseOutTemplate } from './templates/chainActions/chainSpecific/application_closeout' +// import { composeAction as ApplicationCreateTemplate } from './templates/chainActions/chainSpecific/application_create' +// import { composeAction as ApplicationDeleteTemplate } from './templates/chainActions/chainSpecific/application_delete' +// import { composeAction as ApplicationNoOpTemplate } from './templates/chainActions/chainSpecific/application_noOp' +// import { composeAction as ApplicationOptInTemplate } from './templates/chainActions/chainSpecific/application_optIn' +// import { composeAction as ApplicationUpdateTemplate } from './templates/chainActions/chainSpecific/application_update' +// import { composeAction as AssetConfigTemplate } from './templates/chainActions/chainSpecific/asset_config' +// import { composeAction as AssetCreateTemplate } from './templates/chainActions/chainSpecific/asset_create' +// import { composeAction as AssetDestroyTemplate } from './templates/chainActions/chainSpecific/asset_destroy' +// import { composeAction as AssetFreezeTemplate } from './templates/chainActions/chainSpecific/asset_freeze' +// import { composeAction as AssetTransferTemplate } from './templates/chainActions/chainSpecific/asset_transfer' +// import { composeAction as KeyRegistrationTemplate } from './templates/chainActions/chainSpecific/key_registration' +// import { composeAction as PaymentTemplate } from './templates/chainActions/chainSpecific/payment' + +// import { +// AlgorandChainActionType, +// AlgorandChainTransactionParamsStruct, +// AlgorandTxActionSdkEncoded, +// AlgorandTxHeaderParams, +// } from './models' +// import { AlgorandChainState } from './algoChainState' +// import { AlgorandActionHelper } from './algoAction' + +// map a key name to a function that returns an object +// const ComposeAction: { [key: string]: (args: any, suggestedParams: AlgorandTxHeaderParams) => any } = { +// // Standard actions +// TokenTransfer: TokenTransferTemplate, +// ValueTransfer: ValueTransferTemplate, +// // Algorand actions +// AssetConfig: AssetConfigTemplate, +// AssetCreate: AssetCreateTemplate, +// AssetDestroy: AssetDestroyTemplate, +// AssetFreeze: AssetFreezeTemplate, +// AssetTransfer: AssetTransferTemplate, +// AppClear: ApplicationClearTemplate, +// AppCloseOut: ApplicationCloseOutTemplate, +// AppCreate: ApplicationCreateTemplate, +// AppDelete: ApplicationDeleteTemplate, +// AppNoOp: ApplicationNoOpTemplate, +// AppOptIn: ApplicationOptInTemplate, +// AppUpdate: ApplicationUpdateTemplate, +// KeyRegistration: KeyRegistrationTemplate, +// Payment: PaymentTemplate, +// } + +/** Compose an object for a chain contract action */ +export async function composeAction( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + chainState: PolkadotChainState, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + chainActionType: ChainActionType | PolkadotChainActionType, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + args: any, +): Promise { + return notImplemented() + // if (!composerFunction) { + // notSupported(`ComposeAction:${chainActionType}`) + // } + + // let actionHelper = new AlgorandActionHelper(args) + // const chainTxHeaderParams: AlgorandChainTransactionParamsStruct = + // chainState.chainInfo?.nativeInfo?.transactionHeaderParams + // actionHelper.applyCurrentTxHeaderParamsWhereNeeded(chainTxHeaderParams) + // // seperate-out the action param values (required by compose functions) from the suggestedParams (headers) + // const sdkEncodedActionParams: AlgorandTxActionSdkEncoded = composerFunction( + // actionHelper.paramsOnly, + // actionHelper.transactionHeaderParams, + // ) + // // use AlgorandActionHelper to drop empty fields + // actionHelper = new AlgorandActionHelper(sdkEncodedActionParams as AlgorandTxActionSdkEncoded) + // return sdkEncodedActionParams +} diff --git a/src/chains/polkadot_1/polkadotConstants.ts b/src/chains/polkadot_1/polkadotConstants.ts new file mode 100644 index 00000000..17b1f9f6 --- /dev/null +++ b/src/chains/polkadot_1/polkadotConstants.ts @@ -0,0 +1,25 @@ +// TODO: Update values for Polkadot + +import { PolkadotKeyPairType } from './models' + +// sign transaction default parameters +export const TRANSACTION_ENCODING = 'utf8' +export const DEFAULT_TRANSACTION_EXPIRY_IN_SECONDS = 30 +export const DEFAULT_TRANSACTION_BLOCKS_BEHIND_REF_BLOCK = 3 +export const CHAIN_BLOCK_FREQUENCY = 0.5 // time between blocks produced in seconds + +// transaction confirmation default parameters +export const DEFAULT_BLOCKS_TO_CHECK = 20 +export const DEFAULT_CHECK_INTERVAL = 500 +export const DEFAULT_GET_BLOCK_ATTEMPTS = 10 + +// default unit for DOT transfers +// export const DEFAULT_POLKADOT_UNIT = EthUnit.Wei + +// token related +export const NATIVE_CHAIN_TOKEN_SYMBOL = 'DOT' +/** The chain address of the default token/currency contract (if any) */ +export const NATIVE_CHAIN_TOKEN_ADDRESS: any = null +export const DOT_TOKEN_PRECISION = 10 + +export const DEFAULT_POLKADOT_KEY_PAIR_TYPE = PolkadotKeyPairType.Ed25519 diff --git a/src/chains/polkadot_1/polkadotCreateAccount.ts b/src/chains/polkadot_1/polkadotCreateAccount.ts new file mode 100644 index 00000000..09a544e8 --- /dev/null +++ b/src/chains/polkadot_1/polkadotCreateAccount.ts @@ -0,0 +1,153 @@ +import { PolkadotCreateAccountOptions } from './models/accountModels' +import { PolkadotChainState } from './polkadotChainState' +// import { +// generateKeyPair, +// generateNewAccountPhrase, +// getKeypairFromPhrase, +// getPolkadotAddressFromPublicKey, +// } from './polkadotCrypto' +import { isValidPolkadotPublicKey } from './helpers' +import { notImplemented, notSupported } from '../../helpers' +import { CreateAccount } from '../../interfaces' +import { throwNewError } from '../../errors' +import { PolkadotAddress, PolkadotKeypair } from './models' +import { CryptoCurve } from '../../models' +// import { DEFAULT_POLKADOT_KEY_PAIR_TYPE } from './polkadotConstants' + +/** Helper class to compose a transaction for creating a new chain account + * Handles native accounts + * Generates new account keys if not provide + */ +export class PolkadotCreateAccount implements CreateAccount { + private _accountName: PolkadotAddress + + private _accountType: CryptoCurve + + private _chainState: PolkadotChainState + + private _options: PolkadotCreateAccountOptions + + private _generatedKeypair: PolkadotKeypair + + constructor(chainState: PolkadotChainState, options?: PolkadotCreateAccountOptions) { + this._chainState = chainState + this._options = options + } + + /** Account name for the account to be created */ + public get accountName(): any { + return this._accountName + } + + /** Account type to be created */ + public get accountType(): CryptoCurve { + return this._accountType + } + + /** Prefix that represents the address on which chain */ + public getSS58Format(): number { + return this._chainState.chainInfo.nativeInfo.SS58 + } + + /** Whether the account was recycled - not supported in Polkadot */ + get didRecycleAccount() { + return false + } + + /** The keys that were generated as part of the account creation process + * IMPORTANT: Be sure to always read and store these keys after creating an account + * This is the only way to retrieve the auto-generated private keys after an account is created + */ + get generatedKeys() { + if (this._generatedKeypair) { + return this._generatedKeypair + } + return null + } + + /** Account creation options */ + get options() { + return this._options + } + + /** Polkadot account creation does not require any on chain transactions. + * Hence there is no transaction object attached to PolkadotCreateAccount class. + */ + get transaction(): any { + throwNewError( + 'Polkadot account creation does not require any on chain transactions. You should always first check the supportsTransactionToCreateAccount property - if false, transaction is not supported/required for this chain type', + ) + return null + } + + /** Polkadot does not require the chain to execute a createAccount transaction to create the account structure on-chain */ + get supportsTransactionToCreateAccount(): boolean { + return false + } + + /** Compose a transaction to send to the chain to create a new account + * Polkadot does not require a create account transaction to be sent to the chain + */ + async composeTransaction(): Promise { + notSupported('CreateAccount.composeTransaction') + } + + /** Determine if desired account name is usable for a new account. + * Recycling is not supported on Polkadot. + */ + async determineNewAccountName(accountName: PolkadotAddress): Promise { + return { alreadyExists: false, newAccountName: accountName, canRecycle: false } + } + + /** Returns the Polkadot Address as a Polkadot Account name for the public key provided in options + * Or generates a new mnemonic(phrase)/private/public/address + * Updates generatedKeys for the newly generated name (since name/account is derived from publicKey) + */ + async generateAccountName(): Promise { + notImplemented() + return null + // const accountName = await this.generateAccountNameString() + // return toPolkadotEntityName(accountName) + } + + /** Returns a string of the Polkadot Address for the public key provide in options - OR generates a new mnemonic(phrase)/private/public/address */ + async generateAccountNameString(): Promise { + await this.generateKeysIfNeeded() + return this.accountName as string + } + + /** Checks create options - if publicKeys are missing, + * autogenerate the public and private key pair and add them to options + */ + async generateKeysIfNeeded() { + // let publicKey: PolkadotPublicKey + // this.assertValidOptionPublicKeys() + // publicKey = this?._options?.publicKey + // if (!publicKey) { + // await this.generateAccountKeys() + // } + // this._accountName = getPolkadotAddressFromPublicKey(this._generatedKeypair.publicKey) + // this._accountType = this._options.newKeysOptions.keyPairType + } + + // private async generateAccountKeys(): Promise { + // const { newKeysOptions } = this._options + // const { keyPairType, phrase: overridePhrase, derivationPath } = newKeysOptions || {} + // const overrideType = keyPairType || DEFAULT_POLKADOT_KEY_PAIR_TYPE + + // // this._generatedKeypair = getKeypairFromPhrase(overridePhrase, overrideType) + // this._generatedKeypair = await generateKeyPair(keyPairType, phrase, derivationPath) + // this._options.publicKey = this._generatedKeypair.publicKey + // this._options.newKeysOptions = { + // phrase: overridePhrase, + // keyPairType: overrideType, + // } + // } + + private assertValidOptionPublicKeys() { + const { publicKey } = this._options + if (publicKey && isValidPolkadotPublicKey(publicKey)) { + throwNewError('Invalid option - provided publicKey isnt valid') + } + } +} diff --git a/src/chains/polkadot_1/polkadotCrypto.ts b/src/chains/polkadot_1/polkadotCrypto.ts new file mode 100644 index 00000000..62f908b4 --- /dev/null +++ b/src/chains/polkadot_1/polkadotCrypto.ts @@ -0,0 +1,332 @@ +import { + keyExtractSuri, + keyFromPath, + mnemonicGenerate, + mnemonicToLegacySeed, + mnemonicToMiniSecret, + naclKeypairFromSeed, + schnorrkelKeypairFromSeed, + secp256k1KeypairFromSeed, +} from '@polkadot/util-crypto' +import { Keypair } from '@polkadot/util-crypto/types' +// import Keyring from '@polkadot/keyring' +import { isHex } from '@polkadot/util' +import secp256k1 from 'secp256k1' +import { DeriveJunction } from '@polkadot/util-crypto/key/DeriveJunction' +import { + PolkadotEncryptionOptions, + PolkadotKeypair, + PolkadotKeyPairType, + PolkadotPrivateKey, + PolkadotPublicKey, +} from './models' +import { CryptoCurve, PublicKey } from '../../models' +import { AesCrypto, Asymmetric, Ed25519Crypto } from '../../crypto' +import { removeHexPrefix, byteArrayToHexString, hexStringToByteArray, notSupported, isInEnum } from '../../helpers' +import { ensureEncryptedValueIsObject } from '../../crypto/genericCryptoHelpers' +// import * as AsymmetricHelpers from '../../crypto/asymmetricHelpers' +import { throwNewError } from '../../errors' +import { getCurveFromKeyType, toPolkadotPrivateKey, toPolkadotPublicKey, toSymEncryptedDataString } from './helpers' + +// TODO - should change depending on curve +const enum POLKADOT_ASYMMETRIC_SCHEME_NAME { + Ed25519 = 'asym.chainjs.ed25519.polkadot', + Secp256k1 = 'asym.chainjs.secp256k1.polkadot', +} + +/** returns a keypair for a specific curve */ +export function generateKeypairFromSeed(seed: Uint8Array, curve: CryptoCurve): Keypair { + if (curve === CryptoCurve.Secp256k1) return secp256k1KeypairFromSeed(seed) as Keypair + if (curve === CryptoCurve.Ed25519) return naclKeypairFromSeed(seed) as Keypair + if (curve === CryptoCurve.Sr25519) return schnorrkelKeypairFromSeed(seed) as Keypair + throwNewError(`Curve type not supported: ${curve}`) + return null +} + +export function generateNewAccountPhrase(): string { + const mnemonic = mnemonicGenerate() + return mnemonic +} + +export function getKeypairFromPhrase(mnemonic: string, curve: CryptoCurve): Keypair { + const seed = mnemonicToMiniSecret(mnemonic) + const keyPair = generateKeypairFromSeed(seed, curve) + return keyPair +} + +/** get uncompressed public key from Ethereum key */ +export function uncompressEthereumPublicKey(publicKey: PolkadotPublicKey): string { + // if already decompressed an not has trailing 04 + const cleanedPublicKey = removeHexPrefix(publicKey) + const testBuffer = Buffer.from(cleanedPublicKey, 'hex') + const prefixedPublicKey = testBuffer.length === 64 ? `04${cleanedPublicKey}` : cleanedPublicKey + const uncompressedPublicKey = byteArrayToHexString( + secp256k1.publicKeyConvert(hexStringToByteArray(prefixedPublicKey), false), + ) + return uncompressedPublicKey +} + +/** Encrypts a string using a password and optional salt */ +export function encryptWithPassword( + unencrypted: string, + password: string, + options: PolkadotEncryptionOptions, + keypairType?: PolkadotKeyPairType, // TODO: keypairType should be required +): AesCrypto.AesEncryptedDataString | Ed25519Crypto.Ed25519EncryptedDataString { + // TODO: Define Src25519 curve + const curve = getCurveFromKeyType(keypairType) + if (curve === CryptoCurve.Ed25519) { + const passwordKey = Ed25519Crypto.calculatePasswordByteArray(password, options) + const encrypted = Ed25519Crypto.encrypt(unencrypted, passwordKey) + return toSymEncryptedDataString(encrypted, keypairType) + } + if (curve === CryptoCurve.Secp256k1) return AesCrypto.encryptWithPassword(unencrypted, password, options) + // if no curve, throw an error - curve not supported + throw new Error(`Curve not supported ${curve}`) +} + +/** Decrypts the encrypted value using a password, and optional salt using secp256k1, and nacl + * The encrypted value is either a stringified JSON object or a JSON object */ +export function decryptWithPassword( + encrypted: AesCrypto.AesEncryptedDataString | Ed25519Crypto.Ed25519EncryptedDataString | any, + password: string, + options: PolkadotEncryptionOptions, + keypairType?: PolkadotKeyPairType, // TODO: keypairType should be required +): string { + // TODO: Define Src25519 curve + const curve = getCurveFromKeyType(keypairType) + if (curve === CryptoCurve.Ed25519) { + const passwordKey = Ed25519Crypto.calculatePasswordByteArray(password, options) + const decrypted = Ed25519Crypto.decrypt(encrypted, passwordKey) + return decrypted + } + if (curve === CryptoCurve.Secp256k1) return AesCrypto.decryptWithPassword(encrypted, password, options) + // if no curve, throw an error - curve not supported + throw new Error(`Curve not supported ${curve}`) +} + +/** uncompress public key based on keypairType */ +export function uncompressPublicKey( + publicKey: PolkadotPublicKey, + keypairType: PolkadotKeyPairType, +): { curveType: Asymmetric.EciesCurveType; publicKeyUncompressed: PublicKey; scheme: POLKADOT_ASYMMETRIC_SCHEME_NAME } { + let scheme: POLKADOT_ASYMMETRIC_SCHEME_NAME + let curveType: Asymmetric.EciesCurveType + let publicKeyUncompressed + if (keypairType === PolkadotKeyPairType.Ecdsa) { + // TODO: confirm that ecdsa is uncompressed, might be same as Ethereum + publicKeyUncompressed = publicKey + } else if (keypairType === PolkadotKeyPairType.Ethereum) { + publicKeyUncompressed = uncompressEthereumPublicKey(publicKey) + curveType = Asymmetric.EciesCurveType.Secp256k1 + scheme = POLKADOT_ASYMMETRIC_SCHEME_NAME.Secp256k1 + } else if (keypairType === PolkadotKeyPairType.Ed25519) { + publicKeyUncompressed = publicKey + curveType = Asymmetric.EciesCurveType.Ed25519 + scheme = POLKADOT_ASYMMETRIC_SCHEME_NAME.Ed25519 + } else if (keypairType === PolkadotKeyPairType.Sr25519) { + // TODO: add + } else { + notSupported(`uncompressPublicKey keypairType: ${keypairType}`) + } + return { curveType, publicKeyUncompressed, scheme } +} + +/** Encrypts a string using a public key into a stringified JSON object + * The encrypted result can be decrypted with the matching private key */ +export async function encryptWithPublicKey( + unencrypted: string, + publicKey: PolkadotPublicKey, + options: Asymmetric.EciesOptions, + keypairType?: PolkadotKeyPairType, // TODO: keypairType should be required +): Promise { + const { curveType, publicKeyUncompressed, scheme } = uncompressPublicKey(publicKey, keypairType) + const useOptions = { + ...options, + curveType, + scheme, + } + const response = Asymmetric.encryptWithPublicKey(publicKeyUncompressed, unencrypted, useOptions) + return Asymmetric.toAsymEncryptedDataString(JSON.stringify(response)) +} + +// TODO: Refactor - Tray - reuse functions across chains + +/** Decrypts the encrypted value using a private key + * The encrypted value is a stringified JSON object + * ... and must have been encrypted with the public key that matches the private ley provided */ +export async function decryptWithPrivateKey( + encrypted: Asymmetric.AsymmetricEncryptedDataString | Asymmetric.AsymmetricEncryptedData, + privateKey: PolkadotPrivateKey, + options: Asymmetric.EciesOptions, + keypairType?: PolkadotKeyPairType, // TODO: keypairType should be required +): Promise { + const curve = getCurveFromKeyType(keypairType) // TODO: Should be keypairtype not curve + let useOptions = { ...options } + let privateKeyConverted = '' + if (curve === CryptoCurve.Secp256k1) { + useOptions = { ...useOptions, curveType: Asymmetric.EciesCurveType.Secp256k1 } + privateKeyConverted = removeHexPrefix(privateKey) + } else if (curve === CryptoCurve.Ed25519) { + useOptions = { ...useOptions, curveType: Asymmetric.EciesCurveType.Ed25519 } + privateKeyConverted = privateKey.slice(0, privateKey.length / 2) + } else { + // if no curve matched, throw an error - not supported curve + throw new Error(`Curve not supported ${curve}`) + } + const encryptedObject = ensureEncryptedValueIsObject(encrypted) as Asymmetric.AsymmetricEncryptedData + return Asymmetric.decryptWithPrivateKey(encryptedObject, privateKeyConverted, useOptions) +} + +/** Encrypts a string using multiple assymmetric encryptions with multiple public keys - one after the other + * calls a helper function to perform the iterative wrapping + * the first parameter of the helper is a chain-specific function (in this file) to encryptWithPublicKey + * The result is stringified JSON object including an array of encryption results with the last one including the final cipertext + * Encrypts using publicKeys in the order they appear in the array */ +// export async function encryptWithPublicKeys( +// unencrypted: string, +// publicKeys: PolkadotPublicKey[], +// keypairType: PolkadotKeyPairType[], +// options?: Asymmetric.EciesOptions, +// ): Promise { +// // TODO: Make sure to change asymmetricHelpers.encryptWithPublicKeys or nor +// notImplemented() +// return null +// return Asymmetric.toAsymEncryptedDataString( +// await AsymmetricHelpers.encryptWithPublicKeys(encryptWithPublicKey, unencrypted, publicKeys, options), +// ) +// } + +/** Unwraps an object produced by encryptWithPublicKeys() - resulting in the original ecrypted string + * calls a helper function to perform the iterative unwrapping + * the first parameter of the helper is a chain-specific function (in this file) to decryptWithPrivateKey + * Decrypts using privateKeys that match the publicKeys provided in encryptWithPublicKeys() - provide the privateKeys in same order + * The result is the decrypted string */ +// export async function decryptWithPrivateKeys( +// encrypted: Asymmetric.AsymmetricEncryptedDataString, +// privateKeys: PolkadotPublicKey[], +// ): Promise { +// // TODO: Make sure to change asymmetricHelpers.encryptWithPublicKeys or nor +// notImplemented() +// return null +// return AsymmetricHelpers.decryptWithPrivateKeys(decryptWithPrivateKey, encrypted, privateKeys, {}) +// } + +/** Signs data with private key */ +// export function sign(data: string | Buffer, privateKey: string): PolkadotSignature { +// notImplemented() +// // todo: data should be hashed first using ethereum-js-tx Transaction.prototype.hash +// const dataBuffer = toEthBuffer(data) +// const keyBuffer = toBuffer(privateKey, 'hex') +// return toEthereumSignature(ecsign(dataBuffer, keyBuffer)) +// } + +/** Derive a seed from a mnemoic (and optional derivation path) */ +function generateSeedFromMnemonic( + keypairType: PolkadotKeyPairType, + mnemonic: string, + derivationPath?: string, +): { seed: Uint8Array; path: DeriveJunction[] } { + const suri = derivationPath !== undefined ? `${mnemonic}//${derivationPath}` : mnemonic + const { password, path, phrase } = keyExtractSuri(suri) + let seed: Uint8Array + if (isHex(phrase, 256)) { + seed = hexStringToByteArray(phrase) + } else { + const str = phrase as string + const parts = str.split(' ') + if ([12, 15, 18, 21, 24].includes(parts.length)) { + seed = + keypairType === PolkadotKeyPairType.Ethereum + ? mnemonicToLegacySeed(phrase) + : mnemonicToMiniSecret(phrase, password) + } else { + throw new Error('Specified phrase is not a valild mnemonic and is invalid as a raw seed at > 32 bytes') + } + } + return { seed, path } +} + +/** Generates and returns a new public/private key pair + * Supports optional key gen from mnemonic phase + * Note: Reference - createFromUri from @polkadot/keyring + * https://github.com/polkadot-js/common/blob/master/packages/keyring/src/keyring.ts#L197 + */ +export async function generateKeyPair( + keypairType?: PolkadotKeyPairType, + mnemonic?: string, + derivationPath?: string, +): Promise { + const curve = getCurveFromKeyType(keypairType) + const overrideMnemonic = mnemonic || generateNewAccountPhrase() + const { seed, path } = generateSeedFromMnemonic(keypairType, overrideMnemonic, derivationPath) + const derivedKeypair = keyFromPath(generateKeypairFromSeed(seed, curve), path, keypairType) + const keypair: PolkadotKeypair = { + type: keypairType, + publicKey: toPolkadotPublicKey(byteArrayToHexString(derivedKeypair.publicKey)), + privateKey: toPolkadotPrivateKey(byteArrayToHexString(derivedKeypair.secretKey)), + } + return keypair +} + +/** Adds privateKeyEncrypted if missing by encrypting privateKey (using password) */ +function encryptAccountPrivateKeysIfNeeded( + keys: PolkadotKeypair, + password: string, + options: PolkadotEncryptionOptions, +): PolkadotKeypair { + const privateKeyEncrypted = keys.privateKeyEncrypted + ? keys.privateKeyEncrypted + : encryptWithPassword(keys.privateKey, password, options, keys.type) + const encryptedKeys: PolkadotKeypair = { + type: keys.type, + publicKey: keys.publicKey, + privateKey: keys.privateKey, + privateKeyEncrypted, + } + return encryptedKeys +} + +/** Generates new public and private key pair + * Encrypts the private key using password and optional salt + */ +export async function generateNewAccountKeysAndEncryptPrivateKeys( + password: string, + keypairType: PolkadotKeyPairType, + options: PolkadotEncryptionOptions, +): Promise { + const keys = await generateKeyPair(keypairType) + const encryptedKeys = encryptAccountPrivateKeysIfNeeded(keys, password, options) + return encryptedKeys +} + +// export function determineCurveFromAddress() {} + +// export function determineCurveFromKeyPair() { +// // itererate verify - trying each curve +// } + +// /** Returns public key from ethereum signature */ +// export function getEthereumPublicKeyFromSignature( +// signature: EthereumSignature, +// data: string | Buffer, +// encoding: string, +// ): EthereumPublicKey { +// const { v, r, s } = signature +// return toEthereumPublicKey(ecrecover(toEthBuffer(data), v, r, s).toString()) +// } + +// /** Returns public key from polkadot address */ +// export function getPolkadotAddressFromPublicKey(publicKey: PolkadotPublicKey): PolkadotAddress { +// notImplemented() +// } + +// /** Verify that the signed data was signed using the given key (signed with the private key for the provided public key) */ +// export function verifySignedWithPublicKey( +// data: string | Buffer, +// publicKey: PolkadotPublicKey, +// signature: PolkadotSignature, +// ): boolean { +// notImplemented() +// return null +// } diff --git a/src/chains/polkadot_1/polkadotDecompose.ts b/src/chains/polkadot_1/polkadotDecompose.ts new file mode 100644 index 00000000..19ba447f --- /dev/null +++ b/src/chains/polkadot_1/polkadotDecompose.ts @@ -0,0 +1,69 @@ +// import { AlgorandTxAction, AlgorandTxActionRaw, AlgorandTxActionSdkEncoded, AlgorandDecomposeReturn } from './models' +// import { isNullOrEmpty } from '../../helpers' +// import { decomposeAction as TokenTransferTemplate } from './templates/chainActions/standard/token_transfer' +// import { decomposeAction as ValueTransferTemplate } from './templates/chainActions/standard/value_transfer' +// import { decomposeAction as ApplicationClearTemplate } from './templates/chainActions/chainSpecific/application_clear' +// import { decomposeAction as ApplicationCloseOutTemplate } from './templates/chainActions/chainSpecific/application_closeout' +// import { decomposeAction as ApplicationCreateTemplate } from './templates/chainActions/chainSpecific/application_create' +// import { decomposeAction as ApplicationDeleteTemplate } from './templates/chainActions/chainSpecific/application_delete' +// import { decomposeAction as ApplicationNoOpTemplate } from './templates/chainActions/chainSpecific/application_noOp' +// import { decomposeAction as ApplicationOptInTemplate } from './templates/chainActions/chainSpecific/application_optIn' +// import { decomposeAction as ApplicationUpdateTemplate } from './templates/chainActions/chainSpecific/application_update' +// import { decomposeAction as AssetConfigTemplate } from './templates/chainActions/chainSpecific/asset_config' +// import { decomposeAction as AssetCreateTemplate } from './templates/chainActions/chainSpecific/asset_create' +// import { decomposeAction as AssetDestroyTemplate } from './templates/chainActions/chainSpecific/asset_destroy' +// import { decomposeAction as AssetFreezeTemplate } from './templates/chainActions/chainSpecific/asset_freeze' +// import { decomposeAction as AssetTransferTemplate } from './templates/chainActions/chainSpecific/asset_transfer' +// import { decomposeAction as KeyRegistrationTemplate } from './templates/chainActions/chainSpecific/key_registration' +// import { decomposeAction as PaymentTemplate } from './templates/chainActions/chainSpecific/payment' + +import { notImplemented } from '../../helpers' +import { PolkadotDecomposeReturn } from './models' +import { PolkadotTransactionAction } from './models/transactionModels' + +// map a key name to a function that returns an object +// const DecomposeAction: { [key: string]: (args: any) => any } = { +// // Standard actions +// TokenTransfer: TokenTransferTemplate, +// ValueTransfer: ValueTransferTemplate, +// // Algorand actions +// AssetConfig: AssetConfigTemplate, +// AssetCreate: AssetCreateTemplate, +// AssetDestroy: AssetDestroyTemplate, +// AssetFreeze: AssetFreezeTemplate, +// AssetTransfer: AssetTransferTemplate, +// AppClear: ApplicationClearTemplate, +// AppCloseOut: ApplicationCloseOutTemplate, +// AppCreate: ApplicationCreateTemplate, +// AppDelete: ApplicationDeleteTemplate, +// AppNoOp: ApplicationNoOpTemplate, +// AppOptIn: ApplicationOptInTemplate, +// AppUpdate: ApplicationUpdateTemplate, +// KeyRegistration: KeyRegistrationTemplate, +// Payment: PaymentTemplate, +// } + +/** Decompose a transaction action to determine its standard action type (if any) and retrieve its data */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export async function decomposeAction(action: PolkadotTransactionAction): Promise { + return notImplemented() + // const decomposeActionFuncs = Object.values(DecomposeAction) + // const decomposedActions: AlgorandDecomposeReturn[] = [] + + // // interate over all possible decompose and return all that can be decomposed (i.e returns a chainActionType from decomposeFunc) + // await Promise.all( + // decomposeActionFuncs.map(async (decomposeFunc: any) => { + // try { + // const { chainActionType, args } = (await decomposeFunc(action)) || {} + // if (chainActionType) { + // decomposedActions.push({ chainActionType, args }) + // } + // } catch (err) { + // // console.log('problem in decomposeAction:', err) + // } + // }), + // ) + + // // return null and not an empty array if no matches + // return !isNullOrEmpty(decomposedActions) ? decomposedActions : null +} diff --git a/src/chains/polkadot_1/polkadotTransaction.ts b/src/chains/polkadot_1/polkadotTransaction.ts new file mode 100644 index 00000000..08196216 --- /dev/null +++ b/src/chains/polkadot_1/polkadotTransaction.ts @@ -0,0 +1,431 @@ +/* eslint-disable max-len */ +/* eslint-disable import/no-unresolved */ +/* eslint-disable @typescript-eslint/camelcase */ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable no-console */ +import { ChainFeature, ConfirmType, TxExecutionPriority } from '../../models' +import { notImplemented } from '../../helpers' +import { Transaction } from '../../interfaces' +import { PolkadotSignature } from './models/cryptoModels' +import { + PolkadotAddress, + PolkadotPublicKey, + PolkadotPrivateKey, + PolkadotChainSettingsCommunicationSettings, +} from './models' +import { + PolkadotAddressBuffer, + PolkadotTransactionOptions, + PolkadotRawTransaction, + PolkadotTransactionHeader, + PolkadotActionHelperInput, + PolkadotTransactionAction, + PolkadotTransactionCost, + PolkadotSetDesiredFeeOptions, + PolkadotTransactionResources, +} from './models/transactionModels' +import { PolkadotActionHelper } from './polkadotAction' +import { PolkadotChainState } from './polkadotChainState' + +export class PolkadotTransaction implements Transaction { + private _actionHelper: PolkadotActionHelper + + private _chainState: PolkadotChainState + + private _actualCost: string + + private _desiredFee: string + + /** estimated gas for transacton - encoded as string to handle big numbers */ + private _estimatedGas: string + + private _maxFeeIncreasePercentage: number + + private _isValidated: boolean + + private _options: PolkadotTransactionOptions + + private _signBuffer: Buffer + + constructor(chainState: PolkadotChainState, options?: PolkadotTransactionOptions) { + this._chainState = chainState + this._options = options + this.applyDefaultOptions() + } + + private applyDefaultOptions() { + notImplemented() + } + + /** Multisig transactions are not supported by ethereum */ + public get isMultiSig(): boolean { + return false + } + + /** Whether transaction has been validated - via validate() */ + get isValidated() { + return this._isValidated + } + + /** Address from which transaction is being sent- from action.from (if provided) or derived from attached signature */ + get senderAddress() { + notImplemented() + return '' + } + + /** Address retrieved from attached signature - Returns null if no signature attached */ + get signedByAddress(): PolkadotAddress { + notImplemented() + return null + } + + /** Public Key retrieved from attached signature - Returns null if no from value or signature attached */ + get signedByPublicKey(): PolkadotPublicKey { + notImplemented() + return null + } + + /** Header includes values included in transaction when sent to the chain + * These values are set by setRawProperties() is called since it includes gasPrice, gasLimit, etc. + */ + get header(): PolkadotTransactionHeader { + notImplemented() + return null + } + + /** Options provided when the transaction class was created */ + get options() { + return this._options + } + + /** Raw transaction body - all values are Buffer types */ + get raw(): PolkadotRawTransaction { + notImplemented() + return null + } + + /** Whether the raw transaction body has been set (via setting action or setFromRaw()) */ + get hasRaw(): boolean { + notImplemented() + return false + } + + /** Ethereum doesn't have any native multi-sig functionality */ + get supportsMultisigTransaction(): boolean { + return false + } + + /** + * Updates 'raw' transaction properties using the actions attached + * Creates and sets private _ethereumJsTx (web3 EthereumJsTx object) + * Also adds header values (nonce, gasPrice, gasLimit) if not already set in action + */ + private setRawProperties(): void { + notImplemented() + } + + /** update locally cached EthereumJsTx from action helper data */ + updateEthTxFromAction() { + notImplemented() + } + + /** + * Updates nonce and gas fees (if necessary) - these values must be present + */ + public async prepareToBeSigned(): Promise { + notImplemented() + return null + } + + /** Set the body of the transaction using Hex raw transaction data */ + async setFromRaw(raw: PolkadotActionHelperInput): Promise { + notImplemented() + return null + } + + /** Creates a sign buffer using raw transaction body */ + private setSignBuffer() { + notImplemented() + } + + /** calculates a unique nonce value for the tx (if not already set) by using the chain transaction count for a given address */ + async setNonceIfEmpty(fromAddress: PolkadotAddress | PolkadotAddressBuffer) { + notImplemented() + } + + /** Ethereum transaction action (transfer & contract functions) + * Returns null or an array with exactly one action + */ + public get actions(): PolkadotTransactionAction[] { + notImplemented() + return null + } + + /** Private property for the Ethereum contract action - uses _actionHelper */ + private get action(): PolkadotTransactionAction { + notImplemented() + return null + } + + /** Sets actions array + * Array length has to be exactly 1 because ethereum doesn't support multiple actions + */ + public set actions(actions: PolkadotTransactionAction[]) { + notImplemented() + } + + /** Add action to the transaction body + * throws if transaction.actions already has a value + * Ignores asFirstAction parameter since only one action is supported in ethereum */ + public addAction(action: PolkadotTransactionAction, asFirstAction?: boolean): void { + notImplemented() + } + + // validation + + /** Verifies that raw trx exists, sets nonce (using sender's address) if not already set + * Throws if any problems */ + public async validate(): Promise { + notImplemented() + } + + // signatures + + /** Get signature attached to transaction - returns null if no signature */ + get signatures(): PolkadotSignature[] { + notImplemented() + return null + } + + /** Sets the Set of signatures */ + set signatures(signatures: PolkadotSignature[]) { + notImplemented() + } + + /** Add signature to raw transaction - Accepts array with exactly one signature */ + addSignatures = (signatures: PolkadotSignature[]): void => { + notImplemented() + } + + /** Throws if signatures isn't properly formatted */ + private assertValidSignature = (signature: PolkadotSignature) => { + notImplemented() + } + + /** Whether there is an attached signature */ + get hasAnySignatures(): boolean { + notImplemented() + return false + } + + /** Throws if any signatures are attached */ + private assertNoSignatures() { + notImplemented() + } + + /** Throws if transaction is missing any signatures */ + private assertHasSignature(): void { + notImplemented() + } + + /** Whether there is an attached signature for the provided publicKey */ + public hasSignatureForPublicKey = (publicKey: PolkadotPublicKey): boolean => { + notImplemented() + return false + } + + /** Whether there is an attached signature for the publicKey of the address */ + public async hasSignatureForAuthorization(authorization: PolkadotAddress): Promise { + notImplemented() + return false + } + + /** Whether signature is attached to transaction (and/or whether the signature is correct) + * If a specific action.from is specifed, ensure that attached signature matches its address/public key */ + public get hasAllRequiredSignatures(): boolean { + notImplemented() + return false + } + + /** Throws if transaction is missing any signatures */ + private assertHasAllRequiredSignature(): void { + notImplemented() + } + + /** Returns address, for which, a matching signature must be attached to transaction + * ... always an array of length 1 because ethereum only supports one signature + * If no action.from is set, and no signature attached, throws an error since from addr cant be determined + * Throws if action.from is not a valid address */ + public get missingSignatures(): PolkadotAddress[] { + notImplemented() + return null + } + + // Fees + + /** Ethereum has a fee for transactions */ + public get supportsFee(): boolean { + return this._chainState.hasFeature(ChainFeature.TransactionFees) + } + + /** Gets estimated cost in units of gas to execute this transaction (at current chain rates) */ + public async resourcesRequired(): Promise { + notImplemented() + return null + } + + /** Gets the estimated cost for this transaction + * if refresh = true, get updated cost from chain */ + async getEstimatedGas(refresh: boolean = false) { + notImplemented() + return 0 + } + + /** Get the suggested Eth fee (in Ether) for this transaction */ + public async getSuggestedFee(priority: TxExecutionPriority = TxExecutionPriority.Average): Promise { + notImplemented() + return null + } + + /** get the desired fee (in Ether) to spend on sending the transaction */ + public async getDesiredFee(): Promise { + notImplemented() + return null + } + + /** set the fee that you would like to pay (in Ether) - this will set the gasPrice and gasLimit (based on maxFeeIncreasePercentage) + * If gasLimitOverride is provided, gasPrice will be calculated and gasLimit will be set to gasLimitOverride + * */ + public async setDesiredFee(desiredFee: string, options?: PolkadotSetDesiredFeeOptions) { + notImplemented() + } + + /** Hash of transaction - signature must be present to determine transactionId */ + public get transactionId(): string { + notImplemented() + return null + } + + /** get the actual cost (in Ether) for executing the transaction */ + public async getActualCost(): Promise { + notImplemented() + return null + } + + /** get the estimated cost for sending the transaction */ + public async getEstimatedCost(refresh: boolean = false): Promise { + notImplemented() + return null + } + + public get maxFeeIncreasePercentage(): number { + return this._maxFeeIncreasePercentage || 0 + } + + /** The maximum percentage increase over the desiredGas */ + public set maxFeeIncreasePercentage(percentage: number) { + notImplemented() + } + + /** throws if required fee properties aren't set */ + private assertHasFeeSetting(): void { + notImplemented() + } + + /** Get the execution priority for the transaction - higher value attaches more fees */ + public get executionPriority(): TxExecutionPriority { + notImplemented() + return null + } + + public set executionPriority(value: TxExecutionPriority) { + notImplemented() + } + + // Authorizations + + /** Returns address specified by actions[].from property + * throws if actions[].from is not a valid address - needed to determine the required signature */ + public get requiredAuthorizations(): PolkadotAddress[] { + notImplemented() + return null + } + + /** Return the one signature address required */ + private get requiredAuthorization(): PolkadotAddress { + notImplemented() + return null + } + + /** set transaction hash to sign */ + public get signBuffer(): Buffer { + notImplemented() + return null + } + + /** Sign the transaction body with private key and add to attached signatures */ + public async sign(privateKeys: PolkadotPrivateKey[]): Promise { + notImplemented() + return null + } + + // send + + /** Broadcast a signed transaction to the chain + * waitForConfirm specifies whether to wait for a transaction to appear in a block before returning */ + public async send( + waitForConfirm: ConfirmType = ConfirmType.None, + communicationSettings?: PolkadotChainSettingsCommunicationSettings, + ): Promise { + notImplemented() + return null + } + + // helpers + + /** Throws if not yet connected to chain - via chain.connect() */ + private assertIsConnected(): void { + notImplemented() + } + + /** Throws if not validated */ + private assertIsValidated(): void { + notImplemented() + } + + /** Whether action.from (if present) is a valid ethereum address - also checks that from is provided if data was */ + private assertFromIsValid(): void { + notImplemented() + } + + /** Throws if an action isn't attached to this transaction */ + private assertHasAction() { + notImplemented() + } + + /** Throws if no raw transaction body */ + private assertHasRaw(): void { + notImplemented() + } + + private isFromAValidAddressOrEmpty(): boolean { + notImplemented() + return false + } + + /** Whether the from address is null or empty */ + private isFromEmptyOrNullAddress(): boolean { + notImplemented() + return false + } + + getOptionsForEthereumJsTx() { + notImplemented() + return '' + } + + /** JSON representation of transaction data */ + public toJson(): any { + return { header: this.header, actions: this.actions, raw: this.raw, signatures: this.signatures } + } +} diff --git a/src/examples/multiplechains.ts b/src/examples/multiplechains.ts index 9448b66a..d2a1b484 100644 --- a/src/examples/multiplechains.ts +++ b/src/examples/multiplechains.ts @@ -5,7 +5,14 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable no-console */ import { ChainFactory, Chain } from '../index' -import { ChainActionType, ChainEndpoint, ConfirmType, ChainEntityNameBrand, ChainType } from '../models' +import { + ChainActionType, + ChainEndpoint, + ConfirmType, + ChainEntityNameBrand, + ChainType, + TxExecutionPriority, +} from '../models' import { EthereumChainForkType } from '../chains/ethereum_1/models' require('dotenv').config() @@ -31,6 +38,12 @@ const ropstenChainOptions: EthereumChainForkType = { hardFork: 'istanbul', } +const westendEndpoints: ChainEndpoint[] = [ + { + url: 'wss://westend-rpc.polkadot.io', + }, +] + // Example set of options to send tokens for each chain type const chainSendTokenData = { eos: { @@ -77,6 +90,8 @@ const chainSendCurrencyData = { async function sendToken(chain: Chain, options: any) { const sendTokenTx = chain.new.Transaction() sendTokenTx.actions = [await chain.composeAction(ChainActionType.TokenTransfer, options.composeTokenTransferParams)] + const fee = await sendTokenTx.getSuggestedFee(TxExecutionPriority.Fast) + await sendTokenTx.setDesiredFee(fee) await sendTokenTx.prepareToBeSigned() await sendTokenTx.validate() await sendTokenTx.sign([options.privateKey]) @@ -87,7 +102,13 @@ async function sendToken(chain: Chain, options: any) { /** Send 'cryptocurrency' (value) between accounts on the chain */ async function sendCurrency(chain: Chain, options: any) { const sendCurrencyTx = chain.new.Transaction() - sendCurrencyTx.actions = [await chain.composeAction(ChainActionType.ValueTransfer, options.composeValueTransferParams)] + sendCurrencyTx.actions = [ + await chain.composeAction(ChainActionType.ValueTransfer, options.composeValueTransferParams), + ] + if (sendCurrencyTx.supportsFee) { + const fee = await sendCurrencyTx.getSuggestedFee(TxExecutionPriority.Fast) + await sendCurrencyTx.setDesiredFee(fee) + } await sendCurrencyTx.prepareToBeSigned() await sendCurrencyTx.validate() await sendCurrencyTx.sign([options.privateKey]) @@ -95,11 +116,39 @@ async function sendCurrency(chain: Chain, options: any) { return response } +/** Send 'cryptocurrency' (value) between accounts on the chain */ +async function createAccount(chain: Chain, options: any) { + // const sendCurrencyTx = chain.new.Transaction() + // sendCurrencyTx.actions = [await chain.composeAction(ChainActionType.ValueTransfer, options.composeValueTransferParams)] + // const fee = await sendCurrencyTx.getSuggestedFee(TxExecutionPriority.Fast) + // await sendCurrencyTx.setDesiredFee(fee) + // await sendCurrencyTx.prepareToBeSigned() + // await sendCurrencyTx.validate() + // await sendCurrencyTx.sign([options.privateKey]) + // const response = await sendCurrencyTx.send(ConfirmType.None) + // return response + const createAccountOptions = { + newKeysOptions: { + password: '2233', + salt: env.EOS_KYLIN_PK_SALT_V0, + }, + } + + const accountCreate = chain.new.CreateAccount(createAccountOptions) + await accountCreate.generateKeysIfNeeded() + console.log('generatedKeys:', accountCreate.generatedKeys) + console.log('address:', accountCreate.accountName) + const { password, salt } = createAccountOptions.newKeysOptions + // const decryptedPrivateKey = chain.decryptWithPassword(createAccount.generatedKeys.privateKey, password, { salt }) + // console.log('decrypted privateKey: ', decryptedPrivateKey) +} + /** Run the same functions (e.g. transfer a token) for one or more chains using the same code */ async function runFunctionsForMultipleChains() { const chains = [ new ChainFactory().create(ChainType.EosV2, kylinEndpoints), new ChainFactory().create(ChainType.EthereumV1, ropstenEndpoints, { chainForkType: ropstenChainOptions }), + new ChainFactory().create(ChainType.PolkadotV1, westendEndpoints), ] // for each chain, connect to its network (to make sure the endpoint is available) @@ -110,24 +159,24 @@ async function runFunctionsForMultipleChains() { ) // Send Tokens - for each chain, we'll get token option and call the generic sendToken function - await Promise.all( - chains.map(async chain => { - const {chainType} = chain - const tokenData = chainType === ChainType.EosV2 ? chainSendTokenData.eos : chainSendTokenData.ethereum - const response = await sendToken(chain, tokenData) - console.log(`---> sendToken ${chain.chainType} response:`, JSON.stringify(response)) - }), - ) + // await Promise.all( + // chains.map(async chain => { + // const {chainType} = chain + // const tokenData = chainType === ChainType.EosV2 ? chainSendTokenData.eos : chainSendTokenData.ethereum + // const response = await sendToken(chain, tokenData) + // console.log(`---> sendToken ${chain.chainType} response:`, JSON.stringify(response)) + // }), + // ) // Send 'Currency' - for each chain, sends the native currency for the chain (e.g. 'eth' for Ethereum) - await Promise.all( - chains.map(async chain => { - const {chainType} = chain - const currencyData = chainType === ChainType.EosV2 ? chainSendCurrencyData.eos : chainSendCurrencyData.ethereum - const response = await sendCurrency(chain, currencyData) - console.log(`---> sendCurrency ${chain.chainType} response:`, JSON.stringify(response)) - }), - ) + // await Promise.all( + // chains.map(async chain => { + // const {chainType} = chain + // const currencyData = chainType === ChainType.EosV2 ? chainSendCurrencyData.eos : chainSendCurrencyData.ethereum + // const response = await sendCurrency(chain, currencyData) + // console.log(`---> sendCurrency ${chain.chainType} response:`, JSON.stringify(response)) + // }), + // ) } /** Run the example code automatically */ diff --git a/src/helpers.ts b/src/helpers.ts index fbc6e744..bb7d011f 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -273,6 +273,11 @@ export function hasHexPrefix(value: any): boolean { return isAString(value) && (value as string).startsWith('0x') } +/** Return true if value is a hexidecimal encoded string (is prefixed by 0x) */ +export function isHexString(value: any): boolean { + return hasHexPrefix(value) +} + /** Checks that string starts with 0x - appends if not * Also converts hex chars to lowercase for consistency */ diff --git a/src/index.ts b/src/index.ts index 570b0abb..99cda229 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,6 +11,7 @@ import * as Models from './models' import * as ModelsAlgorand from './chains/algorand_1/models' import * as ModelsEos from './chains/eos_2/models' import * as ModelsEthereum from './chains/ethereum_1/models' +import * as ModelsPolkadot from './chains/polkadot_1/models' import * as HelpersAlgorand from './chains/algorand_1/helpers' import * as HelpersEos from './chains/eos_2/helpers' import * as HelpersEthereum from './chains/ethereum_1/helpers' @@ -37,5 +38,6 @@ export { ModelsAlgorand, ModelsEos, ModelsEthereum, + ModelsPolkadot, Transaction, } diff --git a/src/models/cryptoModels.ts b/src/models/cryptoModels.ts index d5b8f3bd..7b1234db 100644 --- a/src/models/cryptoModels.ts +++ b/src/models/cryptoModels.ts @@ -41,8 +41,9 @@ type AccountKeysStruct = { } enum CryptoCurve { - Secp256k1 = 'secp256k1', Ed25519 = 'ed25519', + Secp256k1 = 'secp256k1', + Sr25519 = 'sr25519', } // exporting explicity in order to alias Models.. exports diff --git a/src/models/generalModels.ts b/src/models/generalModels.ts index a3323a83..c5b7492c 100644 --- a/src/models/generalModels.ts +++ b/src/models/generalModels.ts @@ -39,6 +39,15 @@ export enum ChainType { EosV2 = 'eos', EthereumV1 = 'ethereum', AlgorandV1 = 'algorand', + PolkadotV1 = 'polkadot', +} + +/** Standard 'features' that each type of chain supports */ +export const enum ChainFeature { + Account = 'accounts', + AccountCreationTransaction = 'accountCreationTransaction', + Multisig = 'multisig', + TransactionFees = 'transactionFees', } /** Chain urls and related details used to connect to chain */