diff --git a/.husky/pre-commit b/.husky/pre-commit index 1cb0ff5a4..af2e3d497 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,6 +1,6 @@ #!/bin/sh . "$(dirname "$0")/_/husky.sh" -npm i --no-save prettier pretty-quick +npm i --no-save prettier@2.8.8 pretty-quick npx pretty-quick --staged diff --git a/Dockerfile b/Dockerfile index ade6d683d..5837d3d32 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,12 @@ COPY packages/ethereum/scripts /app/packages/ethereum/scripts COPY packages/groestlcoin/package.json /app/packages/groestlcoin/ COPY packages/groestlcoin/scripts /app/packages/groestlcoin/scripts +COPY packages/icp/package.json /app/packages/icp/ +COPY packages/icp/scripts /app/packages/icp/scripts + +COPY packages/mina/package.json /app/packages/mina/ +COPY packages/mina/scripts /app/packages/mina/scripts + COPY packages/module-kit/package.json /app/packages/module-kit/ COPY packages/module-kit/scripts /app/packages/module-kit/scripts @@ -56,9 +62,6 @@ COPY packages/tezos/scripts /app/packages/tezos/scripts COPY packages/serializer/package.json /app/packages/serializer/ COPY packages/serializer/scripts /app/packages/serializer/scripts -COPY packages/icp/package.json /app/packages/icp/ -COPY packages/icp/scripts /app/packages/icp/scripts - COPY lerna.json /app # install dependencies diff --git a/lerna.json b/lerna.json index 8b813e5fd..2a8ec6667 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "0.13.18", + "version": "0.13.19", "useWorkspaces": true, "command": { "publish": { diff --git a/package-lock.json b/package-lock.json index 385be3690..7a6893cf7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,6 +64,10 @@ "resolved": "packages/icp", "link": true }, + "node_modules/@airgap/mina": { + "resolved": "packages/mina", + "link": true + }, "node_modules/@airgap/module-kit": { "resolved": "packages/module-kit", "link": true @@ -415,6 +419,14 @@ "dev": true, "license": "MIT" }, + "node_modules/@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==", + "peerDependencies": { + "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, "node_modules/@hutson/parse-repository-url": { "version": "3.0.2", "dev": true, @@ -910,26 +922,6 @@ "node": "^14.15.0 || >=16.0.0" } }, - "node_modules/@lerna/gitlab-client/node_modules/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/@lerna/global-options": { "version": "6.4.1", "dev": true, @@ -2391,26 +2383,6 @@ "node": ">= 14" } }, - "node_modules/@octokit/request/node_modules/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/@octokit/rest": { "version": "19.0.11", "dev": true, @@ -4188,6 +4160,14 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, + "node_modules/cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "dev": true, @@ -5639,6 +5619,26 @@ "dev": true, "license": "ISC" }, + "node_modules/graphql": { + "version": "16.7.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.7.1.tgz", + "integrity": "sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==", + "engines": { + "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" + } + }, + "node_modules/graphql-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-6.1.0.tgz", + "integrity": "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==", + "dependencies": { + "@graphql-typed-document-node/core": "^3.2.0", + "cross-fetch": "^3.1.5" + }, + "peerDependencies": { + "graphql": "14 - 16" + } + }, "node_modules/handlebars": { "version": "4.7.7", "dev": true, @@ -5880,6 +5880,8 @@ }, "node_modules/husky": { "version": "5.1.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-5.1.3.tgz", + "integrity": "sha512-fbNJ+Gz5wx2LIBtMweJNY1D7Uc8p1XERi5KNRMccwfQA+rXlxWNSdUxswo0gT8XqxywTIw7Ywm/F4v/O35RdMg==", "dev": true, "funding": [ { @@ -5891,7 +5893,6 @@ "url": "https://opencollective.com/husky" } ], - "license": "Parity-7.0.0 AND MIT WITH Patron-1.0.0", "bin": { "husky": "lib/bin.js" }, @@ -7218,6 +7219,15 @@ "node": ">=4" } }, + "node_modules/mina-signer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mina-signer/-/mina-signer-2.1.0.tgz", + "integrity": "sha512-IYSAfkQyd2Qewc4Mf5mTSgz+nAJjJn8ShsHx+BI6gpPLJSlkujdkBEX3iUBKctOeC0clW8tsDosCrkiww2i/kA==", + "dependencies": { + "blakejs": "^1.2.1", + "js-sha256": "^0.9.0" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "license": "ISC" @@ -7477,6 +7487,25 @@ "dev": true, "license": "MIT" }, + "node_modules/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/node-gyp": { "version": "9.3.1", "dev": true, @@ -10847,37 +10876,37 @@ }, "packages/aeternity": { "name": "@airgap/aeternity", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@stablelib/ed25519": "^1.0.3" } }, "packages/astar": { "name": "@airgap/astar", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", - "@airgap/substrate": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", + "@airgap/substrate": "^0.13.19", "@polkadot/util": "2.0.1", "@polkadot/wasm-crypto": "0.20.1" } }, "packages/bitcoin": { "name": "@airgap/bitcoin", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "bitcoinjs-lib": "5.2.0" } }, @@ -11012,7 +11041,7 @@ }, "packages/core": { "name": "@airgap/coinlib-core", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { "@stablelib/blake2b": "^1.0.1", @@ -11176,46 +11205,46 @@ }, "packages/coreum": { "name": "@airgap/coreum", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/cosmos-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/cosmos-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19" } }, "packages/cosmos": { "name": "@airgap/cosmos", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/cosmos-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/cosmos-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19" } }, "packages/cosmos-core": { "name": "@airgap/cosmos-core", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19" } }, "packages/crypto": { "name": "@airgap/crypto", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", "@airgap/sapling-wasm": "^0.0.9", "@polkadot/wasm-crypto": "0.20.1", "@stablelib/hmac": "^1.0.1" @@ -11223,13 +11252,13 @@ }, "packages/ethereum": { "name": "@airgap/ethereum", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@ethereumjs/common": "2.6.5", "@ethereumjs/tx": "3.4.0", "@metamask/eth-sig-util": "4.0.0" @@ -11237,24 +11266,24 @@ }, "packages/groestlcoin": { "name": "@airgap/groestlcoin", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/bitcoin": "^0.13.18", - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/bitcoin": "^0.13.19", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19" } }, "packages/icp": { "name": "@airgap/icp", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@dfinity/agent": "^0.15.4", "@dfinity/identity-secp256k1": "^0.15.4", "@dfinity/nns": "^0.14.0", @@ -11281,25 +11310,6 @@ "version": "5.1.0", "license": "MIT" }, - "packages/icp/node_modules/node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "packages/icp/node_modules/secp256k1": { "version": "5.0.0", "hasInstallScript": true, @@ -11317,47 +11327,61 @@ "version": "3.6.2", "license": "MIT" }, + "packages/mina": { + "name": "@airgap/mina", + "version": "0.13.19", + "license": "MIT", + "dependencies": { + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", + "graphql": "^16.7.1", + "graphql-request": "^6.1.0", + "mina-signer": "^2.0.3" + } + }, "packages/module-kit": { "name": "@airgap/module-kit", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/serializer": "^0.13.19" } }, "packages/moonbeam": { "name": "@airgap/moonbeam", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", - "@airgap/substrate": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", + "@airgap/substrate": "^0.13.19" } }, "packages/optimism": { "name": "@airgap/optimism", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/ethereum": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/ethereum": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@ethereumjs/tx": "3.4.0" } }, "packages/polkadot": { "name": "@airgap/polkadot", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/substrate": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/substrate": "^0.13.19" }, "devDependencies": { "@polkadot/wasm-crypto": "0.20.1" @@ -11365,35 +11389,35 @@ }, "packages/serializer": { "name": "@airgap/serializer", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19" } }, "packages/substrate": { "name": "@airgap/substrate", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@polkadot/util": "2.0.1", "@polkadot/wasm-crypto": "0.20.1" } }, "packages/tezos": { "name": "@airgap/tezos", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", "@airgap/sapling-wasm": "0.0.7", - "@airgap/serializer": "^0.13.18", + "@airgap/serializer": "^0.13.19", "@stablelib/blake2b": "^1.0.1", "@stablelib/ed25519": "^1.0.3", "@stablelib/nacl": "^1.0.4", @@ -11407,13 +11431,6 @@ "packages/tezos/node_modules/@airgap/sapling-wasm": { "version": "0.0.7" }, - "packages/tezos/node_modules/@graphql-typed-document-node/core": { - "version": "3.1.1", - "license": "MIT", - "peerDependencies": { - "graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" - } - }, "packages/tezos/node_modules/@stablelib/blake2b": { "version": "1.0.1", "license": "MIT", @@ -11546,13 +11563,6 @@ "ieee754": "^1.2.1" } }, - "packages/tezos/node_modules/cross-fetch": { - "version": "3.1.5", - "license": "MIT", - "dependencies": { - "node-fetch": "2.6.7" - } - }, "packages/tezos/node_modules/extract-files": { "version": "9.0.0", "license": "MIT", @@ -11575,13 +11585,6 @@ "node": ">= 6" } }, - "packages/tezos/node_modules/graphql": { - "version": "16.6.0", - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" - } - }, "packages/tezos/node_modules/graphql-request": { "version": "5.1.0", "license": "MIT", @@ -11595,24 +11598,6 @@ "graphql": "14 - 16" } }, - "packages/tezos/node_modules/node-fetch": { - "version": "2.6.7", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "packages/tezos/node_modules/typedarray-to-buffer": { "version": "4.0.0", "funding": [ @@ -11633,11 +11618,11 @@ }, "packages/wallet": { "name": "@airgap/wallet", - "version": "0.13.18", + "version": "0.13.19", "license": "MIT", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19" } } }, @@ -11645,19 +11630,19 @@ "@airgap/aeternity": { "version": "file:packages/aeternity", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@stablelib/ed25519": "^1.0.3" } }, "@airgap/astar": { "version": "file:packages/astar", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", - "@airgap/substrate": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", + "@airgap/substrate": "^0.13.19", "@polkadot/util": "2.0.1", "@polkadot/wasm-crypto": "0.20.1" } @@ -11665,10 +11650,10 @@ "@airgap/bitcoin": { "version": "file:packages/bitcoin", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "bitcoinjs-lib": "5.2.0" }, "dependencies": { @@ -11914,37 +11899,37 @@ "@airgap/coreum": { "version": "file:packages/coreum", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/cosmos-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/cosmos-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19" } }, "@airgap/cosmos": { "version": "file:packages/cosmos", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/cosmos-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/cosmos-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19" } }, "@airgap/cosmos-core": { "version": "file:packages/cosmos-core", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19" } }, "@airgap/crypto": { "version": "file:packages/crypto", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", "@airgap/sapling-wasm": "^0.0.9", "@polkadot/wasm-crypto": "0.20.1", "@stablelib/hmac": "^1.0.1" @@ -11953,10 +11938,10 @@ "@airgap/ethereum": { "version": "file:packages/ethereum", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@ethereumjs/common": "2.6.5", "@ethereumjs/tx": "3.4.0", "@metamask/eth-sig-util": "4.0.0" @@ -11965,19 +11950,19 @@ "@airgap/groestlcoin": { "version": "file:packages/groestlcoin", "requires": { - "@airgap/bitcoin": "^0.13.18", - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/bitcoin": "^0.13.19", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19" } }, "@airgap/icp": { "version": "file:packages/icp", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@dfinity/agent": "^0.15.4", "@dfinity/identity-secp256k1": "^0.15.4", "@dfinity/nns": "^0.14.0", @@ -12002,14 +11987,6 @@ "node-addon-api": { "version": "5.1.0" }, - "node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "requires": { - "whatwg-url": "^5.0.0" - } - }, "secp256k1": { "version": "5.0.0", "requires": { @@ -12023,39 +12000,51 @@ } } }, + "@airgap/mina": { + "version": "file:packages/mina", + "requires": { + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", + "graphql": "^16.7.1", + "graphql-request": "^6.1.0", + "mina-signer": "^2.0.3" + } + }, "@airgap/module-kit": { "version": "file:packages/module-kit", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/serializer": "^0.13.19" } }, "@airgap/moonbeam": { "version": "file:packages/moonbeam", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", - "@airgap/substrate": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", + "@airgap/substrate": "^0.13.19" } }, "@airgap/optimism": { "version": "file:packages/optimism", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/ethereum": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/ethereum": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@ethereumjs/tx": "3.4.0" } }, "@airgap/polkadot": { "version": "file:packages/polkadot", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/substrate": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/substrate": "^0.13.19", "@polkadot/wasm-crypto": "0.20.1" } }, @@ -12065,16 +12054,16 @@ "@airgap/serializer": { "version": "file:packages/serializer", "requires": { - "@airgap/coinlib-core": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19" } }, "@airgap/substrate": { "version": "file:packages/substrate", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@polkadot/util": "2.0.1", "@polkadot/wasm-crypto": "0.20.1" } @@ -12082,11 +12071,11 @@ "@airgap/tezos": { "version": "file:packages/tezos", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", "@airgap/sapling-wasm": "0.0.7", - "@airgap/serializer": "^0.13.18", + "@airgap/serializer": "^0.13.19", "@stablelib/blake2b": "^1.0.1", "@stablelib/ed25519": "^1.0.3", "@stablelib/nacl": "^1.0.4", @@ -12100,9 +12089,6 @@ "@airgap/sapling-wasm": { "version": "0.0.7" }, - "@graphql-typed-document-node/core": { - "version": "3.1.1" - }, "@stablelib/blake2b": { "version": "1.0.1", "requires": { @@ -12201,12 +12187,6 @@ "ieee754": "^1.2.1" } }, - "cross-fetch": { - "version": "3.1.5", - "requires": { - "node-fetch": "2.6.7" - } - }, "extract-files": { "version": "9.0.0" }, @@ -12218,9 +12198,6 @@ "mime-types": "^2.1.12" } }, - "graphql": { - "version": "16.6.0" - }, "graphql-request": { "version": "5.1.0", "requires": { @@ -12230,12 +12207,6 @@ "form-data": "^3.0.0" } }, - "node-fetch": { - "version": "2.6.7", - "requires": { - "whatwg-url": "^5.0.0" - } - }, "typedarray-to-buffer": { "version": "4.0.0" } @@ -12244,8 +12215,8 @@ "@airgap/wallet": { "version": "file:packages/wallet", "requires": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19" } }, "@babel/code-frame": { @@ -12446,6 +12417,11 @@ "version": "1.1.3", "dev": true }, + "@graphql-typed-document-node/core": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz", + "integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==" + }, "@hutson/parse-repository-url": { "version": "3.0.2", "dev": true @@ -12815,17 +12791,6 @@ "requires": { "node-fetch": "^2.6.1", "npmlog": "^6.0.2" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - } } }, "@lerna/global-options": { @@ -13835,17 +13800,6 @@ "is-plain-object": "^5.0.0", "node-fetch": "^2.6.7", "universal-user-agent": "^6.0.0" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - } } }, "@octokit/request-error": { @@ -15148,6 +15102,14 @@ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" }, + "cross-fetch": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", + "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", + "requires": { + "node-fetch": "^2.6.12" + } + }, "cross-spawn": { "version": "7.0.3", "dev": true, @@ -16147,6 +16109,20 @@ "version": "4.2.11", "dev": true }, + "graphql": { + "version": "16.7.1", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.7.1.tgz", + "integrity": "sha512-DRYR9tf+UGU0KOsMcKAlXeFfX89UiiIZ0dRU3mR0yJfu6OjZqUcp68NnFLnqQU5RexygFoDy1EW+ccOYcPfmHg==" + }, + "graphql-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-6.1.0.tgz", + "integrity": "sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==", + "requires": { + "@graphql-typed-document-node/core": "^3.2.0", + "cross-fetch": "^3.1.5" + } + }, "handlebars": { "version": "4.7.7", "dev": true, @@ -16305,6 +16281,8 @@ }, "husky": { "version": "5.1.3", + "resolved": "https://registry.npmjs.org/husky/-/husky-5.1.3.tgz", + "integrity": "sha512-fbNJ+Gz5wx2LIBtMweJNY1D7Uc8p1XERi5KNRMccwfQA+rXlxWNSdUxswo0gT8XqxywTIw7Ywm/F4v/O35RdMg==", "dev": true }, "iconv-lite": { @@ -17174,6 +17152,15 @@ "version": "1.0.1", "dev": true }, + "mina-signer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mina-signer/-/mina-signer-2.1.0.tgz", + "integrity": "sha512-IYSAfkQyd2Qewc4Mf5mTSgz+nAJjJn8ShsHx+BI6gpPLJSlkujdkBEX3iUBKctOeC0clW8tsDosCrkiww2i/kA==", + "requires": { + "blakejs": "^1.2.1", + "js-sha256": "^0.9.0" + } + }, "minimalistic-assert": { "version": "1.0.1" }, @@ -17345,6 +17332,14 @@ "version": "3.2.1", "dev": true }, + "node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, "node-gyp": { "version": "9.3.1", "dev": true, diff --git a/packages/aeternity/package.json b/packages/aeternity/package.json index ed248f20c..80755dde8 100644 --- a/packages/aeternity/package.json +++ b/packages/aeternity/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/aeternity", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/aeternity is an Aeternity implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -30,9 +30,9 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@stablelib/ed25519": "^1.0.3" }, "localDependencies": {}, diff --git a/packages/astar/package.json b/packages/astar/package.json index c21071872..32c44cc67 100644 --- a/packages/astar/package.json +++ b/packages/astar/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/astar", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/astar is an Astar implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -32,10 +32,10 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", - "@airgap/substrate": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", + "@airgap/substrate": "^0.13.19", "@polkadot/util": "2.0.1", "@polkadot/wasm-crypto": "0.20.1" }, diff --git a/packages/bitcoin/package.json b/packages/bitcoin/package.json index 611bd6af9..e8fa1b2a6 100644 --- a/packages/bitcoin/package.json +++ b/packages/bitcoin/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/bitcoin", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/bitcoin is a Bitcoin implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -30,10 +30,10 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "bitcoinjs-lib": "5.2.0" }, "localDependencies": { diff --git a/packages/bitcoin/src/v1/protocol/BitcoinSegwitProtocol.ts b/packages/bitcoin/src/v1/protocol/BitcoinSegwitProtocol.ts index 50ed989d8..d281090fe 100644 --- a/packages/bitcoin/src/v1/protocol/BitcoinSegwitProtocol.ts +++ b/packages/bitcoin/src/v1/protocol/BitcoinSegwitProtocol.ts @@ -104,7 +104,7 @@ export class BitcoinSegwitProtocolImpl implements BitcoinSegwitProtocol { standardDerivationPath: `m/84'/0'/0'`, address: { ...(this.legacy.metadata.account?.address ?? {}), - regex: 'bc1...' + regex: '^(?:[13]{1}[a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-z0-9]{39,59})$' } } } diff --git a/packages/core/package.json b/packages/core/package.json index 5acd826f7..45cf66d32 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/coinlib-core", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/coinlib-core is a protocol agnostic library to prepare, sign and broadcast cryptocurrency transactions.", "keywords": [ "airgap", diff --git a/packages/core/src/errors/coinlib-error.ts b/packages/core/src/errors/coinlib-error.ts index efd11c0a8..e28eb7762 100644 --- a/packages/core/src/errors/coinlib-error.ts +++ b/packages/core/src/errors/coinlib-error.ts @@ -15,7 +15,8 @@ export enum Domain { ACTIONS = 'ACTIONS', ICP = 'ICP', COREUM = 'COREUM', - OPTIMISM = 'OPTIMISM' + OPTIMISM = 'OPTIMISM', + MINA = 'MINA' } export class CoinlibError extends Error { diff --git a/packages/core/src/utils/ProtocolSymbols.ts b/packages/core/src/utils/ProtocolSymbols.ts index 5bc15e9b9..1dcb02c89 100644 --- a/packages/core/src/utils/ProtocolSymbols.ts +++ b/packages/core/src/utils/ProtocolSymbols.ts @@ -17,7 +17,8 @@ export enum MainProtocolSymbols { ICP = 'icp', ICP_CKBTC = 'icp_ckbtc', COREUM = 'coreum', - OPTIMISM = 'optimism' + OPTIMISM = 'optimism', + MINA = 'mina' } export enum SubProtocolSymbols { @@ -32,6 +33,7 @@ export enum SubProtocolSymbols { XTZ_W = 'xtz-w', XTZ_UDEFI = 'xtz-udefi', XTZ_UBTC = 'xtz-ubtc', + XTZ_UXTZ = 'xtz-uxtz', XTZ_CTEZ = 'xtz-ctez', XTZ_PLENTY = 'xtz-plenty', XTZ_WRAP = 'xtz-wrap', diff --git a/packages/coreum/package.json b/packages/coreum/package.json index 2c8429dbb..1228b997b 100644 --- a/packages/coreum/package.json +++ b/packages/coreum/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/coreum", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/coreum is a Coreum implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -30,11 +30,11 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/cosmos-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/cosmos-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19" }, "nyc": { "include": [ diff --git a/packages/cosmos-core/package.json b/packages/cosmos-core/package.json index c1a6d3747..847ca8957 100644 --- a/packages/cosmos-core/package.json +++ b/packages/cosmos-core/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/cosmos-core", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/cosmos-core is a Cosmos base implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -30,10 +30,10 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19" }, "nyc": { "include": [ diff --git a/packages/cosmos/package.json b/packages/cosmos/package.json index e7afaf66f..f93cafa87 100644 --- a/packages/cosmos/package.json +++ b/packages/cosmos/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/cosmos", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/cosmos is a Cosmos implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -30,11 +30,11 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/cosmos-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/cosmos-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19" }, "localDependencies": {}, "nyc": { diff --git a/packages/cosmos/src/v0/protocol/CosmosProtocol.ts b/packages/cosmos/src/v0/protocol/CosmosProtocol.ts index e27ba09b9..3dc56ec62 100644 --- a/packages/cosmos/src/v0/protocol/CosmosProtocol.ts +++ b/packages/cosmos/src/v0/protocol/CosmosProtocol.ts @@ -88,7 +88,7 @@ export class CosmosProtocol extends NonExtendedProtocol implements ICoinDelegate public addressValidationPattern: string = '^(cosmos|cosmosvaloper)[a-zA-Z0-9]{39}$' public addressPlaceholder: string = 'cosmos...' - private readonly defaultGas: BigNumber = new BigNumber('200000') + private readonly defaultGas: BigNumber = new BigNumber('310000') public readonly cryptoClient: CosmosCryptoClient = new CosmosCryptoClient() diff --git a/packages/cosmos/src/v1/protocol/CosmosProtocol.ts b/packages/cosmos/src/v1/protocol/CosmosProtocol.ts index e3ab6eace..d4775ed4c 100644 --- a/packages/cosmos/src/v1/protocol/CosmosProtocol.ts +++ b/packages/cosmos/src/v1/protocol/CosmosProtocol.ts @@ -16,7 +16,7 @@ import { CosmosBaseProtocolImpl, CosmosProtocolNetwork, CosmosProtocolOptions, C export type CosmosDenom = 'atom' | 'uatom' // Implementation -const DEFAULT_GAS: Amount = newAmount('200000', 'blockchain') +const DEFAULT_GAS: Amount = newAmount('310000', 'blockchain') export interface CosmosProtocol extends CosmosBaseStakingProtocol {} diff --git a/packages/cosmos/test/v0/specs/cosmos.ts b/packages/cosmos/test/v0/specs/cosmos.ts index d5a2cc988..2aafa105a 100644 --- a/packages/cosmos/test/v0/specs/cosmos.ts +++ b/packages/cosmos/test/v0/specs/cosmos.ts @@ -45,14 +45,14 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { from: ['cosmos1w3mea9ghfdc3r7ax45mehl2tcqw9p0vnlhl0p6'], unsignedTx: new CosmosTransaction( [cosmosMessage], - new CosmosFee([new CosmosCoin('uatom', '1')], '200000'), + new CosmosFee([new CosmosCoin('uatom', '1')], '310000'), '', 'cosmoshub-3', '0', '0' ), signedTx: - 'Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKLWNvc21vczF3M21lYTlnaGZkYzNyN2F4NDVtZWhsMnRjcXc5cDB2bmxobDBwNhItY29zbW9zMXczbWVhOWdoZmRjM3I3YXg0NW1laGwydGNxdzlwMHZubGhsMHA2GgoKBXVhdG9tEgEyEmIKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPfff5bQ1+WAnM35SNBfgfM5hqkufUbuTqsvftUpwooqhIECgIIARIQCgoKBXVhdG9tEgExEMCaDBpAqMbDSuzDmyX/7MqXfUYsZUNmlbiXPNUdbTAd7rGn7sld/SqV2LzAXPVtsqYptNYfCYtNVpnhIVQSUp33UD/L1g==' + 'Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKLWNvc21vczF3M21lYTlnaGZkYzNyN2F4NDVtZWhsMnRjcXc5cDB2bmxobDBwNhItY29zbW9zMXczbWVhOWdoZmRjM3I3YXg0NW1laGwydGNxdzlwMHZubGhsMHA2GgoKBXVhdG9tEgEyEmIKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPfff5bQ1+WAnM35SNBfgfM5hqkufUbuTqsvftUpwooqhIECgIIARIQCgoKBXVhdG9tEgExEPD1EhpAVZB7sqYkqBNne0OpaJZ9ylXOCulitm3VwYu5D+6ZPslEWkarEYUxPp3xFlUMA+rwD0n6zzo5D/bxoGvezMs0NQ==' } ] public validRawTransactions: any[] = [ @@ -62,7 +62,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { new CosmosCoin('uatom', '2') ]) ], - new CosmosFee([new CosmosCoin('uatom', '1')], '200000'), + new CosmosFee([new CosmosCoin('uatom', '1')], '310000'), '', 'cosmoshub-3', '0', diff --git a/packages/cosmos/test/v1/specs/cosmos.ts b/packages/cosmos/test/v1/specs/cosmos.ts index fd52fd1aa..36d2c22e4 100644 --- a/packages/cosmos/test/v1/specs/cosmos.ts +++ b/packages/cosmos/test/v1/specs/cosmos.ts @@ -64,7 +64,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { type: 'unsigned', ...new CosmosTransaction( [cosmosMessage], - new CosmosFee([new CosmosCoin('uatom', '1')], '200000'), + new CosmosFee([new CosmosCoin('uatom', '1')], '310000'), '', 'cosmoshub-3', '0', @@ -74,7 +74,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { signedTx: { type: 'signed', encoded: - 'Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKLWNvc21vczF3M21lYTlnaGZkYzNyN2F4NDVtZWhsMnRjcXc5cDB2bmxobDBwNhItY29zbW9zMXczbWVhOWdoZmRjM3I3YXg0NW1laGwydGNxdzlwMHZubGhsMHA2GgoKBXVhdG9tEgEyEmIKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPfff5bQ1+WAnM35SNBfgfM5hqkufUbuTqsvftUpwooqhIECgIIARIQCgoKBXVhdG9tEgExEMCaDBpAqMbDSuzDmyX/7MqXfUYsZUNmlbiXPNUdbTAd7rGn7sld/SqV2LzAXPVtsqYptNYfCYtNVpnhIVQSUp33UD/L1g==' + 'Co0BCooBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmoKLWNvc21vczF3M21lYTlnaGZkYzNyN2F4NDVtZWhsMnRjcXc5cDB2bmxobDBwNhItY29zbW9zMXczbWVhOWdoZmRjM3I3YXg0NW1laGwydGNxdzlwMHZubGhsMHA2GgoKBXVhdG9tEgEyEmIKTgpGCh8vY29zbW9zLmNyeXB0by5zZWNwMjU2azEuUHViS2V5EiMKIQPfff5bQ1+WAnM35SNBfgfM5hqkufUbuTqsvftUpwooqhIECgIIARIQCgoKBXVhdG9tEgExEPD1EhpAVZB7sqYkqBNne0OpaJZ9ylXOCulitm3VwYu5D+6ZPslEWkarEYUxPp3xFlUMA+rwD0n6zzo5D/bxoGvezMs0NQ==' } as CosmosSignedTransaction } ] @@ -85,7 +85,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { new CosmosCoin('uatom', '2') ]) ], - new CosmosFee([new CosmosCoin('uatom', '1')], '200000'), + new CosmosFee([new CosmosCoin('uatom', '1')], '310000'), '', 'cosmoshub-3', '0', @@ -161,7 +161,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '500' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -201,7 +201,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '500' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -223,7 +223,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { /* not relevant */ ], info: '', - gas_wanted: '200000', + gas_wanted: '310000', gas_used: '61473', tx: { '@type': '/cosmos.tx.v1beta1.Tx', @@ -257,7 +257,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '5000' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -282,7 +282,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { /* not relevant */ ], info: '', - gas_wanted: '200000', + gas_wanted: '310000', gas_used: '61483', tx: { '@type': '/cosmos.tx.v1beta1.Tx', @@ -316,7 +316,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '500' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -364,7 +364,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '500' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -404,7 +404,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '200' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -426,7 +426,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { /* not relevant */ ], info: '', - gas_wanted: '200000', + gas_wanted: '310000', gas_used: '103678', tx: { '@type': '/cosmos.tx.v1beta1.Tx', @@ -454,7 +454,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '500' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -479,7 +479,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { /* not relevant */ ], info: '', - gas_wanted: '200000', + gas_wanted: '310000', gas_used: '61684', tx: { '@type': '/cosmos.tx.v1beta1.Tx', @@ -513,7 +513,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '200' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -567,7 +567,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '500' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -607,7 +607,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '500' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -629,7 +629,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { /* not relevant */ ], info: '', - gas_wanted: '200000', + gas_wanted: '310000', gas_used: '61473', tx: { '@type': '/cosmos.tx.v1beta1.Tx', @@ -663,7 +663,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '5000' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -688,7 +688,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { /* not relevant */ ], info: '', - gas_wanted: '200000', + gas_wanted: '310000', gas_used: '61483', tx: { '@type': '/cosmos.tx.v1beta1.Tx', @@ -722,7 +722,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '500' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -770,7 +770,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '500' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -810,7 +810,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '200' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -832,7 +832,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { /* not relevant */ ], info: '', - gas_wanted: '200000', + gas_wanted: '310000', gas_used: '103678', tx: { '@type': '/cosmos.tx.v1beta1.Tx', @@ -860,7 +860,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '500' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } @@ -885,7 +885,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { /* not relevant */ ], info: '', - gas_wanted: '200000', + gas_wanted: '310000', gas_used: '61684', tx: { '@type': '/cosmos.tx.v1beta1.Tx', @@ -919,7 +919,7 @@ export class CosmosTestProtocolSpec extends TestProtocolSpec { amount: '200' } ], - gas_limit: '200000', + gas_limit: '310000', payer: '', granter: '' } diff --git a/packages/crypto/package.json b/packages/crypto/package.json index 10fc8b3a1..c85873518 100644 --- a/packages/crypto/package.json +++ b/packages/crypto/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/crypto", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/crypto packages provides common crypto functionalities.", "keywords": [ "airgap", @@ -30,8 +30,8 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", "@airgap/sapling-wasm": "^0.0.9", "@polkadot/wasm-crypto": "0.20.1", "@stablelib/hmac": "^1.0.1" diff --git a/packages/ethereum/package.json b/packages/ethereum/package.json index 3bcf1fef9..3ae936980 100644 --- a/packages/ethereum/package.json +++ b/packages/ethereum/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/ethereum", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/ethereum is an Ethereum implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -30,10 +30,10 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@ethereumjs/common": "2.6.5", "@ethereumjs/tx": "3.4.0", "@metamask/eth-sig-util": "4.0.0" diff --git a/packages/ethereum/src/v1/clients/node/EthereumNodeClient.ts b/packages/ethereum/src/v1/clients/node/EthereumNodeClient.ts index 32377e9c3..291d35fee 100644 --- a/packages/ethereum/src/v1/clients/node/EthereumNodeClient.ts +++ b/packages/ethereum/src/v1/clients/node/EthereumNodeClient.ts @@ -1,37 +1,20 @@ import BigNumber from '@airgap/coinlib-core/dependencies/src/bignumber.js-9.0.0/bignumber' import { AirGapTransactionStatus } from '@airgap/module-kit' -export abstract class EthereumNodeClient { - public baseURL: string +export interface EthereumNodeClient { + fetchBalance(address: string): Promise + fetchTransactionCount(address: string): Promise + sendSignedTransaction(transaction: string): Promise + callBalanceOf(contractAddress: string, address: string): Promise + getTransactionStatus(transactionHash: string): Promise + estimateTransferGas(contractAddress: string, fromAddress: string, toAddress: string, hexAmount: string): Promise - constructor(baseURL: string) { - this.baseURL = baseURL - } + estimateTransactionGas(fromAddress: string, toAddress: string, amount?: string, data?: string, gas?: string): Promise + getGasPrice(): Promise - public abstract fetchBalance(address: string): Promise - public abstract fetchTransactionCount(address: string): Promise - public abstract sendSignedTransaction(transaction: string): Promise - public abstract callBalanceOf(contractAddress: string, address: string): Promise - public abstract getTransactionStatus(transactionHash: string): Promise - public abstract estimateTransferGas( - contractAddress: string, - fromAddress: string, - toAddress: string, - hexAmount: string - ): Promise + callBalanceOfOnContracts(contractAddresses: string[], address: string): Promise<{ [contractAddress: string]: BigNumber }> - public abstract estimateTransactionGas( - fromAddress: string, - toAddress: string, - amount?: string, - data?: string, - gas?: string - ): Promise - public abstract getGasPrice(): Promise - - public abstract callBalanceOfOnContracts(contractAddresses: string[], address: string): Promise<{ [contractAddress: string]: BigNumber }> - - public abstract getContractName(contractAddress: string): Promise - public abstract getContractSymbol(contractAddress: string): Promise - public abstract getContractDecimals(contractAddress: string): Promise + getContractName(contractAddress: string): Promise + getContractSymbol(contractAddress: string): Promise + getContractDecimals(contractAddress: string): Promise } diff --git a/packages/ethereum/src/v1/clients/node/AirGapNodeClient.ts b/packages/ethereum/src/v1/clients/node/HttpEthereumNodeClient.ts similarity index 93% rename from packages/ethereum/src/v1/clients/node/AirGapNodeClient.ts rename to packages/ethereum/src/v1/clients/node/HttpEthereumNodeClient.ts index 75834ff6c..a9a2788cd 100644 --- a/packages/ethereum/src/v1/clients/node/AirGapNodeClient.ts +++ b/packages/ethereum/src/v1/clients/node/HttpEthereumNodeClient.ts @@ -158,10 +158,8 @@ export class EthereumRPCDataTransfer extends EthereumRPCData { } } -export class AirGapNodeClient extends EthereumNodeClient { - constructor(baseURL: string) { - super(baseURL) - } +export class HttpEthereumNodeClient implements EthereumNodeClient { + constructor(protected readonly baseURL: string, protected readonly headers?: any) {} public async fetchBalance(address: string): Promise { const body = new EthereumRPCBody('eth_getBalance', [address, EthereumRPCBody.blockLatest]) @@ -286,7 +284,7 @@ export class AirGapNodeClient extends EthereumNodeClient { return EthereumUtils.hexToNumber(response.result).toNumber() } - private contractCallBody( + protected contractCallBody( contractAddress: string, data: EthereumRPCData, extraParams: any[] = [], @@ -296,17 +294,21 @@ export class AirGapNodeClient extends EthereumNodeClient { return new EthereumRPCBody('eth_call', [{ to: contractAddress, data: data.abiEncoded() }, ...extraParams], id, jsonrpc) } - private async send(body: EthereumRPCBody): Promise { - const response = await axios.post(this.baseURL, body.toRPCBody()).catch((error) => { + protected async send(body: EthereumRPCBody): Promise { + const response = await axios.post(this.baseURL, body.toRPCBody(), { headers: this.headers }).catch((error) => { throw new NetworkError(Domain.ETHEREUM, error as AxiosError) }) return response.data } - private async batchSend(bodies: EthereumRPCBody[]): Promise { - const data = (await axios.post(this.baseURL, JSON.stringify(bodies.map((body) => body.toJSON())))).data + protected async batchSend(bodies: EthereumRPCBody[]): Promise { + const response = await axios + .post(this.baseURL, JSON.stringify(bodies.map((body) => body.toJSON())), { headers: this.headers }) + .catch((error) => { + throw new NetworkError(Domain.ETHEREUM, error as AxiosError) + }) - return data + return response.data } } diff --git a/packages/ethereum/src/v1/index.ts b/packages/ethereum/src/v1/index.ts index ccc0abee0..ffd72ad0b 100644 --- a/packages/ethereum/src/v1/index.ts +++ b/packages/ethereum/src/v1/index.ts @@ -1,7 +1,7 @@ import { EtherscanBlockExplorer } from './block-explorer/EtherscanBlockExplorer' import { EthereumInfoClient } from './clients/info/EthereumInfoClient' import { EtherscanInfoClient } from './clients/info/EtherscanInfoClient' -import { AirGapNodeClient, EthereumRPCBody, EthereumRPCData, EthereumRPCResponse } from './clients/node/AirGapNodeClient' +import { HttpEthereumNodeClient, EthereumRPCBody, EthereumRPCData, EthereumRPCResponse } from './clients/node/HttpEthereumNodeClient' import { EthereumNodeClient } from './clients/node/EthereumNodeClient' import { erc20Tokens } from './module/ERC20Tokens' import { EthereumModule } from './module/EthereumModule' @@ -67,7 +67,7 @@ export { DEFAULT_ETHEREUM_UNITS_METADATA } // Clients -export { EthereumNodeClient, AirGapNodeClient, EthereumInfoClient, EtherscanInfoClient } +export { EthereumNodeClient, HttpEthereumNodeClient, EthereumInfoClient, EtherscanInfoClient } // Types diff --git a/packages/ethereum/src/v1/module/ERC20Tokens.ts b/packages/ethereum/src/v1/module/ERC20Tokens.ts index 86a3c49a9..4d0cd3a0a 100644 --- a/packages/ethereum/src/v1/module/ERC20Tokens.ts +++ b/packages/ethereum/src/v1/module/ERC20Tokens.ts @@ -809,6 +809,22 @@ export const erc20Tokens: Record = { identifier: 'eth-erc20-aoa', contractAddress: '0x9ab165D795019b6d8B3e971DdA91071421305e5a', decimals: 18 + }, + 'eth-erc20-weth': { + symbol: 'WETH', + name: 'Wrapped Ether', + marketSymbol: 'weth', + identifier: 'eth-erc20-weth', + contractAddress: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + decimals: 18 + }, + 'eth-erc20-verse': { + symbol: 'VERSE', + name: 'Verse', + marketSymbol: 'verse', + identifier: 'eth-erc20-verse', + contractAddress: '0x249ca82617ec3dfb2589c4c17ab7ec9765350a18', + decimals: 18 } } diff --git a/packages/ethereum/src/v1/protocol/EthereumProtocol.ts b/packages/ethereum/src/v1/protocol/EthereumProtocol.ts index 214a1b4fa..5922dbf06 100644 --- a/packages/ethereum/src/v1/protocol/EthereumProtocol.ts +++ b/packages/ethereum/src/v1/protocol/EthereumProtocol.ts @@ -1,7 +1,7 @@ import { RecursivePartial } from '@airgap/module-kit' import { EtherscanInfoClient } from '../clients/info/EtherscanInfoClient' -import { AirGapNodeClient } from '../clients/node/AirGapNodeClient' +import { HttpEthereumNodeClient } from '../clients/node/HttpEthereumNodeClient' import { EthereumProtocolNetwork, EthereumProtocolOptions } from '../types/protocol' import { DefaultEthereumBaseProtocolImpl, EthereumBaseProtocol } from './EthereumBaseProtocol' @@ -18,7 +18,7 @@ class EthereumProtocolImpl extends DefaultEthereumBaseProtocolImpl implements Et const completeOptions: EthereumProtocolOptions = createEthereumProtocolOptions(options.network) super( - new AirGapNodeClient(completeOptions.network.rpcUrl), + new HttpEthereumNodeClient(completeOptions.network.rpcUrl), new EtherscanInfoClient(completeOptions.network.blockExplorerApi), completeOptions ) diff --git a/packages/ethereum/src/v1/protocol/erc20/ERC20Protocol.ts b/packages/ethereum/src/v1/protocol/erc20/ERC20Protocol.ts index 9ca8378f3..1f15d7f9e 100644 --- a/packages/ethereum/src/v1/protocol/erc20/ERC20Protocol.ts +++ b/packages/ethereum/src/v1/protocol/erc20/ERC20Protocol.ts @@ -22,7 +22,7 @@ import { import { FeeMarketEIP1559Transaction, TransactionFactory } from '@ethereumjs/tx' import { EthereumInfoClient, EthereumInfoClientTransactionsResult } from '../../clients/info/EthereumInfoClient' -import { EthereumRPCDataTransfer } from '../../clients/node/AirGapNodeClient' +import { EthereumRPCDataTransfer } from '../../clients/node/HttpEthereumNodeClient' import { EthereumNodeClient } from '../../clients/node/EthereumNodeClient' import { ERC20ProtocolOptions, EthereumProtocolNetwork, EthereumUnits } from '../../types/protocol' import { @@ -49,8 +49,7 @@ export interface ERC20Protocol<_Units extends string, _ProtocolNetwork extends E export abstract class ERC20ProtocolImpl<_Units extends string, _ProtocolNetwork extends EthereumProtocolNetwork = EthereumProtocolNetwork> extends EthereumBaseProtocolImpl<_Units, _ProtocolNetwork> - implements ERC20Protocol<_Units, _ProtocolNetwork> -{ + implements ERC20Protocol<_Units, _ProtocolNetwork> { protected readonly contractAddress: string public constructor( diff --git a/packages/ethereum/src/v1/protocol/erc20/ERC20Token.ts b/packages/ethereum/src/v1/protocol/erc20/ERC20Token.ts index 3be555443..65a352385 100644 --- a/packages/ethereum/src/v1/protocol/erc20/ERC20Token.ts +++ b/packages/ethereum/src/v1/protocol/erc20/ERC20Token.ts @@ -5,7 +5,7 @@ import { AirGapInterface, RecursivePartial } from '@airgap/module-kit' import { EthereumInfoClient } from '../../clients/info/EthereumInfoClient' import { EtherscanInfoClient } from '../../clients/info/EtherscanInfoClient' -import { AirGapNodeClient } from '../../clients/node/AirGapNodeClient' +import { HttpEthereumNodeClient } from '../../clients/node/HttpEthereumNodeClient' import { EthereumNodeClient } from '../../clients/node/EthereumNodeClient' import { ERC20TokenMetadata, ERC20TokenOptions, EthereumProtocolNetwork } from '../../types/protocol' import { ETHEREUM_MAINNET_PROTOCOL_NETWORK } from '../EthereumProtocol' @@ -21,8 +21,7 @@ export interface ERC20Token<_ProtocolNetwork extends EthereumProtocolNetwork = E export class ERC20TokenImpl<_ProtocolNetwork extends EthereumProtocolNetwork = EthereumProtocolNetwork> extends ERC20ProtocolImpl - implements ERC20Token<_ProtocolNetwork> -{ + implements ERC20Token<_ProtocolNetwork> { public constructor(nodeClient: EthereumNodeClient, infoClient: EthereumInfoClient, options: ERC20TokenOptions<_ProtocolNetwork>) { super(nodeClient, infoClient, options) @@ -59,7 +58,7 @@ export function createERC20Token( const completeOptions: ERC20TokenOptions = createERC20TokenOptions(metadata, options.network, options.mainIdentifier) return new ERC20TokenImpl( - new AirGapNodeClient(completeOptions.network.rpcUrl), + new HttpEthereumNodeClient(completeOptions.network.rpcUrl), new EtherscanInfoClient(completeOptions.network.blockExplorerApi), completeOptions ) diff --git a/packages/ethereum/test/v1/protocol.spec.ts b/packages/ethereum/test/v1/protocol.spec.ts index 0fb14c2e1..96cf8a35e 100644 --- a/packages/ethereum/test/v1/protocol.spec.ts +++ b/packages/ethereum/test/v1/protocol.spec.ts @@ -12,7 +12,7 @@ import chaiAsPromised = require('chai-as-promised') import 'mocha' import sinon = require('sinon') -import { AirGapNodeClient } from '../../src/v1/clients/node/AirGapNodeClient' +import { HttpEthereumNodeClient } from '../../src/v1/clients/node/HttpEthereumNodeClient' import { TestProtocolSpec } from './implementations' import { ERC20TokenTestProtocolSpec } from './specs/erc20-token' @@ -430,7 +430,7 @@ Promise.all( for (let i = 0; i < tests.length; i++) { // Stub specific hashes - const getTransactionStub = sinon.stub(AirGapNodeClient.prototype, 'getTransactionStatus') + const getTransactionStub = sinon.stub(HttpEthereumNodeClient.prototype, 'getTransactionStatus') Object.entries(tests[i]).forEach(([hash, expectedResult]) => { getTransactionStub.withArgs(hash).returns(expectedResult) }) diff --git a/packages/ethereum/test/v1/stubs/erc20-token.stub.ts b/packages/ethereum/test/v1/stubs/erc20-token.stub.ts index 7e340b4bf..20230f067 100644 --- a/packages/ethereum/test/v1/stubs/erc20-token.stub.ts +++ b/packages/ethereum/test/v1/stubs/erc20-token.stub.ts @@ -4,7 +4,7 @@ import { Balance } from '@airgap/module-kit' import * as sinon from 'sinon' import { ERC20Token, EthereumBaseProtocolImpl } from '../../../src/v1' -import { AirGapNodeClient } from '../../../src/v1/clients/node/AirGapNodeClient' +import { HttpEthereumNodeClient } from '../../../src/v1/clients/node/HttpEthereumNodeClient' import { ProtocolHTTPStub, TestProtocolSpec } from '../implementations' export class ERC20TokenProtocolStub implements ProtocolHTTPStub { @@ -30,12 +30,12 @@ export class ERC20TokenProtocolStub implements ProtocolHTTPStub)) sinon - .stub(AirGapNodeClient.prototype, 'estimateTransactionGas') + .stub(HttpEthereumNodeClient.prototype, 'estimateTransactionGas') .withArgs(testProtocolSpec.wallet.addresses[0]) .returns(Promise.resolve(new BigNumber('0x5208'))) sinon - .stub(AirGapNodeClient.prototype, 'getGasPrice') + .stub(HttpEthereumNodeClient.prototype, 'getGasPrice') .withArgs(testProtocolSpec.wallet.addresses[0]) .returns(Promise.resolve(new BigNumber('0x4a817c800'))) } diff --git a/packages/groestlcoin/package.json b/packages/groestlcoin/package.json index 46ae48bc8..2e2f25aad 100644 --- a/packages/groestlcoin/package.json +++ b/packages/groestlcoin/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/groestlcoin", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/groestlcoin is a Groestlcoin implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -30,10 +30,10 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/bitcoin": "^0.13.18", - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/bitcoin": "^0.13.19", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19" }, "localDependencies": { "groestlcoinjs-message": "2.1.0" diff --git a/packages/icp/package.json b/packages/icp/package.json index c62f5cf6b..85e6529c1 100644 --- a/packages/icp/package.json +++ b/packages/icp/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/icp", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/icp is an ICP implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -30,10 +30,10 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@dfinity/agent": "^0.15.4", "@dfinity/identity-secp256k1": "^0.15.4", "@dfinity/nns": "^0.14.0", diff --git a/packages/icp/src/v1/protocol/ICPProtocol.ts b/packages/icp/src/v1/protocol/ICPProtocol.ts index b59f2103e..d9f7b0937 100644 --- a/packages/icp/src/v1/protocol/ICPProtocol.ts +++ b/packages/icp/src/v1/protocol/ICPProtocol.ts @@ -1045,7 +1045,7 @@ export function createICPProtocol(options: RecursivePartial export const ICP_MAINNET_PROTOCOL_NETWORK: ICPProtocolNetwork = { name: 'Mainnet', type: 'mainnet', - rpcUrl: 'https://boundary.ic0.app/', + rpcUrl: 'https://icp-api.io/', blockExplorerUrl: 'https://dashboard.internetcomputer.org/', blockExplorerApi: 'https://ledger-api.internetcomputer.org', ledgerCanisterId: 'ryjl3-tyaaa-aaaaa-aaaba-cai', diff --git a/packages/icp/src/v1/protocol/icrc/CkBTCProtocol.ts b/packages/icp/src/v1/protocol/icrc/CkBTCProtocol.ts index 320db7ad6..8f9e6fc80 100644 --- a/packages/icp/src/v1/protocol/icrc/CkBTCProtocol.ts +++ b/packages/icp/src/v1/protocol/icrc/CkBTCProtocol.ts @@ -295,7 +295,7 @@ export function createCkBTCOnlineProtocol(options: RecursivePartial ./dist/airgap-coinlib-mina.min.js" + }, + "author": "Papers AG (https://papers.ch)", + "dependencies": { + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", + "graphql": "^16.7.1", + "graphql-request": "^6.1.0", + "mina-signer": "^2.0.3" + }, + "nyc": { + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "test/**/*.spec.ts" + ], + "extension": [ + ".ts" + ], + "require": [ + "ts-node/register" + ], + "reporter": [ + "text", + "text-summary" + ], + "report-dir": "../../coverage/mina", + "sourceMap": true, + "instrument": true + }, + "gitHead": "8e8feb6788204501b7173aa825f5456c2a5f185b" +} diff --git a/packages/mina/readme.md b/packages/mina/readme.md new file mode 100644 index 000000000..acf5d250e --- /dev/null +++ b/packages/mina/readme.md @@ -0,0 +1,121 @@ +# AirGap Coin Library + +[![npm](https://img.shields.io/npm/v/airgap-coin-lib.svg?colorB=brightgreen)](https://www.npmjs.com/package/airgap-coin-lib) +[![documentation](https://img.shields.io/badge/documentation-online-brightgreen.svg)](https://airgap-it.github.io/airgap-coin-lib/) +[![build](https://img.shields.io/travis/airgap-it/airgap-coin-lib.svg)](https://travis-ci.org/airgap-it/airgap-coin-lib/) +[![codecov](https://img.shields.io/codecov/c/gh/airgap-it/airgap-coin-lib.svg)](https://codecov.io/gh/airgap-it/airgap-coin-lib/) + +The `airgap-coin-lib` is a protocol-agnostic library that allows easy handling of the most important tasks relating cryptocurrencies and blockchains. + +It implements operations such as preparing, signing and broadcasting transactions for a range of protocols. + +The library consists of a shared interface for all implemented protocols. This is especially useful in the context of AirGap because methods are designed to support offline key management and signing. The following core operations are specified: + +- `prepareTransaction` - This is done on AirGap Wallet (online) side. Either a public key or extended public key is used and will fetch the required information from the network. +- `signTransaction` - This is done in AirGap Vault (offline) side. The output of "prepareTransaction" is the input for this method (hence the output of "prepareTransaction" is transferred via URL scheme (same-device) or QR code (2-device-setup)). +- `broadcastTransaction` - This is done in AirGap Wallet (online) side. The output of "signTransaction" is the input for this method (hence the output of "signTransaction" is transferred via URL scheme (same-device) or QR code (2-device-setup)). + +## Supported Protocols + +The modular design used in this library allows you to simply add new protocols with special logic. Adding a new Bitcoin-like protocol basically means: + +1. select the correct network parameters (see `src/networks.ts`) +2. set the Insight API URL to communicate with the blockchain + +Adding a new Ethereum-like protocol means: + +1. set the correct chain id +2. set the JSON RPC URL + +Currently supported are: + +- Bitcoin +- Ethereum + - Generic ERC20 Tokens +- Aeternity +- Tezos + - FA1.2 tokens + - tzBTC + - USDtz + - FA2 tokens +- Groestlcoin +- Cosmos +- Polkadot +- Kusama + +## Features + +### Protocols + +The way the interface was designed is to allow stateless calls. This means the class stores very little state itself. +All required input comes from the method params (public key, extended public key, etc...) + +Currently we support for Bitcoin-like (UTXO) protocols: + +- Single Address Wallets (deprecated) +- HD Wallets + +Currently we support for Ethereum-like (Account-based) protocols: + +- Single Address Wallets + +### Delegation + +There is a different interface that can be implemented if the protocol supports delegation. The delegation flow usually requires some changes in the user interface of the AirGap Wallet as well. + +### Inter App Communication + +A serializer is included that encodes JSON structures with RLP and base58check. Those strings can then be sent to the other app, either through QR codes or a URL. The serializer can only serialize messages in predefined formats, so new message types have to be added when new protocols are added. + +### Tezos FA1.2 and FA2 tokens + +It is possible to interact (fetch balances, create transfers, etc.) with an FA1.2 and FA2 smart contracts using the `TezosFA12Protocol` and `TezosFA2Protocol` classes. An example on how to use them can be found in `examples/custom/protocols/tezos/fa2-smart-contract.ts`. + +## Synchronising information between wallet and vault + +Such that the system works we need to be able to synchronise wallets. A wallet can be: + +- Single Address Wallet +- HD Wallet + +For the single address wallet we only need to share the public key. For HD Wallet we need to share the extended public key. + +## Getting started + +### Requirements + +``` +npm >= 6 +NodeJS >= 12 +``` + +Build dependencies get installed using `npm install`. + +### Clone and Run + +``` +$ git clone https://github.com/airgap-it/airgap-coin-lib.git +$ cd airgap-coin-lib +$ npm install +``` + +To run the tests, you will have to install the test dependencies + +``` +$ npm run install-test-dependencies +$ npm test +``` + +To remove the test dependencies and clean up the `package.json` and `package-lock.json`, execute this command + +``` +$ npm run install-build-dependencies +``` + +### Contributing + +We welcome contributions from the community. Simple readme updates or bugfixes can be addressed with a PR directly. + +For larger changes like new protocols, new features or larger refactorings, please contact us first by opening an issue. This project is under constant development and until version `1.x.x` has been reached, there will be frequent breaking changes. So make sure to take a look at the `develop` branch. + +Regarding new protocols / currencies, we cannot guarantee that they will be merged, but we're more than happy to discuss the details of a specific integration in a github issue. diff --git a/packages/mina/scripts/copy-files-after-build.js b/packages/mina/scripts/copy-files-after-build.js new file mode 100644 index 000000000..f8ae0e4e3 --- /dev/null +++ b/packages/mina/scripts/copy-files-after-build.js @@ -0,0 +1,183 @@ +'use strict' +var __awaiter = + (this && this.__awaiter) || + function (thisArg, _arguments, P, generator) { + function adopt(value) { + return value instanceof P + ? value + : new P(function (resolve) { + resolve(value) + }) + } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { + try { + step(generator.next(value)) + } catch (e) { + reject(e) + } + } + function rejected(value) { + try { + step(generator['throw'](value)) + } catch (e) { + reject(e) + } + } + function step(result) { + result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected) + } + step((generator = generator.apply(thisArg, _arguments || [])).next()) + }) + } +var __generator = + (this && this.__generator) || + function (thisArg, body) { + var _ = { + label: 0, + sent: function () { + if (t[0] & 1) throw t[1] + return t[1] + }, + trys: [], + ops: [] + }, + f, + y, + t, + g + return ( + (g = { next: verb(0), throw: verb(1), return: verb(2) }), + typeof Symbol === 'function' && + (g[Symbol.iterator] = function () { + return this + }), + g + ) + function verb(n) { + return function (v) { + return step([n, v]) + } + } + function step(op) { + if (f) throw new TypeError('Generator is already executing.') + while (_) + try { + if ( + ((f = 1), + y && + (t = op[0] & 2 ? y['return'] : op[0] ? y['throw'] || ((t = y['return']) && t.call(y), 0) : y.next) && + !(t = t.call(y, op[1])).done) + ) + return t + if (((y = 0), t)) op = [op[0] & 2, t.value] + switch (op[0]) { + case 0: + case 1: + t = op + break + case 4: + _.label++ + return { value: op[1], done: false } + case 5: + _.label++ + y = op[1] + op = [0] + continue + case 7: + op = _.ops.pop() + _.trys.pop() + continue + default: + if (!((t = _.trys), (t = t.length > 0 && t[t.length - 1])) && (op[0] === 6 || op[0] === 2)) { + _ = 0 + continue + } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { + _.label = op[1] + break + } + if (op[0] === 6 && _.label < t[1]) { + _.label = t[1] + t = op + break + } + if (t && _.label < t[2]) { + _.label = t[2] + _.ops.push(op) + break + } + if (t[2]) _.ops.pop() + _.trys.pop() + continue + } + op = body.call(thisArg, _) + } catch (e) { + op = [6, e] + y = 0 + } finally { + f = t = 0 + } + if (op[0] & 5) throw op[1] + return { value: op[0] ? op[1] : void 0, done: true } + } + } +exports.__esModule = true +var fs_1 = require('fs') +var path_1 = require('path') +var findFilesOnLevel = function (base) { + return __awaiter(void 0, void 0, void 0, function () { + var files, filesInFolder, _i, filesInFolder_1, file, path, isDirectory, _a, _b, _c + return __generator(this, function (_d) { + switch (_d.label) { + case 0: + files = [] + filesInFolder = fs_1.readdirSync(base) + ;(_i = 0), (filesInFolder_1 = filesInFolder) + _d.label = 1 + case 1: + if (!(_i < filesInFolder_1.length)) return [3 /*break*/, 5] + file = filesInFolder_1[_i] + path = base + '/' + file + isDirectory = fs_1.lstatSync(path).isDirectory() + if (!isDirectory) return [3 /*break*/, 3] + _b = (_a = files.push).apply + _c = [files] + return [4 /*yield*/, findFilesOnLevel(path)] + case 2: + _b.apply(_a, _c.concat([_d.sent()])) + return [3 /*break*/, 4] + case 3: + if (file.endsWith('json') || file.endsWith('js')) { + files.push(path) + path_1 + .dirname(path) + .split(path_1.sep) + .reduce(function (prevPath, folder) { + var currentPath = path_1.join(prevPath, folder, path_1.sep) + if (currentPath === 'src/') { + return 'dist/' + } + if (!fs_1.existsSync(currentPath)) { + fs_1.mkdirSync(currentPath) + } + return currentPath + }, '') + console.log('Copying file', path.replace('./src', './dist')) + fs_1.copyFileSync(path, path.replace('./src', './dist')) + } + _d.label = 4 + case 4: + _i++ + return [3 /*break*/, 1] + case 5: + return [2 /*return*/, files] + } + }) + }) +} +findFilesOnLevel('./src/v1/serializer/v3/schemas/generated') + .then(function () {}) + ['catch'](console.error) +fs_1.copyFileSync('./package.json', './dist/package.json') +fs_1.copyFileSync('./readme.md', './dist/readme.md') diff --git a/packages/mina/scripts/copy-files-after-build.ts b/packages/mina/scripts/copy-files-after-build.ts new file mode 100644 index 000000000..7460b5e91 --- /dev/null +++ b/packages/mina/scripts/copy-files-after-build.ts @@ -0,0 +1,41 @@ +import { mkdirSync, copyFileSync, readdirSync, lstatSync, existsSync } from 'fs' +import { dirname, join, sep } from 'path' + +const findFilesOnLevel = async (base: string) => { + const files: string[] = [] + const filesInFolder = readdirSync(base) + for (const file of filesInFolder) { + const path = `${base}/${file}` + const isDirectory = lstatSync(path).isDirectory() + if (isDirectory) { + files.push(...(await findFilesOnLevel(path))) + } else if ((file as any).endsWith('json') || (file as any).endsWith('js')) { + files.push(path) + dirname(path) + .split(sep) + .reduce((prevPath, folder) => { + const currentPath = join(prevPath, folder, sep) + if (currentPath === 'src/') { + return 'dist/' + } + + if (!existsSync(currentPath)) { + mkdirSync(currentPath) + } + + return currentPath + }, '') + + console.log('Copying file', path.replace('./src', './dist')) + + copyFileSync(path, path.replace('./src', './dist')) + } + } + return files +} +findFilesOnLevel('./src/v1/serializer/v3/schemas/generated') + .then(() => {}) + .catch(console.error) + +copyFileSync('./package.json', './dist/package.json') +copyFileSync('./readme.md', './dist/readme.md') diff --git a/packages/mina/src/index.ts b/packages/mina/src/index.ts new file mode 100644 index 000000000..c317f850b --- /dev/null +++ b/packages/mina/src/index.ts @@ -0,0 +1 @@ +export * from './v1' diff --git a/packages/mina/src/v1/block-explorer/MinaExplorerBlockExplorer.ts b/packages/mina/src/v1/block-explorer/MinaExplorerBlockExplorer.ts new file mode 100644 index 000000000..1d0054f15 --- /dev/null +++ b/packages/mina/src/v1/block-explorer/MinaExplorerBlockExplorer.ts @@ -0,0 +1,21 @@ +import { AirGapBlockExplorer, BlockExplorerMetadata } from '@airgap/module-kit' + +export class MinaExplorerBlockExplorer implements AirGapBlockExplorer { + public constructor(private readonly url: string) {} + + private readonly metadata: BlockExplorerMetadata = { + name: 'Mina Explorer', + url: this.url + } + public async getMetadata(): Promise { + return this.metadata + } + + public async createAddressUrl(address: string): Promise { + return `${this.url}/wallet/${address}` + } + + public async createTransactionUrl(transactionId: string): Promise { + return `${this.url}/transaction/${transactionId}` + } +} \ No newline at end of file diff --git a/packages/mina/src/v1/index.ts b/packages/mina/src/v1/index.ts new file mode 100644 index 000000000..af66a36a3 --- /dev/null +++ b/packages/mina/src/v1/index.ts @@ -0,0 +1,25 @@ +import { MinaModule } from './module/MinaModule' +import { createMinaProtocol, MinaProtocol } from './protocol/MinaProtocol' +import { MinaCryptoConfiguration } from './types/crypto' +import { MinaProtocolNetwork, MinaProtocolOptions, MinaUnits } from './types/protocol' +import { MinaSignedTransaction, MinaTransactionCursor, MinaUnsignedTransaction } from './types/transaction' + +// Module + +export { MinaModule } + +// Protocol + +export { MinaProtocol, createMinaProtocol } + +// Types + +export { + MinaCryptoConfiguration, + MinaUnits, + MinaProtocolNetwork, + MinaProtocolOptions, + MinaUnsignedTransaction, + MinaSignedTransaction, + MinaTransactionCursor +} diff --git a/packages/mina/src/v1/indexer/MinaExplorerIndexer.ts b/packages/mina/src/v1/indexer/MinaExplorerIndexer.ts new file mode 100644 index 000000000..08ed7ea1e --- /dev/null +++ b/packages/mina/src/v1/indexer/MinaExplorerIndexer.ts @@ -0,0 +1,98 @@ +import { gql, request } from 'graphql-request' + +import { AccountTransaction } from '../types/indexer' + +import { MinaIndexer } from './MinaIndexer' + +interface GetTransactionsResponse { + transactions?: Partial[] +} + +interface GetBlocksResponse { + blocks?: { + blockHeight?: number | null + }[] +} + +interface GetFeesResponse { + transactions?: { + fee?: string | number | null + }[] +} + +const EMPTY_MEMO: string = 'E4YM2vTHhWEg66xpj52JErHUBU4pZ1yageL4TVDDpTTSsv8mK6YaH' + +export class MinaExplorerIndexer implements MinaIndexer { + public constructor(private readonly url: string) {} + + public async getTransactions(publicKey: string, limit: number, dateTimeOffset?: string): Promise { + const query = gql` + query GetTransactions($publicKey: String!, $limit: Int!, $dateTimeOffset: DateTime) { + transactions( + limit: $limit + sortBy: DATETIME_DESC + query: { canonical: true, OR: [{ to: $publicKey }, { from: $publicKey }], dateTime_lt: $dateTimeOffset } + ) { + to + from + amount + fee + memo + hash + dateTime + failureReason + } + } + ` + + const response: GetTransactionsResponse = await request(this.url, query, { + publicKey, + limit, + dateTimeOffset: dateTimeOffset ?? null + }) + + return ( + response.transactions?.map((transaction: Partial) => ({ + to: transaction.to ?? '', + from: transaction.from ?? '', + amount: transaction.amount ?? 0, + fee: transaction.fee ?? 0, + memo: transaction.memo && transaction.memo !== EMPTY_MEMO ? transaction.memo : undefined, + kind: transaction.kind ?? undefined, + hash: transaction.hash ?? undefined, + dateTime: transaction.dateTime ?? '', + failureReason: transaction.failureReason ?? undefined + })) ?? [] + ) + } + + public async getLatestFees(blockSpan: number): Promise { + const blockQuery = gql` + query GetLatestBlock { + blocks(limit: 1, sortBy: DATETIME_DESC, query: { canonical: true }) { + blockHeight + } + } + ` + + const blocksResponse: GetBlocksResponse = await request(this.url, blockQuery) + const blockHeight = (blocksResponse?.blocks ?? [])[0]?.blockHeight + if (!blockHeight) { + return [] + } + + const feesQuery = gql` + query GetFees($minBlockHeight: Int) { + transactions(sortBy: FEE_ASC, query: { canonical: true, blockHeight_gt: $minBlockHeight }) { + fee + } + } + ` + + const feesResponse: GetFeesResponse = await request(this.url, feesQuery, { + minBlockHeight: blockHeight - blockSpan + }) + + return feesResponse.transactions?.map(({ fee }) => (fee ?? 0).toString()) ?? [] + } +} diff --git a/packages/mina/src/v1/indexer/MinaIndexer.ts b/packages/mina/src/v1/indexer/MinaIndexer.ts new file mode 100644 index 000000000..2aeb6bace --- /dev/null +++ b/packages/mina/src/v1/indexer/MinaIndexer.ts @@ -0,0 +1,6 @@ +import { AccountTransaction } from '../types/indexer' + +export interface MinaIndexer { + getTransactions(publicKey: string, limit: number, dateTimeOffset?: string): Promise + getLatestFees(blockSpan: number): Promise +} diff --git a/packages/mina/src/v1/module.ts b/packages/mina/src/v1/module.ts new file mode 100644 index 000000000..d9ee26ff9 --- /dev/null +++ b/packages/mina/src/v1/module.ts @@ -0,0 +1,8 @@ +import { AirGapModule } from '@airgap/module-kit' +import { MinaModule } from './module/MinaModule' + +export * from './index' + +export function create(): AirGapModule { + return new MinaModule() +} diff --git a/packages/mina/src/v1/module/MinaModule.ts b/packages/mina/src/v1/module/MinaModule.ts new file mode 100644 index 000000000..ec733005c --- /dev/null +++ b/packages/mina/src/v1/module/MinaModule.ts @@ -0,0 +1,81 @@ +import { Domain, MainProtocolSymbols } from '@airgap/coinlib-core' +import { ConditionViolationError } from '@airgap/coinlib-core/errors' +import { + AirGapBlockExplorer, + AirGapModule, + AirGapOfflineProtocol, + AirGapOnlineProtocol, + AirGapProtocol, + AirGapV3SerializerCompanion, + createSupportedProtocols, + ModuleNetworkRegistry, + ProtocolConfiguration, + ProtocolNetwork +} from '@airgap/module-kit' + +import { MinaExplorerBlockExplorer } from '../block-explorer/MinaExplorerBlockExplorer' +import { createMinaProtocol } from '../module' +import { MINA_MAINNET_PROTOCOL_NETWORK } from '../protocol/MinaProtocol' +import { MinaV3SerializerCompanion } from '../serializer/v3/serializer-companion' +import { MinaProtocolNetwork } from '../types/protocol' + +export class MinaModule implements AirGapModule { + private readonly networkRegistries: Record + public readonly supportedProtocols: Record + + public constructor() { + const networkRegistry: ModuleNetworkRegistry = new ModuleNetworkRegistry({ + supportedNetworks: [MINA_MAINNET_PROTOCOL_NETWORK] + }) + + this.networkRegistries = { + [MainProtocolSymbols.MINA]: networkRegistry + } + this.supportedProtocols = createSupportedProtocols(this.networkRegistries) + } + + public async createOfflineProtocol(identifier: string): Promise { + return this.createProtocol(identifier) + } + + public async createOnlineProtocol( + identifier: string, + networkOrId?: MinaProtocolNetwork | string + ): Promise { + const network: ProtocolNetwork | undefined = + typeof networkOrId === 'object' ? networkOrId : this.networkRegistries[identifier]?.findNetwork(networkOrId) + + if (network === undefined) { + throw new ConditionViolationError(Domain.MINA, 'Protocol network type not supported.') + } + + return this.createProtocol(identifier, network) + } + + public async createBlockExplorer( + identifier: string, + networkOrId?: MinaProtocolNetwork | string + ): Promise { + const network: ProtocolNetwork | undefined = + typeof networkOrId === 'object' ? networkOrId : this.networkRegistries[identifier]?.findNetwork(networkOrId) + + if (network === undefined) { + throw new ConditionViolationError(Domain.MINA, 'Block Explorer network type not supported.') + } + + return new MinaExplorerBlockExplorer(network.blockExplorerUrl) + } + + public async createV3SerializerCompanion(): Promise { + return new MinaV3SerializerCompanion() + } + + private createProtocol(identifier: string, network?: ProtocolNetwork): AirGapProtocol { + switch (identifier) { + case MainProtocolSymbols.MINA: + return createMinaProtocol({ network }) + default: + throw new ConditionViolationError(Domain.MINA, `Protocol ${identifier} not supported.`) + } + } +} diff --git a/packages/mina/src/v1/node/GraphQLNode.ts b/packages/mina/src/v1/node/GraphQLNode.ts new file mode 100644 index 000000000..8b2b0d241 --- /dev/null +++ b/packages/mina/src/v1/node/GraphQLNode.ts @@ -0,0 +1,107 @@ +import { gql, request } from 'graphql-request' + +import { AccountBalance } from '../types/node' +import { MinaPayment, MinaSignature } from '../types/transaction' + +import { MinaNode } from './MinaNode' + +interface AccountNonceResponse { + account?: { + nonce?: string | null + inferredNonce?: string | null + } +} + +interface AccountBalanceResponse { + account?: { + balance?: Partial | null + } +} + +interface SendTransactionResponse { + sendPayment?: { + payment?: { + hash?: string | null + } + } +} + +export class GraphQLNode implements MinaNode { + public constructor(private readonly url: string) {} + + public async getNonce(publicKey: string): Promise { + const query = gql` + query AccountNonce($publicKey: PublicKey!) { + account(publicKey: $publicKey) { + nonce + inferredNonce + } + } + ` + + const response: AccountNonceResponse = await request(this.url, query, { publicKey }) + + return response.account?.inferredNonce ?? response.account?.nonce ?? '0' + } + + public async getBalance(publicKey: string): Promise { + const query = gql` + query AccountBalance($publicKey: PublicKey!) { + account(publicKey: $publicKey) { + balance { + total + liquid + } + } + } + ` + + const response: AccountBalanceResponse = await request(this.url, query, { publicKey }) + + return { + total: response.account?.balance?.total ?? '0', + liquid: response.account?.balance?.liquid ?? '0' + } + } + + public async sendTransaction(payment: MinaPayment, signature: MinaSignature): Promise { + const mutation = gql` + mutation SendTransaction( + $to: PublicKey! + $from: PublicKey! + $amount: UInt64! + $fee: UInt64! + $nonce: UInt32! + $memo: String + $validUntil: UInt32 + $rawSignature: String + $field: String + $scalar: String + ) { + sendPayment( + input: { to: $to, from: $from, amount: $amount, fee: $fee, nonce: $nonce, memo: $memo, validUntil: $validUntil } + signature: { rawSignature: $rawSignature, field: $field, scalar: $scalar } + ) { + payment { + hash + } + } + } + ` + + const response: SendTransactionResponse = await request(this.url, mutation, { + to: payment.to, + from: payment.from, + amount: payment.amount, + fee: payment.fee, + nonce: payment.nonce, + memo: payment.memo ?? null, + validUntil: payment.validUntil ?? null, + rawSignature: signature.type === 'raw' ? signature.value : null, + field: signature.type === 'legacy' ? signature.field : null, + scalar: signature.type === 'legacy' ? signature.scalar : null + }) + + return response.sendPayment?.payment?.hash ?? '' + } +} diff --git a/packages/mina/src/v1/node/MinaNode.ts b/packages/mina/src/v1/node/MinaNode.ts new file mode 100644 index 000000000..214f46904 --- /dev/null +++ b/packages/mina/src/v1/node/MinaNode.ts @@ -0,0 +1,8 @@ +import { AccountBalance } from '../types/node' +import { MinaPayment, MinaSignature } from '../types/transaction' + +export interface MinaNode { + getNonce(publicKey: string): Promise + getBalance(publicKey: string): Promise + sendTransaction(payment: MinaPayment, signature: MinaSignature): Promise +} diff --git a/packages/mina/src/v1/protocol/MinaProtocol.ts b/packages/mina/src/v1/protocol/MinaProtocol.ts new file mode 100644 index 000000000..42098714e --- /dev/null +++ b/packages/mina/src/v1/protocol/MinaProtocol.ts @@ -0,0 +1,354 @@ +import { Domain, MainProtocolSymbols } from '@airgap/coinlib-core' +import BigNumber from '@airgap/coinlib-core/dependencies/src/bignumber.js-9.0.0/bignumber' +// @ts-ignore +import * as BitGo from '@airgap/coinlib-core/dependencies/src/bitgo-utxo-lib-5d91049fd7a988382df81c8260e244ee56d57aac/src' +import { BalanceError, ConditionViolationError } from '@airgap/coinlib-core/errors' +import { encodeDerivative } from '@airgap/crypto' +import { + Address, + AirGapProtocol, + AirGapTransaction, + AirGapTransactionsWithCursor, + Amount, + Balance, + CryptoDerivative, + FeeDefaults, + KeyPair, + newAmount, + newSignedTransaction, + newUnsignedTransaction, + ProtocolMetadata, + ProtocolUnitsMetadata, + PublicKey, + RecursivePartial, + SecretKey, + TransactionDetails, + TransactionFullConfiguration, + TransactionSimpleConfiguration +} from '@airgap/module-kit' +import Client from 'mina-signer' + +import { MinaExplorerIndexer } from '../indexer/MinaExplorerIndexer' +import { MinaIndexer } from '../indexer/MinaIndexer' +import { GraphQLNode } from '../node/GraphQLNode' +import { MinaNode } from '../node/MinaNode' +import { MinaCryptoConfiguration } from '../types/crypto' +import { ACCOUNT_TRANSFER_KIND, AccountTransaction } from '../types/indexer' +import { AccountBalance } from '../types/node' +import { MinaNetworkType, MinaProtocolNetwork, MinaProtocolOptions, MinaUnits } from '../types/protocol' +import { MinaPayment, MinaSignedTransaction, MinaTransactionCursor, MinaUnsignedTransaction } from '../types/transaction' +import { derivePublicKey, finalizeSecretKey } from '../utils/key' +import { quantile } from '../utils/math' + +// Interface + +export interface MinaProtocol + extends AirGapProtocol<{ + AddressResult: Address + ProtocolNetwork: MinaProtocolNetwork + CryptoConfiguration: MinaCryptoConfiguration + Units: MinaUnits + FeeEstimation: FeeDefaults + SignedTransaction: MinaSignedTransaction + UnsignedTransaction: MinaUnsignedTransaction + TransactionCursor: MinaTransactionCursor + }> {} + +// Implementation + +export class MinaProtocolImpl implements MinaProtocol { + private readonly options: MinaProtocolOptions + private readonly node: MinaNode + private readonly indexer: MinaIndexer + + private readonly bitcoinJS: { + lib: any + config: { + network: any + } + } = { + lib: BitGo, + config: { network: BitGo.networks.bitcoin } + } + + public constructor(options: RecursivePartial = {}, node?: MinaNode, indexer?: MinaIndexer) { + this.options = createMinaProtocolOptions(options.network) + this.node = node ?? new GraphQLNode(this.options.network.rpcUrl) + this.indexer = indexer ?? new MinaExplorerIndexer(this.options.network.blockExplorerApi) + } + + // Common + + private readonly units: ProtocolUnitsMetadata = { + MINA: { + symbol: { value: 'MINA' }, + decimals: 9 + } + } + + public readonly metadata: ProtocolMetadata = { + identifier: MainProtocolSymbols.MINA, + name: 'Mina', + + units: this.units, + mainUnit: 'MINA', + + account: { + standardDerivationPath: `m/44'/12586'/0'/0/0`, + address: { + isCaseSensitive: true, + regex: '^B62[a-km-zA-HJ-NP-Z1-9]{52}$', + placeholder: 'B62...' + } + } + } + + public async getMetadata(): Promise> { + return this.metadata + } + + public async getAddressFromPublicKey(publicKey: PublicKey): Promise { + return publicKey.value + } + + public async getDetailsFromTransaction( + transaction: MinaSignedTransaction | MinaUnsignedTransaction, + publicKey: PublicKey + ): Promise[]> { + return this.getDetailsFromMinaPayment(publicKey, transaction.data) + } + + private getDetailsFromMinaPayment(publicKey: PublicKey, payment: MinaPayment): AirGapTransaction[] { + return [ + { + from: [payment.from], + to: [payment.to], + isInbound: payment.to === publicKey.value, + amount: newAmount(payment.amount, 'blockchain'), + fee: newAmount(payment.fee, 'blockchain'), + arbitraryData: payment.memo, + network: this.options.network + } + ] + } + + // Offline + + private readonly cryptoConfiguration: MinaCryptoConfiguration = { + algorithm: 'secp256k1' + } + + public async getCryptoConfiguration(): Promise { + return this.cryptoConfiguration + } + + public async getKeyPairFromDerivative(derivative: CryptoDerivative): Promise { + const node = this.derivativeToBip32Node(derivative) + const secretKey: SecretKey = finalizeSecretKey(node.keyPair.getPrivateKeyBuffer()) + + return { + secretKey, + publicKey: derivePublicKey(this.getMinaClient(), secretKey) + } + } + + public async signTransactionWithSecretKey(transaction: MinaUnsignedTransaction, secretKey: SecretKey): Promise { + const client: Client = this.getMinaClient(transaction.networkType) + const signed = client.signPayment( + { + to: transaction.data.to, + from: transaction.data.from, + amount: transaction.data.amount, + fee: transaction.data.fee, + nonce: transaction.data.nonce, + memo: transaction.data.memo, + validUntil: transaction.data.validUntil + }, + secretKey.value + ) + + return newSignedTransaction({ + data: { + to: signed.data.to, + from: signed.data.from, + amount: signed.data.amount.toString(), + fee: signed.data.fee.toString(), + nonce: signed.data.nonce.toString(), + memo: signed.data.memo, + validUntil: signed.data.validUntil?.toString() + }, + signature: { + type: 'legacy', + field: signed.signature.field, + scalar: signed.signature.scalar + } + }) + } + + // Online + + public async getNetwork(): Promise { + return this.options.network + } + + public async getTransactionsForPublicKey( + publicKey: PublicKey, + limit: number, + cursor?: MinaTransactionCursor | undefined + ): Promise> { + const transactions: AccountTransaction[] = await this.indexer.getTransactions(publicKey.value, limit, cursor?.lastDateTime) + + const lastDateTime: string | undefined = transactions[transactions.length - 1]?.dateTime + const hasNext: boolean = transactions.length >= limit + + const airGapTransactions: AirGapTransaction[] = transactions.map((transaction: AccountTransaction) => ({ + from: [transaction.from], + to: [transaction.to], + isInbound: transaction.to === publicKey.value, + amount: newAmount(transaction.amount, 'blockchain'), + fee: newAmount(transaction.fee, 'blockchain'), + arbitraryData: transaction.memo, + network: this.options.network, + type: transaction.kind !== ACCOUNT_TRANSFER_KIND ? transaction.kind : undefined, + timestamp: Date.parse(transaction.dateTime), + status: { + type: transaction.hash ? 'applied' : transaction.failureReason ? 'failed' : 'unknown', + hash: transaction.hash + } + })) + + return { + transactions: airGapTransactions, + cursor: { + hasNext, + lastDateTime + } + } + } + + public async getBalanceOfPublicKey(publicKey: PublicKey): Promise> { + const balance: AccountBalance = await this.node.getBalance(publicKey.value) + + return { + total: newAmount(balance.total, 'blockchain'), + transferable: newAmount(balance.liquid, 'blockchain') + } + } + + public async getTransactionMaxAmountWithPublicKey( + publicKey: PublicKey, + _to: string[], + configuration?: TransactionFullConfiguration + ): Promise> { + const balance: AccountBalance = await this.node.getBalance(publicKey.value) + const fee = configuration?.fee ? newAmount(configuration.fee).blockchain(this.units).value : '0' + + const maxAmount = new BigNumber(balance.liquid).minus(fee) + + return newAmount(maxAmount, 'blockchain') + } + + public async getTransactionFeeWithPublicKey( + _publicKey: PublicKey, + _details: TransactionDetails[], + _configuration?: TransactionSimpleConfiguration + ): Promise> { + const fees: string[] = await this.indexer.getLatestFees(20) + const feesQuantile = (q: number): BigNumber => + quantile(fees, q, { + isSorted: true /* `getLatestFees` returns an already sorted (ASC) list */, + roundingMode: BigNumber.ROUND_CEIL + }) + + return { + low: newAmount(feesQuantile(0.25), 'blockchain'), + medium: newAmount(feesQuantile(0.5), 'blockchain'), + high: newAmount(feesQuantile(0.75), 'blockchain') + } + } + + public async prepareTransactionWithPublicKey( + publicKey: PublicKey, + details: TransactionDetails[], + configuration?: TransactionFullConfiguration + ): Promise { + if (details.length !== 1) { + throw new ConditionViolationError(Domain.MINA, 'Multiple transactions not supported.') + } + + const paymentDetails: TransactionDetails = details[0] + const nonce: string = await this.node.getNonce(publicKey.value) + const fee: Amount = + configuration?.fee ?? (await this.getTransactionFeeWithPublicKey(publicKey, details, configuration)).medium + + const balance: Balance = await this.getBalanceOfPublicKey(publicKey) + + const amountBlockchain: Amount = newAmount(paymentDetails.amount).blockchain(this.units) + const feeBlockchain: Amount = newAmount(fee).blockchain(this.units) + const transferableBlockchain: Amount = newAmount(balance.transferable ?? balance.total).blockchain(this.units) + + if (new BigNumber(transferableBlockchain.value).minus(amountBlockchain.value).minus(feeBlockchain.value).lt(0)) { + throw new BalanceError(Domain.MINA, 'Insfficient balance.') + } + + return newUnsignedTransaction({ + networkType: this.options.network.minaType, + data: { + to: paymentDetails.to, + from: publicKey.value, + amount: amountBlockchain.value, + fee: feeBlockchain.value, + nonce, + memo: paymentDetails.arbitraryData + } + }) + } + + public async broadcastTransaction(transaction: MinaSignedTransaction): Promise { + return this.node.sendTransaction(transaction.data, transaction.signature) + } + + // Custom + + private getMinaClient(network: MinaNetworkType = this.options.network.minaType): Client { + return new Client({ network }) + } + + private derivativeToBip32Node(derivative: CryptoDerivative) { + const bip32Node = encodeDerivative('bip32', derivative) + + return this.bitcoinJS.lib.HDNode.fromBase58(bip32Node.secretKey, this.bitcoinJS.config.network) + } +} + +// Factory + +export function createMinaProtocol(options: RecursivePartial = {}): MinaProtocol { + return new MinaProtocolImpl(options) +} + +export const MINA_MAINNET_PROTOCOL_NETWORK: MinaProtocolNetwork = { + name: 'Mainnet', + type: 'mainnet', + rpcUrl: 'https://proxy.minaexplorer.com', + blockExplorerUrl: 'https://minaexplorer.com/', + blockExplorerApi: 'https://graphql.minaexplorer.com', + minaType: 'mainnet' +} + +export const MINA_TESTNET_PROTOCOL_NETWORK: MinaProtocolNetwork = { + name: 'Devnet', + type: 'testnet', + rpcUrl: 'https://proxy.devnet.minaexplorer.com', + blockExplorerUrl: 'https://devnet.minaexplorer.com', + blockExplorerApi: 'https://devnet.graphql.minaexplorer.com', + minaType: 'testnet' +} + +const DEFAULT_MINA_PROTOCOL_NETWORK: MinaProtocolNetwork = MINA_MAINNET_PROTOCOL_NETWORK + +export function createMinaProtocolOptions(network: Partial = {}): MinaProtocolOptions { + return { + network: { ...DEFAULT_MINA_PROTOCOL_NETWORK, ...network } + } +} diff --git a/packages/mina/src/v1/serializer/v3/schemas/converter/transaction-converter.ts b/packages/mina/src/v1/serializer/v3/schemas/converter/transaction-converter.ts new file mode 100644 index 000000000..d3072ae90 --- /dev/null +++ b/packages/mina/src/v1/serializer/v3/schemas/converter/transaction-converter.ts @@ -0,0 +1,27 @@ +import { newSignedTransaction, newUnsignedTransaction } from '@airgap/module-kit' + +import { MinaSignedTransaction, MinaUnsignedTransaction } from '../../../../types/transaction' +import { MinaTransactionSignRequest } from '../definitions/transaction-sign-request-mina' +import { MinaTransactionSignResponse } from '../definitions/transaction-sign-response-mina' + +export function minaUnsignedTransactionToRequest(unsigned: MinaUnsignedTransaction, publicKey: string, callbackUrl?: string): MinaTransactionSignRequest { + const { type: _, ...rest } = unsigned + + return { + transaction: rest, + publicKey, + callbackURL: callbackUrl + } +} + +export function minaSignedTransactionToResponse(signed: MinaSignedTransaction, accountIdentifier: string): MinaTransactionSignResponse { + return { transaction: JSON.stringify(signed), accountIdentifier } +} + +export function minaTransactionSignRequestToUnsigned(request: MinaTransactionSignRequest): MinaUnsignedTransaction { + return newUnsignedTransaction(request.transaction) +} + +export function minaTransactionSignResponseToSigned(response: MinaTransactionSignResponse): MinaSignedTransaction { + return newSignedTransaction(JSON.parse(response.transaction)) +} \ No newline at end of file diff --git a/packages/mina/src/v1/serializer/v3/schemas/definitions/transaction-sign-request-mina.ts b/packages/mina/src/v1/serializer/v3/schemas/definitions/transaction-sign-request-mina.ts new file mode 100644 index 000000000..3c59228d5 --- /dev/null +++ b/packages/mina/src/v1/serializer/v3/schemas/definitions/transaction-sign-request-mina.ts @@ -0,0 +1,5 @@ +import { TransactionSignRequest } from '@airgap/serializer' + +import { MinaUnsignedTransaction } from '../../../../types/transaction' + +export interface MinaTransactionSignRequest extends TransactionSignRequest> {} \ No newline at end of file diff --git a/packages/mina/src/v1/serializer/v3/schemas/definitions/transaction-sign-response-mina.ts b/packages/mina/src/v1/serializer/v3/schemas/definitions/transaction-sign-response-mina.ts new file mode 100644 index 000000000..70e968d8d --- /dev/null +++ b/packages/mina/src/v1/serializer/v3/schemas/definitions/transaction-sign-response-mina.ts @@ -0,0 +1,3 @@ +import { TransactionSignResponse } from '@airgap/serializer' + +export interface MinaTransactionSignResponse extends TransactionSignResponse {} \ No newline at end of file diff --git a/packages/mina/src/v1/serializer/v3/schemas/generated/transaction-sign-request-mina.json b/packages/mina/src/v1/serializer/v3/schemas/generated/transaction-sign-request-mina.json new file mode 100644 index 000000000..780edb699 --- /dev/null +++ b/packages/mina/src/v1/serializer/v3/schemas/generated/transaction-sign-request-mina.json @@ -0,0 +1,76 @@ +{ + "$ref": "#/definitions/MinaTransactionSignRequest", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MinaNetworkType": { + "enum": [ + "mainnet", + "testnet" + ], + "type": "string" + }, + "MinaTransactionSignRequest": { + "additionalProperties": false, + "properties": { + "callbackURL": { + "type": "string" + }, + "publicKey": { + "type": "string" + }, + "transaction": { + "additionalProperties": false, + "properties": { + "data": { + "additionalProperties": false, + "properties": { + "amount": { + "type": "string" + }, + "fee": { + "type": "string" + }, + "from": { + "type": "string" + }, + "memo": { + "type": "string" + }, + "nonce": { + "type": "string" + }, + "to": { + "type": "string" + }, + "validUntil": { + "type": "string" + } + }, + "required": [ + "to", + "from", + "amount", + "fee", + "nonce" + ], + "type": "object" + }, + "networkType": { + "$ref": "#/definitions/MinaNetworkType" + } + }, + "required": [ + "networkType", + "data" + ], + "type": "object" + } + }, + "required": [ + "publicKey", + "transaction" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/mina/src/v1/serializer/v3/schemas/generated/transaction-sign-response-mina.json b/packages/mina/src/v1/serializer/v3/schemas/generated/transaction-sign-response-mina.json new file mode 100644 index 000000000..11e179d1e --- /dev/null +++ b/packages/mina/src/v1/serializer/v3/schemas/generated/transaction-sign-response-mina.json @@ -0,0 +1,22 @@ +{ + "$ref": "#/definitions/MinaTransactionSignResponse", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MinaTransactionSignResponse": { + "additionalProperties": false, + "properties": { + "accountIdentifier": { + "type": "string" + }, + "transaction": { + "type": "string" + } + }, + "required": [ + "accountIdentifier", + "transaction" + ], + "type": "object" + } + } +} \ No newline at end of file diff --git a/packages/mina/src/v1/serializer/v3/serializer-companion.ts b/packages/mina/src/v1/serializer/v3/serializer-companion.ts new file mode 100644 index 000000000..abc0bc1fa --- /dev/null +++ b/packages/mina/src/v1/serializer/v3/serializer-companion.ts @@ -0,0 +1,118 @@ +import { Domain, MainProtocolSymbols } from '@airgap/coinlib-core' +import { validators } from '@airgap/coinlib-core/dependencies/src/validate.js-0.13.1/validate' +import { UnsupportedError } from '@airgap/coinlib-core/errors' +import { AirGapV3SerializerCompanion, SignedTransaction, UnsignedTransaction, V3SchemaConfiguration } from '@airgap/module-kit' +import { IACMessageType, SchemaRoot, TransactionSignRequest, TransactionSignResponse } from '@airgap/serializer' + +import { MinaSignedTransaction, MinaUnsignedTransaction } from '../../types/transaction' + +import { minaSignedTransactionToResponse, minaTransactionSignRequestToUnsigned, minaTransactionSignResponseToSigned, minaUnsignedTransactionToRequest } from './schemas/converter/transaction-converter' +import { MinaTransactionValidator } from './validators/transaction-validator' +import { minaValidators } from './validators/validators' + +const minaTransactionSignRequest: SchemaRoot = require('./schemas/generated/transaction-sign-request-mina.json') +const minaTransactionSignResponse: SchemaRoot = require('./schemas/generated/transaction-sign-response-mina.json') + +export class MinaV3SerializerCompanion implements AirGapV3SerializerCompanion { + public readonly schemas: V3SchemaConfiguration[] = [ + { + type: IACMessageType.TransactionSignRequest, + schema: { schema: minaTransactionSignRequest }, + protocolIdentifier: MainProtocolSymbols.MINA + }, + { + type: IACMessageType.TransactionSignResponse, + schema: { schema: minaTransactionSignResponse }, + protocolIdentifier: MainProtocolSymbols.MINA + } + ] + + private readonly minaTransactionValidator: MinaTransactionValidator = new MinaTransactionValidator() + + public constructor() { + Object.keys(minaValidators).forEach((key: string) => { + validators[key] = minaValidators[key] + }) + } + + public async toTransactionSignRequest( + identifier: string, + unsignedTransaction: UnsignedTransaction, + publicKey: string, + callbackUrl?: string + ): Promise { + switch (identifier) { + case MainProtocolSymbols.MINA: + return minaUnsignedTransactionToRequest(unsignedTransaction as MinaUnsignedTransaction, publicKey, callbackUrl) + default: + throw new UnsupportedError(Domain.MINA, `Protocol ${identifier} not supported`) + } + } + + public async fromTransactionSignRequest( + identifier: string, + transactionSignRequest: TransactionSignRequest + ): Promise { + switch (identifier) { + case MainProtocolSymbols.MINA: + return minaTransactionSignRequestToUnsigned(transactionSignRequest) + default: + throw new UnsupportedError(Domain.MINA, `Protocol ${identifier} not supported`) + } + } + + public async validateTransactionSignRequest(identifier: string, transactionSignRequest: TransactionSignRequest): Promise { + switch (identifier) { + case MainProtocolSymbols.MINA: + try { + await this.minaTransactionValidator.validateUnsignedTransaction(transactionSignRequest) + + return true + } catch { + return false + } + default: + throw new UnsupportedError(Domain.MINA, `Protocol ${identifier} not supported`) + } + } + + public async toTransactionSignResponse( + identifier: string, + signedTransaction: SignedTransaction, + accountIdentifier: string + ): Promise { + switch (identifier) { + case MainProtocolSymbols.MINA: + return minaSignedTransactionToResponse(signedTransaction as MinaSignedTransaction, accountIdentifier) + default: + throw new UnsupportedError(Domain.MINA, `Protocol ${identifier} not supported`) + } + } + + public async fromTransactionSignResponse( + identifier: string, + transactionSignResponse: TransactionSignResponse + ): Promise { + switch (identifier) { + case MainProtocolSymbols.MINA: + return minaTransactionSignResponseToSigned(transactionSignResponse) + default: + throw new UnsupportedError(Domain.MINA, `Protocol ${identifier} not supported`) + } + } + + public async validateTransactionSignResponse(identifier: string, transactionSignResponse: TransactionSignResponse): Promise { + switch (identifier) { + case MainProtocolSymbols.MINA: + try { + await this.minaTransactionValidator.validateSignedTransaction(transactionSignResponse) + + return true + } catch { + return false + } + default: + throw new UnsupportedError(Domain.MINA, `Protocol ${identifier} not supported`) + } + } +} diff --git a/packages/mina/src/v1/serializer/v3/validators/transaction-validator.ts b/packages/mina/src/v1/serializer/v3/validators/transaction-validator.ts new file mode 100644 index 000000000..fbac70494 --- /dev/null +++ b/packages/mina/src/v1/serializer/v3/validators/transaction-validator.ts @@ -0,0 +1,44 @@ +import { async } from '@airgap/coinlib-core/dependencies/src/validate.js-0.13.1/validate' +import { TransactionValidator, validateSyncScheme } from '@airgap/serializer' + +import { MinaTransactionSignRequest } from '../schemas/definitions/transaction-sign-request-mina' +import { MinaTransactionSignResponse } from '../schemas/definitions/transaction-sign-response-mina' + +const unsignedTransactionConstraints = { + networkType: { + presence: { allowEmpty: false }, + type: 'String', + isValidMinaNetworkType: true + }, + data: { + presence: { allowEmpty: false }, + isValidMinaPayment: true + } +} +const signedTransactionConstraints = { + data: { + presence: { allowEmpty: false }, + isValidMinaPayment: true + }, + signature: { + presence: { allowEmpty: false }, + isValidMinaSignature: true + } +} + +const success = () => undefined +const error = (errors) => errors + +export class MinaTransactionValidator implements TransactionValidator { + public async validateUnsignedTransaction(request: MinaTransactionSignRequest): Promise { + const transaction = request.transaction + validateSyncScheme({}) + + return async(transaction, unsignedTransactionConstraints).then(success, error) + } + + public async validateSignedTransaction(response: MinaTransactionSignResponse): Promise { + const transaction = JSON.parse(response.transaction) + return async(transaction, signedTransactionConstraints).then(success, error) + } +} diff --git a/packages/mina/src/v1/serializer/v3/validators/validators.ts b/packages/mina/src/v1/serializer/v3/validators/validators.ts new file mode 100644 index 000000000..733bbdc9b --- /dev/null +++ b/packages/mina/src/v1/serializer/v3/validators/validators.ts @@ -0,0 +1,83 @@ +import { createMinaProtocol } from '../../../protocol/MinaProtocol' +import { MinaPayment, MinaSignature } from '../../../types/transaction' + +export const minaValidators = { + isValidMinaPayment: async (value: unknown) => { + if (typeof value !== 'object') { + return 'not an object' + } + + const protocol = createMinaProtocol() + const metadata = await protocol.getMetadata() + const addressPattern = RegExp(metadata.account?.address?.regex ?? '.*') + const amountPattern = RegExp('\d+') + + const payment = value as Partial + if (typeof payment.to !== 'string' || !addressPattern.test(payment.to)) { + return 'not a valid Mina `to` address' + } + if (typeof payment.from !== 'string' || !addressPattern.test(payment.from)) { + return 'not a valid Mina `from` address' + } + if (typeof payment.amount !== 'string' || !amountPattern.test(payment.amount)) { + return 'not a valid amount' + } + if (typeof payment.fee !== 'string' || !amountPattern.test(payment.fee)) { + return 'not a valid fee' + } + if (typeof payment.nonce !== 'string' || !amountPattern.test(payment.nonce) || parseInt(payment.nonce, 10) < 0) { + return 'not a valid nonce' + } + if (typeof payment.memo !== 'undefined' && typeof payment.memo !== 'string') { + return 'not a valid memo' + } + if (typeof payment.validUntil !== 'undefined' && (typeof payment.validUntil !== 'string' || !amountPattern.test(payment.validUntil))) { + return 'not a valid timestamp' + } + + return null + }, + isValidMinaSignature: (value: unknown) => { + if (typeof value !== 'object') { + return 'not an object' + } + + const signature = value as Partial + + if (signature.type === 'raw') { + if (typeof signature.value !== 'string') { + return 'not a valid Mina raw signature' + } + + return null + } + + if (signature.type === 'legacy') { + if (typeof signature.field !== 'string') { + return 'not a valid Mina legacy signature' + } + if (typeof signature.scalar !== 'string') { + return 'not a valid Mina legacy signature' + } + + return null + } + + return 'not a valid Mina signature type' + }, + isValidMinaNetworkType: (value: unknown) => { + if (!value) { + return null + } + + if (typeof value !== 'string') { + return 'not a string' + } + + if (value !== 'mainnet' && value !== 'testnet') { + return 'not a valid Mina network type' + } + + return null + } +} \ No newline at end of file diff --git a/packages/mina/src/v1/types/crypto.ts b/packages/mina/src/v1/types/crypto.ts new file mode 100644 index 000000000..14677a3ef --- /dev/null +++ b/packages/mina/src/v1/types/crypto.ts @@ -0,0 +1,3 @@ +import { Secp256K1CryptoConfiguration } from '@airgap/module-kit' + +export type MinaCryptoConfiguration = Secp256K1CryptoConfiguration diff --git a/packages/mina/src/v1/types/indexer.ts b/packages/mina/src/v1/types/indexer.ts new file mode 100644 index 000000000..c8724258b --- /dev/null +++ b/packages/mina/src/v1/types/indexer.ts @@ -0,0 +1,13 @@ +export const ACCOUNT_TRANSFER_KIND: string = 'PAYMENT' + +export interface AccountTransaction { + to: string + from: string + amount: string | number + fee: string | number + memo?: string + kind?: string + hash?: string + dateTime: string + failureReason?: string // TODO: confirm type +} diff --git a/packages/mina/src/v1/types/node.ts b/packages/mina/src/v1/types/node.ts new file mode 100644 index 000000000..7886a4f5a --- /dev/null +++ b/packages/mina/src/v1/types/node.ts @@ -0,0 +1,4 @@ +export interface AccountBalance { + total: string + liquid: string +} diff --git a/packages/mina/src/v1/types/protocol.ts b/packages/mina/src/v1/types/protocol.ts new file mode 100644 index 000000000..b06a82aa9 --- /dev/null +++ b/packages/mina/src/v1/types/protocol.ts @@ -0,0 +1,14 @@ +import { ProtocolNetwork } from '@airgap/module-kit' + +export type MinaUnits = 'MINA' + +export type MinaNetworkType = 'mainnet' | 'testnet' + +export interface MinaProtocolNetwork extends ProtocolNetwork { + blockExplorerApi: string + minaType: MinaNetworkType +} + +export interface MinaProtocolOptions { + network: MinaProtocolNetwork +} diff --git a/packages/mina/src/v1/types/transaction.ts b/packages/mina/src/v1/types/transaction.ts new file mode 100644 index 000000000..97e7c2220 --- /dev/null +++ b/packages/mina/src/v1/types/transaction.ts @@ -0,0 +1,40 @@ +import { SignedTransaction, TransactionCursor, UnsignedTransaction } from '@airgap/module-kit' + +import { MinaNetworkType } from './protocol' + +export interface MinaPayment { + to: string + from: string + amount: string + fee: string + nonce: string + memo?: string + validUntil?: string +} + +export interface MinaRawSignature { + type: 'raw' + value: string +} + +export interface MinaLegacySignature { + type: 'legacy' + field: string + scalar: string +} + +export type MinaSignature = MinaRawSignature | MinaLegacySignature + +export interface MinaUnsignedTransaction extends UnsignedTransaction { + networkType: MinaNetworkType + data: MinaPayment +} + +export interface MinaSignedTransaction extends SignedTransaction { + data: MinaPayment + signature: MinaSignature +} + +export interface MinaTransactionCursor extends TransactionCursor { + lastDateTime?: string +} diff --git a/packages/mina/src/v1/utils/key.ts b/packages/mina/src/v1/utils/key.ts new file mode 100644 index 000000000..957e4afbe --- /dev/null +++ b/packages/mina/src/v1/utils/key.ts @@ -0,0 +1,16 @@ +import * as bs58check from '@airgap/coinlib-core/dependencies/src/bs58check-2.1.2' +import { newPublicKey, newSecretKey, PublicKey, SecretKey } from '@airgap/module-kit' +import Client from 'mina-signer' + +export function finalizeSecretKey(rawSecretKey: Buffer): SecretKey { + rawSecretKey[0] &= 0x3f + const secretKey: Buffer = Buffer.concat([Buffer.from('5a01', 'hex'), rawSecretKey.reverse()]) + + return newSecretKey(bs58check.encode(secretKey), 'encoded') +} + +export function derivePublicKey(client: Client, secretKey: SecretKey): PublicKey { + const publicKey = client.derivePublicKey(secretKey.value) + + return newPublicKey(publicKey, 'encoded') +} diff --git a/packages/mina/src/v1/utils/math.ts b/packages/mina/src/v1/utils/math.ts new file mode 100644 index 000000000..37492cf90 --- /dev/null +++ b/packages/mina/src/v1/utils/math.ts @@ -0,0 +1,25 @@ +import BigNumber from '@airgap/coinlib-core/dependencies/src/bignumber.js-9.0.0/bignumber' + +export function quantile( + values: (string | number | BigNumber)[], + q: number, + options: { + isSorted: boolean + roundingMode?: BigNumber.RoundingMode + } = { isSorted: false } +): BigNumber { + const bigNumberValues: BigNumber[] = values.map((value: string | number | BigNumber) => + BigNumber.isBigNumber(value) ? value : new BigNumber(value) + ) + + const sortedValues: BigNumber[] = options.isSorted ? bigNumberValues : bigNumberValues.sort() + const index = Math.floor((sortedValues.length - 1) * q) + + if (sortedValues.length % 2 !== 0 || index === sortedValues.length - 1) { + return sortedValues[index] + } else { + const avg = sortedValues[index].plus(sortedValues[index + 1]).div(2) + + return options.roundingMode ? avg.integerValue(options.roundingMode) : avg + } +} diff --git a/packages/mina/test/_setup.spec.ts b/packages/mina/test/_setup.spec.ts new file mode 100644 index 000000000..d02b2bf41 --- /dev/null +++ b/packages/mina/test/_setup.spec.ts @@ -0,0 +1,14 @@ +import Axios from '@airgap/coinlib-core/dependencies/src/axios-0.19.0' + +const MockAdapter = require('axios-mock-adapter') + +// This sets the mock adapter on the default instance +const mock = new MockAdapter(Axios) + +mock.onAny().replyOnce((config) => { + console.log('UNMOCKED URL, RETURNING ERROR 500', config.url) + + return [500, {}] +}) + +// mock.onAny().passThrough() diff --git a/packages/mina/test/block-explorer.ts b/packages/mina/test/block-explorer.ts new file mode 100644 index 000000000..6a9106e48 --- /dev/null +++ b/packages/mina/test/block-explorer.ts @@ -0,0 +1,64 @@ +// tslint:disable no-floating-promises +import { AirGapBlockExplorer } from '@airgap/module-kit' +import chai = require('chai') +import chaiAsPromised = require('chai-as-promised') +import 'mocha' + +import { MinaExplorerBlockExplorer } from '../src/v1/block-explorer/MinaExplorerBlockExplorer' +import { MINA_MAINNET_PROTOCOL_NETWORK } from '../src/v1/protocol/MinaProtocol' + +// use chai-as-promised plugin +chai.use(chaiAsPromised) +const expect = chai.expect + +const blockExplorers: AirGapBlockExplorer[] = [new MinaExplorerBlockExplorer(MINA_MAINNET_PROTOCOL_NETWORK.blockExplorerUrl)] + +Promise.all( + blockExplorers.map(async (blockExplorer: AirGapBlockExplorer) => { + const blockExplorerMetadata = await blockExplorer.getMetadata() + + const address = 'dummyAddress' + const txId = 'dummyTxId' + + const addressUrl = await blockExplorer.createAddressUrl(address) + const transactionUrl = await blockExplorer.createTransactionUrl(txId) + + describe(`Block Explorer ${blockExplorerMetadata.name}`, () => { + it('should replace address', async () => { + expect(addressUrl).to.contain(address) + }) + + it('should replace txId', async () => { + expect(transactionUrl).to.contain(txId) + }) + + it('should contain blockexplorer url', async () => { + expect(addressUrl).to.contain(blockExplorerMetadata.url) + expect(transactionUrl).to.contain(blockExplorerMetadata.url) + }) + + it('should not contain placeholder brackets', async () => { + // Placeholders should be replaced + expect(addressUrl).to.not.contain('{{') + expect(addressUrl).to.not.contain('}}') + expect(transactionUrl).to.not.contain('{{') + expect(transactionUrl).to.not.contain('}}') + }) + + it('should always use https://', async () => { + expect(addressUrl).to.not.contain('http://') + expect(transactionUrl).to.not.contain('http://') + expect(addressUrl).to.contain('https://') + expect(transactionUrl).to.contain('https://') + }) + + it('should never contain 2 / after each other', async () => { + // We remove "https://" so we can check if the rest of the url contains "//" + expect(addressUrl.split('https://').join('')).to.not.contain('//') + expect(transactionUrl.split('https://').join('')).to.not.contain('//') + }) + }) + }) +).then(() => { + run() +}) diff --git a/packages/mina/test/implementations.ts b/packages/mina/test/implementations.ts new file mode 100644 index 000000000..4fba20b85 --- /dev/null +++ b/packages/mina/test/implementations.ts @@ -0,0 +1,57 @@ +import { derive, mnemonicToSeed } from '@airgap/crypto' +import { Amount, CryptoDerivative, PublicKey, SecretKey, Signature } from '@airgap/module-kit' + +import { MinaProtocol, MinaSignedTransaction, MinaUnsignedTransaction } from '../src/v1' +import { MinaUnits } from '../src/v1/types/protocol' + +interface ProtocolHTTPStub { + registerStub(testProtocolSpec: TestProtocolSpec): Promise + noBalanceStub(testProtocolSpec: TestProtocolSpec): Promise + transactionListStub(testProtocolSpec: TestProtocolSpec, address: string): Promise +} + +abstract class TestProtocolSpec<_Units extends string = MinaUnits> { + public name: string = 'TEST' + // tslint:disable:no-object-literal-type-assertion + public abstract lib: MinaProtocol + public abstract stub: ProtocolHTTPStub + // tslint:enable:no-object-literal-type-assertion + public validAddresses: string[] = [] + public abstract wallet: { + secretKey: SecretKey + publicKey: PublicKey + derivationPath: string + addresses: string[] + } + public abstract txs: { + to: string[] + from: string[] + amount: Amount<_Units> + fee: Amount<_Units> + memo?: string + unsignedTx: MinaUnsignedTransaction + signedTx: MinaSignedTransaction + }[] + public messages: { message: string; signature: Signature }[] = [] + public encryptAsymmetric: { message: string; encrypted: string }[] = [] + public encryptAES: { message: string; encrypted: string }[] = [] + + public abstract seed(): string + public abstract mnemonic(): string + + public async derivative(derivationPath?: string): Promise { + const [metadata, cryptoConfiguration] = await Promise.all([this.lib.getMetadata(), this.lib.getCryptoConfiguration()]) + + return derive( + cryptoConfiguration, + await mnemonicToSeed(cryptoConfiguration, this.mnemonic()), + derivationPath ?? metadata.account.standardDerivationPath + ) + } + + public transactionList(address: string): any { + return {} + } +} + +export { TestProtocolSpec, ProtocolHTTPStub } diff --git a/packages/mina/test/paging.spec.ts b/packages/mina/test/paging.spec.ts new file mode 100644 index 000000000..5e52902d9 --- /dev/null +++ b/packages/mina/test/paging.spec.ts @@ -0,0 +1,25 @@ +// import chai = require('chai') +// import chaiAsPromised = require('chai-as-promised') +// import 'mocha' +// import sinon = require('sinon') + +// import { TestProtocolSpec } from './implementations' +// import { MinaTestProtocolSpec } from './specs/mina' + +// // use chai-as-promised plugin +// chai.use(chaiAsPromised) +// const expect = chai.expect + +// const protocols: TestProtocolSpec[] = [new MinaTestProtocolSpec()] + +// protocols.forEach(async (protocol: TestProtocolSpec) => { +// describe(`Transaction Paging`, () => { +// afterEach(async () => { +// sinon.restore() +// }) + +// it(`should properly page transactions for ${protocol.name.toUpperCase()}`, async () => { + +// }) +// }) +// }) diff --git a/packages/mina/test/protocol.spec.ts b/packages/mina/test/protocol.spec.ts new file mode 100644 index 000000000..6a62e805f --- /dev/null +++ b/packages/mina/test/protocol.spec.ts @@ -0,0 +1,197 @@ +// tslint:disable no-floating-promises +import { AirGapTransaction } from '@airgap/module-kit' +import chai = require('chai') +import chaiAsPromised = require('chai-as-promised') +import 'mocha' +import sinon = require('sinon') + +import { TestProtocolSpec } from './implementations' +import { MinaTestProtocolSpec } from './specs/mina' + +// use chai-as-promised plugin +chai.use(chaiAsPromised) +const expect = chai.expect + +const protocols = [new MinaTestProtocolSpec()] + +Promise.all( + protocols.map(async (protocol: TestProtocolSpec) => { + const protocolMetadata = await protocol.lib.getMetadata() + + describe(`Protocol ${protocol.name}`, () => { + describe(`KeyPair`, () => { + beforeEach(async () => { + await protocol.stub.registerStub(protocol) + }) + + afterEach(async () => { + sinon.restore() + }) + + it('getKeyPairFromDerivative - should be able to create a key pair from a derivative (extended keys)', async () => { + const { secretKey, publicKey } = await protocol.lib.getKeyPairFromDerivative( + await protocol.derivative(protocol.wallet.derivationPath) + ) + + expect(secretKey).to.deep.equal(protocol.wallet.secretKey) + expect(publicKey).to.deep.equal(protocol.wallet.publicKey) + }) + + it('getAddressFromPublicKey - should be able to create a valid address from a supplied publicKey', async () => { + const { publicKey } = await protocol.lib.getKeyPairFromDerivative(await protocol.derivative(protocol.wallet.derivationPath)) + const address = await protocol.lib.getAddressFromPublicKey(publicKey) + + // check if address format matches + if (protocolMetadata.account?.address?.regex) { + expect(address.match(new RegExp(protocolMetadata.account.address.regex))).not.to.equal(null) + } + + // check if address matches to supplied one + expect(address).to.equal(protocol.wallet.addresses[0], 'address does not match') + }) + }) + + describe(`Prepare Transaction`, () => { + beforeEach(async () => { + await protocol.stub.registerStub(protocol) + }) + + afterEach(async () => { + sinon.restore() + }) + + it('prepareTransactionWithPublicKey - Is able to prepare a tx using its public key', async () => { + const preparedTx = await protocol.lib.prepareTransactionWithPublicKey( + protocol.wallet.publicKey, + [ + { + to: protocol.txs[0].to[0], + amount: protocol.txs[0].amount, + arbitraryData: protocol.txs[0].memo + } + ], + { fee: protocol.txs[0].fee } + ) + + protocol.txs.forEach((tx) => { + expect(preparedTx).to.deep.include(tx.unsignedTx) + }) + }) + + it('prepareTransactionWithPublicKey - Is able to prepare a transaction with amount 0', async () => { + // should not throw an exception when trying to create a 0 TX, given enough funds are available for the gas + await protocol.stub.registerStub(protocol) + + try { + await protocol.lib.prepareTransactionWithPublicKey( + protocol.wallet.publicKey, + [ + { + to: protocol.txs[0].to[0], + amount: { value: '0', unit: 'blockchain' }, + arbitraryData: protocol.txs[0].memo + } + ], + { fee: protocol.txs[0].fee } + ) + } catch (error) { + throw error + } + + await protocol.stub.noBalanceStub(protocol) + + try { + await protocol.lib.prepareTransactionWithPublicKey( + protocol.wallet.publicKey, + [ + { + to: protocol.txs[0].to[0], + amount: { value: '0', unit: 'blockchain' }, + arbitraryData: protocol.txs[0].memo + } + ], + { fee: protocol.txs[0].fee } + ) + throw new Error(`should have failed`) + } catch (error) { + expect(error.toString()).to.contain('balance') + } + }) + }) + + describe(`Sign Transaction`, () => { + beforeEach(async () => { + await protocol.stub.registerStub(protocol) + }) + + afterEach(async () => { + sinon.restore() + }) + + it('signTransactionWithSecretKey - Is able to sign a transaction using a SecretKey', async () => { + const { secretKey } = await protocol.lib.getKeyPairFromDerivative(await protocol.derivative(protocol.wallet.derivationPath)) + + for (const { unsignedTx, signedTx } of protocol.txs) { + const tx = await protocol.lib.signTransactionWithSecretKey(unsignedTx, secretKey) + expect(tx.signature).to.deep.equal(signedTx.signature) + } + }) + }) + + describe(`Extract TX`, () => { + it('getDetailsFromTransaction - Is able to extract all necessary properties from an unsigned TX', async () => { + for (const tx of protocol.txs) { + const airgapTxs: AirGapTransaction[] = await protocol.lib.getDetailsFromTransaction(tx.unsignedTx, protocol.wallet.publicKey) + + if (airgapTxs.length !== 1) { + throw new Error('Unexpected number of transactions') + } + + const airgapTx: AirGapTransaction = JSON.parse(JSON.stringify(airgapTxs[0])) + + expect(airgapTx.to, 'to property does not match').to.deep.equal(tx.to) + expect(airgapTx.from, 'from property does not match').to.deep.equal(tx.from) + + expect(airgapTx.amount, 'amount does not match').to.deep.equal(protocol.txs[0].amount) + expect(airgapTx.fee, 'fee does not match').to.deep.equal(protocol.txs[0].fee) + + expect(airgapTx.arbitraryData, 'arbitraryDetails does not match').to.equal(protocol.txs[0].memo) + } + }) + + it('getDetailsFromTransaction - Is able to extract all necessary properties from a signed TX', async () => { + for (const tx of protocol.txs) { + const airgapTxs: AirGapTransaction[] = await protocol.lib.getDetailsFromTransaction(tx.unsignedTx, protocol.wallet.publicKey) + + if (airgapTxs.length !== 1) { + throw new Error('Unexpected number of transactions') + } + + const airgapTx: AirGapTransaction = JSON.parse(JSON.stringify(airgapTxs[0])) + expect( + airgapTx.to.map((obj) => obj.toLowerCase()), + 'from' + ).to.deep.equal(tx.to.map((obj) => obj.toLowerCase())) + expect( + airgapTx.from.sort().map((obj) => obj.toLowerCase()), + 'to' + ).to.deep.equal(tx.from.sort().map((obj) => obj.toLowerCase())) + expect(airgapTx.amount).to.deep.equal(protocol.txs[0].amount) + expect(airgapTx.fee).to.deep.equal(protocol.txs[0].fee) + expect(airgapTx.arbitraryData, 'arbitraryDetails does not match').to.equal(protocol.txs[0].memo) + } + }) + + it('should match all valid addresses', async () => { + for (const address of protocol.validAddresses) { + const match = protocolMetadata.account?.address?.regex ? address.match(protocolMetadata.account.address.regex) : false + + expect(match && match.length > 0, `address: ${address}`).to.be.true + } + }) + }) + }) + }) +).then(() => { + run() +}) diff --git a/packages/mina/test/specs/mina.ts b/packages/mina/test/specs/mina.ts new file mode 100644 index 000000000..c2696ce8a --- /dev/null +++ b/packages/mina/test/specs/mina.ts @@ -0,0 +1,92 @@ +// tslint:disable: no-object-literal-type-assertion +import { Amount, PublicKey, SecretKey } from '@airgap/module-kit' + +import { MinaSignedTransaction, MinaUnits, MinaUnsignedTransaction } from '../../src/v1' +import { MinaProtocolImpl } from '../../src/v1/protocol/MinaProtocol' +import { TestProtocolSpec } from '../implementations' +import { MinaProtocolStub } from '../stubs/mina.stub' + +// Test Mnemonic: food talent voyage degree siege clever account medal film remind good kind +// Derivation path: m/44'/12586'/0'/0/0 +// Address: B62qqVRdiV7S96WnjJyRqu4KRBuU1gk6pVaWxNSpPDytPRBYHd255E8 +export class MinaTestProtocolSpec extends TestProtocolSpec { + public name = 'Mina' + public stub = new MinaProtocolStub() + public lib = new MinaProtocolImpl({}, this.stub.nodeStub) + + public validAddresses = [ + 'B62qihbqTwvF7gdFd3Q18D3XsAZ8cvdpUz4VzTnP9R4zNSNoPty1KuU', + 'B62qkR4H7VYXXwv4NJzAscKAD7yHDgUaLproZZPiZ9BbW7naTJmSbdx' + ] + + public wallet = { + secretKey: { + type: 'priv', + format: 'encoded', + value: 'EKERE61HRXV6mcHvUyKswwxAkobXozMjttxRtsJLSEfRMLmbuVQy' + } as SecretKey, + publicKey: { + type: 'pub', + format: 'encoded', + value: 'B62qqVRdiV7S96WnjJyRqu4KRBuU1gk6pVaWxNSpPDytPRBYHd255E8' + } as PublicKey, + derivationPath: `m/44'/12586'/0'/0/0`, + addresses: ['B62qqVRdiV7S96WnjJyRqu4KRBuU1gk6pVaWxNSpPDytPRBYHd255E8'] + } + + public txs = [ + { + from: [this.wallet.addresses[0]], + to: [this.validAddresses[0]], + amount: { + value: '1000000000', + unit: 'blockchain' + } as Amount, + fee: { + value: '1000000', + unit: 'blockchain' + } as Amount, + memo: 'memo', + unsignedTx: { + type: 'unsigned', + data: { + to: this.validAddresses[0], + from: this.wallet.addresses[0], + amount: '1000000000', + fee: '1000000', + nonce: '10' /* needs to be stubbed */, + memo: 'memo' + }, + networkType: 'mainnet' + } as MinaUnsignedTransaction, + signedTx: { + type: 'signed', + data: { + to: this.validAddresses[0], + from: this.wallet.addresses[0], + amount: '1000000000', + fee: '1000000', + nonce: '10' /* needs to be stubbed */, + memo: 'memo' + }, + signature: { + type: 'legacy', + field: '14542459405744139734294257855287679118140061519919501046564075909230894262608', + scalar: '17849722270390822285179237315719745921096202277479829915389859623256497270925' + } + } as MinaSignedTransaction + } + ] + + public seed(): string { + return '55decc156b78772b5ae97cc4a7a4780c4b299d866abed355a8a6649905eadef4d28f76ff7491526addff6c03f3b200ebaa81dacd9f24def6ec88339a19562b91' + } + + public mnemonic(): string { + return 'food talent voyage degree siege clever account medal film remind good kind' + } + + public messages = [] + + public encryptAES = [] +} diff --git a/packages/mina/test/stubs/mina.stub.ts b/packages/mina/test/stubs/mina.stub.ts new file mode 100644 index 000000000..6a320649c --- /dev/null +++ b/packages/mina/test/stubs/mina.stub.ts @@ -0,0 +1,35 @@ +// tslint:disable: max-classes-per-file +import { MinaNode } from '../../src/v1/node/MinaNode' +import { AccountBalance } from '../../src/v1/types/node' +import { MinaPayment, MinaSignature } from '../../src/v1/types/transaction' +import { ProtocolHTTPStub, TestProtocolSpec } from '../implementations' + +class MinaNodeStub implements MinaNode { + public mode: 'regular' | 'noBalance' = 'regular' + + public async getNonce(_publicKey: string): Promise { + return '10' + } + + public async getBalance(_publicKey: string): Promise { + return this.mode === 'noBalance' ? { total: '0', liquid: '0' } : { total: '10000000000', liquid: '10000000000' } + } + + public async sendTransaction(_payment: MinaPayment, _signature: MinaSignature): Promise { + return '' + } +} + +export class MinaProtocolStub implements ProtocolHTTPStub { + public readonly nodeStub: MinaNodeStub = new MinaNodeStub() + + public async registerStub(testProtocolSpec: TestProtocolSpec): Promise { + this.nodeStub.mode = 'regular' + } + + public async noBalanceStub(testProtocolSpec: TestProtocolSpec): Promise { + this.nodeStub.mode = 'noBalance' + } + + public async transactionListStub(testProtocolSpec: TestProtocolSpec, address: string): Promise {} +} diff --git a/packages/mina/test/tsconfig.json b/packages/mina/test/tsconfig.json new file mode 100644 index 000000000..7d1c848db --- /dev/null +++ b/packages/mina/test/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "strict": false + }, + "include": ["."] +} diff --git a/packages/mina/tsconfig.json b/packages/mina/tsconfig.json new file mode 100644 index 000000000..81fc71d20 --- /dev/null +++ b/packages/mina/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist/", + "typeRoots": ["../../node_modules/@types", "./node_modules/@types"], + "strict": false + }, + "include": ["src/**/*.ts"], + "exclude": ["src/dependencies/cache/**/*", "src/dependencies/github/**/*"] +} diff --git a/packages/mina/tslint.json b/packages/mina/tslint.json new file mode 100644 index 000000000..394d8a239 --- /dev/null +++ b/packages/mina/tslint.json @@ -0,0 +1,3 @@ +{ + "extends": ["../../tslint.json"] +} diff --git a/packages/module-kit/package.json b/packages/module-kit/package.json index d4a887b4a..bbc1a886d 100644 --- a/packages/module-kit/package.json +++ b/packages/module-kit/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/module-kit", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/module-kit package provides the common interfaces and functionalities to implement AirGap modules.", "keywords": [ "airgap", @@ -29,8 +29,8 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/serializer": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/serializer": "^0.13.19" }, "nyc": { "include": [ diff --git a/packages/moonbeam/package.json b/packages/moonbeam/package.json index 5ffff058a..e4a42536a 100644 --- a/packages/moonbeam/package.json +++ b/packages/moonbeam/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/moonbeam", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/moonbeam is a Moonbeam implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -32,10 +32,10 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", - "@airgap/substrate": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", + "@airgap/substrate": "^0.13.19" }, "localDependencies": {}, "nyc": { diff --git a/packages/moonbeam/src/v1/controller/MoonbeamAccountController.ts b/packages/moonbeam/src/v1/controller/MoonbeamAccountController.ts index 0a5ce2a0b..76509dddb 100644 --- a/packages/moonbeam/src/v1/controller/MoonbeamAccountController.ts +++ b/packages/moonbeam/src/v1/controller/MoonbeamAccountController.ts @@ -69,7 +69,7 @@ export class MoonbeamAccountController extends SubstrateCommonAccountController< return { address: address.asString(), - balance: balance.toString(), + balance: balance.total.toString(), totalBond: totalBond.toString(), delegatees: delegatorState?.delegations.elements.map((bond) => bond.owner.asAddress()) ?? [], availableActions: await this.getStakingActions(delegatorState), @@ -152,7 +152,7 @@ export class MoonbeamAccountController extends SubstrateCommonAccountController< const delegatorDetails = { address: delegatorAddress.asString(), - balance: balance.toString(), + balance: balance.total.toString(), totalBond: totalBond.toString(), delegatees: delegatorState?.delegations.elements.map((bond) => bond.owner.asAddress()) ?? [], availableActions: await this.getStakingActions(delegatorState, collatorDetails, delegationScheduledRequests), diff --git a/packages/optimism/package.json b/packages/optimism/package.json index d5caa9ebe..fbb4fa2ad 100644 --- a/packages/optimism/package.json +++ b/packages/optimism/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/optimism", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/optimism is an Optimism implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -31,11 +31,11 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/ethereum": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/ethereum": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@ethereumjs/tx": "3.4.0" }, "nyc": { diff --git a/packages/optimism/src/v1/client/node/AirGapNodeClient.ts b/packages/optimism/src/v1/client/node/AirGapNodeClient.ts deleted file mode 100644 index fdcf54cf9..000000000 --- a/packages/optimism/src/v1/client/node/AirGapNodeClient.ts +++ /dev/null @@ -1,127 +0,0 @@ -// tslint:disable: max-classes-per-file -import { Domain, NetworkError } from '@airgap/coinlib-core' -import axios, { AxiosError } from '@airgap/coinlib-core/dependencies/src/axios-0.19.0' -import BigNumber from '@airgap/coinlib-core/dependencies/src/bignumber.js-9.0.0/bignumber' -import { - AirGapNodeClient as AirGapEthereumNodeClient, - EthereumNodeClient, - EthereumRPCBody, - EthereumRPCData, - EthereumRPCResponse, - EthereumUnsignedTransaction, - EthereumUtils -} from '@airgap/ethereum/v1' -import { AirGapTransactionStatus } from '@airgap/module-kit' -import { Transaction } from '@ethereumjs/tx' - -import { OptimismNodeClient } from './OptimismNodeClient' - -class OptimismRPCDataGetL1Fee extends EthereumRPCData { - public static methodName: string = 'getL1Fee' - private readonly bytes: Buffer - - constructor(tx: EthereumUnsignedTransaction) { - super(`${OptimismRPCDataGetL1Fee.methodName}(bytes)`) - this.bytes = - tx.ethereumType === 'raw' - ? Transaction.fromTxData({ - nonce: tx.nonce, - gasLimit: tx.gasLimit, - gasPrice: tx.gasPrice, - to: tx.to, - value: tx.value, - data: tx.data - }).serialize() - : Buffer.from(tx.serialized, 'hex') - } - - public abiEncoded(): string { - let bytesLength = EthereumUtils.toHex(this.bytes.length) - if (bytesLength.startsWith('0x')) { - bytesLength = bytesLength.slice(2) - } - - return super.abiEncoded() + EthereumRPCData.addLeadingZeroPadding(bytesLength + this.bytes.toString('hex'), 256) - } -} - -export class AirGapNodeClient extends OptimismNodeClient { - private readonly ethereumNodeClient: EthereumNodeClient - - constructor(baseURL: string) { - super(baseURL) - this.ethereumNodeClient = new AirGapEthereumNodeClient(baseURL) - } - - public async getL1Fee(contractAddress: string, tx: EthereumUnsignedTransaction): Promise { - const data = new OptimismRPCDataGetL1Fee(tx) - const body = new EthereumRPCBody('eth_call', [{ to: contractAddress, data: data.abiEncoded() }, EthereumRPCBody.blockLatest]) - - const response = await this.send(body) - const fee = new BigNumber(response.result) - - return fee.isNaN() ? new BigNumber(0) : fee - } - - public async fetchBalance(address: string): Promise { - return this.ethereumNodeClient.fetchBalance(address) - } - - public async fetchTransactionCount(address: string): Promise { - return this.ethereumNodeClient.fetchTransactionCount(address) - } - - public async sendSignedTransaction(transaction: string): Promise { - return this.ethereumNodeClient.sendSignedTransaction(transaction) - } - - public async callBalanceOf(contractAddress: string, address: string): Promise { - return this.ethereumNodeClient.callBalanceOf(contractAddress, address) - } - - public async getTransactionStatus(transactionHash: string): Promise { - return this.ethereumNodeClient.getTransactionStatus(transactionHash) - } - - public async estimateTransferGas(contractAddress: string, fromAddress: string, toAddress: string, hexAmount: string): Promise { - return this.ethereumNodeClient.estimateTransferGas(contractAddress, fromAddress, toAddress, hexAmount) - } - - public async estimateTransactionGas( - fromAddress: string, - toAddress: string, - amount?: string, - data?: string, - gas?: string - ): Promise { - return this.ethereumNodeClient.estimateTransactionGas(fromAddress, toAddress, amount, data, gas) - } - - public async getGasPrice(): Promise { - return this.ethereumNodeClient.getGasPrice() - } - - public async callBalanceOfOnContracts(contractAddresses: string[], address: string): Promise<{ [contractAddress: string]: BigNumber }> { - return this.ethereumNodeClient.callBalanceOfOnContracts(contractAddresses, address) - } - - public async getContractName(contractAddress: string): Promise { - return this.ethereumNodeClient.getContractName(contractAddress) - } - - public async getContractSymbol(contractAddress: string): Promise { - return this.ethereumNodeClient.getContractSymbol(contractAddress) - } - - public async getContractDecimals(contractAddress: string): Promise { - return this.ethereumNodeClient.getContractDecimals(contractAddress) - } - - private async send(body: EthereumRPCBody): Promise { - const response = await axios.post(this.baseURL, body.toRPCBody()).catch((error) => { - throw new NetworkError(Domain.ETHEREUM, error as AxiosError) - }) - - return response.data - } -} diff --git a/packages/optimism/src/v1/client/node/HttpOptimismNodeClient.ts b/packages/optimism/src/v1/client/node/HttpOptimismNodeClient.ts new file mode 100644 index 000000000..14e63b80f --- /dev/null +++ b/packages/optimism/src/v1/client/node/HttpOptimismNodeClient.ts @@ -0,0 +1,51 @@ +// tslint:disable: max-classes-per-file +import BigNumber from '@airgap/coinlib-core/dependencies/src/bignumber.js-9.0.0/bignumber' +import { HttpEthereumNodeClient, EthereumRPCBody, EthereumRPCData, EthereumUnsignedTransaction, EthereumUtils } from '@airgap/ethereum/v1' +import { Transaction } from '@ethereumjs/tx' + +import { OptimismNodeClient } from './OptimismNodeClient' + +class OptimismRPCDataGetL1Fee extends EthereumRPCData { + public static methodName: string = 'getL1Fee' + private readonly bytes: Buffer + + constructor(tx: EthereumUnsignedTransaction) { + super(`${OptimismRPCDataGetL1Fee.methodName}(bytes)`) + this.bytes = + tx.ethereumType === 'raw' + ? Transaction.fromTxData({ + nonce: tx.nonce, + gasLimit: tx.gasLimit, + gasPrice: tx.gasPrice, + to: tx.to, + value: tx.value, + data: tx.data + }).serialize() + : Buffer.from(tx.serialized, 'hex') + } + + public abiEncoded(): string { + let bytesLength = EthereumUtils.toHex(this.bytes.length) + if (bytesLength.startsWith('0x')) { + bytesLength = bytesLength.slice(2) + } + + return super.abiEncoded() + EthereumRPCData.addLeadingZeroPadding(bytesLength + this.bytes.toString('hex'), 256) + } +} + +export class HttpOptimismNodeClient extends HttpEthereumNodeClient implements OptimismNodeClient { + constructor(baseURL: string, headers?: any) { + super(baseURL, headers) + } + + public async getL1Fee(contractAddress: string, tx: EthereumUnsignedTransaction): Promise { + const data = new OptimismRPCDataGetL1Fee(tx) + const body = new EthereumRPCBody('eth_call', [{ to: contractAddress, data: data.abiEncoded() }, EthereumRPCBody.blockLatest]) + + const response = await this.send(body) + const fee = new BigNumber(response.result) + + return fee.isNaN() ? new BigNumber(0) : fee + } +} diff --git a/packages/optimism/src/v1/client/node/OptimismNodeClient.ts b/packages/optimism/src/v1/client/node/OptimismNodeClient.ts index 9133fef60..d5f9f3e77 100644 --- a/packages/optimism/src/v1/client/node/OptimismNodeClient.ts +++ b/packages/optimism/src/v1/client/node/OptimismNodeClient.ts @@ -1,6 +1,6 @@ import BigNumber from '@airgap/coinlib-core/dependencies/src/bignumber.js-9.0.0/bignumber' import { EthereumNodeClient, EthereumUnsignedTransaction } from '@airgap/ethereum/v1' -export abstract class OptimismNodeClient extends EthereumNodeClient { - public abstract getL1Fee(contractAddress: string, tx: EthereumUnsignedTransaction): Promise +export interface OptimismNodeClient extends EthereumNodeClient { + getL1Fee(contractAddress: string, tx: EthereumUnsignedTransaction): Promise } diff --git a/packages/optimism/src/v1/protocol/OptimismProtocol.ts b/packages/optimism/src/v1/protocol/OptimismProtocol.ts index 9113a8277..741c73cc5 100644 --- a/packages/optimism/src/v1/protocol/OptimismProtocol.ts +++ b/packages/optimism/src/v1/protocol/OptimismProtocol.ts @@ -8,7 +8,7 @@ import { } from '@airgap/ethereum/v1' import { newAmount, RecursivePartial } from '@airgap/module-kit' -import { AirGapNodeClient } from '../client/node/AirGapNodeClient' +import { HttpOptimismNodeClient } from '../client/node/HttpOptimismNodeClient' import { OptimismProtocolNetwork, OptimismProtocolOptions } from '../types/protocol' import { OptimismBaseProtocol, OptimismBaseProtocolImpl } from './OptimismBaseProtocol' @@ -23,7 +23,7 @@ class OptimismProtocolImpl extends OptimismBaseProtocolImpl implements OptimismP constructor(options: RecursivePartial) { const completeOptions = createOptimismProtocolOptions(options.network) - const nodeClient = new AirGapNodeClient(completeOptions.network.rpcUrl) + const nodeClient = new HttpOptimismNodeClient(completeOptions.network.rpcUrl) const infoClient = new EtherscanInfoClient(completeOptions.network.blockExplorerApi) const baseProtocolOptions: EthereumBaseProtocolOptions = { diff --git a/packages/optimism/src/v1/protocol/erc20/ERC20Token.ts b/packages/optimism/src/v1/protocol/erc20/ERC20Token.ts index e861d77cb..ac5a145ea 100644 --- a/packages/optimism/src/v1/protocol/erc20/ERC20Token.ts +++ b/packages/optimism/src/v1/protocol/erc20/ERC20Token.ts @@ -10,7 +10,7 @@ import { } from '@airgap/ethereum/v1' import { AirGapInterface, implementsInterface, RecursivePartial } from '@airgap/module-kit' -import { AirGapNodeClient } from '../../client/node/AirGapNodeClient' +import { HttpOptimismNodeClient } from '../../client/node/HttpOptimismNodeClient' import { OptimismProtocolNetwork, OptimismProtocolOptions } from '../../types/protocol' import { OptimismBaseProtocol, OptimismBaseProtocolImpl } from '../OptimismBaseProtocol' import { OPTIMISM_MAINNET_PROTOCOL_NETWORK } from '../OptimismProtocol' @@ -29,10 +29,9 @@ export interface ERC20Token extends AirGapInterface class ERC20TokenImpl extends OptimismBaseProtocolImpl, ERC20TokenOptions> - implements ERC20Token -{ + implements ERC20Token { constructor(options: ERC20TokenOptions) { - const nodeClient = new AirGapNodeClient(options.network.rpcUrl) + const nodeClient = new HttpOptimismNodeClient(options.network.rpcUrl) const infoClient = new EtherscanInfoClient(options.network.blockExplorerApi) const ethereumProtocol = new EthereumERC20TokenImpl(nodeClient, infoClient, options) diff --git a/packages/polkadot/package.json b/packages/polkadot/package.json index 1a09a2c1f..027bc784e 100644 --- a/packages/polkadot/package.json +++ b/packages/polkadot/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/polkadot", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/polkadot is a Polkadot implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -32,9 +32,9 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/substrate": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/substrate": "^0.13.19" }, "devDependencies": { "@polkadot/wasm-crypto": "0.20.1" diff --git a/packages/serializer/package.json b/packages/serializer/package.json index 2b5a1984b..572c283d8 100644 --- a/packages/serializer/package.json +++ b/packages/serializer/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/serializer", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/serializer provides serializers used in AirGap applications.", "keywords": [ "airgap", @@ -37,7 +37,7 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19" }, "localDependencies": {}, "nyc": { diff --git a/packages/substrate/package.json b/packages/substrate/package.json index d8926f646..19c87fe4a 100644 --- a/packages/substrate/package.json +++ b/packages/substrate/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/substrate", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/substrate is a Substrate base implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -32,10 +32,10 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", - "@airgap/serializer": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", + "@airgap/serializer": "^0.13.19", "@polkadot/util": "2.0.1", "@polkadot/wasm-crypto": "0.20.1" }, diff --git a/packages/tezos/package.json b/packages/tezos/package.json index 53c2a627a..d7fb01aca 100644 --- a/packages/tezos/package.json +++ b/packages/tezos/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/tezos", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/tezos is a Tezos implementation of the ICoinProtocol interface from @airgap/coinlib-core.", "keywords": [ "airgap", @@ -30,11 +30,11 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/crypto": "^0.13.18", - "@airgap/module-kit": "^0.13.18", + "@airgap/coinlib-core": "^0.13.19", + "@airgap/crypto": "^0.13.19", + "@airgap/module-kit": "^0.13.19", "@airgap/sapling-wasm": "0.0.7", - "@airgap/serializer": "^0.13.18", + "@airgap/serializer": "^0.13.19", "@stablelib/blake2b": "^1.0.1", "@stablelib/ed25519": "^1.0.3", "@stablelib/nacl": "^1.0.4", diff --git a/packages/tezos/src/v0/index.ts b/packages/tezos/src/v0/index.ts index fc4c7d77f..b5f50876b 100644 --- a/packages/tezos/src/v0/index.ts +++ b/packages/tezos/src/v0/index.ts @@ -31,6 +31,7 @@ import { TezosQUIPU, TezosQUIPUProtocolConfig } from './protocol/fa/TezosQUIPU' import { TezosSIRS, TezosSIRSProtocolConfig } from './protocol/fa/TezosSIRS' import { TezosStaker } from './protocol/fa/TezosStaker' import { TezosUBTC, TezosUBTCProtocolConfig } from './protocol/fa/TezosUBTC' +import { TezosUXTZ } from './protocol/fa/TezosUXTZ' import { TezosUDEFI, TezosUDEFIProtocolConfig } from './protocol/fa/TezosUDEFI' import { TezosUSD } from './protocol/fa/TezosUSD' import { TezosUSDT, TezosUSDTProtocolConfig } from './protocol/fa/TezosUSDT' @@ -93,6 +94,7 @@ export { TezosYOU, TezosUDEFI, TezosUBTC, + TezosUXTZ, TezosWrapped, TezosWRAP, TezosKolibriUSD, diff --git a/packages/tezos/src/v0/protocol/fa/TezosUXTZ.ts b/packages/tezos/src/v0/protocol/fa/TezosUXTZ.ts new file mode 100644 index 000000000..90bed507a --- /dev/null +++ b/packages/tezos/src/v0/protocol/fa/TezosUXTZ.ts @@ -0,0 +1,37 @@ +import { FeeDefaults, ProtocolSymbols, SubProtocolSymbols } from '@airgap/coinlib-core' +import { TezosProtocolNetwork } from '../TezosProtocolOptions' + +import { TezosFA2Protocol } from './TezosFA2Protocol' +import { TezosFA2ProtocolConfig, TezosFA2ProtocolOptions } from './TezosFAProtocolOptions' + +export class TezosUXTZ extends TezosFA2Protocol { + constructor( + public readonly options: TezosFA2ProtocolOptions = new TezosFA2ProtocolOptions( + new TezosProtocolNetwork(), + new TezosUXTZProtocolConfig() + ) + ) { + super(options) + } +} + +export class TezosUXTZProtocolConfig extends TezosFA2ProtocolConfig { + constructor( + symbol: string = 'uXTZ', + name: string = 'youves uXTZ', + marketSymbol: string = 'uxtz', + identifier: ProtocolSymbols = SubProtocolSymbols.XTZ_UXTZ, + contractAddress: string = 'KT1XRPEPXbZK25r3Htzp2o1x7xdMMmfocKNW', + feeDefaults: FeeDefaults = { + low: '0.100', + medium: '0.200', + high: '0.300' + }, + decimals: number = 12, + tokenId: number = 3, + tokenMetadataBigMapID: number = 7708, + ledgerBigMapID: number = 7706 + ) { + super(contractAddress, identifier, symbol, name, marketSymbol, feeDefaults, decimals, tokenId, tokenMetadataBigMapID, ledgerBigMapID) + } +} diff --git a/packages/tezos/src/v1/index.ts b/packages/tezos/src/v1/index.ts index a24eec359..a8525fd63 100644 --- a/packages/tezos/src/v1/index.ts +++ b/packages/tezos/src/v1/index.ts @@ -23,6 +23,7 @@ import { createStakerProtocol, createStakerProtocolOptions, StakerProtocol } fro import { createTetherUSDProtocol, createTetherUSDProtocolOptions, TetherUSDProtocol } from './protocol/fa/tokens/TetherUSDProtocol' import { createTzBTCProtocol, createTzBTCProtocolOptions, TzBTCProtocol } from './protocol/fa/tokens/TzBTCProtocol' import { createUBTCProtocol, createUBTCProtocolOptions, UBTCProtocol } from './protocol/fa/tokens/UBTCProtocol' +import { createUXTZProtocol, createUXTZProtocolOptions, UXTZProtocol } from './protocol/fa/tokens/UXTZProtocol' import { createUDEFIProtocol, createUDEFIProtocolOptions, UDEFIProtocol } from './protocol/fa/tokens/UDEFIProtocol' import { createUSDTezProtocol, createUSDTezProtocolOptions, USDTezProtocol } from './protocol/fa/tokens/USDTezProtocol' import { createUUSDProtocol, createUUSDProtocolOptions, UUSDProtocol } from './protocol/fa/tokens/UUSDProtocol' @@ -151,6 +152,9 @@ export { UBTCProtocol, createUBTCProtocol, createUBTCProtocolOptions, + UXTZProtocol, + createUXTZProtocol, + createUXTZProtocolOptions, UDEFIProtocol, createUDEFIProtocol, createUDEFIProtocolOptions, diff --git a/packages/tezos/src/v1/module/TezosModule.ts b/packages/tezos/src/v1/module/TezosModule.ts index 8b81f2498..c36d5bd4a 100644 --- a/packages/tezos/src/v1/module/TezosModule.ts +++ b/packages/tezos/src/v1/module/TezosModule.ts @@ -26,6 +26,7 @@ import { createSiriusProtocol } from '../protocol/fa/tokens/SiriusProtocol' import { createStakerProtocol } from '../protocol/fa/tokens/StakerProtocol' import { createTzBTCProtocol } from '../protocol/fa/tokens/TzBTCProtocol' import { createUBTCProtocol } from '../protocol/fa/tokens/UBTCProtocol' +import { createUXTZProtocol } from '../protocol/fa/tokens/UXTZProtocol' import { createUDEFIProtocol } from '../protocol/fa/tokens/UDEFIProtocol' import { createUSDTezProtocol } from '../protocol/fa/tokens/USDTezProtocol' import { createUUSDProtocol } from '../protocol/fa/tokens/UUSDProtocol' @@ -54,6 +55,7 @@ export class TezosModule implements AirGapModule<{ ProtocolNetwork: TezosProtoco SubProtocolSymbols.XTZ_W, SubProtocolSymbols.XTZ_UDEFI, SubProtocolSymbols.XTZ_UBTC, + SubProtocolSymbols.XTZ_UXTZ, SubProtocolSymbols.XTZ_CTEZ, SubProtocolSymbols.XTZ_PLENTY, SubProtocolSymbols.XTZ_WRAP, @@ -150,6 +152,8 @@ export class TezosModule implements AirGapModule<{ ProtocolNetwork: TezosProtoco return createUDEFIProtocol({ network }) case SubProtocolSymbols.XTZ_UBTC: return createUBTCProtocol({ network }) + case SubProtocolSymbols.XTZ_UXTZ: + return createUXTZProtocol({ network }) case SubProtocolSymbols.XTZ_CTEZ: return createCTezProtocol({ network }) case SubProtocolSymbols.XTZ_PLENTY: diff --git a/packages/tezos/src/v1/protocol/fa/TezosFA2Protocol.ts b/packages/tezos/src/v1/protocol/fa/TezosFA2Protocol.ts index 618254b7e..da39e1a3e 100644 --- a/packages/tezos/src/v1/protocol/fa/TezosFA2Protocol.ts +++ b/packages/tezos/src/v1/protocol/fa/TezosFA2Protocol.ts @@ -8,7 +8,9 @@ import { PublicKey, RecursivePartial, TransactionFullConfiguration, - TransactionDetails + TransactionDetails, + AirGapTransactionsWithCursor, + AirGapTransaction } from '@airgap/module-kit' import { TezosContractCall } from '../../contract/TezosContractCall' @@ -25,7 +27,7 @@ import { MichelsonAddress } from '../../types/michelson/primitives/MichelsonAddr import { MichelsonInt } from '../../types/michelson/primitives/MichelsonInt' import { MichelsonString } from '../../types/michelson/primitives/MichelsonString' import { TezosFA2ProtocolNetwork, TezosFA2ProtocolOptions, TezosUnits } from '../../types/protocol' -import { TezosUnsignedTransaction } from '../../types/transaction' +import { TezosTransactionCursor, TezosUnsignedTransaction } from '../../types/transaction' import { isMichelinePrimitive, isMichelinePrimitiveApplication, isMichelineSequence } from '../../utils/micheline' import { parseAddress } from '../../utils/pack' import { TezosFA2Accountant } from '../../utils/protocol/fa/TezosFA2Accountant' @@ -232,6 +234,30 @@ export class TezosFA2ProtocolImpl<_Units extends string, _Entrypoints extends st }) } + public override async getTransactionsForAddress( + address: string, + limit: number, + cursor?: TezosTransactionCursor + ): Promise> { + const transactions: Omit, 'network'>[] = await this.indexer.getTokenTransactionsForAddress( + { contractAddress: this.contract.address, id: this.tokenId ?? 0 }, + address, + limit, + cursor?.offset + ) + + return { + transactions: transactions.map((transaction: Omit, 'network'>) => ({ + ...transaction, + network: this.options.network + })), + cursor: { + hasNext: transactions.length >= limit, + offset: (cursor?.offset ?? 0) + transactions.length + } + } + } + protected async createTransferCalls( publicKey: PublicKey, details: TransactionDetails<_Units>[], diff --git a/packages/tezos/src/v1/protocol/fa/tokens/UXTZProtocol.ts b/packages/tezos/src/v1/protocol/fa/tokens/UXTZProtocol.ts new file mode 100644 index 000000000..f430cda8e --- /dev/null +++ b/packages/tezos/src/v1/protocol/fa/tokens/UXTZProtocol.ts @@ -0,0 +1,72 @@ +import { SubProtocolSymbols } from '@airgap/coinlib-core' +import { newAmount, RecursivePartial } from '@airgap/module-kit' + +import { TezosFA2ProtocolNetwork, TezosFA2ProtocolOptions } from '../../../types/protocol' +import { TezosFA2Protocol, TezosFA2ProtocolImpl, TEZOS_FA2_MAINNET_PROTOCOL_NETWORK } from '../TezosFA2Protocol' + +// Interface + +type UXTZUnits = 'uXTZ' + +export interface UXTZProtocol extends TezosFA2Protocol {} + +// Implementation + +export class UXTZProtocolImpl extends TezosFA2ProtocolImpl implements UXTZProtocol { + public constructor(options: RecursivePartial) { + const completeOptions: UXTZProtocolOptions = createUXTZProtocolOptions(options.network) + + super({ + network: completeOptions.network, + + name: 'youves uXTZ', + identifier: SubProtocolSymbols.XTZ_UXTZ, + + units: { + uXTZ: { + symbol: { value: 'uXTZ' }, + decimals: 12 + } + }, + mainUnit: 'uXTZ', + + feeDefaults: { + // TODO: check why it is so high + low: newAmount(0.1, 'tez'), + medium: newAmount(0.2, 'tez'), + high: newAmount(0.3, 'tez') + } + }) + } +} + +// Factory + +type UXTZProtocolOptions = Pick, 'network'> + +export function createUXTZProtocol(options: RecursivePartial = {}): UXTZProtocol { + return new UXTZProtocolImpl(options) +} + +export const UXTZ_MAINNET_PROTOCOL_NETWORK: TezosFA2ProtocolNetwork = { + ...TEZOS_FA2_MAINNET_PROTOCOL_NETWORK, + contractAddress: 'KT1XRPEPXbZK25r3Htzp2o1x7xdMMmfocKNW', + tokenId: 3, + tokenMetadataBigMapId: 7708, + ledgerBigMapId: 7706 +} + +const DEFAULT_UXTZ_PROTOCOL_NETWORK: TezosFA2ProtocolNetwork = UXTZ_MAINNET_PROTOCOL_NETWORK + +export function createUXTZProtocolOptions(network: RecursivePartial = {}): UXTZProtocolOptions { + return { + network: { + ...DEFAULT_UXTZ_PROTOCOL_NETWORK, + ...network, + callbackContracts: { + ...DEFAULT_UXTZ_PROTOCOL_NETWORK.callbackContracts, + ...network.callbackContracts + } + } + } +} diff --git a/packages/tezos/src/v1/serializer/v3/serializer-companion.ts b/packages/tezos/src/v1/serializer/v3/serializer-companion.ts index 4c427bf59..3732e0a0d 100644 --- a/packages/tezos/src/v1/serializer/v3/serializer-companion.ts +++ b/packages/tezos/src/v1/serializer/v3/serializer-companion.ts @@ -174,6 +174,16 @@ export class TezosV3SerializerCompanion implements AirGapV3SerializerCompanion { schema: { schema: tezosTransactionSignResponse }, protocolIdentifier: SubProtocolSymbols.XTZ_UBTC }, + { + type: IACMessageType.TransactionSignRequest, + schema: { schema: tezosTransactionSignRequest }, + protocolIdentifier: SubProtocolSymbols.XTZ_UXTZ + }, + { + type: IACMessageType.TransactionSignResponse, + schema: { schema: tezosTransactionSignResponse }, + protocolIdentifier: SubProtocolSymbols.XTZ_UXTZ + }, { type: IACMessageType.TransactionSignRequest, schema: { schema: tezosTransactionSignRequest }, diff --git a/packages/wallet/package.json b/packages/wallet/package.json index 2926e0e83..296a7e9f0 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -1,6 +1,6 @@ { "name": "@airgap/wallet", - "version": "0.13.18", + "version": "0.13.19", "description": "The @airgap/wallet package provides interfaces for offline/online wallet functionalities.", "keywords": [ "airgap" @@ -27,8 +27,8 @@ }, "author": "Papers AG (https://papers.ch)", "dependencies": { - "@airgap/coinlib-core": "^0.13.18", - "@airgap/module-kit": "^0.13.18" + "@airgap/coinlib-core": "^0.13.19", + "@airgap/module-kit": "^0.13.19" }, "nyc": { "include": [ diff --git a/scripts/generate-schemas.sh b/scripts/generate-schemas.sh index 26f5b2771..129886390 100755 --- a/scripts/generate-schemas.sh +++ b/scripts/generate-schemas.sh @@ -50,6 +50,10 @@ # node_modules/.bin/ts-json-schema-generator --path 'packages/groestlcoin/src/v1/serializer/v3/schemas/definitions/transaction-sign-response-groestlcoin.ts' --tsconfig 'tsconfig.json' -c > packages/groestlcoin/src/v1/serializer/v3/schemas/generated/transaction-sign-response-groestlcoin.json # node_modules/.bin/ts-json-schema-generator --path 'packages/groestlcoin/src/v1/serializer/v3/schemas/definitions/transaction-sign-request-groestlcoin.ts' --tsconfig 'tsconfig.json' -c > packages/groestlcoin/src/v1/serializer/v3/schemas/generated/transaction-sign-request-groestlcoin.json +# Mina +# node_modules/.bin/ts-json-schema-generator --path 'packages/mina/src/v1/serializer/v3/schemas/definitions/transaction-sign-response-mina.ts' --tsconfig 'tsconfig.json' -c > packages/mina/src/v1/serializer/v3/schemas/generated/transaction-sign-response-mina.json +# node_modules/.bin/ts-json-schema-generator --path 'packages/mina/src/v1/serializer/v3/schemas/definitions/transaction-sign-request-mina.ts' --tsconfig 'tsconfig.json' -c > packages/mina/src/v1/serializer/v3/schemas/generated/transaction-sign-request-mina.json + # Moonbeam # node_modules/.bin/ts-json-schema-generator --path 'packages/moonbeam/src/v1/serializer/v3/schemas/definitions/transaction-sign-response-moonbeam.ts' --tsconfig 'tsconfig.json' -c > packages/moonbeam/src/v1/serializer/v3/schemas/generated/transaction-sign-response-moonbeam.json # node_modules/.bin/ts-json-schema-generator --path 'packages/moonbeam/src/v1/serializer/v3/schemas/definitions/transaction-sign-request-moonbeam.ts' --tsconfig 'tsconfig.json' -c > packages/moonbeam/src/v1/serializer/v3/schemas/generated/transaction-sign-request-moonbeam.json diff --git a/tsconfig.json b/tsconfig.json index 1f1444af5..acdd32dcd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -45,10 +45,14 @@ "@airgap/groestlcoin/*": ["./node_modules/@airgap/groestlcoin/src/*"], "@airgap/icp": ["./node_modules/@airgap/icp/src"], "@airgap/icp/*": ["./node_modules/@airgap/icp/src/*"], + "@airgap/mina": ["./node_modules/@airgap/mina/src"], + "@airgap/mina/*": ["./node_modules/@airgap/mina/src/*"], "@airgap/module-kit": ["./node_modules/@airgap/module-kit/src"], "@airgap/module-kit/*": ["./node_modules/@airgap/module-kit/src/*"], "@airgap/moonbeam": ["./node_modules/@airgap/moonbeam/src"], "@airgap/moonbeam/*": ["./node_modules/@airgap/moonbeam/src/*"], + "@airgap/optimism": ["./node_modules/@airgap/optimism/src"], + "@airgap/optimism/*": ["./node_modules/@airgap/optimism/src/*"], "@airgap/polkadot": ["./node_modules/@airgap/polkadot/src"], "@airgap/polkadot/*": ["./node_modules/@airgap/polkadot/src/*"], "@airgap/serializer": ["./node_modules/@airgap/serializer/src"],