├── .changeset ├── README.md └── config.json ├── .eslintignore ├── .eslintrc.yaml ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .mocharc.yml ├── .nvmrc ├── .prettierignore ├── .prettierrc.yaml ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── .yarn ├── plugins │ └── @yarnpkg │ │ └── plugin-workspace-tools.cjs └── releases │ └── yarn-3.1.1.cjs ├── .yarnrc.yml ├── LICENSE ├── README.md ├── package.json ├── packages ├── bitcoin-ledger │ ├── CHANGELOG.md │ ├── lib │ │ ├── BitcoinLedgerProvider.ts │ │ ├── CreateBitcoinLedgerApp.ts │ │ ├── index.ts │ │ └── types.ts │ ├── package.json │ └── tsconfig.json ├── bitcoin │ ├── CHANGELOG.md │ ├── lib │ │ ├── chain │ │ │ ├── BitcoinBaseChainProvider.ts │ │ │ ├── esplora │ │ │ │ ├── BitcoinEsploraApiProvider.ts │ │ │ │ ├── BitcoinEsploraBaseProvider.ts │ │ │ │ ├── BitcoinEsploraBatchBaseProvider.ts │ │ │ │ └── types.ts │ │ │ └── jsonRpc │ │ │ │ ├── BitcoinJsonRpcBaseProvider.ts │ │ │ │ ├── BitcoinJsonRpcProvider.ts │ │ │ │ └── types.ts │ │ ├── fee │ │ │ └── BitcoinFeeApiProvider.ts │ │ ├── index.ts │ │ ├── networks.ts │ │ ├── swap │ │ │ ├── BitcoinSwapBaseProvider.ts │ │ │ ├── BitcoinSwapEsploraProvider.ts │ │ │ ├── BitcoinSwapRpcProvider.ts │ │ │ └── types.ts │ │ ├── types.ts │ │ ├── typings │ │ │ ├── bitcoinjs-lib-classify.d.ts │ │ │ ├── coinselect-accumulative.d.ts │ │ │ └── coinselect.d.ts │ │ ├── utils.ts │ │ └── wallet │ │ │ ├── BitcoinBaseWallet.ts │ │ │ ├── BitcoinHDWallet.ts │ │ │ ├── BitcoinNodeWallet.ts │ │ │ └── IBitcoinWallet.ts │ ├── package.json │ └── tsconfig.json ├── client │ ├── CHANGELOG.md │ ├── lib │ │ ├── Chain.ts │ │ ├── Client.ts │ │ ├── Fee.ts │ │ ├── Http.ts │ │ ├── JsonRpc.ts │ │ ├── Nft.ts │ │ ├── Swap.ts │ │ ├── Wallet.ts │ │ ├── index.ts │ │ └── types.ts │ ├── package.json │ └── tsconfig.json ├── errors │ ├── CHANGELOG.md │ ├── lib │ │ └── index.ts │ ├── package.json │ └── tsconfig.json ├── evm-contracts │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── contracts │ │ ├── ILiqualityHTLC.sol │ │ ├── LibTransfer.sol │ │ ├── LiqualityHTLC.sol │ │ ├── nft │ │ │ ├── ERC1155.sol │ │ │ └── ERC721.sol │ │ ├── test │ │ │ └── TestERC20.sol │ │ └── utils │ │ │ └── Multicall.sol │ ├── hardhat.config.ts │ ├── package.json │ ├── tasks │ │ ├── accounts.ts │ │ ├── deploy │ │ │ └── index.ts │ │ └── utils │ │ │ └── index.ts │ ├── test │ │ ├── htlc │ │ │ ├── LiqualityHTLC.ts │ │ │ ├── LiqualityHTLCERC20.behavior.ts │ │ │ └── LiqualityHTLCEther.behavior.ts │ │ ├── types.ts │ │ └── utils.ts │ └── tsconfig.json ├── evm-ledger │ ├── CHANGELOG.md │ ├── lib │ │ ├── CreateEvmLedgerApp.ts │ │ ├── EvmLedgerProvider.ts │ │ ├── EvmLedgerSigner.ts │ │ ├── index.ts │ │ └── types.ts │ ├── package.json │ └── tsconfig.json ├── evm │ ├── .gitignore │ ├── CHANGELOG.md │ ├── hardhat.config.ts │ ├── lib │ │ ├── chain │ │ │ ├── EvmChainProvider.ts │ │ │ ├── EvmMulticallProvider.ts │ │ │ └── OptimismChainProvider.ts │ │ ├── fee │ │ │ ├── EIP1559FeeApiProvider │ │ │ │ ├── ethereum.ts │ │ │ │ ├── index.ts │ │ │ │ └── polygon.ts │ │ │ ├── EIP1559FeeProvider.ts │ │ │ └── RpcFeeProvider.ts │ │ ├── index.ts │ │ ├── naming │ │ │ └── EnsProvider.ts │ │ ├── networks.ts │ │ ├── nft │ │ │ ├── CovalentNftProvider.ts │ │ │ ├── EvmNftProvider.ts │ │ │ ├── InfuraNftProvider.ts │ │ │ └── OpenSeaNftProvider.ts │ │ ├── swap │ │ │ ├── EvmBaseSwapProvider.ts │ │ │ └── EvmSwapProvider.ts │ │ ├── types.ts │ │ ├── utils.ts │ │ └── wallet │ │ │ ├── EvmBaseWalletProvider.ts │ │ │ └── EvmWalletProvider.ts │ ├── package.json │ └── tsconfig.json ├── hw-ledger │ ├── CHANGELOG.md │ ├── lib │ │ ├── LedgerProvider.ts │ │ ├── WebHidTransportCreator.ts │ │ ├── index.ts │ │ └── types.ts │ ├── package.json │ └── tsconfig.json ├── logger │ ├── CHANGELOG.md │ ├── lib │ │ └── index.ts │ ├── package.json │ └── tsconfig.json ├── near │ ├── CHANGELOG.md │ ├── lib │ │ ├── chain │ │ │ └── NearChainProvider.ts │ │ ├── index.ts │ │ ├── networks.ts │ │ ├── swap │ │ │ ├── NearSwapProvider.ts │ │ │ └── bytecode.ts │ │ ├── types.ts │ │ ├── utils.ts │ │ └── wallet │ │ │ ├── NearWalletProvider.ts │ │ │ └── near-seed-phrase.d.ts │ ├── package.json │ └── tsconfig.json ├── solana │ ├── CHANGELOG.md │ ├── lib │ │ ├── chain │ │ │ └── SolanaChainProvider.ts │ │ ├── index.ts │ │ ├── networks.ts │ │ ├── nft │ │ │ └── SolanaNftProvider.ts │ │ ├── swap │ │ │ └── SolanaSwapProvider.ts │ │ ├── types.ts │ │ ├── utils.ts │ │ └── wallet │ │ │ └── SolanaWalletProvider.ts │ ├── package.json │ └── tsconfig.json ├── terra │ ├── CHANGELOG.md │ ├── lib │ │ ├── chain │ │ │ └── TerraChainProvider.ts │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── networks.ts │ │ ├── swap │ │ │ ├── TerraSwapBaseProvider.ts │ │ │ └── TerraSwapProvider.ts │ │ ├── types │ │ │ ├── fcd.ts │ │ │ └── index.ts │ │ ├── utils.ts │ │ └── wallet │ │ │ └── TerraWalletProvider.ts │ ├── package.json │ └── tsconfig.json ├── types │ ├── CHANGELOG.md │ ├── lib │ │ ├── Address.ts │ │ ├── Asset.ts │ │ ├── Block.ts │ │ ├── Chain.ts │ │ ├── Client.ts │ │ ├── Fees.ts │ │ ├── Naming.ts │ │ ├── Network.ts │ │ ├── Nft.ts │ │ ├── Swap.ts │ │ ├── Transaction.ts │ │ ├── Wallet.ts │ │ └── index.ts │ ├── package.json │ └── tsconfig.json └── utils │ ├── CHANGELOG.md │ ├── lib │ ├── crypto-hashing.d.ts │ ├── crypto.ts │ ├── hex.ts │ ├── index.ts │ ├── math.ts │ ├── string.ts │ └── swap.ts │ ├── package.json │ └── tsconfig.json ├── scripts └── generate-package-table.mjs ├── test ├── integration │ ├── chain │ │ └── chain.test.ts │ ├── clients │ │ ├── bitcoin │ │ │ ├── behaviors │ │ │ │ ├── transactions.behavior.ts │ │ │ │ └── wallet.behavior.ts │ │ │ ├── clients.ts │ │ │ ├── config.ts │ │ │ ├── index.ts │ │ │ └── utils.ts │ │ ├── evm │ │ │ ├── clients.ts │ │ │ ├── config.ts │ │ │ ├── index.ts │ │ │ ├── mock │ │ │ │ └── EIP1559MockFeeProvider.ts │ │ │ ├── signTypedData.behavior.ts │ │ │ └── updateTransactionFee.behavior.ts │ │ ├── index.ts │ │ ├── near │ │ │ ├── clients.ts │ │ │ ├── config.ts │ │ │ └── index.ts │ │ ├── solana │ │ │ ├── clients.ts │ │ │ ├── config.ts │ │ │ └── index.ts │ │ └── terra │ │ │ ├── clients.ts │ │ │ ├── config.ts │ │ │ └── index.ts │ ├── common.ts │ ├── config.ts │ ├── deploy │ │ ├── evm.ts │ │ └── index.ts │ ├── environment │ │ ├── NodeTransportCreator.ts │ │ ├── btc │ │ │ ├── docker-compose.yaml │ │ │ ├── mine.sh │ │ │ └── miner.Dockerfile │ │ ├── ganache.ts │ │ └── index.ts │ ├── index.ts │ ├── swap │ │ └── swap.test.ts │ ├── types.ts │ └── wallet │ │ ├── sign.typed.data.test.ts │ │ └── wallet.test.ts └── tsconfig.json ├── tsconfig.json ├── turbo.json ├── typedoc.json └── yarn.lock /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.6.4/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "linked": [], 6 | "access": "public", 7 | "baseBranch": "dev", 8 | "updateInternalDependencies": "patch", 9 | "ignore": [] 10 | } 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .yarn/ 2 | dist 3 | node_modules 4 | .turbo 5 | .yarn 6 | yarn-debug.log* 7 | yarn-error.log* 8 | packages/evm-contracts/**/* -------------------------------------------------------------------------------- /.eslintrc.yaml: -------------------------------------------------------------------------------- 1 | extends: 2 | - "eslint:recommended" 3 | - "plugin:@typescript-eslint/eslint-recommended" 4 | - "plugin:@typescript-eslint/recommended" 5 | - "prettier" 6 | parser: "@typescript-eslint/parser" 7 | parserOptions: 8 | project: "tsconfig.json" 9 | plugins: 10 | - "@typescript-eslint" 11 | - "eslint-plugin-tsdoc" 12 | root: true 13 | rules: 14 | "@typescript-eslint/no-explicit-any": "off" 15 | "tsdoc/syntax": "warn" 16 | "@typescript-eslint/no-floating-promises": 17 | - error 18 | - ignoreIIFE: true 19 | ignoreVoid: true 20 | "@typescript-eslint/no-inferrable-types": "off" 21 | "@typescript-eslint/no-unused-vars": 22 | - error 23 | - argsIgnorePattern: "_" 24 | varsIgnorePattern: "_" -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | /.yarn/releases/** binary 2 | /.yarn/plugins/** binary -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | day: "monday" 9 | target-branch: "dev" 10 | labels: 11 | - "dependencies" 12 | open-pull-requests-limit: 5 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - dev 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v2 17 | 18 | - name: Setup Node.js 16.x 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: 16.x 22 | 23 | # yarn cache 24 | - name: Get yarn cache directory path 25 | id: yarn-cache-dir-path 26 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)" 27 | 28 | - name: Restore yarn cache 29 | uses: actions/cache@v3.3.1 30 | id: yarn-cache 31 | with: 32 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 33 | key: yarn-cache-folder-${{ hashFiles('**/yarn.lock', '.yarnrc.yml') }} 34 | restore-keys: | 35 | yarn-cache-folder- 36 | 37 | - name: Install Dependencies 38 | run: yarn 39 | 40 | - name: Create Release Pull Request or Publish to npm 41 | id: changesets 42 | uses: changesets/action@v1 43 | with: 44 | publish: yarn build-release 45 | env: 46 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 47 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build Test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | paths-ignore: 7 | - '**/node_modules/**' 8 | - '.github/**' 9 | - '.github/*' 10 | branches-ignore: 11 | - renovate* 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3.0.2 19 | - uses: actions/setup-node@v3.3.0 20 | with: 21 | node-version: 16 22 | # yarn cache 23 | - name: Get yarn cache directory path 24 | id: yarn-cache-dir-path 25 | run: echo "::set-output name=dir::$(yarn config get cacheFolder)" 26 | 27 | - name: Restore yarn cache 28 | uses: actions/cache@v3.3.1 29 | id: yarn-cache 30 | with: 31 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 32 | key: yarn-cache-folder-${{ hashFiles('**/yarn.lock', '.yarnrc.yml') }} 33 | restore-keys: | 34 | yarn-cache-folder- 35 | # execute commands 36 | - run: yarn 37 | - run: yarn lint 38 | - run: yarn build 39 | - name: Run tests 40 | run: | 41 | yarn test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | dist 4 | docs 5 | .idea 6 | .turbo 7 | .pnpm-debug.log 8 | .DS_Store 9 | 10 | .yarn/cache 11 | .yarn/versions 12 | .yarn/install-state.gz 13 | .yarn/unplugged -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn dlx commitlint --edit $1 -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn dlx lint-staged -------------------------------------------------------------------------------- /.mocharc.yml: -------------------------------------------------------------------------------- 1 | require: "ts-node/register" 2 | timeout: 300000 3 | recursive: true -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.15.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | .turbo 3 | node_modules 4 | typechain 5 | *.json 6 | packages/evm-contracts/**/* -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | printWidth: 140 2 | tabWidth: 4 3 | useTabs: false 4 | semi: true 5 | singleQuote: true 6 | trailingComma: es5 7 | bracketSpacing: true 8 | overrides: 9 | - files: ".prettierrc.yaml" 10 | options: 11 | parser: yaml 12 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["eamodio.gitlens", "dbaeumer.vscode-eslint", "juanblanco.solidity"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Tests Debug", 8 | "preLaunchTask": "npm: bootup", 9 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 10 | "env": { 11 | "TS_NODE_FILES":"true", 12 | }, 13 | "args": [ 14 | "-r", 15 | "ts-node/register", 16 | "--timeout", 17 | "999999", 18 | "--colors", 19 | "${workspaceFolder}/test/integration/index.ts", 20 | ], 21 | "console": "integratedTerminal", 22 | "internalConsoleOptions": "neverOpen", 23 | "protocol": "inspector" 24 | }, 25 | ] 26 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[typescript]": { 3 | "editor.formatOnSave": true 4 | }, 5 | "eslint.packageManager": "yarn", 6 | "npm.packageManager": "yarn", 7 | "cSpell.words": [ 8 | "arbitrum", 9 | "bitcoinjs", 10 | "chainify", 11 | "changesets", 12 | "commitlint", 13 | "eslintignore", 14 | "Liquality", 15 | "luxon", 16 | "memoizee", 17 | "Multicall", 18 | "prettierrc", 19 | "regtest", 20 | "setimmediate", 21 | "trivago", 22 | "typechain" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "env:integration", 7 | "problemMatcher": [], 8 | "label": "npm: bootup", 9 | "detail": "docker-compose -f test/integration/environment/btc/docker-compose.yaml up -d --force-recreate --renew-anon-volumes && sleep 10" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | npmRegistryServer: "https://registry.npmjs.org" 4 | 5 | plugins: 6 | - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs 7 | spec: "@yarnpkg/plugin-workspace-tools" 8 | 9 | yarnPath: .yarn/releases/yarn-3.1.1.cjs 10 | npmAuthToken: ${NPM_TOKEN-''} -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Liquality 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chainify", 3 | "version": "1.0.10", 4 | "devDependencies": { 5 | "@changesets/changelog-github": "^0.4.2", 6 | "@changesets/cli": "^2.20.0", 7 | "@commitlint/cli": "^16.1.0", 8 | "@commitlint/config-conventional": "^16.0.0", 9 | "@ledgerhq/hw-transport-node-hid": "6.24.1", 10 | "@typechain/ethers-v5": "^9.0.0", 11 | "@typechain/hardhat": "^4.0.0", 12 | "@types/chai": "^4.3.0", 13 | "@types/chai-as-promised": "^7.1.4", 14 | "@types/mocha": "^9.1.0", 15 | "@types/node": "^17.0.10", 16 | "@typescript-eslint/eslint-plugin": "^5.10.1", 17 | "@typescript-eslint/parser": "^5.10.1", 18 | "chai": "^4.3.6", 19 | "chai-as-promised": "^7.1.1", 20 | "cross-env": "^7.0.3", 21 | "eslint": "^8.7.0", 22 | "eslint-config-prettier": "^8.3.0", 23 | "eslint-plugin-tsdoc": "^0.2.14", 24 | "ethers": "5.7.0", 25 | "ganache": "^7.0.3", 26 | "hardhat": "^2.8.3", 27 | "husky": "^7.0.4", 28 | "lint-staged": "^12.3.2", 29 | "markdown-table": "^3.0.2", 30 | "mocha": "^9.2.0", 31 | "plop": "^3.0.5", 32 | "pre-commit": "^1.2.2", 33 | "prettier": "^2.5.1", 34 | "prettier-plugin-organize-imports": "^2.3.4", 35 | "turbo": "^1.2.14", 36 | "typechain": "^7.0.0", 37 | "typedoc": "^0.22.13", 38 | "typedoc-github-wiki-theme": "^1.0.0", 39 | "typedoc-plugin-markdown": "^3.11.14", 40 | "typedoc-plugin-missing-exports": "^0.22.6", 41 | "typedoc-plugin-rename-defaults": "^0.4.0", 42 | "typescript": "^4.5.5" 43 | }, 44 | "scripts": { 45 | "dev": "turbo run dev --parallel --no-cache", 46 | "env:integration": "docker-compose -f test/integration/environment/btc/docker-compose.yaml up -d --force-recreate --renew-anon-volumes", 47 | "env:integration:bootup": "sleep 10", 48 | "test": "yarn env:integration && yarn env:integration:bootup && yarn test:integration", 49 | "test:integration": "yarn build && cross-env NODE_ENV=test TS_NODE_PROJECT=test/tsconfig.json TS_NODE_FILES=true mocha --exit test/integration/index.ts", 50 | "build": "yarn turbo run build --filter=@chainify/evm-contracts && turbo run build --concurrency=10 --filter=!@chainify/evm-contracts", 51 | "clean": "yarn workspaces foreach run clean && rm -rf node_modules ", 52 | "lint": "yarn lint:ts && yarn prettier:check", 53 | "lint:ts": "eslint --config ./.eslintrc.yaml --ignore-path ./.eslintignore --ext .js,.ts .", 54 | "prettier:check": "prettier --check \"**/*.{js,json,sol,ts}\"", 55 | "prettier:fix": "prettier --write ./packages", 56 | "changeset": "changeset", 57 | "version": "yarn changeset version", 58 | "release": "yarn workspaces foreach --from '@chainify/*' npm publish --access public", 59 | "build-release": "yarn build --force && yarn release", 60 | "release:beta": "yarn release --tag beta", 61 | "docs": "typedoc" 62 | }, 63 | "workspaces": [ 64 | "packages/*" 65 | ], 66 | "packageManager": "yarn@3.1.1", 67 | "resolutions": { 68 | "@ledgerhq/devices": "7.0.7", 69 | "@ledgerhq/hw-transport": "6.27.10", 70 | "@ledgerhq/hw-transport-webhid": "6.27.10", 71 | "@ledgerhq/cryptoassets": "7.3.0", 72 | "@ledgerhq/hw-app-eth": "6.30.5", 73 | "@ledgerhq/hw-app-btc": "9.1.2" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/bitcoin-ledger/lib/CreateBitcoinLedgerApp.ts: -------------------------------------------------------------------------------- 1 | import { CreateLedgerApp } from '@chainify/hw-ledger'; 2 | import { Network } from '@chainify/types'; 3 | import HwAppBitcoin from '@ledgerhq/hw-app-btc'; 4 | import Transport from '@ledgerhq/hw-transport'; 5 | 6 | export const CreateBitcoinLedgerApp: CreateLedgerApp = (transport: Transport, scrambleKey: string, network: Network) => { 7 | { 8 | const currency = network.isTestnet ? 'bitcoin_testnet' : 'bitcoin'; 9 | return new HwAppBitcoin({ 10 | transport, 11 | scrambleKey, 12 | currency, 13 | }); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /packages/bitcoin-ledger/lib/index.ts: -------------------------------------------------------------------------------- 1 | export { BitcoinLedgerProvider } from './BitcoinLedgerProvider'; 2 | export * from './CreateBitcoinLedgerApp'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /packages/bitcoin-ledger/lib/types.ts: -------------------------------------------------------------------------------- 1 | import { BitcoinTypes } from '@chainify/bitcoin'; 2 | import { CreateOptions } from '@chainify/hw-ledger'; 3 | 4 | export interface BitcoinLedgerProviderOptions extends CreateOptions { 5 | baseDerivationPath: string; 6 | basePublicKey?: string; 7 | baseChainCode?: string; 8 | addressType: BitcoinTypes.AddressType; 9 | network: BitcoinTypes.BitcoinNetwork; 10 | } 11 | -------------------------------------------------------------------------------- /packages/bitcoin-ledger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainify/bitcoin-ledger", 3 | "version": "2.4.1", 4 | "description": "", 5 | "author": "Liquality ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "./dist/lib/index.js", 9 | "types": "./dist/lib/index.d.ts", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "clean": "rm -rf node_modules .turbo dist" 16 | }, 17 | "dependencies": { 18 | "@chainify/bitcoin": "workspace:*", 19 | "@chainify/client": "workspace:*", 20 | "@chainify/errors": "workspace:*", 21 | "@chainify/hw-ledger": "workspace:*", 22 | "@chainify/types": "workspace:*", 23 | "@chainify/utils": "workspace:*", 24 | "@ledgerhq/hw-app-btc": "9.1.2", 25 | "bip32": "2.0.6", 26 | "bitcoinjs-lib": "5.2.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/bitcoin-ledger/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true 8 | }, 9 | "include": ["lib"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/chain/BitcoinBaseChainProvider.ts: -------------------------------------------------------------------------------- 1 | import { AddressType, Transaction } from '@chainify/types'; 2 | import { AddressTxCounts, UTXO } from '../types'; 3 | 4 | export abstract class BitcoinBaseChainProvider { 5 | public abstract formatTransaction(tx: any, currentHeight: number): Promise; 6 | 7 | public abstract getRawTransactionByHash(transactionHash: string): Promise; 8 | 9 | public abstract getTransactionHex(transactionHash: string): Promise; 10 | 11 | public abstract getFeePerByte(numberOfBlocks?: number): Promise; 12 | 13 | public abstract getUnspentTransactions(addresses: AddressType[]): Promise; 14 | 15 | public abstract getAddressTransactionCounts(_addresses: AddressType[]): Promise; 16 | 17 | public abstract getMinRelayFee(): Promise; 18 | } 19 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/chain/esplora/BitcoinEsploraBatchBaseProvider.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@chainify/client'; 2 | import { AddressType, BigNumber } from '@chainify/types'; 3 | import { flatten, uniq } from 'lodash'; 4 | import { UTXO } from '../../types'; 5 | import { BitcoinEsploraBaseProvider } from './BitcoinEsploraBaseProvider'; 6 | import * as EsploraTypes from './types'; 7 | 8 | export class BitcoinEsploraBatchBaseProvider extends BitcoinEsploraBaseProvider { 9 | private _batchHttpClient: HttpClient; 10 | 11 | constructor(options: EsploraTypes.EsploraBatchApiProviderOptions) { 12 | super(options); 13 | this._batchHttpClient = new HttpClient({ baseURL: options.batchUrl }); 14 | } 15 | 16 | async getUnspentTransactions(_addresses: AddressType[]): Promise { 17 | const addresses = _addresses.map((a) => a.toString()); 18 | const data: EsploraTypes.BatchUTXOs = await this._batchHttpClient.nodePost('/addresses/utxo', { 19 | addresses: uniq(addresses), 20 | }); 21 | 22 | const utxos = data.map(({ address, utxo }) => { 23 | return utxo.map((obj) => ({ 24 | ...obj, 25 | address, 26 | satoshis: obj.value, 27 | amount: new BigNumber(obj.value).dividedBy(1e8).toNumber(), 28 | blockHeight: obj.status.block_height, 29 | })); 30 | }); 31 | 32 | return flatten(utxos); 33 | } 34 | 35 | async getAddressTransactionCounts(_addresses: AddressType[]) { 36 | const addresses = _addresses.map((a) => a.toString()); 37 | const data: EsploraTypes.Address[] = await this._batchHttpClient.nodePost('/addresses', { 38 | addresses: uniq(addresses), 39 | }); 40 | 41 | return data.reduce((acc: { [index: string]: number }, obj) => { 42 | acc[obj.address] = obj.chain_stats.tx_count + obj.mempool_stats.tx_count; 43 | return acc; 44 | }, {}); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/chain/esplora/types.ts: -------------------------------------------------------------------------------- 1 | import { BitcoinNetwork } from '../../types'; 2 | 3 | export type FeeEstimates = { [index: string]: number }; 4 | 5 | export type TxStatus = { 6 | confirmed: boolean; 7 | block_hash?: string; 8 | block_height?: number; 9 | block_time?: number; 10 | }; 11 | 12 | export type UTXO = { 13 | txid: string; 14 | vout: number; 15 | status: TxStatus; 16 | value: number; 17 | }; 18 | 19 | export type Address = { 20 | address: string; 21 | chain_stats: { 22 | funded_txo_count: number; 23 | funded_txo_sum: number; 24 | spent_txo_count: number; 25 | spent_txo_sum: number; 26 | tx_count: number; 27 | }; 28 | mempool_stats: { 29 | funded_txo_count: number; 30 | funded_txo_sum: number; 31 | spent_txo_count: number; 32 | spent_txo_sum: number; 33 | tx_count: number; 34 | }; 35 | }; 36 | 37 | export type Vout = { 38 | scriptpubkey: string; 39 | scriptpubkey_asm: string; 40 | scriptpubkey_type: string; 41 | scriptpubkey_address?: string; 42 | value: number; 43 | }; 44 | 45 | export type Vin = { 46 | txid: string; 47 | vout: number; 48 | prevout: Vout; 49 | scriptsig: string; 50 | scriptsig_asm: string; 51 | is_coinbase: boolean; 52 | sequence: number; 53 | }; 54 | 55 | export type Transaction = { 56 | txid: string; 57 | version: number; 58 | locktime: number; 59 | vin: Vin[]; 60 | vout: Vout[]; 61 | size: number; 62 | weight: number; 63 | fee: number; 64 | status: TxStatus; 65 | }; 66 | 67 | export type Block = { 68 | id: string; 69 | height: number; 70 | version: number; 71 | timestamp: number; 72 | tx_count: number; 73 | size: number; 74 | weight: number; 75 | merlke_root: string; 76 | previousblockhash: string; 77 | mediantime: number; 78 | nonce: number; 79 | bits: number; 80 | difficulty: number; 81 | }; 82 | 83 | export type BatchUTXOs = { address: string; utxo: UTXO[] }[]; 84 | 85 | export interface EsploraApiProviderOptions { 86 | url: string; 87 | network: BitcoinNetwork; 88 | numberOfBlockConfirmation?: number; 89 | defaultFeePerByte?: number; 90 | } 91 | 92 | export interface EsploraBatchApiProviderOptions extends EsploraApiProviderOptions { 93 | batchUrl: string; 94 | } 95 | 96 | export type FeeOptions = { 97 | slowTargetBlocks?: number; 98 | averageTargetBlocks?: number; 99 | fastTargetBlocks?: number; 100 | }; 101 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/chain/jsonRpc/BitcoinJsonRpcBaseProvider.ts: -------------------------------------------------------------------------------- 1 | import { JsonRpcProvider } from '@chainify/client'; 2 | import { AddressType, BigNumber, Transaction } from '@chainify/types'; 3 | import { AddressTxCounts, UTXO as BitcoinUTXO } from '../../types'; 4 | import { decodeRawTransaction, normalizeTransactionObject } from '../../utils'; 5 | import { BitcoinBaseChainProvider } from '../BitcoinBaseChainProvider'; 6 | import { ProviderOptions, ReceivedByAddress, UTXO } from './types'; 7 | 8 | export class BitcoinJsonRpcBaseProvider extends BitcoinBaseChainProvider { 9 | public jsonRpc: JsonRpcProvider; 10 | protected _options: ProviderOptions; 11 | 12 | constructor(options: ProviderOptions) { 13 | super(); 14 | this.jsonRpc = new JsonRpcProvider(options.uri, options.username, options.password); 15 | this._options = { 16 | feeBlockConfirmations: 1, 17 | defaultFeePerByte: 3, 18 | ...options, 19 | }; 20 | } 21 | 22 | public async formatTransaction(tx: any, currentHeight: number): Promise> { 23 | const hex = await this.getTransactionHex(tx.txid); 24 | const confirmations = tx.status.confirmed ? currentHeight - tx.status.block_height + 1 : 0; 25 | const decodedTx = decodeRawTransaction(hex, this._options.network); 26 | decodedTx.confirmations = confirmations; 27 | return normalizeTransactionObject(decodedTx, tx.fee, { hash: tx.status.block_hash, number: tx.status.block_height }); 28 | } 29 | 30 | public async getRawTransactionByHash(transactionHash: string): Promise { 31 | return await this.jsonRpc.send('getrawtransaction', [transactionHash, 0]); 32 | } 33 | 34 | public async getTransactionHex(transactionHash: string): Promise { 35 | return this.jsonRpc.send('getrawtransaction', [transactionHash]); 36 | } 37 | 38 | public async getFeePerByte(numberOfBlocks?: number): Promise { 39 | try { 40 | const { feerate } = await this.jsonRpc.send('estimatesmartfee', [numberOfBlocks]); 41 | 42 | if (feerate && feerate > 0) { 43 | return Math.ceil((feerate * 1e8) / 1000); 44 | } 45 | 46 | throw new Error('Invalid estimated fee'); 47 | } catch (e) { 48 | return this._options.defaultFeePerByte; 49 | } 50 | } 51 | 52 | public async getUnspentTransactions(_addresses: AddressType[]): Promise { 53 | const addresses = _addresses.map((a) => a.toString()); 54 | const utxos: UTXO[] = await this.jsonRpc.send('listunspent', [0, 9999999, addresses]); 55 | return utxos.map((utxo) => ({ ...utxo, value: new BigNumber(utxo.amount).times(1e8).toNumber() })); 56 | } 57 | 58 | public async getAddressTransactionCounts(_addresses: AddressType[]): Promise { 59 | const addresses = _addresses.map((a) => a.toString()); 60 | const receivedAddresses: ReceivedByAddress[] = await this.jsonRpc.send('listreceivedbyaddress', [0, false, true]); 61 | return addresses.reduce((acc: AddressTxCounts, addr) => { 62 | const receivedAddress = receivedAddresses.find((receivedAddress) => receivedAddress.address === addr); 63 | const transactionCount = receivedAddress ? receivedAddress.txids.length : 0; 64 | acc[addr] = transactionCount; 65 | return acc; 66 | }, {}); 67 | } 68 | 69 | public async getMinRelayFee(): Promise { 70 | const { relayfee } = await this.jsonRpc.send('getnetworkinfo', []); 71 | return (relayfee * 1e8) / 1000; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/chain/jsonRpc/types.ts: -------------------------------------------------------------------------------- 1 | import { BitcoinNetwork, Transaction } from '../../types'; 2 | 3 | export interface UTXO { 4 | txid: string; 5 | vout: number; 6 | address: string; 7 | label: string; 8 | scriptPubKey: string; 9 | amount: number; 10 | confirmations: number; 11 | redeemScript: string; 12 | witnessScript: string; 13 | spendable: boolean; 14 | solvable: boolean; 15 | desc: string; 16 | safe: boolean; 17 | } 18 | export interface ReceivedByAddress { 19 | involvesWatchOnly: boolean; 20 | address: string; 21 | account: string; 22 | amount: number; 23 | cofirmations: number; 24 | label: string; 25 | txids: string[]; 26 | } 27 | export interface MinedTransaction extends Transaction { 28 | blockhash: string; 29 | confirmations: number; 30 | blocktime: number; 31 | number: number; 32 | } 33 | export interface FundRawResponse { 34 | hex: string; 35 | fee: number; 36 | changepos: number; 37 | } 38 | export interface AddressInfo { 39 | iswatchonly: boolean; 40 | pubkey: string; 41 | hdkeypath: string; 42 | } 43 | export declare type AddressGrouping = string[][]; 44 | export interface ReceivedByAddress { 45 | involvesWatchonly: boolean; 46 | address: string; 47 | account: string; 48 | amount: number; 49 | confirmations: number; 50 | label: string; 51 | txids: string[]; 52 | } 53 | export interface Block { 54 | hash: string; 55 | confirmations: number; 56 | size: number; 57 | strippedSize: number; 58 | weight: number; 59 | height: number; 60 | version: number; 61 | versionHex: string; 62 | merkleroot: string; 63 | tx: string[]; 64 | time: number; 65 | mediantime: number; 66 | nonce: number; 67 | bits: string; 68 | difficulty: number; 69 | chainwork: string; 70 | nTx: number; 71 | previousblockhash: string; 72 | nextblockhash?: string; 73 | } 74 | 75 | export interface ProviderOptions { 76 | // RPC URI 77 | uri: string; 78 | // Bitcoin network 79 | network: BitcoinNetwork; 80 | // Authentication username 81 | username?: string; 82 | // Authentication password 83 | password?: string; 84 | // Number of block confirmations to target for fee. Defaul: 1 85 | feeBlockConfirmations?: number; 86 | // Default fee per byte for transactions. Default: 3 87 | defaultFeePerByte?: number; 88 | } 89 | 90 | export type FeeOptions = { 91 | slowTargetBlocks?: number; 92 | averageTargetBlocks?: number; 93 | fastTargetBlocks?: number; 94 | }; 95 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/fee/BitcoinFeeApiProvider.ts: -------------------------------------------------------------------------------- 1 | import { Fee, HttpClient } from '@chainify/client'; 2 | import { FeeDetails, FeeProvider } from '@chainify/types'; 3 | 4 | export class BitcoinFeeApiProvider extends Fee implements FeeProvider { 5 | private _httpClient: HttpClient; 6 | 7 | constructor(endpoint = 'https://mempool.space/api/v1/fees/recommended') { 8 | super(); 9 | this._httpClient = new HttpClient({ baseURL: endpoint }); 10 | } 11 | 12 | async getFees(): Promise { 13 | const data = await this._httpClient.nodeGet('/'); 14 | 15 | return { 16 | slow: { 17 | fee: data.hourFee, 18 | wait: 60 * 60, 19 | }, 20 | average: { 21 | fee: data.halfHourFee, 22 | wait: 30 * 60, 23 | }, 24 | fast: { 25 | fee: data.fastestFee, 26 | wait: 10 * 60, 27 | }, 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/index.ts: -------------------------------------------------------------------------------- 1 | export { BitcoinBaseChainProvider } from './chain/BitcoinBaseChainProvider'; 2 | export { BitcoinEsploraApiProvider } from './chain/esplora/BitcoinEsploraApiProvider'; 3 | export { BitcoinEsploraBaseProvider } from './chain/esplora/BitcoinEsploraBaseProvider'; 4 | export { BitcoinEsploraBatchBaseProvider } from './chain/esplora/BitcoinEsploraBatchBaseProvider'; 5 | export { BitcoinJsonRpcBaseProvider } from './chain/jsonRpc/BitcoinJsonRpcBaseProvider'; 6 | export { BitcoinJsonRpcProvider } from './chain/jsonRpc/BitcoinJsonRpcProvider'; 7 | export { BitcoinFeeApiProvider } from './fee/BitcoinFeeApiProvider'; 8 | export { BitcoinNetworks } from './networks'; 9 | export { BitcoinSwapBaseProvider } from './swap/BitcoinSwapBaseProvider'; 10 | export { BitcoinSwapEsploraProvider } from './swap/BitcoinSwapEsploraProvider'; 11 | export { BitcoinSwapRpcProvider } from './swap/BitcoinSwapRpcProvider'; 12 | export * as BitcoinTypes from './types'; 13 | export * as BitcoinUtils from './utils'; 14 | export { BitcoinBaseWalletProvider } from './wallet/BitcoinBaseWallet'; 15 | export { BitcoinHDWalletProvider } from './wallet/BitcoinHDWallet'; 16 | export { BitcoinNodeWalletProvider } from './wallet/BitcoinNodeWallet'; 17 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/networks.ts: -------------------------------------------------------------------------------- 1 | import { networks } from 'bitcoinjs-lib'; 2 | import { BitcoinNetwork } from './types'; 3 | 4 | const bitcoin: BitcoinNetwork = { 5 | name: 'bitcoin', 6 | ...networks.bitcoin, 7 | coinType: '0', 8 | isTestnet: false, 9 | }; 10 | 11 | const bitcoin_testnet: BitcoinNetwork = { 12 | name: 'bitcoin_testnet', 13 | ...networks.testnet, 14 | coinType: '1', 15 | isTestnet: true, 16 | }; 17 | 18 | const bitcoin_regtest: BitcoinNetwork = { 19 | name: 'bitcoin_regtest', 20 | ...networks.regtest, 21 | coinType: '1', 22 | isTestnet: true, 23 | }; 24 | 25 | const BitcoinNetworks = { 26 | bitcoin, 27 | bitcoin_testnet, 28 | bitcoin_regtest, 29 | }; 30 | 31 | export { BitcoinNetworks }; 32 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/swap/BitcoinSwapEsploraProvider.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@chainify/client'; 2 | import { SwapParams, Transaction } from '@chainify/types'; 3 | import { Transaction as BitcoinTransaction } from '../types'; 4 | import { BitcoinBaseWalletProvider } from '../wallet/BitcoinBaseWallet'; 5 | import { BitcoinSwapBaseProvider } from './BitcoinSwapBaseProvider'; 6 | import { BitcoinSwapProviderOptions, PaymentVariants, TransactionMatchesFunction } from './types'; 7 | 8 | export class BitcoinSwapEsploraProvider extends BitcoinSwapBaseProvider { 9 | private _httpClient: HttpClient; 10 | 11 | constructor(options: BitcoinSwapProviderOptions, walletProvider?: BitcoinBaseWalletProvider) { 12 | super(options, walletProvider); 13 | this._httpClient = new HttpClient({ baseURL: options.scraperUrl }); 14 | } 15 | 16 | public async findSwapTransaction(swapParams: SwapParams, _blockNumber: number, predicate: TransactionMatchesFunction) { 17 | const currentHeight: number = await this.walletProvider.getChainProvider().getBlockHeight(); 18 | const swapOutput: Buffer = this.getSwapOutput(swapParams); 19 | const paymentVariants: PaymentVariants = this.getSwapPaymentVariants(swapOutput); 20 | for (const paymentVariant of Object.values(paymentVariants)) { 21 | const addressTransaction = this.findAddressTransaction(paymentVariant.address, currentHeight, predicate); 22 | if (addressTransaction) return addressTransaction; 23 | } 24 | } 25 | 26 | private async findAddressTransaction(address: string, currentHeight: number, predicate: TransactionMatchesFunction) { 27 | // TODO: This does not go through pages as swap addresses have at most 2 transactions 28 | // Investigate whether retrieving more transactions is required. 29 | const transactions = await this._httpClient.nodeGet(`/address/${address}/txs`); 30 | 31 | for (const transaction of transactions) { 32 | const formattedTransaction: Transaction = await this.walletProvider 33 | .getChainProvider() 34 | .getProvider() 35 | .formatTransaction(transaction, currentHeight); 36 | if (predicate(formattedTransaction)) { 37 | return formattedTransaction; 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/swap/BitcoinSwapRpcProvider.ts: -------------------------------------------------------------------------------- 1 | import { SwapParams, Transaction } from '@chainify/types'; 2 | import { BitcoinBaseChainProvider } from '../chain/BitcoinBaseChainProvider'; 3 | import { Transaction as BitcoinTransaction } from '../types'; 4 | import { IBitcoinWallet } from '../wallet/IBitcoinWallet'; 5 | import { BitcoinSwapBaseProvider } from './BitcoinSwapBaseProvider'; 6 | import { BitcoinSwapProviderOptions } from './types'; 7 | 8 | export class BitcoinSwapRpcProvider extends BitcoinSwapBaseProvider { 9 | constructor(options: BitcoinSwapProviderOptions, walletProvider?: IBitcoinWallet) { 10 | super(options, walletProvider); 11 | } 12 | 13 | public async findSwapTransaction( 14 | _swapParams: SwapParams, 15 | blockNumber: number, 16 | predicate: (tx: Transaction) => boolean 17 | ) { 18 | // TODO: Are mempool TXs possible? 19 | const block = await this.walletProvider.getChainProvider().getBlockByNumber(blockNumber, true); 20 | const swapTransaction = block.transactions.find(predicate); 21 | return swapTransaction; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/swap/types.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from '@chainify/types'; 2 | import { payments } from 'bitcoinjs-lib'; 3 | import { BitcoinNetwork, SwapMode, Transaction as BitcoinTransaction } from '../types'; 4 | 5 | export interface BitcoinSwapProviderOptions { 6 | network: BitcoinNetwork; 7 | mode?: SwapMode; 8 | scraperUrl?: string; 9 | } 10 | 11 | export type TransactionMatchesFunction = (tx: Transaction) => boolean; 12 | 13 | export type PaymentVariants = { 14 | [SwapMode.P2WSH]?: payments.Payment; 15 | [SwapMode.P2SH_SEGWIT]?: payments.Payment; 16 | [SwapMode.P2SH]?: payments.Payment; 17 | }; 18 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/types.ts: -------------------------------------------------------------------------------- 1 | import { Network } from '@chainify/types'; 2 | import { Network as BitcoinJsLibNetwork } from 'bitcoinjs-lib'; 3 | 4 | export * as BitcoinEsploraTypes from './chain/esplora/types'; 5 | export * as BitcoinJsonRpcTypes from './chain/jsonRpc/types'; 6 | export * from './swap/types'; 7 | 8 | export interface BitcoinNetwork extends Network, BitcoinJsLibNetwork {} 9 | 10 | export interface BitcoinNodeWalletOptions { 11 | addressType?: AddressType; 12 | network?: BitcoinNetwork; 13 | } 14 | export interface BitcoinWalletProviderOptions extends BitcoinNodeWalletOptions { 15 | baseDerivationPath: string; 16 | } 17 | 18 | export interface BitcoinHDWalletProviderOptions extends BitcoinWalletProviderOptions { 19 | mnemonic: string; 20 | } 21 | 22 | export interface OutputTarget { 23 | address?: string; 24 | script?: Buffer; 25 | value: number; 26 | } 27 | 28 | export interface ScriptPubKey { 29 | asm: string; 30 | hex: string; 31 | reqSigs: number; 32 | type: string; 33 | addresses: string[]; 34 | } 35 | 36 | export interface Output { 37 | value: number; 38 | n: number; 39 | scriptPubKey: ScriptPubKey; 40 | } 41 | 42 | export interface Input { 43 | txid: string; 44 | vout: number; 45 | scriptSig: { 46 | asm: string; 47 | hex: string; 48 | }; 49 | txinwitness: string[]; 50 | sequence: number; 51 | coinbase?: string; 52 | } 53 | 54 | export interface Transaction { 55 | txid: string; 56 | hash: string; 57 | version: number; 58 | locktime: number; 59 | size: number; 60 | vsize: number; 61 | weight: number; 62 | vin: Input[]; 63 | vout: Output[]; 64 | confirmations?: number; 65 | hex: string; 66 | } 67 | 68 | export interface UTXO { 69 | txid: string; 70 | vout: number; 71 | value: number; 72 | address: string; 73 | derivationPath?: string; 74 | } 75 | 76 | export enum AddressType { 77 | LEGACY = 'legacy', 78 | P2SH_SEGWIT = 'p2sh-segwit', 79 | BECH32 = 'bech32', 80 | } 81 | 82 | export enum SwapMode { 83 | P2SH = 'p2sh', 84 | P2SH_SEGWIT = 'p2shSegwit', 85 | P2WSH = 'p2wsh', 86 | } 87 | 88 | export type AddressTxCounts = { [index: string]: number }; 89 | 90 | export interface PsbtInputTarget { 91 | index: number; 92 | derivationPath: string; 93 | } 94 | 95 | export interface P2SHInput { 96 | inputTxHex: string; 97 | index: number; 98 | vout: any; 99 | outputScript: Buffer; 100 | } 101 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/typings/bitcoinjs-lib-classify.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'bitcoinjs-lib/src/classify'; 2 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/typings/coinselect-accumulative.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'coinselect/accumulative'; 2 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/typings/coinselect.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'coinselect'; 2 | -------------------------------------------------------------------------------- /packages/bitcoin/lib/wallet/IBitcoinWallet.ts: -------------------------------------------------------------------------------- 1 | import { Chain, Wallet } from '@chainify/client'; 2 | import { Address, FeeType, Transaction, TransactionRequest } from '@chainify/types'; 3 | import { PsbtInputTarget } from '../types'; 4 | 5 | export interface IBitcoinWallet extends Wallet { 6 | getChainProvider(): Chain; 7 | 8 | sendTransaction(txRequest: TransactionRequest): Promise; 9 | 10 | updateTransactionFee(tx: string | Transaction, newFee: FeeType): Promise; 11 | 12 | getWalletAddress(address: string): Promise
; 13 | 14 | signPSBT(data: string, inputs: PsbtInputTarget[]): Promise; 15 | } 16 | -------------------------------------------------------------------------------- /packages/bitcoin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainify/bitcoin", 3 | "version": "2.3.0", 4 | "description": "", 5 | "author": "Liquality ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "./dist/lib/index.js", 9 | "types": "./dist/lib/index.d.ts", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "clean": "rm -rf node_modules .turbo dist" 16 | }, 17 | "dependencies": { 18 | "@chainify/client": "workspace:*", 19 | "@chainify/errors": "workspace:*", 20 | "@chainify/types": "workspace:*", 21 | "@chainify/utils": "workspace:*", 22 | "@types/bip32": "^2.0.0", 23 | "@types/bip39": "^3.0.0", 24 | "@types/bitcoinjs-lib": "^5.0.0", 25 | "@types/lodash": "^4.14.178", 26 | "@types/memoizee": "^0.4.7", 27 | "bip174": "^2.0.1", 28 | "bip32": "2.0.6", 29 | "bip39": "^3.0.4", 30 | "bitcoinjs-lib": "5.2.0", 31 | "bitcoinjs-message": "2.2.0", 32 | "coinselect": "^3.1.12", 33 | "lodash": "4.17.21", 34 | "memoizee": "^0.4.15" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/bitcoin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true 8 | }, 9 | "include": ["lib"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/client/lib/Client.ts: -------------------------------------------------------------------------------- 1 | import Chain from './Chain'; 2 | import Nft from './Nft'; 3 | import Swap from './Swap'; 4 | import Wallet from './Wallet'; 5 | 6 | export default class Client< 7 | ChainType extends Chain = Chain, 8 | WalletType extends Wallet = Wallet, 9 | SwapType extends Swap = Swap, 10 | NftType extends Nft = Nft 11 | > { 12 | private _chain: ChainType; 13 | private _wallet: WalletType; 14 | private _swap: SwapType; 15 | private _nft: NftType; 16 | 17 | constructor(chain?: ChainType, wallet?: WalletType, swap?: SwapType, nft?: NftType) { 18 | this._chain = chain; 19 | this._wallet = wallet; 20 | this._swap = swap; 21 | this._nft = nft; 22 | } 23 | 24 | connect(provider: ChainType | WalletType | SwapType | NftType) { 25 | switch (true) { 26 | case provider instanceof Chain: { 27 | this.chain = provider as ChainType; 28 | if (this.wallet) { 29 | this.wallet.setChainProvider(this.chain); 30 | } 31 | break; 32 | } 33 | 34 | case provider instanceof Wallet: { 35 | this.wallet = provider as WalletType; 36 | this.connectChain(); 37 | 38 | if (this.swap) { 39 | this.swap.setWallet(this.wallet); 40 | } 41 | 42 | if (this.nft) { 43 | this.nft.setWallet(this.wallet); 44 | } 45 | break; 46 | } 47 | 48 | case provider instanceof Swap: { 49 | this.swap = provider as SwapType; 50 | this.connectWallet(this.swap); 51 | if (this.nft) { 52 | this.nft.setWallet(this.wallet); 53 | } 54 | this.connectChain(); 55 | break; 56 | } 57 | 58 | case provider instanceof Nft: { 59 | this._nft = provider as NftType; 60 | this.connectWallet(this.nft); 61 | if (this.swap) { 62 | this.swap.setWallet(this.wallet); 63 | } 64 | this.connectChain(); 65 | break; 66 | } 67 | } 68 | 69 | return this; 70 | } 71 | 72 | get chain() { 73 | return this._chain; 74 | } 75 | 76 | set chain(chain: ChainType) { 77 | this._chain = chain; 78 | } 79 | 80 | get wallet() { 81 | return this._wallet; 82 | } 83 | 84 | set wallet(wallet: WalletType) { 85 | this._wallet = wallet; 86 | } 87 | 88 | get swap() { 89 | return this._swap; 90 | } 91 | 92 | set swap(swap: SwapType) { 93 | this._swap = swap; 94 | } 95 | 96 | get nft() { 97 | return this._nft; 98 | } 99 | 100 | set nft(nft: NftType) { 101 | this._nft = nft; 102 | } 103 | 104 | private connectChain() { 105 | const chain = this.wallet?.getChainProvider() as ChainType; 106 | if (chain) { 107 | this.chain = chain; 108 | } 109 | } 110 | 111 | private connectWallet(source: SwapType | NftType) { 112 | const wallet = source?.getWallet() as WalletType; 113 | if (wallet) { 114 | this.wallet = wallet; 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /packages/client/lib/Fee.ts: -------------------------------------------------------------------------------- 1 | import { Asset, BigNumber, FeeDetails, FeeProvider } from '@chainify/types'; 2 | 3 | export default abstract class Fee implements FeeProvider { 4 | public gasUnits: BigNumber; 5 | 6 | constructor(gasUnits?: BigNumber) { 7 | this.gasUnits = gasUnits; 8 | } 9 | 10 | abstract getFees(feeAsset?: Asset): Promise; 11 | } 12 | -------------------------------------------------------------------------------- /packages/client/lib/Http.ts: -------------------------------------------------------------------------------- 1 | import { NodeError } from '@chainify/errors'; 2 | import { Logger } from '@chainify/logger'; 3 | import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; 4 | 5 | const logger = new Logger('HttpClient'); 6 | export default class HttpClient { 7 | private _node: AxiosInstance; 8 | 9 | constructor(config: AxiosRequestConfig) { 10 | this._node = axios.create(config); 11 | } 12 | 13 | public static async post(url: string, data: I, config?: AxiosRequestConfig): Promise { 14 | const response = axios 15 | .post(url, data, config) 16 | .then((response) => response.data as O) 17 | .catch(HttpClient.handleError); 18 | 19 | return response as unknown as O; 20 | } 21 | 22 | public static async get(url: string, params: I = {} as I, config?: AxiosRequestConfig): Promise { 23 | const response = await axios 24 | .get(url, { ...config, params }) 25 | .then((response) => response.data as O) 26 | .catch(HttpClient.handleError); 27 | 28 | return response as unknown as O; 29 | } 30 | 31 | public async nodeGet(url: string, params: I = {} as I, config?: AxiosRequestConfig): Promise { 32 | const response = await this._node 33 | .get(url, { ...config, params }) 34 | .then((response) => response.data as O) 35 | .catch(HttpClient.handleError); 36 | 37 | return response as unknown as O; 38 | } 39 | 40 | public async nodePost(url: string, data: I, config?: AxiosRequestConfig): Promise { 41 | const response = this._node 42 | .post(url, data, config) 43 | .then((response) => response.data as O) 44 | .catch(HttpClient.handleError); 45 | 46 | return response as unknown as O; 47 | } 48 | 49 | public setConfig(config: AxiosRequestConfig) { 50 | this._node = axios.create(config); 51 | } 52 | 53 | private static handleError(error: any): void { 54 | const { message, ...attrs } = error; 55 | const errorMessage = error?.response?.data || message; 56 | 57 | if (error.response) { 58 | // The request was made and the server responded with a status code 59 | // that falls out of the range of 2xx 60 | logger.debug('error.response'); 61 | logger.debug(error.response.data); 62 | logger.debug(error.response.status); 63 | logger.debug(error.response.headers); 64 | } else if (error.request) { 65 | // The request was made but no response was received 66 | // `error.request` is an instance of XMLHttpRequest in the browser and an instance of 67 | // http.ClientRequest in node.js 68 | logger.debug('error.request'); 69 | logger.debug(error.request); 70 | } else { 71 | // Something happened in setting up the request that triggered an Error 72 | logger.debug('error.message'); 73 | logger.debug(error.message); 74 | } 75 | 76 | throw new NodeError(errorMessage, { ...attrs }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/client/lib/JsonRpc.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@chainify/logger'; 2 | import { AxiosRequestConfig } from 'axios'; 3 | import HttpClient from './Http'; 4 | 5 | const logger = new Logger('JsonRpcProvider'); 6 | 7 | export default class JsonRpcProvider { 8 | private readonly _httpClient: HttpClient; 9 | private _nextId: number; 10 | 11 | constructor(url?: string, username?: string, password?: string) { 12 | const config: AxiosRequestConfig = { 13 | baseURL: url || JsonRpcProvider.defaultUrl(), 14 | responseType: 'text', 15 | transformResponse: undefined, // https://github.com/axios/axios/issues/907, 16 | validateStatus: () => true, 17 | }; 18 | 19 | if (username || password) { 20 | config.auth = { username, password }; 21 | } 22 | 23 | this._nextId = 42; 24 | 25 | this._httpClient = new HttpClient(config); 26 | } 27 | 28 | static defaultUrl(): string { 29 | return 'http://localhost:8545'; 30 | } 31 | 32 | public async send(method: string, params: Array): Promise { 33 | const request = { 34 | method: method, 35 | params: params, 36 | id: this._nextId++, 37 | jsonrpc: '2.0', 38 | }; 39 | 40 | const result = this._httpClient.nodePost('/', request).then( 41 | (result) => { 42 | logger.debug({ 43 | action: 'response', 44 | request: request, 45 | response: result, 46 | provider: this, 47 | }); 48 | return this.getResult(result); 49 | }, 50 | (error) => { 51 | logger.debug({ 52 | action: 'response', 53 | error: error, 54 | request: request, 55 | provider: this, 56 | }); 57 | 58 | throw error; 59 | } 60 | ); 61 | 62 | return result; 63 | } 64 | 65 | private getResult(payload: { error?: { code?: number; data?: any; message?: string }; result?: any }): any { 66 | if (payload.error) { 67 | const error: any = new Error(payload.error.message); 68 | error.code = payload.error.code; 69 | error.data = payload.error.data; 70 | throw error; 71 | } 72 | 73 | return payload.result; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /packages/client/lib/Nft.ts: -------------------------------------------------------------------------------- 1 | import { UnsupportedMethodError } from '@chainify/errors'; 2 | import { AddressType, BigNumber, FeeType, NFTAsset, Transaction } from '@chainify/types'; 3 | import Wallet from './Wallet'; 4 | 5 | export default abstract class Nft { 6 | protected walletProvider: Wallet; 7 | 8 | constructor(walletProvider?: Wallet) { 9 | this.walletProvider = walletProvider; 10 | } 11 | 12 | public setWallet(wallet: Wallet): void { 13 | this.walletProvider = wallet; 14 | } 15 | 16 | public getWallet(): Wallet { 17 | return this.walletProvider; 18 | } 19 | 20 | public abstract transfer( 21 | contract: AddressType, 22 | receiver: AddressType, 23 | tokenIDs: string[], 24 | values?: number[], 25 | data?: string, 26 | fee?: FeeType 27 | ): Promise; 28 | 29 | public abstract balanceOf(contractAddress: AddressType, owners: AddressType[], tokenIDs: number[]): Promise; 30 | 31 | public abstract approve(contract: AddressType, operator: AddressType, tokenID: number): Promise; 32 | 33 | public abstract approveAll(contract: AddressType, operator: AddressType, state: boolean): Promise; 34 | 35 | public abstract isApprovedForAll(contract: AddressType, operator: AddressType): Promise; 36 | 37 | public estimateTransfer( 38 | _contractAddress: AddressType, 39 | _receiver: AddressType, 40 | _tokenIDs: string[], 41 | _amounts?: number[], 42 | _data?: string 43 | ): Promise { 44 | throw new UnsupportedMethodError('Method not supported'); 45 | } 46 | 47 | public estimateApprove(_contract: AddressType, _operator: AddressType, _tokenID: number): Promise { 48 | throw new UnsupportedMethodError('Method not supported'); 49 | } 50 | 51 | public estimateApproveAll(_contract: AddressType, _operator: AddressType, _state: boolean): Promise { 52 | throw new UnsupportedMethodError('Method not supported'); 53 | } 54 | 55 | public abstract fetch(): Promise; 56 | } 57 | -------------------------------------------------------------------------------- /packages/client/lib/Swap.ts: -------------------------------------------------------------------------------- 1 | import { InvalidSwapParamsError, PendingTxError, TxFailedError, TxNotFoundError } from '@chainify/errors'; 2 | import { FeeType, SwapParams, SwapProvider, Transaction, TxStatus } from '@chainify/types'; 3 | import { sha256, validateExpiration, validateSecretHash, validateValue } from '@chainify/utils'; 4 | import Wallet from './Wallet'; 5 | 6 | export default abstract class Swap = Wallet> implements SwapProvider { 7 | protected walletProvider: WalletProvider; 8 | 9 | constructor(walletProvider?: WalletProvider) { 10 | this.walletProvider = walletProvider; 11 | } 12 | 13 | public setWallet(wallet: WalletProvider): void { 14 | this.walletProvider = wallet; 15 | this.onWalletProviderUpdate(wallet); 16 | } 17 | 18 | public getWallet(): WalletProvider { 19 | return this.walletProvider; 20 | } 21 | 22 | public async verifyInitiateSwapTransaction(swapParams: SwapParams, initTx: string | Transaction): Promise { 23 | this.validateSwapParams(swapParams); 24 | const transaction = typeof initTx === 'string' ? await this.walletProvider.getChainProvider().getTransactionByHash(initTx) : initTx; 25 | 26 | if (!transaction) { 27 | throw new TxNotFoundError(`Transaction not found: ${initTx}`); 28 | } 29 | 30 | const doesMatch = await this.doesTransactionMatchInitiation(swapParams, transaction); 31 | 32 | if (!(transaction.confirmations > 0)) { 33 | throw new PendingTxError(`Transaction not confirmed ${transaction.confirmations}`); 34 | } 35 | 36 | if (transaction.status !== TxStatus.Success) { 37 | throw new TxFailedError('Transaction not successful'); 38 | } 39 | 40 | if (!doesMatch) { 41 | throw new InvalidSwapParamsError(`Swap params does not match the transaction`); 42 | } 43 | 44 | return true; 45 | } 46 | 47 | public validateSwapParams(swapParams: SwapParams): void { 48 | validateValue(swapParams.value); 49 | validateSecretHash(swapParams.secretHash); 50 | validateExpiration(swapParams.expiration); 51 | } 52 | 53 | public async generateSecret(message: string): Promise { 54 | const address = await this.walletProvider.getAddress(); 55 | const signedMessage = await this.walletProvider.signMessage(message, address); 56 | const secret = sha256(signedMessage); 57 | return secret; 58 | } 59 | 60 | public abstract initiateSwap(swapParams: SwapParams, fee?: FeeType): Promise; 61 | public abstract findInitiateSwapTransaction(swapParams: SwapParams, _blockNumber?: number): Promise; 62 | 63 | public abstract claimSwap(swapParams: SwapParams, initTx: string, secret: string, fee?: FeeType): Promise; 64 | public abstract findClaimSwapTransaction(swapParams: SwapParams, initTxHash: string, blockNumber?: number): Promise; 65 | 66 | public abstract refundSwap(swapParams: SwapParams, initTx: string, fee?: FeeType): Promise; 67 | public abstract findRefundSwapTransaction(swapParams: SwapParams, initiationTxHash: string, blockNumber?: number): Promise; 68 | 69 | public abstract getSwapSecret(claimTxHash: string, initTxHash?: string): Promise; 70 | 71 | public abstract canUpdateFee(): boolean; 72 | public abstract updateTransactionFee(tx: string | Transaction, newFee: FeeType): Promise; 73 | 74 | protected abstract doesTransactionMatchInitiation(swapParams: SwapParams, transaction: Transaction): Promise | boolean; 75 | 76 | protected abstract onWalletProviderUpdate(wallet: WalletProvider): void; 77 | } 78 | -------------------------------------------------------------------------------- /packages/client/lib/Wallet.ts: -------------------------------------------------------------------------------- 1 | import { UnsupportedMethodError } from '@chainify/errors'; 2 | import { 3 | Address, 4 | AddressType, 5 | Asset, 6 | BigNumber, 7 | FeeType, 8 | NamingProvider, 9 | Network, 10 | Transaction, 11 | TransactionRequest, 12 | WalletProvider, 13 | } from '@chainify/types'; 14 | import Chain from './Chain'; 15 | 16 | export default abstract class Wallet implements WalletProvider { 17 | protected chainProvider: Chain; 18 | protected namingProvider: NamingProvider; 19 | 20 | constructor(chainProvider?: Chain, namingProvider?: NamingProvider) { 21 | this.chainProvider = chainProvider; 22 | this.namingProvider = namingProvider; 23 | } 24 | 25 | public setChainProvider(chainProvider: Chain): void { 26 | this.chainProvider = chainProvider; 27 | this.onChainProviderUpdate(chainProvider); 28 | } 29 | 30 | public getChainProvider(): Chain { 31 | return this.chainProvider; 32 | } 33 | 34 | public setNamingProvider(namingProvider: NamingProvider) { 35 | this.namingProvider = namingProvider; 36 | } 37 | 38 | public getNamingProvider(): NamingProvider { 39 | return this.namingProvider; 40 | } 41 | 42 | public signTypedData(_data: any): Promise { 43 | throw new UnsupportedMethodError('Method not supported'); 44 | } 45 | 46 | public abstract getConnectedNetwork(): Promise; 47 | 48 | public abstract getSigner(): S; 49 | 50 | public abstract getAddress(): Promise; 51 | 52 | public abstract getUnusedAddress(change?: boolean, numAddressPerCall?: number): Promise
; 53 | 54 | public abstract getUsedAddresses(numAddressPerCall?: number): Promise; 55 | 56 | public abstract getAddresses(start?: number, numAddresses?: number, change?: boolean): Promise; 57 | 58 | public abstract signMessage(message: string, from: AddressType): Promise; 59 | 60 | public abstract sendTransaction(txRequest: TransactionRequest): Promise; 61 | 62 | public abstract sendBatchTransaction(txRequests: TransactionRequest[]): Promise; 63 | 64 | public abstract sendSweepTransaction(address: AddressType, asset: Asset, fee?: FeeType): Promise; 65 | 66 | public abstract updateTransactionFee(tx: string | Transaction, newFee: FeeType): Promise; 67 | 68 | public abstract getBalance(assets: Asset[]): Promise; 69 | 70 | public abstract exportPrivateKey(): Promise; 71 | 72 | public abstract isWalletAvailable(): Promise; 73 | 74 | public abstract canUpdateFee(): boolean; 75 | 76 | protected abstract onChainProviderUpdate(chainProvider: Chain): void; 77 | } 78 | -------------------------------------------------------------------------------- /packages/client/lib/index.ts: -------------------------------------------------------------------------------- 1 | import Chain from './Chain'; 2 | import Client from './Client'; 3 | import Fee from './Fee'; 4 | import HttpClient from './Http'; 5 | import JsonRpcProvider from './JsonRpc'; 6 | import Nft from './Nft'; 7 | import Swap from './Swap'; 8 | import Wallet from './Wallet'; 9 | 10 | export * as ClientTypes from './types'; 11 | export { Client, Chain, Fee, Wallet, Swap, Nft, HttpClient, JsonRpcProvider }; 12 | -------------------------------------------------------------------------------- /packages/client/lib/types.ts: -------------------------------------------------------------------------------- 1 | export { AxiosRequestConfig } from 'axios'; 2 | -------------------------------------------------------------------------------- /packages/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainify/client", 3 | "version": "2.2.1", 4 | "description": "", 5 | "author": "Liquality ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "./dist/lib/index.js", 9 | "types": "./dist/lib/index.d.ts", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "clean": "rm -rf node_modules .turbo dist" 16 | }, 17 | "dependencies": { 18 | "@chainify/errors": "workspace:*", 19 | "@chainify/logger": "workspace:*", 20 | "@chainify/types": "workspace:*", 21 | "@chainify/utils": "workspace:*", 22 | "axios": "^0.25.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true 8 | }, 9 | "include": ["lib"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/errors/lib/index.ts: -------------------------------------------------------------------------------- 1 | import BaseError from 'standard-error'; 2 | 3 | function createError(name: string) { 4 | const Error = class extends BaseError {}; 5 | Error.prototype.name = name; 6 | return Error; 7 | } 8 | 9 | export const StandardError = createError('StandardError'); 10 | export const ProviderNotFoundError = createError('ProviderNotFoundError'); 11 | export const InvalidProviderError = createError('InvalidProviderError'); 12 | export const DuplicateProviderError = createError('DuplicateProviderError'); 13 | export const NoProviderError = createError('NoProviderError'); 14 | export const UnsupportedMethodError = createError('UnsupportedMethodError'); 15 | export const UnimplementedMethodError = createError('UnimplementedMethodError'); 16 | export const InvalidProviderResponseError = createError('InvalidProviderResponseError'); 17 | export const PendingTxError = createError('PendingTxError'); 18 | export const TxNotFoundError = createError('TxNotFoundError'); 19 | export const TxFailedError = createError('TxFailedError'); 20 | export const BlockNotFoundError = createError('BlockNotFoundError'); 21 | export const InvalidDestinationAddressError = createError('InvalidDestinationAddressError'); 22 | export const WalletError = createError('WalletError'); 23 | export const NodeError = createError('NodeError'); 24 | export const InvalidSecretError = createError('InvalidSecretError'); 25 | export const InvalidAddressError = createError('InvalidAddressError'); 26 | export const InvalidExpirationError = createError('InvalidExpirationError'); 27 | export const InsufficientBalanceError = createError('InsufficientBalanceError'); 28 | export const ReplaceFeeInsufficientError = createError('ReplaceFeeInsufficientError'); 29 | export const InvalidSwapParamsError = createError('InvalidSwapParamsError'); 30 | export const InvalidValueError = createError('InvalidValueError'); 31 | -------------------------------------------------------------------------------- /packages/errors/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainify/errors", 3 | "version": "2.2.0", 4 | "description": "", 5 | "author": "Liquality ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "./dist/lib/index.js", 9 | "types": "./dist/lib/index.d.ts", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "clean": "rm -rf node_modules .turbo dist" 16 | }, 17 | "dependencies": { 18 | "standard-error": "^1.1.0" 19 | }, 20 | "devDependencies": { 21 | "@types/standard-error": "^1.1.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/errors/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true 8 | }, 9 | "include": ["lib"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/evm-contracts/.gitignore: -------------------------------------------------------------------------------- 1 | # directories 2 | .yarn/* 3 | !.yarn/patches 4 | !.yarn/releases 5 | !.yarn/plugins 6 | !.yarn/sdks 7 | !.yarn/versions 8 | **/artifacts 9 | **/build 10 | **/cache 11 | **/coverage 12 | **/.coverage_artifacts 13 | **/.coverage_cache 14 | **/.coverage_contracts 15 | **/dist 16 | **/node_modules 17 | 18 | # files 19 | *.env 20 | *.log 21 | .pnp.* 22 | coverage.json 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # types 28 | src/types 29 | 30 | # factories 31 | !src/types/factories 32 | src/types/factories/* 33 | !src/types/factories/Greeter__factory.ts -------------------------------------------------------------------------------- /packages/evm-contracts/README.md: -------------------------------------------------------------------------------- 1 | # Liquality HTLC 2 | 3 | ## Gas usage 4 | 5 | ### Ether HTLC 6 | 7 | Screenshot 2022-01-27 at 16 25 27 8 | 9 | ### ERC20 HTLC 10 | 11 | Screenshot 2022-01-27 at 16 26 07 12 | 13 | ### Coverage 14 | 15 | Screenshot 2022-01-27 at 16 28 40 16 | 17 | ## Usage 18 | 19 | ### Pre Requisites 20 | 21 | Before running any command, you need to create a `.env` file and set a BIP-39 compatible mnemonic as an environment 22 | variable. Follow the example in `.env.example`. If you don't already have a mnemonic, use this [website](https://iancoleman.io/bip39/) to generate one. 23 | 24 | Then, proceed with installing dependencies: 25 | 26 | ```sh 27 | yarn install 28 | ``` 29 | 30 | ### Compile 31 | 32 | Compile the smart contracts with Hardhat: 33 | 34 | ```sh 35 | $ yarn compile 36 | ``` 37 | 38 | ### TypeChain 39 | 40 | Compile the smart contracts and generate TypeChain artifacts: 41 | 42 | ```sh 43 | $ yarn typechain 44 | ``` 45 | 46 | ### Lint Solidity 47 | 48 | Lint the Solidity code: 49 | 50 | ```sh 51 | $ yarn lint:sol 52 | ``` 53 | 54 | ### Lint TypeScript 55 | 56 | Lint the TypeScript code: 57 | 58 | ```sh 59 | $ yarn lint:ts 60 | ``` 61 | 62 | ### Test 63 | 64 | Run the Mocha tests: 65 | 66 | ```sh 67 | $ yarn test 68 | ``` 69 | 70 | ### Coverage 71 | 72 | Generate the code coverage report: 73 | 74 | ```sh 75 | $ yarn coverage 76 | ``` 77 | 78 | ### Report Gas 79 | 80 | See the gas usage per unit test and average gas per method call: 81 | 82 | ```sh 83 | $ REPORT_GAS=true yarn test 84 | ``` 85 | 86 | ### Clean 87 | 88 | Delete the smart contract artifacts, the coverage reports and the Hardhat cache: 89 | 90 | ```sh 91 | $ yarn clean 92 | ``` 93 | 94 | ### Deploy 95 | 96 | Deploy the contracts to Hardhat Network: 97 | 98 | ```sh 99 | $ yarn deploy --greeting "Bonjour, le monde!" 100 | ``` 101 | 102 | ## Syntax Highlighting 103 | 104 | If you use VSCode, you can enjoy syntax highlighting for your Solidity code via the 105 | [vscode-solidity](https://github.com/juanfranblanco/vscode-solidity) extension. The recommended approach to set the 106 | compiler version is to add the following fields to your VSCode user settings: 107 | 108 | ```json 109 | { 110 | "solidity.compileUsingRemoteVersion": "v0.8.4+commit.c7e474f2", 111 | "solidity.defaultCompiler": "remote" 112 | } 113 | ``` 114 | 115 | Where of course `v0.8.4+commit.c7e474f2` can be replaced with any other version. 116 | -------------------------------------------------------------------------------- /packages/evm-contracts/contracts/ILiqualityHTLC.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | /// @title ILiqualityHTLC 5 | interface ILiqualityHTLC { 6 | struct HTLCData { 7 | uint256 amount; 8 | uint256 expiration; 9 | bytes32 secretHash; 10 | address tokenAddress; 11 | address refundAddress; 12 | address recipientAddress; 13 | } 14 | 15 | /// @notice Emitted when a successful HTLC was created 16 | event Initiate(bytes32 id, HTLCData htlc); 17 | 18 | /// @notice Emitted when a successful Refund was performed 19 | event Refund(bytes32 indexed id); 20 | 21 | /// @notice Emitted when a successful Claim was performed 22 | event Claim(bytes32 indexed id, bytes32 secret); 23 | 24 | /// @notice Initiates a HTLC based on the input parameters. 25 | function initiate(HTLCData calldata htlc) external payable returns (bytes32); 26 | 27 | /// @notice Claims an existing HTLC for the provided `id` using the `secret`. 28 | /// @param id htlc id 29 | /// @param secret the secret used to create the `secretHash` 30 | function claim(bytes32 id, bytes32 secret) external; 31 | 32 | /// @notice Refunds an existing HTLC for the provided `id`. The refunded amount goes to the `refundAddress` 33 | /// @param id htlc id 34 | function refund(bytes32 id) external; 35 | 36 | /// @notice Emitted when the sha256 of the provided secret during claim does not match the secret hash 37 | error LiqualityHTLC__WrongSecret(); 38 | 39 | /// @notice Emitted when the provided expiration is smaller than the current block timestmap 40 | error LiqualityHTLC__InvalidExpiration(); 41 | 42 | /// @notice Emitted when the amount is 0 43 | error LiqualityHTLC__InvalidSwapAmount(); 44 | 45 | /// @notice Emitted when initiating Ether HTLC and the amount does not match the msg.value 46 | error LiqualityHTLC__InvalidMsgValue(); 47 | 48 | /// @notice Emitted when the generated id already exists 49 | error LiqualityHTLC__SwapAlreadyExists(); 50 | 51 | /// @notice Emitted when the generated swap does not exists 52 | error LiqualityHTLC__SwapDoesNotExist(); 53 | 54 | /// @notice Emitted when there is an attempt for refund when the swap is not expired 55 | error LiqualityHTLC__SwapNotExpired(); 56 | 57 | /// @notice Emitted when the msg.sender does not match the provided `refundAddress` 58 | error LiqualityHTLC__InvalidSender(); 59 | } 60 | -------------------------------------------------------------------------------- /packages/evm-contracts/contracts/LibTransfer.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity >=0.8.11; 4 | 5 | library LibTransfer { 6 | function transferEth(address to, uint256 value) internal { 7 | // solhint-disable-next-line 8 | (bool success, ) = to.call{ value: value }(''); 9 | require(success, 'transfer failed'); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/evm-contracts/contracts/LiqualityHTLC.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | 4 | import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; 5 | 6 | import './ILiqualityHTLC.sol'; 7 | import './LibTransfer.sol'; 8 | 9 | /// @title LiqualityHTLC 10 | /// @notice HTLC contract to support Liquality's cross-chain swaps. 11 | contract LiqualityHTLC is ILiqualityHTLC { 12 | using SafeERC20 for IERC20; 13 | 14 | mapping(bytes32 => HTLCData) public htlcs; 15 | 16 | /// @inheritdoc ILiqualityHTLC 17 | function initiate(HTLCData calldata htlc) external payable returns (bytes32 id) { 18 | if (htlc.expiration < block.timestamp) { 19 | revert LiqualityHTLC__InvalidExpiration(); 20 | } 21 | 22 | if (htlc.amount == 0) { 23 | revert LiqualityHTLC__InvalidSwapAmount(); 24 | } 25 | 26 | // handle Ether swaps 27 | if (htlc.tokenAddress == address(0x0)) { 28 | if (htlc.amount != msg.value) { 29 | revert LiqualityHTLC__InvalidMsgValue(); 30 | } 31 | } 32 | // handle ERC20 swaps 33 | else { 34 | // protection against permanantly locking ETH when using ERC20 tokens 35 | if (msg.value > 0) { 36 | revert LiqualityHTLC__InvalidMsgValue(); 37 | } 38 | IERC20(htlc.tokenAddress).safeTransferFrom(msg.sender, address(this), htlc.amount); 39 | } 40 | 41 | id = sha256(abi.encode(htlc.refundAddress, block.timestamp, htlc.amount, htlc.expiration, htlc.secretHash, htlc.recipientAddress)); 42 | 43 | if (htlcs[id].expiration != 0) { 44 | revert LiqualityHTLC__SwapAlreadyExists(); 45 | } 46 | 47 | htlcs[id] = htlc; 48 | emit Initiate(id, htlc); 49 | } 50 | 51 | /// @inheritdoc ILiqualityHTLC 52 | function claim(bytes32 id, bytes32 secret) external { 53 | HTLCData memory h = htlcs[id]; 54 | 55 | if (h.expiration == 0) { 56 | revert LiqualityHTLC__SwapDoesNotExist(); 57 | } 58 | 59 | if (sha256(abi.encodePacked(secret)) != h.secretHash) { 60 | revert LiqualityHTLC__WrongSecret(); 61 | } 62 | 63 | // free some storage for gas refund 64 | delete htlcs[id]; 65 | 66 | emit Claim(id, secret); 67 | 68 | // handle Ether claims 69 | if (h.tokenAddress == address(0x0)) { 70 | LibTransfer.transferEth(h.recipientAddress, h.amount); 71 | } 72 | // handle ERC20 claims 73 | else { 74 | IERC20(h.tokenAddress).safeTransfer(h.recipientAddress, h.amount); 75 | } 76 | } 77 | 78 | /// @inheritdoc ILiqualityHTLC 79 | function refund(bytes32 id) external { 80 | HTLCData memory h = htlcs[id]; 81 | 82 | if (h.expiration == 0) { 83 | revert LiqualityHTLC__SwapDoesNotExist(); 84 | } 85 | 86 | if (block.timestamp <= h.expiration) { 87 | revert LiqualityHTLC__SwapNotExpired(); 88 | } 89 | 90 | // free some storage for gas refund 91 | delete htlcs[id]; 92 | 93 | emit Refund(id); 94 | 95 | // handle Ether refunds 96 | if (h.tokenAddress == address(0x0)) { 97 | LibTransfer.transferEth(h.refundAddress, h.amount); 98 | } 99 | // handle ERC20 refunds 100 | else { 101 | IERC20(h.tokenAddress).safeTransfer(h.refundAddress, h.amount); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /packages/evm-contracts/contracts/nft/ERC1155.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | import '@openzeppelin/contracts/token/ERC1155/ERC1155.sol'; 4 | -------------------------------------------------------------------------------- /packages/evm-contracts/contracts/nft/ERC721.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.11; 3 | import '@openzeppelin/contracts/token/ERC721/ERC721.sol'; 4 | -------------------------------------------------------------------------------- /packages/evm-contracts/contracts/test/TestERC20.sol: -------------------------------------------------------------------------------- 1 | import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; 2 | 3 | // SPDX-License-Identifier: MIT 4 | pragma solidity 0.8.11; 5 | 6 | contract TestERC20 is ERC20 { 7 | constructor() ERC20('Test', 'TT') {} 8 | 9 | function mint(address account, uint256 amount) external { 10 | _mint(account, amount); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/evm-contracts/tasks/accounts.ts: -------------------------------------------------------------------------------- 1 | import { Signer } from '@ethersproject/abstract-signer'; 2 | import { task } from 'hardhat/config'; 3 | 4 | task('accounts', 'Prints the list of accounts', async (_taskArgs, hre) => { 5 | const accounts: Signer[] = await hre.ethers.getSigners(); 6 | 7 | for (const account of accounts) { 8 | console.log(await account.getAddress()); 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /packages/evm-contracts/tasks/deploy/index.ts: -------------------------------------------------------------------------------- 1 | // // @ts-nocheck 2 | // // @ts-ignore 3 | // import { LiqualityHTLC__factory, Multicall__factory } from '@chainify/evm/lib/typechain'; 4 | // import { task } from 'hardhat/config'; 5 | // import { TaskArguments } from 'hardhat/types'; 6 | // import { create2_deployer_abi, create2_deployer_bytecode } from '../utils'; 7 | 8 | // task('deploy:HTLC').setAction(async function (taskArguments: TaskArguments, { ethers }) { 9 | // const funder = (await ethers.getSigners())[0]; 10 | // const privateKey = ''; 11 | // const wallet = new ethers.Wallet(privateKey, funder.provider); 12 | // console.log('Wallet address: ', wallet.address); 13 | // console.log('Wallet balance: ', await wallet.getBalance()); 14 | 15 | // const create2_factory = new ethers.ContractFactory(create2_deployer_abi, create2_deployer_bytecode, wallet); 16 | // const create2DeployTx = await create2_factory.deploy({ gasLimit: 500000, nonce: 0 }); 17 | 18 | // const factory = new ethers.Contract(create2DeployTx.address, create2_deployer_abi, wallet); 19 | // console.log('Factory address', factory.address); 20 | 21 | // // TODO: add salt 22 | // const salt = ''; 23 | // const htlc = await factory.deploy(LiqualityHTLC__factory.bytecode, salt, { gasLimit: 1500000, nonce: 1 }); 24 | // const receipt = await htlc.wait(); 25 | 26 | // const addr = ethers.utils.getCreate2Address(factory.address, salt, ethers.utils.keccak256(LiqualityHTLC__factory.bytecode)); 27 | // const event = create2_factory.interface.parseLog(receipt.logs[0]); 28 | 29 | // console.log('HTLC address computed: ', addr); 30 | // console.log('HTLC address from event: ', event.args.addr); 31 | // }); 32 | 33 | // task('deploy:Multicall').setAction(async function (taskArguments: TaskArguments, { ethers }) { 34 | // const funder = (await ethers.getSigners())[0]; 35 | // const privateKey = ''; 36 | // const wallet = new ethers.Wallet(privateKey, funder.provider); 37 | // console.log('Wallet address: ', wallet.address); 38 | // console.log('Wallet balance: ', await wallet.getBalance()); 39 | // const multicallFactory = new Multicall__factory(wallet); 40 | // const create2DeployTx = await multicallFactory.deploy({ gasLimit: 800000 }); 41 | // console.log('Multicall address', create2DeployTx.address); 42 | // }); 43 | -------------------------------------------------------------------------------- /packages/evm-contracts/tasks/utils/index.ts: -------------------------------------------------------------------------------- 1 | export const create2_deployer_bytecode = 2 | '608060405234801561001057600080fd5b506101b3806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80639c4ae2d014610030575b600080fd5b6100f36004803603604081101561004657600080fd5b810190808035906020019064010000000081111561006357600080fd5b82018360208201111561007557600080fd5b8035906020019184600183028401116401000000008311171561009757600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290803590602001909291905050506100f5565b005b6000818351602085016000f59050803b61010e57600080fd5b7fb03c53b28e78a88e31607a27e1fa48234dce28d5d9d9ec7b295aeb02e674a1e18183604051808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020018281526020019250505060405180910390a150505056fea265627a7a7231582097b1a3a312a5c93dd6d4ccd5262025b8d4f69a71177c7eb02a32f5e22fffd8e264736f6c63430005110032'; 3 | 4 | export const create2_deployer_abi = [ 5 | { 6 | anonymous: false, 7 | inputs: [ 8 | { indexed: false, internalType: 'address', name: 'addr', type: 'address' }, 9 | { indexed: false, internalType: 'uint256', name: 'salt', type: 'uint256' }, 10 | ], 11 | name: 'Deployed', 12 | type: 'event', 13 | }, 14 | { 15 | constant: false, 16 | inputs: [ 17 | { internalType: 'bytes', name: 'code', type: 'bytes' }, 18 | { internalType: 'uint256', name: 'salt', type: 'uint256' }, 19 | ], 20 | name: 'deploy', 21 | outputs: [], 22 | payable: false, 23 | stateMutability: 'nonpayable', 24 | type: 'function', 25 | }, 26 | ]; 27 | -------------------------------------------------------------------------------- /packages/evm-contracts/test/htlc/LiqualityHTLC.ts: -------------------------------------------------------------------------------- 1 | import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; 2 | import { ethers } from 'hardhat'; 3 | import { Signers } from '../types'; 4 | import { shouldBehaveLikeLiqualityHTLCForERC20 } from './LiqualityHTLCERC20.behavior'; 5 | import { shouldBehaveLikeLiqualityHTLCForEther } from './LiqualityHTLCEther.behavior'; 6 | 7 | describe('Unit tests', function () { 8 | before(async function () { 9 | this.signers = {} as Signers; 10 | const signers: SignerWithAddress[] = await ethers.getSigners(); 11 | this.signers.deployer = signers[0]; 12 | this.signers.sender = signers[1]; 13 | this.signers.recipient = signers[2]; 14 | }); 15 | 16 | describe('Liquality Ether HTLC', function () { 17 | shouldBehaveLikeLiqualityHTLCForEther(); 18 | }); 19 | 20 | describe('Liquality ERC20 HTLC', function () { 21 | shouldBehaveLikeLiqualityHTLCForERC20(); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/evm-contracts/test/types.ts: -------------------------------------------------------------------------------- 1 | import type { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; 2 | import type { LiqualityHTLC } from '../src/types/LiqualityHTLC'; 3 | import { TestERC20 } from '../src/types/TestERC20'; 4 | 5 | declare module 'mocha' { 6 | export interface Context { 7 | htlc: LiqualityHTLC; 8 | token: TestERC20; 9 | signers: Signers; 10 | } 11 | } 12 | 13 | export interface Signers { 14 | deployer: SignerWithAddress; 15 | sender: SignerWithAddress; 16 | recipient: SignerWithAddress; 17 | } 18 | -------------------------------------------------------------------------------- /packages/evm-contracts/test/utils.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from 'hardhat'; 2 | import { Signers } from './types'; 3 | 4 | export function generateId(htlcData: any, blockTimestamp: number) { 5 | const abiCoder = ethers.utils.defaultAbiCoder; 6 | 7 | const data = abiCoder.encode( 8 | ['address', 'uint256', 'uint256', 'uint256', 'bytes32', 'address'], 9 | [ 10 | htlcData.refundAddress, 11 | blockTimestamp, 12 | htlcData.amount.toString(), 13 | htlcData.expiration, 14 | htlcData.secretHash, 15 | htlcData.recipientAddress, 16 | ] 17 | ); 18 | 19 | return ethers.utils.sha256(data); 20 | } 21 | 22 | export async function getDefaultHtlcData(signers: Signers, expiration: number, tokenAddress = ethers.constants.AddressZero) { 23 | const secretHash = ethers.utils.sha256(getDefaultSecret()); 24 | 25 | return { 26 | amount: ethers.utils.parseEther('0.1'), 27 | expiration, 28 | secretHash: secretHash, 29 | tokenAddress: tokenAddress, 30 | refundAddress: signers.sender.address, 31 | recipientAddress: signers.recipient.address, 32 | }; 33 | } 34 | 35 | export function getDefaultSecret(secret = 'secret') { 36 | return ethers.utils.sha256(Buffer.from(secret)); 37 | } 38 | -------------------------------------------------------------------------------- /packages/evm-contracts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": true, 4 | "declarationMap": true, 5 | "emitDecoratorMetadata": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "lib": ["es6"], 10 | "module": "commonjs", 11 | "moduleResolution": "node", 12 | "noImplicitAny": true, 13 | "removeComments": true, 14 | "resolveJsonModule": true, 15 | "sourceMap": true, 16 | "strict": true, 17 | "target": "es6" 18 | }, 19 | "exclude": ["node_modules"], 20 | "files": ["./hardhat.config.ts"], 21 | "include": ["tasks/**/*", "test/**/*", "types/**/*"] 22 | } 23 | -------------------------------------------------------------------------------- /packages/evm-ledger/lib/CreateEvmLedgerApp.ts: -------------------------------------------------------------------------------- 1 | import { CreateLedgerApp } from '@chainify/hw-ledger'; 2 | import { Network } from '@chainify/types'; 3 | import HwAppEthereum from '@ledgerhq/hw-app-eth'; 4 | import Transport from '@ledgerhq/hw-transport'; 5 | 6 | export const CreateEvmLedgerApp: CreateLedgerApp = (transport: Transport, scrambleKey: string, _network: Network) => { 7 | { 8 | return new HwAppEthereum(transport, scrambleKey); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /packages/evm-ledger/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './CreateEvmLedgerApp'; 2 | export { EvmLedgerProvider } from './EvmLedgerProvider'; 3 | export { EvmLedgerSigner } from './EvmLedgerSigner'; 4 | export * from './types'; 5 | -------------------------------------------------------------------------------- /packages/evm-ledger/lib/types.ts: -------------------------------------------------------------------------------- 1 | import { CreateOptions } from '@chainify/hw-ledger'; 2 | import { Address } from '@chainify/types'; 3 | import HwAppEthereum from '@ledgerhq/hw-app-eth'; 4 | 5 | export type GetAppType = () => Promise; 6 | 7 | export interface EvmLedgerCreateOptions extends CreateOptions { 8 | derivationPath?: string; 9 | addressCache?: Address; 10 | } 11 | 12 | export interface LedgerAddressType { 13 | publicKey: string; 14 | address: string; 15 | chainCode?: string; 16 | } 17 | -------------------------------------------------------------------------------- /packages/evm-ledger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainify/evm-ledger", 3 | "version": "2.4.3", 4 | "description": "", 5 | "author": "Liquality ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "./dist/lib/index.js", 9 | "types": "./dist/lib/index.d.ts", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "clean": "rm -rf node_modules .turbo dist" 16 | }, 17 | "dependencies": { 18 | "@chainify/client": "workspace:*", 19 | "@chainify/errors": "workspace:*", 20 | "@chainify/evm": "workspace:*", 21 | "@chainify/hw-ledger": "workspace:*", 22 | "@chainify/types": "workspace:*", 23 | "@chainify/utils": "workspace:*", 24 | "@ethersproject/abstract-provider": "5.7.0", 25 | "@ethersproject/abstract-signer": "5.7.0", 26 | "@ethersproject/address": "5.7.0", 27 | "@ethersproject/bignumber": "5.7.0", 28 | "@ethersproject/bytes": "5.7.0", 29 | "@ethersproject/properties": "5.7.0", 30 | "@ethersproject/providers": "5.7.0", 31 | "@ethersproject/strings": "5.7.0", 32 | "@ethersproject/transactions": "5.7.0", 33 | "@ledgerhq/hw-app-eth": "6.30.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/evm-ledger/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true, 8 | }, 9 | "include": ["lib"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/evm/.gitignore: -------------------------------------------------------------------------------- 1 | # directories 2 | artifacts 3 | build 4 | cache 5 | coverage 6 | .coverage_artifacts 7 | .coverage_cache 8 | .coverage_contracts 9 | dist 10 | node_modules 11 | 12 | # typechain 13 | lib/typechain 14 | 15 | -------------------------------------------------------------------------------- /packages/evm/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import '@nomiclabs/hardhat-ethers'; 2 | import '@nomiclabs/hardhat-waffle'; 3 | import '@typechain/hardhat'; 4 | import { HardhatUserConfig } from 'hardhat/config'; 5 | 6 | const config: HardhatUserConfig = { 7 | typechain: { 8 | outDir: 'lib/typechain', 9 | target: 'ethers-v5', 10 | }, 11 | }; 12 | 13 | export default config; 14 | -------------------------------------------------------------------------------- /packages/evm/lib/chain/EvmMulticallProvider.ts: -------------------------------------------------------------------------------- 1 | import { AddressType, Asset, AssetTypes, BigNumber } from '@chainify/types'; 2 | import { Interface } from '@ethersproject/abi'; 3 | import { BaseProvider } from '@ethersproject/providers'; 4 | import { ERC20__factory, Multicall3, Multicall3__factory } from '../typechain'; 5 | import { MulticallData } from '../types'; 6 | 7 | interface ContractCall { 8 | target: string; 9 | callData: string; 10 | } 11 | 12 | export class EvmMulticallProvider { 13 | private _multicallAddress: string; 14 | private _multicall: Multicall3; 15 | 16 | // https://github.com/mds1/multicall 17 | constructor(chainProvider: BaseProvider, multicallAddress = '0xcA11bde05977b3631167028862bE2a173976CA11') { 18 | this._multicallAddress = multicallAddress; 19 | this._multicall = Multicall3__factory.connect(this._multicallAddress, chainProvider); 20 | } 21 | 22 | public getMulticallAddress(): string { 23 | return this._multicallAddress; 24 | } 25 | 26 | public buildBalanceCallData(asset: Asset, user: AddressType) { 27 | if (asset.type === AssetTypes.native) { 28 | return { 29 | target: this._multicallAddress, 30 | abi: Multicall3__factory.abi, 31 | name: 'getEthBalance', 32 | params: [user], 33 | }; 34 | } else { 35 | return { 36 | target: asset.contractAddress, 37 | abi: ERC20__factory.abi, 38 | name: 'balanceOf', 39 | params: [user], 40 | }; 41 | } 42 | } 43 | 44 | public setMulticallAddress(multicallAddress: string) { 45 | this._multicall = Multicall3__factory.connect(multicallAddress, this._multicall.provider); 46 | this._multicallAddress = multicallAddress; 47 | } 48 | 49 | public async getEthBalance(address: string): Promise { 50 | return new BigNumber((await this._multicall.getEthBalance(address)).toString()); 51 | } 52 | 53 | public async getMultipleBalances(address: AddressType, assets: Asset[]): Promise { 54 | const user = address.toString(); 55 | const result = await this.multicall(assets.map((asset: Asset) => this.buildBalanceCallData(asset, user))); 56 | return result.map((r) => (r ? new BigNumber(r.toString()) : null)); 57 | } 58 | 59 | public async multicall(calls: ReadonlyArray): Promise { 60 | const aggregatedCallData: ContractCall[] = calls.map((call: MulticallData) => { 61 | const callData = new Interface(call.abi).encodeFunctionData(call.name, call.params); 62 | return { target: call.target, callData }; 63 | }); 64 | 65 | const result = await this._multicall.callStatic.tryAggregate(false, aggregatedCallData); 66 | 67 | return calls.map((call: MulticallData, index: number) => { 68 | const [success, returnData] = result[index]; 69 | 70 | if (success) { 71 | const decodedResult = new Interface(call.abi).decodeFunctionResult(call.name, returnData); 72 | return decodedResult.length === 1 ? decodedResult[0] : decodedResult; 73 | } 74 | 75 | return null; 76 | }) as T; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /packages/evm/lib/chain/OptimismChainProvider.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, FeeDetails, Network } from '@chainify/types'; 2 | import { L2Provider } from '@eth-optimism/sdk'; 3 | import { StaticJsonRpcProvider } from '@ethersproject/providers'; 4 | import { FeeOptions } from '../types'; 5 | import { calculateFee } from '../utils'; 6 | import { EvmChainProvider } from './EvmChainProvider'; 7 | 8 | export class OptimismChainProvider extends EvmChainProvider { 9 | private feeOptions: FeeOptions; 10 | protected provider: L2Provider; 11 | 12 | constructor(network: Network, provider: L2Provider, feeOptions?: FeeOptions, multicall = true) { 13 | super(network, provider, null, multicall); 14 | 15 | this.feeOptions = { 16 | slowMultiplier: feeOptions?.slowMultiplier || 1, 17 | averageMultiplier: feeOptions?.averageMultiplier || 1.5, 18 | fastMultiplier: feeOptions?.fastMultiplier || 2, 19 | }; 20 | } 21 | 22 | getProvider(): L2Provider { 23 | return this.provider; 24 | } 25 | 26 | setProvider(provider: L2Provider): void { 27 | this.provider = provider; 28 | } 29 | 30 | public async getFees(): Promise { 31 | const l2FeeData = await this.provider.getFeeData(); 32 | const l2BaseGasPrice = new BigNumber(l2FeeData.gasPrice?.toString()).div(1e9).toNumber(); 33 | const l2Fee = this.calculateFee(l2BaseGasPrice); 34 | 35 | const l1FeeData = await this.provider.getL1GasPrice(); 36 | const l1BaseGasPrice = new BigNumber(l1FeeData.toString()).div(1e9).toNumber(); 37 | const l1Fee = this.calculateFee(l1BaseGasPrice); 38 | 39 | return { 40 | slow: { fee: l2Fee.slow, multilayerFee: { l1: l1Fee.slow, l2: l2Fee.slow } }, 41 | average: { fee: l2Fee.average, multilayerFee: { l1: l1Fee.average, l2: l2Fee.average } }, 42 | fast: { fee: l2Fee.fast, multilayerFee: { l1: l1Fee.fast, l2: l2Fee.fast } }, 43 | }; 44 | } 45 | 46 | private calculateFee(fee: number) { 47 | return { 48 | slow: calculateFee(fee, this.feeOptions.slowMultiplier), 49 | average: calculateFee(fee, this.feeOptions.averageMultiplier), 50 | fast: calculateFee(fee, this.feeOptions.fastMultiplier), 51 | }; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/evm/lib/fee/EIP1559FeeApiProvider/ethereum.ts: -------------------------------------------------------------------------------- 1 | import { NodeError } from '@chainify/errors'; 2 | import { FeeDetails } from '@chainify/types'; 3 | import { Math } from '@chainify/utils'; 4 | 5 | export class EthereumFeeParser { 6 | public parse(response: EthereumResponse): FeeDetails { 7 | if (response.code === 200) { 8 | const suggestedBaseFeePerGas = Number(response.raw.etherscan.suggestBaseFee); 9 | 10 | return { 11 | slow: { 12 | fee: { 13 | maxFeePerGas: Number(response.raw.etherscan.SafeGasPrice), 14 | maxPriorityFeePerGas: Math.sub(response.raw.etherscan.SafeGasPrice, suggestedBaseFeePerGas).toNumber(), 15 | suggestedBaseFeePerGas, 16 | }, 17 | }, 18 | average: { 19 | fee: { 20 | maxFeePerGas: Number(response.raw.etherscan.ProposeGasPrice), 21 | maxPriorityFeePerGas: Math.sub(response.raw.etherscan.ProposeGasPrice, suggestedBaseFeePerGas).toNumber(), 22 | suggestedBaseFeePerGas, 23 | }, 24 | }, 25 | fast: { 26 | fee: { 27 | maxFeePerGas: Number(response.raw.etherscan.FastGasPrice), 28 | maxPriorityFeePerGas: Math.sub(response.raw.etherscan.FastGasPrice, suggestedBaseFeePerGas).toNumber(), 29 | suggestedBaseFeePerGas, 30 | }, 31 | }, 32 | }; 33 | } else { 34 | throw new NodeError('Could not fetch Ethereum fee data', response); 35 | } 36 | } 37 | } 38 | 39 | export interface EthereumResponse { 40 | code: number; 41 | data: Data; 42 | raw: Raw; 43 | } 44 | 45 | interface Data { 46 | timestamp: number; 47 | fast: number; 48 | standard: number; 49 | slow: number; 50 | } 51 | 52 | interface Raw { 53 | etherscan: Etherscan; 54 | } 55 | 56 | interface Etherscan { 57 | LastBlock: string; 58 | SafeGasPrice: string; 59 | ProposeGasPrice: string; 60 | FastGasPrice: string; 61 | suggestBaseFee: string; 62 | gasUsedRatio: string; 63 | } 64 | -------------------------------------------------------------------------------- /packages/evm/lib/fee/EIP1559FeeApiProvider/index.ts: -------------------------------------------------------------------------------- 1 | import { Fee, HttpClient } from '@chainify/client'; 2 | import { UnsupportedMethodError } from '@chainify/errors'; 3 | import { FeeDetails } from '@chainify/types'; 4 | import { EvmNetworks } from '../../networks'; 5 | import { EthereumFeeParser, EthereumResponse } from './ethereum'; 6 | import { PolygonFeeParser, PolygonResponse } from './polygon'; 7 | 8 | export class EIP1559FeeApiProvider extends Fee { 9 | private _httpClient: HttpClient; 10 | private _chainId: number; 11 | 12 | constructor(url: string, chainId: number) { 13 | super(); 14 | this._httpClient = new HttpClient({ baseURL: url }); 15 | this._chainId = chainId; 16 | } 17 | 18 | public async getFees(): Promise { 19 | const result = await this._httpClient.nodeGet('/'); 20 | const fee = this.parseFeeResponse(result); 21 | return fee; 22 | } 23 | 24 | private parseFeeResponse(response: Response): FeeDetails { 25 | switch (this._chainId) { 26 | case EvmNetworks.ethereum_mainnet.chainId: { 27 | return new EthereumFeeParser().parse(response as EthereumResponse); 28 | } 29 | 30 | case EvmNetworks.polygon_mainnet.chainId: { 31 | return new PolygonFeeParser().parse(response as PolygonResponse); 32 | } 33 | 34 | default: { 35 | throw new UnsupportedMethodError(`EIP1559 not supported for ${this._chainId}`); 36 | } 37 | } 38 | } 39 | } 40 | 41 | type Response = EthereumResponse | PolygonResponse; 42 | -------------------------------------------------------------------------------- /packages/evm/lib/fee/EIP1559FeeApiProvider/polygon.ts: -------------------------------------------------------------------------------- 1 | import { NodeError } from '@chainify/errors'; 2 | import { FeeDetails } from '@chainify/types'; 3 | import { Math } from '@chainify/utils'; 4 | 5 | export class PolygonFeeParser { 6 | public parse(response: PolygonResponse): FeeDetails { 7 | if (response.status === '1' && response.message === 'OK') { 8 | const suggestedBaseFeePerGas = Number(response.result.suggestBaseFee); 9 | 10 | return { 11 | slow: { 12 | fee: { 13 | maxFeePerGas: Number(response.result.SafeGasPrice), 14 | maxPriorityFeePerGas: Math.sub(response.result.SafeGasPrice, suggestedBaseFeePerGas).toNumber(), 15 | suggestedBaseFeePerGas, 16 | }, 17 | }, 18 | average: { 19 | fee: { 20 | maxFeePerGas: Number(response.result.ProposeGasPrice), 21 | maxPriorityFeePerGas: Math.sub(response.result.ProposeGasPrice, suggestedBaseFeePerGas).toNumber(), 22 | suggestedBaseFeePerGas, 23 | }, 24 | }, 25 | fast: { 26 | fee: { 27 | maxFeePerGas: Number(response.result.FastGasPrice), 28 | maxPriorityFeePerGas: Math.sub(response.result.FastGasPrice, suggestedBaseFeePerGas).toNumber(), 29 | suggestedBaseFeePerGas, 30 | }, 31 | }, 32 | }; 33 | } else { 34 | throw new NodeError('Could not fetch Polygon fee data', response); 35 | } 36 | } 37 | } 38 | 39 | interface Result { 40 | LastBlock: string; 41 | SafeGasPrice: string; 42 | ProposeGasPrice: string; 43 | FastGasPrice: string; 44 | suggestBaseFee: string; 45 | gasUsedRatio: string; 46 | UsdPrice: string; 47 | } 48 | 49 | export interface PolygonResponse { 50 | status: string; 51 | message: string; 52 | result: Result; 53 | } 54 | -------------------------------------------------------------------------------- /packages/evm/lib/fee/EIP1559FeeProvider.ts: -------------------------------------------------------------------------------- 1 | import { Fee } from '@chainify/client'; 2 | import { BigNumber, FeeDetails } from '@chainify/types'; 3 | import { Network, StaticJsonRpcProvider } from '@ethersproject/providers'; 4 | import { suggestFees } from '@liquality/fee-suggestions'; 5 | import { toGwei } from '../utils'; 6 | 7 | export class EIP1559FeeProvider extends Fee { 8 | provider: StaticJsonRpcProvider; 9 | 10 | constructor(provider: StaticJsonRpcProvider | string, network?: Network) { 11 | super(); 12 | 13 | if (typeof provider === 'string') { 14 | this.provider = new StaticJsonRpcProvider(provider, network?.chainId); 15 | } else { 16 | this.provider = provider; 17 | } 18 | } 19 | 20 | getBaseFeeMultiplier(baseFeeTrend: number) { 21 | switch (baseFeeTrend) { 22 | case 2: 23 | return 1.6; 24 | case 1: 25 | return 1.4; 26 | case 0: 27 | return 1.2; 28 | default: 29 | return 1.1; 30 | } 31 | } 32 | 33 | calculateMaxFeePerGas(maxPriorityFeePerGas: BigNumber, potentialMaxFee: BigNumber) { 34 | return maxPriorityFeePerGas.gt(potentialMaxFee) ? potentialMaxFee.plus(maxPriorityFeePerGas) : potentialMaxFee; 35 | } 36 | 37 | async getFees(): Promise { 38 | const { maxPriorityFeeSuggestions, baseFeeSuggestion, currentBaseFee, baseFeeTrend, confirmationTimeByPriorityFee } = 39 | await suggestFees(this.provider); 40 | 41 | const bigCurrentBaseFee = toGwei(currentBaseFee); 42 | const bigBaseFeeSuggestion = toGwei(baseFeeSuggestion); 43 | const slowMaxPriorityFeePerGas = toGwei(confirmationTimeByPriorityFee[45]); 44 | const averageMaxPriorityFeePerGas = toGwei(confirmationTimeByPriorityFee[30]); 45 | const fastMaxPriorityFeePerGas = toGwei(baseFeeTrend === 2 ? maxPriorityFeeSuggestions.urgent : confirmationTimeByPriorityFee[15]); 46 | 47 | const multiplier = this.getBaseFeeMultiplier(baseFeeTrend); 48 | const potentialMaxFee = bigBaseFeeSuggestion.times(multiplier); 49 | 50 | const extra = { 51 | currentBaseFeePerGas: bigCurrentBaseFee.toNumber(), 52 | suggestedBaseFeePerGas: bigBaseFeeSuggestion.toNumber(), 53 | baseFeeTrend, 54 | }; 55 | 56 | const fees = { 57 | slow: { 58 | fee: { 59 | ...extra, 60 | maxFeePerGas: this.calculateMaxFeePerGas(slowMaxPriorityFeePerGas, potentialMaxFee).toNumber(), 61 | maxPriorityFeePerGas: slowMaxPriorityFeePerGas.toNumber(), 62 | }, 63 | }, 64 | average: { 65 | fee: { 66 | ...extra, 67 | maxFeePerGas: this.calculateMaxFeePerGas(averageMaxPriorityFeePerGas, potentialMaxFee).toNumber(), 68 | maxPriorityFeePerGas: averageMaxPriorityFeePerGas.toNumber(), 69 | }, 70 | }, 71 | fast: { 72 | fee: { 73 | ...extra, 74 | maxFeePerGas: this.calculateMaxFeePerGas(fastMaxPriorityFeePerGas, potentialMaxFee).toNumber(), 75 | maxPriorityFeePerGas: fastMaxPriorityFeePerGas.toNumber(), 76 | }, 77 | }, 78 | }; 79 | 80 | return fees; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /packages/evm/lib/fee/RpcFeeProvider.ts: -------------------------------------------------------------------------------- 1 | import { Fee } from '@chainify/client'; 2 | import { BigNumber, FeeDetails } from '@chainify/types'; 3 | import { Network, StaticJsonRpcProvider } from '@ethersproject/providers'; 4 | import { FeeOptions } from '../types'; 5 | import { calculateFee } from '../utils'; 6 | 7 | export class RpcFeeProvider extends Fee { 8 | private provider: StaticJsonRpcProvider; 9 | private feeOptions: FeeOptions; 10 | 11 | constructor(provider: StaticJsonRpcProvider | string, feeOptions?: FeeOptions, network?: Network) { 12 | super(); 13 | 14 | if (typeof provider === 'string') { 15 | this.provider = new StaticJsonRpcProvider(provider, network?.chainId); 16 | } else { 17 | this.provider = provider; 18 | } 19 | 20 | this.feeOptions = { 21 | slowMultiplier: feeOptions?.slowMultiplier || 1, 22 | averageMultiplier: feeOptions?.averageMultiplier || 1.5, 23 | fastMultiplier: feeOptions?.fastMultiplier || 2, 24 | }; 25 | } 26 | 27 | async getFees(): Promise { 28 | const feeData = await this.provider.getFeeData(); 29 | const baseGasPrice = new BigNumber(feeData.gasPrice?.toString()).div(1e9).toNumber(); 30 | return { 31 | slow: { fee: calculateFee(baseGasPrice, this.feeOptions.slowMultiplier) }, 32 | average: { fee: calculateFee(baseGasPrice, this.feeOptions.averageMultiplier) }, 33 | fast: { fee: calculateFee(baseGasPrice, this.feeOptions.fastMultiplier) }, 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/evm/lib/index.ts: -------------------------------------------------------------------------------- 1 | export { EvmChainProvider } from './chain/EvmChainProvider'; 2 | export { EvmMulticallProvider } from './chain/EvmMulticallProvider'; 3 | export { OptimismChainProvider } from './chain/OptimismChainProvider'; 4 | export { EIP1559FeeApiProvider } from './fee/EIP1559FeeApiProvider'; 5 | export { EIP1559FeeProvider } from './fee/EIP1559FeeProvider'; 6 | export { RpcFeeProvider } from './fee/RpcFeeProvider'; 7 | export { EnsProvider } from './naming/EnsProvider'; 8 | export { EvmNetworks } from './networks'; 9 | export { CovalentNftProvider } from './nft/CovalentNftProvider'; 10 | export { EvmNftProvider } from './nft/EvmNftProvider'; 11 | export { InfuraNftProvider } from './nft/InfuraNftProvider'; 12 | export { OpenSeaNftProvider } from './nft/OpenSeaNftProvider'; 13 | export { EvmBaseSwapProvider } from './swap/EvmBaseSwapProvider'; 14 | export { EvmSwapProvider } from './swap/EvmSwapProvider'; 15 | export * as Typechain from './typechain'; 16 | export * as EvmTypes from './types'; 17 | export * as EvmUtils from './utils'; 18 | export { EvmBaseWalletProvider } from './wallet/EvmBaseWalletProvider'; 19 | export { EvmWalletProvider } from './wallet/EvmWalletProvider'; 20 | -------------------------------------------------------------------------------- /packages/evm/lib/naming/EnsProvider.ts: -------------------------------------------------------------------------------- 1 | import { AddressType, NamingProvider } from '@chainify/types'; 2 | import { StaticJsonRpcProvider } from '@ethersproject/providers'; 3 | 4 | export class EnsProvider implements NamingProvider { 5 | private _provider: StaticJsonRpcProvider; 6 | 7 | constructor(provider: StaticJsonRpcProvider) { 8 | this._provider = provider; 9 | } 10 | 11 | /** 12 | * @param address - resolve name to address 13 | * @returns - address 14 | */ 15 | public async resolveName(name: string): Promise { 16 | return this._provider.resolveName(name); 17 | } 18 | 19 | /** 20 | * @param address - look up address 21 | * @returns - ens 22 | */ 23 | public async lookupAddress(address: AddressType): Promise { 24 | return this._provider.lookupAddress(address.toString()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/evm/lib/nft/CovalentNftProvider.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@chainify/client'; 2 | import { AssetTypes, NFTAsset } from '@chainify/types'; 3 | import { BaseProvider } from '@ethersproject/providers'; 4 | import { NftProviderConfig, NftTypes } from '../types'; 5 | import { EvmBaseWalletProvider } from '../wallet/EvmBaseWalletProvider'; 6 | import { EvmNftProvider } from './EvmNftProvider'; 7 | 8 | export class CovalentNftProvider extends EvmNftProvider { 9 | private readonly _config: NftProviderConfig; 10 | private readonly _httpClient: HttpClient; 11 | 12 | constructor(walletProvider: EvmBaseWalletProvider, config: NftProviderConfig) { 13 | super(walletProvider); 14 | this._config = config; 15 | this._httpClient = new HttpClient({ 16 | baseURL: config.url, 17 | responseType: 'text', 18 | transformResponse: undefined, 19 | }); 20 | } 21 | 22 | async fetch(): Promise { 23 | const [userAddress, network] = await Promise.all([this.walletProvider.getAddress(), this.walletProvider.getConnectedNetwork()]); 24 | const response = await this._httpClient.nodeGet( 25 | `/${network.chainId}/address/${userAddress}/balances_v2/?format=JSON&nft=true&no-nft-fetch=false&key=${this._config.apiKey}` 26 | ); 27 | 28 | return response.data.items.reduce((result, asset) => { 29 | if (asset.type === AssetTypes.nft) { 30 | const { contract_name, contract_ticker_symbol, contract_address, supports_erc, nft_data } = asset; 31 | const schema_type = supports_erc?.pop()?.toUpperCase(); 32 | 33 | if (schema_type in NftTypes && contract_address) { 34 | let nftAsset: NFTAsset = { 35 | token_id: null, 36 | asset_contract: { 37 | address: contract_address, 38 | name: contract_name, 39 | symbol: contract_ticker_symbol, 40 | }, 41 | collection: { 42 | name: contract_name, 43 | }, 44 | }; 45 | 46 | this.cache[contract_address] = { 47 | contract: this.schemas[schema_type].attach(contract_address), 48 | schema: schema_type, 49 | }; 50 | 51 | if (nft_data.length) { 52 | nft_data.forEach((data) => { 53 | const { external_data } = data; 54 | 55 | nftAsset = { 56 | ...nftAsset, 57 | token_id: data?.token_id, 58 | name: external_data?.name, 59 | description: external_data?.description, 60 | external_link: external_data?.external_url, 61 | image_original_url: external_data?.image, 62 | image_preview_url: external_data?.image, 63 | image_thumbnail_url: external_data?.image, 64 | }; 65 | 66 | result.push(nftAsset); 67 | }); 68 | } 69 | } 70 | } 71 | return result; 72 | }, []); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /packages/evm/lib/nft/InfuraNftProvider.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@chainify/client'; 2 | import { NFTAsset } from '@chainify/types'; 3 | import { BaseProvider } from '@ethersproject/providers'; 4 | import { NftProviderConfig, NftTypes } from '../types'; 5 | import { EvmBaseWalletProvider } from '../wallet/EvmBaseWalletProvider'; 6 | import { EvmNftProvider } from './EvmNftProvider'; 7 | 8 | export class InfuraNftProvider extends EvmNftProvider { 9 | private readonly _config: NftProviderConfig; 10 | private readonly _httpClient: HttpClient; 11 | 12 | constructor(walletProvider: EvmBaseWalletProvider, config: NftProviderConfig) { 13 | super(walletProvider); 14 | this._config = config; 15 | 16 | this._httpClient = new HttpClient({ 17 | baseURL: config.url, 18 | transformResponse: undefined, 19 | headers: { Authorization: `Basic ${this._config.apiKey}` }, 20 | }); 21 | } 22 | 23 | async fetch(): Promise { 24 | const [userAddress, network] = await Promise.all([this.walletProvider.getAddress(), this.walletProvider.getConnectedNetwork()]); 25 | const chainId = Number(network.chainId); 26 | const response = await this._httpClient.nodeGet(`/networks/${chainId}/accounts/${userAddress.toString()}/assets/nfts`); 27 | 28 | return ( 29 | response?.assets?.reduce((result: NFTAsset[], nft: { contract: any; tokenId: any; supply: any; type: any; metadata: any }) => { 30 | const { contract, tokenId, supply, type, metadata } = nft; 31 | if (type in NftTypes && contract) { 32 | this.cache[contract] = { 33 | contract: this.schemas[type].attach(contract), 34 | schema: type as NftTypes, 35 | }; 36 | const _image = metadata?.image?.replace('ipfs://', 'https://ipfs.io/ipfs/') || ''; 37 | const description = 38 | metadata?.attributes?.find((i: any) => i.trait_type === 'Description')?.value || metadata?.description; 39 | const nftAsset: NFTAsset = { 40 | asset_contract: { 41 | address: contract, 42 | name: metadata?.name, 43 | symbol: '', 44 | }, 45 | collection: { 46 | name: metadata?.name, 47 | }, 48 | token_id: tokenId, 49 | amount: supply, 50 | standard: type, 51 | name: metadata?.name, 52 | description, 53 | image_original_url: _image, 54 | image_preview_url: _image, 55 | image_thumbnail_url: _image, 56 | external_link: '', 57 | }; 58 | 59 | result.push(nftAsset); 60 | } 61 | 62 | return result; 63 | }, []) || [] 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/evm/lib/nft/OpenSeaNftProvider.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient } from '@chainify/client'; 2 | import { NFTAsset } from '@chainify/types'; 3 | import { BaseProvider } from '@ethersproject/providers'; 4 | import { NftProviderConfig, NftTypes } from '../types'; 5 | import { EvmBaseWalletProvider } from '../wallet/EvmBaseWalletProvider'; 6 | import { EvmNftProvider } from './EvmNftProvider'; 7 | 8 | export class OpenSeaNftProvider extends EvmNftProvider { 9 | private readonly _httpClient: HttpClient; 10 | 11 | constructor(walletProvider: EvmBaseWalletProvider, config: NftProviderConfig) { 12 | super(walletProvider); 13 | this._httpClient = new HttpClient({ 14 | baseURL: config.url, 15 | responseType: 'text', 16 | transformResponse: undefined, 17 | ...(config.apiKey && { 18 | headers: { 19 | 'X-Api-Key': config.apiKey, 20 | }, 21 | }), 22 | }); 23 | } 24 | 25 | async fetch(): Promise { 26 | const userAddress = await this.walletProvider.getAddress(); 27 | const nfts = await this._httpClient.nodeGet(`assets?owner=${userAddress}`); 28 | 29 | return nfts.assets.reduce((result, nft) => { 30 | if (nft.asset_contract) { 31 | const { schema_name, address } = nft.asset_contract; 32 | 33 | if (schema_name in NftTypes && address) { 34 | this.cache[address] = { 35 | contract: this.schemas[schema_name].attach(address), 36 | schema: schema_name as NftTypes, 37 | }; 38 | 39 | const { 40 | image_preview_url, 41 | image_thumbnail_url, 42 | image_original_url, 43 | asset_contract, 44 | collection, 45 | token_id, 46 | id, 47 | external_link, 48 | description, 49 | } = nft; 50 | 51 | const { image_url, name, symbol } = asset_contract; 52 | 53 | result.push({ 54 | asset_contract: { 55 | address, 56 | external_link, 57 | image_url, 58 | name, 59 | symbol, 60 | }, 61 | collection: { 62 | name: collection.name, 63 | }, 64 | description, 65 | external_link, 66 | id, 67 | image_original_url, 68 | image_preview_url, 69 | image_thumbnail_url, 70 | name, 71 | token_id, 72 | }); 73 | } 74 | } 75 | 76 | return result; 77 | }, []); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /packages/evm/lib/types.ts: -------------------------------------------------------------------------------- 1 | import { AddressType, FeeType, TransactionRequest } from '@chainify/types'; 2 | import { Fragment, JsonFragment } from '@ethersproject/abi'; 3 | import { BlockWithTransactions as EthersBlockWithTransactions } from '@ethersproject/abstract-provider'; 4 | import { PopulatedTransaction as EthersPopulatedTransaction } from '@ethersproject/contracts'; 5 | import { Block as EthersBlock, TransactionResponse as EthersTransactionResponse } from '@ethersproject/providers'; 6 | import { MessageTypes, SignTypedDataVersion, TypedDataV1, TypedMessage } from '@metamask/eth-sig-util'; 7 | 8 | export interface SignTypedMessageType { 9 | data: V extends 'V1' ? TypedDataV1 : TypedMessage; 10 | version: SignTypedDataVersion; 11 | from: string; 12 | } 13 | 14 | export interface EvmSwapOptions { 15 | contractAddress: string; 16 | numberOfBlocksPerRequest?: number; 17 | totalNumberOfBlocks?: number; 18 | gasLimitMargin?: number; 19 | } 20 | 21 | export type FeeOptions = { 22 | slowMultiplier?: number; 23 | averageMultiplier?: number; 24 | fastMultiplier?: number; 25 | }; 26 | 27 | export type EthereumTransactionRequest = TransactionRequest & { 28 | from?: AddressType; 29 | nonce?: number; 30 | gasLimit?: number; 31 | gasPrice?: number; 32 | chainId?: number; 33 | type?: number; 34 | maxPriorityFeePerGas?: number; 35 | maxFeePerGas?: number; 36 | }; 37 | 38 | export type EthereumFeeData = FeeType & { 39 | maxFeePerGas?: null | number; 40 | maxPriorityFeePerGas?: null | number; 41 | gasPrice?: null | number; 42 | }; 43 | 44 | export { EthersTransactionResponse, EthersBlock, EthersBlockWithTransactions, EthersPopulatedTransaction }; 45 | 46 | export enum NftTypes { 47 | ERC721 = 'ERC721', 48 | ERC1155 = 'ERC1155', 49 | } 50 | 51 | export type NftProviderConfig = { 52 | url: string; 53 | apiKey: string; 54 | }; 55 | 56 | export type MoralisConfig = NftProviderConfig & { 57 | appId: string; 58 | }; 59 | 60 | export interface MulticallData { 61 | target: string; 62 | abi: ReadonlyArray; 63 | name: string; 64 | params: ReadonlyArray; 65 | } 66 | -------------------------------------------------------------------------------- /packages/evm/lib/wallet/EvmWalletProvider.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from '@chainify/client'; 2 | import { Address, AddressType, NamingProvider, Network, WalletOptions } from '@chainify/types'; 3 | import { compare, remove0x } from '@chainify/utils'; 4 | import { StaticJsonRpcProvider } from '@ethersproject/providers'; 5 | import { Wallet as EthersWallet } from '@ethersproject/wallet'; 6 | import { signTypedData, SignTypedDataVersion } from '@metamask/eth-sig-util'; 7 | import { SignTypedMessageType } from '../types'; 8 | import { EvmBaseWalletProvider } from './EvmBaseWalletProvider'; 9 | import { personalSign } from '@metamask/eth-sig-util'; 10 | export class EvmWalletProvider extends EvmBaseWalletProvider { 11 | private _wallet: EthersWallet; 12 | private _walletOptions: WalletOptions; 13 | 14 | constructor(walletOptions: WalletOptions, chainProvider?: Chain, namingProvider?: NamingProvider) { 15 | super(chainProvider, namingProvider); 16 | this._walletOptions = walletOptions; 17 | this._wallet = EthersWallet.fromMnemonic(walletOptions.mnemonic, walletOptions.derivationPath); 18 | 19 | if (chainProvider) { 20 | this._wallet = this._wallet.connect(chainProvider.getProvider()); 21 | } 22 | 23 | this.signer = this._wallet; 24 | } 25 | 26 | public async getAddress(): Promise
{ 27 | const name = this.getNamingProvider() ? await this.getNamingProvider().lookupAddress(this._wallet.address) : null; 28 | 29 | return new Address({ 30 | address: this._wallet.address, 31 | derivationPath: this._walletOptions.derivationPath, 32 | publicKey: this._wallet.publicKey, 33 | name, 34 | }); 35 | } 36 | 37 | public async signMessage(message: string, _from: AddressType): Promise { 38 | const { privateKey } = this._wallet._signingKey(); 39 | const _pk = privateKey.startsWith('0x') ? privateKey.substring(2) : privateKey; 40 | return personalSign({ privateKey: Buffer.from(_pk, 'hex',), data: message }); 41 | } 42 | 43 | 44 | public async signTypedData({ data, from, version }: SignTypedMessageType): Promise { 45 | if (!data) { 46 | throw new Error(`Undefined data - message required to sign typed data.`); 47 | } 48 | 49 | if (!from) { 50 | throw new Error(`Undefined address - from address required to sign typed data.`); 51 | } 52 | 53 | if (!compare(from, this.signer.address)) { 54 | throw new Error(`Non-matching address - from address does not match the signer`); 55 | } 56 | 57 | return signTypedData({ 58 | privateKey: Buffer.from(remove0x(this.signer.privateKey), 'hex'), 59 | data, 60 | version: version || SignTypedDataVersion.V1, 61 | }); 62 | } 63 | 64 | public async getUnusedAddress(): Promise
{ 65 | return this.getAddress(); 66 | } 67 | 68 | public async getUsedAddresses(): Promise { 69 | return this.getAddresses(); 70 | } 71 | 72 | public async getAddresses(): Promise { 73 | const address = await this.getAddress(); 74 | return [address]; 75 | } 76 | 77 | public async exportPrivateKey(): Promise { 78 | return remove0x(this._wallet.privateKey); 79 | } 80 | 81 | public async isWalletAvailable(): Promise { 82 | return Boolean(this.getAddress()); 83 | } 84 | 85 | public canUpdateFee(): boolean { 86 | return true; 87 | } 88 | 89 | protected onChainProviderUpdate(chainProvider: Chain): void { 90 | this._wallet = this._wallet.connect(chainProvider.getProvider()); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /packages/evm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainify/evm", 3 | "version": "3.0.0", 4 | "description": "", 5 | "author": "Liquality ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "./dist/lib/index.js", 9 | "types": "./dist/lib/index.d.ts", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "build": "cp -R ../../node_modules/@chainify/evm-contracts/artifacts . && hardhat --config ./hardhat.config.ts typechain && tsc", 15 | "clean": "rm -rf node_modules .turbo dist ./lib/typechain cache artifacts" 16 | }, 17 | "dependencies": { 18 | "@chainify/client": "workspace:*", 19 | "@chainify/errors": "workspace:*", 20 | "@chainify/logger": "workspace:*", 21 | "@chainify/types": "workspace:*", 22 | "@chainify/utils": "workspace:*", 23 | "@eth-optimism/sdk": "1.1.5", 24 | "@ethersproject/abi": "5.7.0", 25 | "@ethersproject/abstract-provider": "5.7.0", 26 | "@ethersproject/abstract-signer": "5.7.0", 27 | "@ethersproject/constants": "5.7.0", 28 | "@ethersproject/contracts": "5.7.0", 29 | "@ethersproject/providers": "5.7.0", 30 | "@ethersproject/solidity": "5.7.0", 31 | "@ethersproject/transactions": "5.7.0", 32 | "@ethersproject/wallet": "5.7.0", 33 | "@liquality/fee-suggestions": "2.0.2", 34 | "@metamask/eth-sig-util": "4.0.1", 35 | "ethers": "5.7.0" 36 | }, 37 | "devDependencies": { 38 | "@nomiclabs/hardhat-ethers": "^2.0.4", 39 | "@nomiclabs/hardhat-waffle": "^2.0.2", 40 | "@typechain/hardhat": "^4.0.0", 41 | "hardhat": "^2.8.3" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/evm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true, 8 | "noImplicitAny": false // we need that for lib/typechain 9 | }, 10 | "include": ["lib"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/hw-ledger/lib/WebHidTransportCreator.ts: -------------------------------------------------------------------------------- 1 | import Transport from '@ledgerhq/hw-transport'; 2 | import TransportWebHID from '@ledgerhq/hw-transport-webhid'; 3 | import { TransportCreator } from './types'; 4 | 5 | export class WebHidTransportCreator implements TransportCreator { 6 | private _transport: Transport = null; 7 | private _onDisconnectCallbacks: Array<() => void> = []; 8 | 9 | async create(onDisconnect?: () => void): Promise { 10 | if (!this._transport || !(this._transport as TransportWebHID)?.device?.opened) { 11 | this._transport = await TransportWebHID.create(); 12 | this._transport.on('disconnect', async () => { 13 | this._onDisconnectCallbacks.forEach((cb) => { 14 | cb(); 15 | }); 16 | await this._transport?.close(); 17 | this._transport = null; 18 | this._onDisconnectCallbacks = []; 19 | }); 20 | } 21 | 22 | if (onDisconnect) { 23 | this._onDisconnectCallbacks.push(onDisconnect); 24 | } 25 | 26 | return this._transport; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/hw-ledger/lib/index.ts: -------------------------------------------------------------------------------- 1 | export { LedgerProvider } from './LedgerProvider'; 2 | export * from './types'; 3 | export { WebHidTransportCreator } from './WebHidTransportCreator'; 4 | -------------------------------------------------------------------------------- /packages/hw-ledger/lib/types.ts: -------------------------------------------------------------------------------- 1 | import { Address, Network } from '@chainify/types'; 2 | import HwAppBitcoin from '@ledgerhq/hw-app-btc'; 3 | import HwAppEthereum from '@ledgerhq/hw-app-eth'; 4 | import Transport from '@ledgerhq/hw-transport'; 5 | 6 | export interface CreateOptions { 7 | transportCreator: TransportCreator; 8 | createLedgerApp: CreateLedgerApp; 9 | network: Network; 10 | scrambleKey?: string; 11 | } 12 | 13 | export type Newable = { new (...args: any[]): T }; 14 | 15 | export type GetAddressesFuncType = (start?: number, numAddresses?: number, change?: boolean) => Promise; 16 | 17 | export interface TransportCreator { 18 | create: (onDisconnect?: () => void) => Promise; 19 | } 20 | 21 | export type CreateLedgerApp = (transport: Transport, scrambleKey: string, network: Network) => HwAppBitcoin | HwAppEthereum; 22 | -------------------------------------------------------------------------------- /packages/hw-ledger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainify/hw-ledger", 3 | "version": "2.4.1", 4 | "description": "", 5 | "author": "Liquality ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "./dist/lib/index.js", 9 | "types": "./dist/lib/index.d.ts", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "clean": "rm -rf node_modules .turbo dist" 16 | }, 17 | "dependencies": { 18 | "@chainify/errors": "workspace:*", 19 | "@chainify/logger": "workspace:*", 20 | "@chainify/types": "workspace:*", 21 | "@chainify/utils": "workspace:*", 22 | "@ledgerhq/hw-transport": "6.27.10", 23 | "@ledgerhq/hw-transport-webhid": "6.27.10" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/hw-ledger/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true, 8 | "target": "ES6" 9 | }, 10 | "include": ["lib"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/logger/lib/index.ts: -------------------------------------------------------------------------------- 1 | const LogLevels: { [name: string]: number } = { debug: 1, default: 2, info: 2, warning: 3, error: 4, off: 5 }; 2 | let _logLevel = LogLevels['default']; 3 | 4 | let _globalLogger: Logger = null; 5 | 6 | export enum LogLevel { 7 | DEBUG = 'DEBUG', 8 | INFO = 'INFO', 9 | WARNING = 'WARNING', 10 | ERROR = 'ERROR', 11 | OFF = 'OFF', 12 | } 13 | 14 | export class Logger { 15 | readonly version: string; 16 | 17 | static levels = LogLevel; 18 | 19 | constructor(version: string) { 20 | this.version = version; 21 | } 22 | 23 | public debug(...args: Array): void { 24 | this._log(Logger.levels.DEBUG, args); 25 | } 26 | 27 | public info(...args: Array): void { 28 | this._log(Logger.levels.INFO, args); 29 | } 30 | 31 | public warn(...args: Array): void { 32 | this._log(Logger.levels.WARNING, args); 33 | } 34 | 35 | public error(...args: Array): void { 36 | this._log(Logger.levels.ERROR, args); 37 | } 38 | 39 | public static globalLogger(): Logger { 40 | if (!_globalLogger) { 41 | _globalLogger = new Logger('global'); 42 | } 43 | return _globalLogger; 44 | } 45 | 46 | public static setLogLevel(logLevel: LogLevel): void { 47 | const level = LogLevels[logLevel.toLowerCase()]; 48 | if (level == null) { 49 | Logger.globalLogger().warn('invalid log level - ' + logLevel); 50 | return; 51 | } 52 | _logLevel = level; 53 | } 54 | 55 | public static from(version: string): Logger { 56 | return new Logger(version); 57 | } 58 | 59 | private _log(logLevel: LogLevel, args: Array): void { 60 | const level = logLevel.toLowerCase(); 61 | if (LogLevels[level] == null) { 62 | Logger.globalLogger().debug('invalid log level name', 'logLevel', logLevel); 63 | } 64 | if (_logLevel > LogLevels[level]) { 65 | return; 66 | } 67 | // eslint-disable-next-line prefer-spread 68 | console.log.apply(console, [`${logLevel}: ${this.version}: `, ...args]); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /packages/logger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainify/logger", 3 | "version": "2.2.0", 4 | "description": "", 5 | "author": "Liquality ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "./dist/lib/index.js", 9 | "types": "./dist/lib/index.d.ts", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "clean": "rm -rf node_modules .turbo dist" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/logger/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true, 8 | "target": "ES6" 9 | }, 10 | "include": ["lib"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/near/lib/index.ts: -------------------------------------------------------------------------------- 1 | export { NearChainProvider } from './chain/NearChainProvider'; 2 | export { NearNetworks } from './networks'; 3 | export { NearSwapProvider } from './swap/NearSwapProvider'; 4 | export * as NearTypes from './types'; 5 | export * as NearUtils from './utils'; 6 | export { NearWalletProvider } from './wallet/NearWalletProvider'; 7 | -------------------------------------------------------------------------------- /packages/near/lib/networks.ts: -------------------------------------------------------------------------------- 1 | import { NearNetwork } from './types'; 2 | 3 | const near_mainnet: NearNetwork = { 4 | name: 'Near Mainnet', 5 | networkId: 'mainnet', 6 | rpcUrl: 'https://near-mainnet.infura.io/v3/37efa691ffec4c41a60aa4a69865d8f6', 7 | helperUrl: 'https://near-mainnet-api.liq-chainhub.net', 8 | coinType: '397', 9 | isTestnet: false, 10 | }; 11 | 12 | const near_testnet: NearNetwork = { 13 | name: 'Near Testnet', 14 | networkId: 'testnet', 15 | rpcUrl: 'https://rpc.testnet.near.org', 16 | helperUrl: 'https://near-testnet-api.liq-chainhub.net', 17 | coinType: '397', 18 | isTestnet: true, 19 | }; 20 | 21 | const NearNetworks = { 22 | near_mainnet, 23 | near_testnet, 24 | }; 25 | 26 | export { NearNetworks }; 27 | -------------------------------------------------------------------------------- /packages/near/lib/types.ts: -------------------------------------------------------------------------------- 1 | import { AddressType, Network, TransactionRequest, WalletOptions } from '@chainify/types'; 2 | import BN from 'bn.js'; 3 | import { Account, Connection } from 'near-api-js'; 4 | import { FinalExecutionOutcome } from 'near-api-js/lib/providers'; 5 | import { ChunkResult, Transaction } from 'near-api-js/lib/providers/provider'; 6 | import { Action } from 'near-api-js/lib/transaction'; 7 | 8 | export { Account, InMemorySigner, KeyPair, keyStores, providers, transactions } from 'near-api-js'; 9 | export { BlockResult } from 'near-api-js/lib/providers/provider'; 10 | export { parseSeedPhrase } from 'near-seed-phrase'; 11 | export { Action }; 12 | export { BN }; 13 | 14 | export interface NearWalletOptions extends WalletOptions { 15 | helperUrl: string; 16 | } 17 | export interface NearNetwork extends Network { 18 | helperUrl: string; 19 | } 20 | 21 | export interface NearScraperData { 22 | block_hash: string; 23 | block_timestamp: string; 24 | hash: string; 25 | action_index: number; 26 | signer_id: string; 27 | receiver_id: string; 28 | action_kind: string; 29 | args: Args; 30 | } 31 | 32 | export interface NearTxLog { 33 | hash: string; 34 | sender: string; 35 | receiver: string; 36 | blockHash: string; 37 | code?: string; 38 | value?: number; 39 | htlc?: { 40 | method: string; 41 | secretHash?: string; 42 | expiration?: number; 43 | recipient?: string; 44 | secret?: string; 45 | }; 46 | } 47 | 48 | export interface NearTxRequest extends TransactionRequest { 49 | actions?: Action[]; 50 | } 51 | 52 | export interface NearTxResponse extends FinalExecutionOutcome { 53 | transaction: NearTransaction; 54 | status: ExecutionStatus; 55 | } 56 | 57 | export interface NearChunk extends ChunkResult { 58 | transactions: NearTransaction[]; 59 | } 60 | 61 | export interface NearTransaction extends Transaction { 62 | signer_id: string; 63 | public_key: string; 64 | nonce: number; 65 | receiver_id: string; 66 | actions: Action[]; 67 | signature: string; 68 | hash: string; 69 | } 70 | 71 | export interface NearWallet { 72 | seedPhrase: string; 73 | secretKey: string; 74 | publicKey: string; 75 | address?: AddressType; 76 | } 77 | 78 | export class NearAccount extends Account { 79 | constructor(connection: Connection, accountId: string) { 80 | super(connection, accountId); 81 | } 82 | public async signAndSendTransaction({ receiverId, actions }: any): Promise { 83 | return super.signAndSendTransaction({ receiverId, actions }); 84 | } 85 | } 86 | 87 | interface ExecutionError { 88 | error_message: string; 89 | error_type: string; 90 | } 91 | 92 | interface ExecutionStatus { 93 | SuccessValue?: string; 94 | SuccessReceiptId?: string; 95 | Failure?: ExecutionError; 96 | } 97 | 98 | interface Args { 99 | gas: number; 100 | deposit: string; 101 | code_sha256: string; 102 | args_json: Record; 103 | args_base64: string; 104 | method_name: string; 105 | } 106 | -------------------------------------------------------------------------------- /packages/near/lib/wallet/near-seed-phrase.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'near-seed-phrase'; 2 | -------------------------------------------------------------------------------- /packages/near/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainify/near", 3 | "version": "2.3.0", 4 | "description": "", 5 | "author": "Liquality ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "./dist/lib/index.js", 9 | "types": "./dist/lib/index.d.ts", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "clean": "rm -rf node_modules .turbo dist" 16 | }, 17 | "dependencies": { 18 | "@chainify/client": "workspace:*", 19 | "@chainify/errors": "workspace:*", 20 | "@chainify/types": "workspace:*", 21 | "@chainify/utils": "workspace:*", 22 | "bn.js": "^5.2.0", 23 | "near-api-js": "^0.44.2", 24 | "near-seed-phrase": "^0.2.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/near/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true, 8 | "target": "ES6" 9 | }, 10 | "include": ["lib"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/solana/lib/index.ts: -------------------------------------------------------------------------------- 1 | export { SolanaChainProvider } from './chain/SolanaChainProvider'; 2 | export { SolanaNetworks } from './networks'; 3 | export { SolanaNftProvider } from './nft/SolanaNftProvider'; 4 | export * as SolanaTypes from './types'; 5 | export * as SolanaUtils from './utils'; 6 | export { SolanaWalletProvider } from './wallet/SolanaWalletProvider'; 7 | -------------------------------------------------------------------------------- /packages/solana/lib/networks.ts: -------------------------------------------------------------------------------- 1 | import { Network } from '@chainify/types'; 2 | 3 | const solana_mainnet: Network = { 4 | name: 'Solana Mainnet', 5 | networkId: 'mainnet', 6 | rpcUrl: 'https://api.mainnet-beta.solana.com', 7 | coinType: '501', 8 | isTestnet: false, 9 | }; 10 | 11 | const solana_testnet: Network = { 12 | name: 'Solana Testnet', 13 | networkId: 'testnet', 14 | rpcUrl: 'https://api.devnet.solana.com', 15 | coinType: '501', 16 | isTestnet: true, 17 | }; 18 | 19 | const SolanaNetworks = { 20 | solana_mainnet, 21 | solana_testnet, 22 | }; 23 | 24 | export { SolanaNetworks }; 25 | -------------------------------------------------------------------------------- /packages/solana/lib/swap/SolanaSwapProvider.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liquality/chainify/4088357a1692474fe1bbd31cb002701780a55e33/packages/solana/lib/swap/SolanaSwapProvider.ts -------------------------------------------------------------------------------- /packages/solana/lib/types.ts: -------------------------------------------------------------------------------- 1 | import { TransactionRequest } from '@chainify/types'; 2 | import { Transaction } from '@solana/web3.js'; 3 | 4 | export interface SolanaTxRequest extends TransactionRequest { 5 | transaction?: Transaction; 6 | } 7 | -------------------------------------------------------------------------------- /packages/solana/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { Block, Transaction, TxStatus } from '@chainify/types'; 2 | import { Math } from '@chainify/utils'; 3 | import { BlockResponse, ParsedInstruction, ParsedTransactionWithMeta, RpcResponseAndContext, SignatureStatus } from '@solana/web3.js'; 4 | 5 | export function parseBlockResponse(data: BlockResponse): Block { 6 | return { 7 | hash: data.blockhash, 8 | timestamp: data.blockTime, 9 | number: data.parentSlot + 1, 10 | parentHash: data.previousBlockhash, 11 | _raw: data, 12 | }; 13 | } 14 | 15 | export function parseTransactionResponse( 16 | data: ParsedTransactionWithMeta, 17 | signatures?: RpcResponseAndContext 18 | ): Transaction { 19 | const txInstructions = data.transaction.message.instructions as ParsedInstruction[]; 20 | const result: Transaction = { 21 | hash: data.transaction.signatures[0], 22 | value: 0, 23 | _raw: data, 24 | fee: data.meta?.fee, 25 | }; 26 | 27 | if (signatures) { 28 | // If the confirmations are null then the tx is rooted i.e. finalized by a supermajority of the cluster 29 | result.confirmations = signatures.value.confirmations === null ? 10 : signatures.value.confirmations; 30 | // Error if transaction failed, null if transaction succeeded. 31 | result.status = signatures.value.err === null ? TxStatus.Success : TxStatus.Failed; 32 | } 33 | 34 | for (const instruction of txInstructions) { 35 | switch (instruction.program) { 36 | case 'system': { 37 | // SOL transfers 38 | if (instruction.parsed?.type === 'transfer') { 39 | const parsedInfo = instruction.parsed.info; 40 | if (parsedInfo) { 41 | result.value = Math.add(parsedInfo.lamports || 0, result.value).toNumber(); 42 | result.from = parsedInfo.source; 43 | result.to = parsedInfo.destination; 44 | result.fee; 45 | } 46 | } 47 | break; 48 | } 49 | 50 | // SPL transfers 51 | case 'spl-token': { 52 | break; 53 | } 54 | } 55 | } 56 | 57 | return result; 58 | } 59 | -------------------------------------------------------------------------------- /packages/solana/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainify/solana", 3 | "version": "2.4.1", 4 | "description": "", 5 | "author": "Liquality ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "./dist/lib/index.js", 9 | "types": "./dist/lib/index.d.ts", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "clean": "rm -rf node_modules .turbo dist" 16 | }, 17 | "dependencies": { 18 | "@chainify/client": "workspace:*", 19 | "@chainify/errors": "workspace:*", 20 | "@chainify/logger": "workspace:*", 21 | "@chainify/types": "workspace:*", 22 | "@chainify/utils": "workspace:*", 23 | "@solana/spl-token": "0.2.0", 24 | "@solana/spl-token-registry": "0.2.4574", 25 | "@solana/web3.js": "1.50.1", 26 | "bip39": "^3.0.4", 27 | "ed25519-hd-key": "^1.2.0", 28 | "tweetnacl": "^1.0.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/solana/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true, 8 | "target": "ES6" 9 | }, 10 | "include": ["lib"] 11 | } 12 | -------------------------------------------------------------------------------- /packages/terra/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const assetCodeToDenom: Record = { 2 | LUNA: 'uluna', 3 | UST: 'uusd', 4 | }; 5 | 6 | export const denomToAssetCode: Record = Object.keys(assetCodeToDenom).reduce((result, key) => { 7 | result[assetCodeToDenom[key]] = key; 8 | return result; 9 | }, {} as Record); 10 | 11 | export const DEFAULT_GAS_ADJUSTMENT = 1.75; 12 | -------------------------------------------------------------------------------- /packages/terra/lib/index.ts: -------------------------------------------------------------------------------- 1 | export { TerraChainProvider } from './chain/TerraChainProvider'; 2 | export * as TerraConstants from './constants'; 3 | export { TerraNetworks } from './networks'; 4 | export { TerraSwapBaseProvider } from './swap/TerraSwapBaseProvider'; 5 | export { TerraSwapProvider } from './swap/TerraSwapProvider'; 6 | export * as TerraTypes from './types'; 7 | export { TerraWalletProvider } from './wallet/TerraWalletProvider'; 8 | -------------------------------------------------------------------------------- /packages/terra/lib/networks.ts: -------------------------------------------------------------------------------- 1 | import { TerraNetwork } from './types'; 2 | 3 | const terra_mainnet: TerraNetwork = { 4 | name: 'mainnet', 5 | networkId: 'mainnet', 6 | rpcUrl: 'https://lcd.terra.dev', 7 | helperUrl: 'https://fcd.terra.dev/v1', 8 | coinType: '330', 9 | isTestnet: false, 10 | chainId: 'columbus-5', 11 | codeId: 1480, 12 | }; 13 | 14 | const terra_testnet: TerraNetwork = { 15 | name: 'testnet', 16 | networkId: 'testnet', 17 | rpcUrl: 'https://bombay-lcd.terra.dev', 18 | helperUrl: 'https://bombay-fcd.terra.dev/v1', 19 | coinType: '330', 20 | isTestnet: true, 21 | chainId: 'bombay-12', 22 | codeId: 23733, 23 | }; 24 | 25 | export const TerraNetworks = { 26 | terra_mainnet, 27 | terra_testnet, 28 | }; 29 | -------------------------------------------------------------------------------- /packages/terra/lib/types/fcd.ts: -------------------------------------------------------------------------------- 1 | import { Coin } from '@terra-money/terra.js'; 2 | 3 | interface Log { 4 | msg_index: number; 5 | log: 6 | | string 7 | | { 8 | tax: string; 9 | }; 10 | events?: Event[]; 11 | } 12 | 13 | interface Event { 14 | type: string; 15 | attributes: { 16 | key: string; 17 | value: string; 18 | }[]; 19 | } 20 | 21 | interface Value { 22 | fee: { 23 | amount: Coin[]; 24 | gas: string; 25 | }; 26 | msg: Message[]; 27 | signatures: Signature[]; 28 | memo: string; 29 | timeout_height?: string; 30 | } 31 | 32 | interface Message { 33 | type: string; 34 | value: { [key: string]: any }; 35 | } 36 | 37 | interface Signature { 38 | pub_key: { 39 | type: string; 40 | value: string; 41 | }; 42 | signature: string; 43 | } 44 | 45 | interface LcdTx { 46 | type: string; 47 | value: Value; 48 | } 49 | 50 | export interface LcdTransaction { 51 | height: string; 52 | txhash: string; 53 | codespace?: string; 54 | code?: number; 55 | raw_log: string; 56 | logs: Log[]; 57 | gas_wanted: string; 58 | gas_used: string; 59 | tx: LcdTx; 60 | timestamp: string; // unix time (GMT) 61 | } 62 | -------------------------------------------------------------------------------- /packages/terra/lib/types/index.ts: -------------------------------------------------------------------------------- 1 | import { Network, TransactionRequest, WalletOptions } from '@chainify/types'; 2 | import { Msg, TxInfo } from '@terra-money/terra.js'; 3 | export * as FCD from './fcd'; 4 | 5 | export interface TerraWalletProviderOptions extends WalletOptions { 6 | gasAdjustment?: number; 7 | } 8 | export interface TerraNetwork extends Network { 9 | codeId: number; 10 | helperUrl: string; 11 | assetsUrl?: string; 12 | } 13 | 14 | export interface TerraTxRequest extends TransactionRequest { 15 | msgs?: Msg[]; 16 | memo?: string; 17 | gasLimit?: number; 18 | } 19 | 20 | export interface TerraHTLC { 21 | buyer: string; 22 | seller: string; 23 | expiration: number; 24 | value: number; 25 | secret_hash: string; 26 | code_id: number; 27 | } 28 | 29 | export interface TerraTxInfo extends TxInfo { 30 | htlc?: TerraHTLC; 31 | initMsg?: any; 32 | method?: string; 33 | } 34 | -------------------------------------------------------------------------------- /packages/terra/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { Block, Transaction, TxStatus } from '@chainify/types'; 2 | import { BlockInfo, isTxError, MsgExecuteContract, MsgInstantiateContract, MsgSend, TxInfo } from '@terra-money/terra.js'; 3 | import { get } from 'lodash'; 4 | import { DateTime } from 'luxon'; 5 | import { denomToAssetCode } from './constants'; 6 | import { TerraHTLC, TerraTxInfo } from './types'; 7 | 8 | interface ExecuteMsg { 9 | [key: string]: any; 10 | } 11 | 12 | export function parseBlockResponse(data: BlockInfo): Block { 13 | return { 14 | hash: data.block_id.hash, 15 | timestamp: parseTimestamp(data.block.header.time), 16 | number: Number(data.block.header.height), 17 | parentHash: data.block.last_commit.block_id.hash, 18 | _raw: data, 19 | }; 20 | } 21 | 22 | const parseTimestamp = (fullDate: string): number => { 23 | return DateTime.fromISO(fullDate).toSeconds(); 24 | }; 25 | 26 | export function parseTxResponse(data: TxInfo, currentBlock: number): Transaction { 27 | const result: Transaction = { 28 | hash: data.txhash, 29 | value: 0, 30 | _raw: data, 31 | confirmations: currentBlock - data.height, 32 | status: isTxError(data) ? TxStatus.Failed : TxStatus.Success, 33 | }; 34 | 35 | // handle fees 36 | const fee = data.tx?.auth_info?.fee?.amount.toAmino()[0]; 37 | if (fee) { 38 | const { amount, denom } = fee; 39 | result.fee = Number(amount); 40 | result.feeAssetCode = denomToAssetCode[denom]; 41 | } 42 | 43 | const msg = data.tx.body?.messages?.[0]; 44 | // Initiate swap 45 | if (msg instanceof MsgInstantiateContract) { 46 | const init = msg.init_msg as TerraHTLC; 47 | // TODO: is this the best way to get the contract address? 48 | result.to = get(data, 'logs[0].eventsByType.instantiate_contract.contract_address[0]', null); 49 | result.value = Number(msg.init_coins?.toAmino()?.[0].amount); 50 | result.valueAsset = denomToAssetCode[msg.init_coins?.toAmino()?.[0].denom]; 51 | 52 | // htlc init msg 53 | if (init.buyer && init.expiration && init.secret_hash && init.seller && init.value) { 54 | result._raw.htlc = { ...init, code_id: msg.code_id }; 55 | result._raw.method = 'init'; 56 | } 57 | // any other deploy tx 58 | else { 59 | result._raw.initMsg = { ...init, code_id: msg.code_id }; 60 | } 61 | } 62 | // Claim & Refund & Transfer ERC20 63 | else if (msg instanceof MsgExecuteContract) { 64 | result.from = msg.sender; 65 | result.to = msg.contract; 66 | 67 | // Claim 68 | const action = msg.execute_msg as ExecuteMsg; 69 | if (action.claim) { 70 | result.secret = action.claim.secret; 71 | result._raw.method = 'claim'; 72 | } else if (action.refund) { 73 | result._raw.method = 'refund'; 74 | } 75 | // ERC20 76 | else if (action.transfer) { 77 | result.to = action.transfer.recipient.toString(); 78 | result.value = Number(action.transfer.amount); 79 | result.valueAsset = msg.contract; 80 | } 81 | } 82 | // Send LUNA & UST 83 | else if (msg instanceof MsgSend) { 84 | const { amount, denom } = msg.amount.toAmino()[0]; 85 | result.from = msg.from_address; 86 | result.to = msg.to_address; 87 | result.value = Number(amount); 88 | result.valueAsset = denomToAssetCode[denom]; 89 | } 90 | 91 | return result; 92 | } 93 | -------------------------------------------------------------------------------- /packages/terra/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainify/terra", 3 | "version": "2.3.0", 4 | "description": "", 5 | "author": "Liquality ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "./dist/lib/index.js", 9 | "types": "./dist/lib/index.d.ts", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "clean": "rm -rf node_modules .turbo dist" 16 | }, 17 | "dependencies": { 18 | "@chainify/client": "workspace:*", 19 | "@chainify/errors": "workspace:*", 20 | "@chainify/logger": "workspace:*", 21 | "@chainify/types": "workspace:*", 22 | "@chainify/utils": "workspace:*", 23 | "@terra-money/terra.js": "3.1.6", 24 | "@types/luxon": "^2.0.9", 25 | "lodash": "4.17.21", 26 | "luxon": "^2.3.1" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/terra/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true 8 | }, 9 | "include": ["lib"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/types/lib/Address.ts: -------------------------------------------------------------------------------- 1 | export class Address { 2 | address: string; 3 | derivationPath?: string; 4 | publicKey?: string; 5 | privateKey?: string; 6 | name?: string; 7 | 8 | constructor(fields?: { address: string; derivationPath?: string; publicKey?: string; privateKey?: string; name?: string }) { 9 | if (fields) { 10 | Object.assign(this, fields); 11 | } 12 | } 13 | 14 | public toString = () => { 15 | return this.address; 16 | }; 17 | } 18 | 19 | export type AddressType = Address | string; 20 | -------------------------------------------------------------------------------- /packages/types/lib/Asset.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from '@liquality/cryptoassets'; 2 | export { ChainId } from '@liquality/cryptoassets'; 3 | 4 | export type AssetType = 'native' | 'erc20' | 'nft'; 5 | 6 | export enum AssetTypes { 7 | native = 'native', 8 | erc20 = 'erc20', 9 | nft = 'nft', 10 | } 11 | 12 | export interface Asset { 13 | name: string; 14 | code: string; 15 | chain: ChainId; 16 | type: AssetType; 17 | decimals: number; 18 | contractAddress?: string; 19 | } 20 | 21 | export interface TokenDetails { 22 | decimals: number; 23 | name: string; 24 | symbol: string; 25 | } 26 | -------------------------------------------------------------------------------- /packages/types/lib/Block.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from './Transaction'; 2 | 3 | export interface Block { 4 | // Block number 5 | number: number; 6 | // Block hash 7 | hash: string; 8 | // Block timestamp in seconds 9 | timestamp: number; 10 | // Hash of the parent block 11 | parentHash?: string; 12 | // The difficulty field 13 | difficulty?: number; 14 | // Nonce 15 | nonce?: number; 16 | // The size of this block in bytes 17 | size?: number; 18 | // List of transactions 19 | transactions?: Transaction[]; 20 | // The chain specific block data 21 | _raw: BlockType; 22 | } 23 | -------------------------------------------------------------------------------- /packages/types/lib/Chain.ts: -------------------------------------------------------------------------------- 1 | import { Asset, BigNumber } from '.'; 2 | import { AddressType } from './Address'; 3 | import { Block } from './Block'; 4 | import { Transaction } from './Transaction'; 5 | 6 | export interface ChainProvider { 7 | getBlockByHash(blockHash: string, includeTx?: boolean): Promise; 8 | 9 | getBlockByNumber(blockNumber: number, includeTx?: boolean): Promise; 10 | 11 | getBlockHeight(): Promise; 12 | 13 | getTransactionByHash(txHash: string): Promise; 14 | 15 | getBalance(addresses: AddressType[], assets: Asset[]): Promise; 16 | } 17 | -------------------------------------------------------------------------------- /packages/types/lib/Client.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liquality/chainify/4088357a1692474fe1bbd31cb002701780a55e33/packages/types/lib/Client.ts -------------------------------------------------------------------------------- /packages/types/lib/Fees.ts: -------------------------------------------------------------------------------- 1 | export interface EIP1559Fee { 2 | maxFeePerGas: number; 3 | maxPriorityFeePerGas: number; 4 | 5 | baseFeeTrend?: number; 6 | currentBaseFeePerGas?: number; 7 | suggestedBaseFeePerGas?: number; 8 | } 9 | 10 | export interface MultilayerGasPrice { 11 | l1: number; 12 | l2: number; 13 | } 14 | 15 | export type FeeType = EIP1559Fee | number; 16 | 17 | export interface FeeDetail { 18 | // Fee price 19 | fee: FeeType; 20 | // Fee price for multilayer fee network 21 | multilayerFee?: MultilayerGasPrice; 22 | // Estimated time to confirmation 23 | wait?: number; 24 | } 25 | 26 | export interface FeeDetails { 27 | slow: FeeDetail; 28 | average: FeeDetail; 29 | fast: FeeDetail; 30 | } 31 | 32 | export interface FeeProvider { 33 | getFees(): Promise; 34 | } 35 | -------------------------------------------------------------------------------- /packages/types/lib/Naming.ts: -------------------------------------------------------------------------------- 1 | import { AddressType } from './Address'; 2 | 3 | export interface NamingProvider { 4 | /** 5 | * @param address - resolve name to address 6 | * @returns - address 7 | */ 8 | resolveName(name: string): Promise; 9 | 10 | /** 11 | * @param address - look up address 12 | * @returns - ens 13 | */ 14 | lookupAddress(address: AddressType): Promise; 15 | } 16 | -------------------------------------------------------------------------------- /packages/types/lib/Network.ts: -------------------------------------------------------------------------------- 1 | export interface Network { 2 | name: string; 3 | coinType: string; 4 | isTestnet: boolean; 5 | chainId?: string | number; 6 | networkId?: string | number; 7 | rpcUrl?: string; 8 | scraperUrl?: string; 9 | explorerUrl?: string; 10 | helperUrl?: string; 11 | batchScraperUrl?: string; 12 | feeProviderUrl?: string; 13 | } 14 | -------------------------------------------------------------------------------- /packages/types/lib/Nft.ts: -------------------------------------------------------------------------------- 1 | export interface NFTAsset { 2 | token_id?: string; 3 | asset_contract?: { 4 | address?: string; 5 | name?: string; 6 | symbol?: string; 7 | image_url?: string; 8 | external_link?: string; 9 | }; 10 | collection?: { 11 | name: string; 12 | }; 13 | id?: number; 14 | description?: string; 15 | external_link?: string; 16 | image_original_url?: string; 17 | image_preview_url?: string; 18 | image_thumbnail_url?: string; 19 | name?: string; 20 | amount?: string; 21 | standard?: string; 22 | } 23 | -------------------------------------------------------------------------------- /packages/types/lib/Swap.ts: -------------------------------------------------------------------------------- 1 | import { Asset, BigNumber, FeeType } from '.'; 2 | import { AddressType } from './Address'; 3 | import { Transaction } from './Transaction'; 4 | 5 | export interface SwapParams { 6 | asset: Asset; 7 | /** 8 | * The amount of native value locked in the swap 9 | */ 10 | value: BigNumber; 11 | /** 12 | * Recepient address of the swap 13 | */ 14 | recipientAddress: AddressType; 15 | /** 16 | * Refund address of the swap 17 | */ 18 | refundAddress: AddressType; 19 | /** 20 | * Secret Hash 21 | */ 22 | secretHash: string; 23 | /** 24 | * Expiration of the swap 25 | */ 26 | expiration: number; 27 | } 28 | 29 | export interface SwapProvider { 30 | findInitiateSwapTransaction(swapParams: SwapParams, blockNumber?: number): Promise; 31 | 32 | findClaimSwapTransaction(swapParams: SwapParams, initiationTxHash: string, blockNumber?: number): Promise; 33 | 34 | findRefundSwapTransaction(swapParams: SwapParams, initiationTxHash: string, blockNumber?: number): Promise; 35 | 36 | generateSecret(message: string): Promise; 37 | 38 | getSwapSecret(claimTxHash: string, initTxHash?: string): Promise; 39 | 40 | initiateSwap(swapParams: SwapParams, fee: FeeType): Promise; 41 | 42 | verifyInitiateSwapTransaction(swapParams: SwapParams, initiationTxHash: string | Transaction): Promise; 43 | 44 | claimSwap(swapParams: SwapParams, initiationTxHash: string, secret: string, fee: FeeType): Promise; 45 | 46 | refundSwap(swapParams: SwapParams, initiationTxHash: string, fee: FeeType): Promise; 47 | } 48 | -------------------------------------------------------------------------------- /packages/types/lib/Transaction.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, FeeType } from '.'; 2 | import { AddressType } from './Address'; 3 | import { Asset } from './Asset'; 4 | 5 | export interface Transaction { 6 | // Transaction hash 7 | hash: string; 8 | // The value of the transaction 9 | value: number; 10 | // The asset send in the transaction 11 | valueAsset?: string; 12 | // transaction recipient 13 | to?: AddressType; 14 | // transaction sender 15 | from?: AddressType; 16 | // transaction status 17 | status?: TxStatus; 18 | // Hash of the block containing the transaction 19 | blockHash?: string; 20 | // The block number containing the trnasaction 21 | blockNumber?: number; 22 | // The number of confirmations of the transaction 23 | confirmations?: number; 24 | // Transaction data 25 | data?: string; 26 | // Secret of a HTLC 27 | secret?: string; 28 | // The price per unit of fee 29 | feePrice?: number; 30 | // The total fee paid for the transaction 31 | fee?: number; 32 | // The asset code used to pay the tx fee 33 | feeAssetCode?: string; 34 | // The raw transaction object 35 | _raw: TransactionType; 36 | // The transaction logs/events 37 | logs?: any; 38 | } 39 | 40 | export enum TxStatus { 41 | Pending = 'PENDING', 42 | Failed = 'FAILED', 43 | Success = 'SUCCESS', 44 | Unknown = 'UNKNOWN', 45 | } 46 | 47 | export type TransactionRequest = { 48 | asset?: Asset; 49 | feeAsset?: Asset; 50 | to?: AddressType; 51 | data?: string; 52 | value?: BigNumber; 53 | fee?: FeeType; 54 | gasLimit?: number; 55 | }; 56 | -------------------------------------------------------------------------------- /packages/types/lib/Wallet.ts: -------------------------------------------------------------------------------- 1 | import { Address, AddressType } from './Address'; 2 | import { Asset } from './Asset'; 3 | import { FeeType } from './Fees'; 4 | import { Transaction, TransactionRequest } from './Transaction'; 5 | 6 | export interface WalletOptions { 7 | mnemonic: string; 8 | derivationPath: string; 9 | } 10 | 11 | export interface WalletProvider { 12 | getAddresses(startingIndex?: number, numAddresses?: number, change?: boolean): Promise; 13 | 14 | getUsedAddresses(numAddressPerCall?: number): Promise; 15 | 16 | sendTransaction(options: TransactionRequest): Promise; 17 | 18 | sendSweepTransaction(address: AddressType, asset: Asset, fee?: FeeType): Promise; 19 | 20 | updateTransactionFee(tx: string | Transaction, newFee: FeeType): Promise; 21 | 22 | sendBatchTransaction(transactions: TransactionRequest[]): Promise; 23 | 24 | getUnusedAddress(change?: boolean, numAddressPerCall?: number): Promise
; 25 | 26 | signMessage(message: string, from: string): Promise; 27 | 28 | getConnectedNetwork(): Promise; 29 | 30 | isWalletAvailable(): Promise; 31 | 32 | canUpdateFee?: boolean | (() => boolean); 33 | 34 | exportPrivateKey?: () => Promise; 35 | } 36 | -------------------------------------------------------------------------------- /packages/types/lib/index.ts: -------------------------------------------------------------------------------- 1 | import { BigNumberish as EthersBigNumberish } from '@ethersproject/bignumber'; 2 | import BigNumber from 'bignumber.js'; 3 | 4 | export * from './Address'; 5 | export * from './Asset'; 6 | export * from './Block'; 7 | export * from './Chain'; 8 | export * from './Fees'; 9 | export * from './Naming'; 10 | export * from './Network'; 11 | export * from './Nft'; 12 | export * from './Swap'; 13 | export * from './Transaction'; 14 | export * from './Wallet'; 15 | export { BigNumber }; 16 | 17 | export type BigNumberish = string | number | EthersBigNumberish | BigNumber; 18 | 19 | export type Nullable = T | null; 20 | -------------------------------------------------------------------------------- /packages/types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainify/types", 3 | "version": "2.3.0", 4 | "description": "", 5 | "author": "Liquality ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "./dist/lib/index.js", 9 | "types": "./dist/lib/index.d.ts", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "clean": "rm -rf node_modules .turbo dist" 16 | }, 17 | "dependencies": { 18 | "@ethersproject/bignumber": "5.7.0", 19 | "@liquality/cryptoassets": "1.6.0", 20 | "bignumber.js": "^9.0.2" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/types/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true 8 | }, 9 | "include": ["lib"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/utils/lib/crypto-hashing.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'crypto-hashing'; 2 | -------------------------------------------------------------------------------- /packages/utils/lib/crypto.ts: -------------------------------------------------------------------------------- 1 | import { sha256 as ethersSha256 } from '@ethersproject/sha2'; 2 | import bech32 from 'bech32'; 3 | import base58 from 'bs58'; 4 | import cryptoHash from 'crypto-hashing'; 5 | import { ensure0x, remove0x } from './hex'; 6 | 7 | /** 8 | * Ensure message is in buffer format. 9 | * @param message - any string. 10 | * @returns Buffer. 11 | */ 12 | export function ensureBuffer(message: string | Buffer | any) { 13 | if (Buffer.isBuffer(message)) return message; 14 | 15 | switch (typeof message) { 16 | case 'string': 17 | message = isHex(message) ? Buffer.from(message, 'hex') : Buffer.from(message); 18 | break; 19 | case 'object': 20 | message = Buffer.from(JSON.stringify(message)); 21 | break; 22 | } 23 | 24 | return Buffer.isBuffer(message) ? message : false; 25 | } 26 | 27 | /** 28 | * Get hash160 of message. 29 | * @param message - message in string or Buffer. 30 | * @returns the hash160 of a string. 31 | */ 32 | export function hash160(message: string): string { 33 | return cryptoHash('hash160', ensureBuffer(message)).toString('hex'); 34 | } 35 | 36 | export function sha256(data: string) { 37 | return remove0x(ethersSha256(ensure0x(data))); 38 | } 39 | 40 | export function isHex(hex: string) { 41 | if (!hex.match(/([0-9]|[a-f])/gim)) return false; 42 | 43 | const buf = Buffer.from(hex, 'hex').toString('hex'); 44 | 45 | return buf === hex.toLowerCase(); 46 | } 47 | 48 | /** 49 | * Pad a hex string with '0' 50 | * @param hex - The hex string to pad. 51 | * @param lengthBytes - The length of the final string in bytes 52 | * @returns a padded string with length greater or equal to the given length 53 | * rounded up to the nearest even number. 54 | */ 55 | export function padHexStart(hex: string, lengthBytes?: number) { 56 | let lengthString = lengthBytes * 2 || hex.length; 57 | lengthString += lengthString % 2; 58 | 59 | return hex.padStart(lengthString, '0'); 60 | } 61 | 62 | export { base58, bech32 }; 63 | -------------------------------------------------------------------------------- /packages/utils/lib/hex.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Appends 0x if missing from hex string 3 | */ 4 | export function ensure0x(hash: string) { 5 | if (hash) { 6 | return hash.startsWith('0x') ? hash : `0x${hash}`; 7 | } 8 | } 9 | 10 | /** 11 | * Removes 0x if it exists in hex string 12 | */ 13 | export function remove0x(hash: string) { 14 | if (hash) { 15 | return hash.startsWith('0x') ? hash.slice(2) : hash; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/utils/lib/index.ts: -------------------------------------------------------------------------------- 1 | import 'setimmediate'; 2 | export * from './crypto'; 3 | export * from './hex'; 4 | export * as Math from './math'; 5 | export * from './string'; 6 | export * from './swap'; 7 | 8 | export function asyncSetImmediate() { 9 | return new Promise((resolve) => setImmediate(resolve)); 10 | } 11 | 12 | export const retry = async (method: () => Promise, startWaitTime = 500, waitBackoff = 2, retryNumber = 5) => { 13 | let waitTime = startWaitTime; 14 | for (let i = 0; i < retryNumber; i++) { 15 | try { 16 | const result = await method(); 17 | if (result) { 18 | return result; 19 | } 20 | } catch (err) { 21 | // throw error on last try 22 | if (i + 1 == retryNumber) { 23 | throw err; 24 | } 25 | } finally { 26 | await sleep(waitTime); 27 | waitTime *= waitBackoff; 28 | } 29 | } 30 | return null; 31 | }; 32 | 33 | export async function sleep(ms: number) { 34 | await new Promise((resolve) => setTimeout(resolve, ms)); 35 | } 36 | -------------------------------------------------------------------------------- /packages/utils/lib/math.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, BigNumberish } from '@chainify/types'; 2 | 3 | export function add(a: BigNumberish, b: BigNumberish) { 4 | return new BigNumber(a.toString()).plus(b.toString()); 5 | } 6 | 7 | export function sub(a: BigNumberish, b: BigNumberish) { 8 | return new BigNumber(a.toString()).minus(b.toString()); 9 | } 10 | 11 | export function mul(a: BigNumberish, b: BigNumberish) { 12 | return new BigNumber(a.toString()).multipliedBy(b.toString()); 13 | } 14 | 15 | export function div(a: BigNumberish, b: BigNumberish) { 16 | return new BigNumber(a.toString()).dividedBy(b.toString()); 17 | } 18 | 19 | export function eq(a: BigNumberish, b: BigNumberish) { 20 | return new BigNumber(a.toString()).eq(b.toString()); 21 | } 22 | 23 | export function lte(a: BigNumberish, b: BigNumberish) { 24 | return new BigNumber(a.toString()).lte(b.toString()); 25 | } 26 | 27 | export function lt(a: BigNumberish, b: BigNumberish) { 28 | return new BigNumber(a.toString()).lt(b.toString()); 29 | } 30 | 31 | export function gte(a: BigNumberish, b: BigNumberish) { 32 | return new BigNumber(a.toString()).gte(b.toString()); 33 | } 34 | -------------------------------------------------------------------------------- /packages/utils/lib/string.ts: -------------------------------------------------------------------------------- 1 | import { BigNumberish } from '@chainify/types'; 2 | 3 | export function compare(a: BigNumberish, b: BigNumberish) { 4 | return a?.toString().toLowerCase() === b?.toString().toLowerCase(); 5 | } 6 | 7 | export function toStringDeep(input: I): O { 8 | const newObj: O = {} as O; 9 | Object.keys(input).map((k) => { 10 | if ((input as any)[k]?.toString) { 11 | (newObj as any)[k] = (input as any)[k].toString(); 12 | } else { 13 | (newObj as any)[k] = (input as any)[k]; 14 | } 15 | }); 16 | return newObj; 17 | } 18 | -------------------------------------------------------------------------------- /packages/utils/lib/swap.ts: -------------------------------------------------------------------------------- 1 | import { InvalidExpirationError, InvalidSecretError, InvalidValueError } from '@chainify/errors'; 2 | import { BigNumberish } from '@chainify/types'; 3 | import { sha256 } from './crypto'; 4 | import { remove0x } from './hex'; 5 | import { lte } from './math'; 6 | 7 | export function validateExpiration(expiration: number) { 8 | if (isNaN(expiration)) { 9 | throw new InvalidExpirationError(`Invalid expiration. NaN: ${expiration}`); 10 | } 11 | 12 | if (expiration < 500000000 || expiration > 5000000000000) { 13 | throw new InvalidExpirationError(`Invalid expiration. Out of bounds: ${expiration}`); 14 | } 15 | } 16 | 17 | export function validateSecret(secret: string) { 18 | if (typeof secret !== 'string') { 19 | throw new InvalidSecretError(`Invalid secret type`); 20 | } 21 | 22 | const _secret = remove0x(secret); 23 | 24 | if (Buffer.from(_secret, 'hex').toString('hex') !== _secret) { 25 | throw new InvalidSecretError(`Invalid secret. Not Hex.`); 26 | } 27 | 28 | const secretBuff = Buffer.from(_secret, 'hex'); 29 | if (secretBuff.length !== 32) { 30 | throw new InvalidSecretError(`Invalid secret size`); 31 | } 32 | } 33 | 34 | export function validateSecretHash(secretHash: string) { 35 | if (typeof secretHash !== 'string') { 36 | throw new InvalidSecretError(`Invalid secret hash type`); 37 | } 38 | 39 | const _secretHash = remove0x(secretHash); 40 | 41 | if (remove0x(Buffer.from(_secretHash, 'hex').toString('hex')) !== remove0x(_secretHash)) { 42 | throw new InvalidSecretError(`Invalid secret hash. Not Hex.`); 43 | } 44 | 45 | if (Buffer.byteLength(_secretHash, 'hex') !== 32) { 46 | throw new InvalidSecretError(`Invalid secret hash: ${_secretHash}`); 47 | } 48 | 49 | // sha256('0000000000000000000000000000000000000000000000000000000000000000') 50 | if ('66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925' === _secretHash) { 51 | throw new InvalidSecretError(`Invalid secret hash: ${_secretHash}. Secret 0 detected.`); 52 | } 53 | } 54 | 55 | export function validateSecretAndHash(secret: string, secretHash: string) { 56 | validateSecret(secret); 57 | validateSecretHash(secretHash); 58 | 59 | const computedSecretHash = Buffer.from(sha256(secret), 'hex'); 60 | if (!computedSecretHash.equals(Buffer.from(remove0x(secretHash), 'hex'))) { 61 | throw new InvalidSecretError(`Invalid secret: Does not match expected secret hash: ${secretHash}`); 62 | } 63 | } 64 | 65 | export function validateValue(value: BigNumberish) { 66 | if (lte(value, 0)) { 67 | throw new InvalidValueError(`Invalid value: ${value}`); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@chainify/utils", 3 | "version": "2.3.0", 4 | "description": "", 5 | "author": "Liquality ", 6 | "homepage": "", 7 | "license": "ISC", 8 | "main": "./dist/lib/index.js", 9 | "types": "./dist/lib/index.d.ts", 10 | "files": [ 11 | "dist/**" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "clean": "rm -rf node_modules .turbo dist" 16 | }, 17 | "dependencies": { 18 | "@chainify/errors": "workspace:*", 19 | "@chainify/types": "workspace:*", 20 | "@ethersproject/sha2": "5.7.0", 21 | "bech32": "^2.0.0", 22 | "bs58": "5.0.0", 23 | "crypto-hashing": "^1.0.0", 24 | "setimmediate": "^1.0.5" 25 | }, 26 | "devDependencies": { 27 | "@types/setimmediate": "^1.0.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true 8 | }, 9 | "include": ["lib"] 10 | } 11 | -------------------------------------------------------------------------------- /scripts/generate-package-table.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import fs from 'fs'; 3 | import { markdownTable } from 'markdown-table'; 4 | import path from 'path'; 5 | 6 | const imageShield = (pkg) => `https://img.shields.io/npm/v/${pkg}?style=for-the-badge`; 7 | const npmLink = (pkg) => `https://npmjs.com/package/${pkg}`; 8 | const getTableRow = (pkg) => [`[${pkg.name}](./packages/${pkg.folder})`, `[![Chainify](${imageShield(pkg.name)})](${npmLink(pkg.name)})`]; 9 | 10 | const projectFolder = path.resolve(process.cwd()); 11 | const packagesFolder = `${projectFolder}/packages`; 12 | 13 | const allPackages = fs.readdirSync(packagesFolder); 14 | 15 | const packages = []; 16 | for (const pkg of allPackages) { 17 | const packageJson = fs.readFileSync(`${packagesFolder}/${pkg}/package.json`); 18 | const parsed = JSON.parse(packageJson); 19 | if (parsed?.name) { 20 | packages.push({ name: parsed.name, folder: pkg }); 21 | } 22 | } 23 | 24 | const allRows = []; 25 | for (const pkg of packages) { 26 | const row = getTableRow(pkg); 27 | allRows.push(row); 28 | } 29 | 30 | const table = markdownTable([['Package', 'Version'], ...allRows], { align: ['l', 'c', 'r'] }); 31 | 32 | console.log(table); 33 | -------------------------------------------------------------------------------- /test/integration/chain/chain.test.ts: -------------------------------------------------------------------------------- 1 | import { UnsupportedMethodError } from '@chainify/errors'; 2 | import { Math, sleep } from '@chainify/utils'; 3 | import { expect } from 'chai'; 4 | import { Chain } from '../types'; 5 | 6 | export function shouldBehaveLikeChainProvider(chain: Chain) { 7 | const { client, config } = chain; 8 | 9 | describe(`${client.chain.getNetwork().name} Chain Provider`, function () { 10 | it('should return network', async () => { 11 | const network = client.chain.getNetwork(); 12 | 13 | expect(network.name).to.be.not.undefined; 14 | expect(network.coinType).to.be.not.undefined; 15 | expect(network.isTestnet).to.be.not.undefined; 16 | }); 17 | 18 | it('should fetch fees', async () => { 19 | const fee = await client.chain.getFees(); 20 | expect(fee.slow.fee).to.not.be.undefined; 21 | expect(fee.average.fee).to.not.be.undefined; 22 | expect(fee.fast.fee).to.not.be.undefined; 23 | }); 24 | 25 | it('should fetch block data', async () => { 26 | const blockHeight = await client.chain.getBlockHeight(); 27 | expect(blockHeight).to.be.gte(0); 28 | 29 | // let the chain indexer to fetch the data 30 | await sleep(1000); 31 | 32 | const blockByNumber = await client.chain.getBlockByNumber(Number(blockHeight) - 10, true); 33 | expect(blockByNumber).to.be.not.undefined; 34 | 35 | try { 36 | const blockByHash = await client.chain.getBlockByHash(blockByNumber.hash, true); 37 | expect(blockByNumber.hash).to.be.eq(blockByHash.hash); 38 | } catch (error) { 39 | if (!(error instanceof UnsupportedMethodError)) { 40 | throw error; 41 | } 42 | } 43 | }); 44 | 45 | it('should fetch transaction data', async () => { 46 | const blockHeight = await client.chain.getBlockHeight(); 47 | 48 | // let the chain indexer to fetch the data 49 | await sleep(1000); 50 | 51 | const blockByNumber = await client.chain.getBlockByNumber(Number(blockHeight) - 10, true); 52 | for (const tx of blockByNumber.transactions) { 53 | const receipt = await client.chain.getTransactionByHash(tx.hash); 54 | expect(Math.gte(receipt.confirmations, 1)).to.be.true; 55 | expect(receipt.hash).to.be.eq(tx.hash); 56 | expect(receipt.value).to.be.eq(tx.value); 57 | } 58 | }); 59 | 60 | it('should fetch multiple balances at once', async () => { 61 | if (config.walletExpectedResult.address) { 62 | const balances = await client.chain.getBalance([config.walletExpectedResult.address], config.assets); 63 | expect(balances.length).to.equal(config.assets.length); 64 | } 65 | }); 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /test/integration/clients/bitcoin/clients.ts: -------------------------------------------------------------------------------- 1 | import * as BTC from '@chainify/bitcoin'; 2 | import { BitcoinLedgerProvider } from '@chainify/bitcoin-ledger'; 3 | import { Client } from '@chainify/client'; 4 | import { NodeTransportCreator } from '../../environment/NodeTransportCreator'; 5 | import { BtcHdWalletConfig, BtcLedgerConfig, BtcNodeConfig } from './config'; 6 | 7 | function getBtcClientWithNodeWallet(network: BTC.BitcoinTypes.BitcoinNetwork) { 8 | const config = BtcNodeConfig(network); 9 | const chainProvider = new BTC.BitcoinJsonRpcProvider(config.chainOptions as any); 10 | const walletProvider = new BTC.BitcoinNodeWalletProvider(null, chainProvider); 11 | const swapProvider = new BTC.BitcoinSwapRpcProvider({ network }, walletProvider); 12 | return new Client(chainProvider, walletProvider, swapProvider); 13 | } 14 | 15 | function getBtcClientWithHDWallet(network: BTC.BitcoinTypes.BitcoinNetwork) { 16 | const config = BtcHdWalletConfig(network); 17 | const chainProvider = new BTC.BitcoinJsonRpcProvider(config.chainOptions as any); 18 | const walletProvider = new BTC.BitcoinHDWalletProvider(config.walletOptions as any, chainProvider); 19 | const swapProvider = new BTC.BitcoinSwapRpcProvider({ network }, walletProvider); 20 | return new Client(chainProvider, walletProvider, swapProvider); 21 | } 22 | 23 | function getBtcLedgerClient(network: BTC.BitcoinTypes.BitcoinNetwork) { 24 | const config = BtcLedgerConfig(network); 25 | const chainProvider = new BTC.BitcoinJsonRpcProvider(config.chainOptions as any); 26 | 27 | const walletProvider = new BitcoinLedgerProvider( 28 | { ...config.walletOptions, transportCreator: new NodeTransportCreator() } as any, 29 | chainProvider 30 | ); 31 | const swapProvider = new BTC.BitcoinSwapRpcProvider({ network }, walletProvider); 32 | return new Client(chainProvider, walletProvider, swapProvider); 33 | } 34 | 35 | export const BitcoinNodeWalletClient = getBtcClientWithNodeWallet(BTC.BitcoinNetworks.bitcoin_regtest); 36 | export const BitcoinHDWalletClient = getBtcClientWithHDWallet(BTC.BitcoinNetworks.bitcoin_regtest); 37 | export const BitcoinLedgerClient = getBtcLedgerClient(BTC.BitcoinNetworks.bitcoin_regtest); 38 | -------------------------------------------------------------------------------- /test/integration/clients/bitcoin/index.ts: -------------------------------------------------------------------------------- 1 | import { assign } from 'lodash'; 2 | import { shouldBehaveLikeChainProvider } from '../../chain/chain.test'; 3 | import { Chains, describeExternal, fundAddress } from '../../common'; 4 | import { shouldBehaveLikeSwapProvider } from '../../swap/swap.test'; 5 | import { shouldBehaveLikeWalletProvider } from '../../wallet/wallet.test'; 6 | import { shouldBehaveLikeBitcoinTransaction } from './behaviors/transactions.behavior'; 7 | import { shouldBehaveLikeBitcoinWallet } from './behaviors/wallet.behavior'; 8 | import { importBitcoinAddresses } from './utils'; 9 | 10 | export function shouldBehaveLikeBitcoinClient() { 11 | describe('Bitcoin Client - HD Wallet', () => { 12 | before(async () => { 13 | const { config } = Chains.btc.hd; 14 | await importBitcoinAddresses(Chains.btc.hd); 15 | await fundAddress(Chains.btc.node, config.walletExpectedResult.address); 16 | }); 17 | shouldBehaveLikeChainProvider(Chains.btc.hd); 18 | shouldBehaveLikeWalletProvider(Chains.btc.hd); 19 | shouldBehaveLikeSwapProvider(Chains.btc.hd); 20 | shouldBehaveLikeBitcoinWallet(Chains.btc.hd); 21 | shouldBehaveLikeBitcoinTransaction(Chains.btc.hd); 22 | }); 23 | 24 | describe('Bitcoin Client - Node Wallet', () => { 25 | before(async () => { 26 | const { client, config } = Chains.btc.node; 27 | const address = await client.wallet.getAddress(); 28 | const recipientAddress = await client.wallet.getUnusedAddress(); 29 | const privateKey = await client.chain.sendRpcRequest('dumpprivkey', [address.toString()]); 30 | 31 | Chains.btc.node.config = assign(Chains.btc.node.config, { 32 | recipientAddress: recipientAddress.toString(), 33 | walletExpectedResult: { ...config.walletExpectedResult, privateKey }, 34 | }); 35 | }); 36 | shouldBehaveLikeChainProvider(Chains.btc.node); 37 | shouldBehaveLikeWalletProvider(Chains.btc.node); 38 | shouldBehaveLikeSwapProvider(Chains.btc.node); 39 | shouldBehaveLikeBitcoinTransaction(Chains.btc.node); 40 | }); 41 | 42 | describeExternal('Bitcoin Client - Ledger', () => { 43 | before(async () => { 44 | const { config } = Chains.btc.ledger; 45 | await importBitcoinAddresses(Chains.btc.ledger); 46 | await fundAddress(Chains.btc.node, config.walletExpectedResult.address); 47 | }); 48 | 49 | shouldBehaveLikeChainProvider(Chains.btc.ledger); 50 | shouldBehaveLikeWalletProvider(Chains.btc.ledger); 51 | shouldBehaveLikeSwapProvider(Chains.btc.ledger); 52 | shouldBehaveLikeBitcoinTransaction(Chains.btc.ledger); 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /test/integration/clients/bitcoin/utils.ts: -------------------------------------------------------------------------------- 1 | import { Address } from '@chainify/types'; 2 | import { Chain } from '../../types'; 3 | 4 | export async function importBitcoinAddresses(chain: Chain) { 5 | const change = await chain.client.wallet.getAddresses(0, 200, true); 6 | const nonChange = await chain.client.wallet.getAddresses(0, 200, false); 7 | const all = [...nonChange, ...change].map((address) => address.address); 8 | const request = all.map((address) => ({ scriptPubKey: { address }, timestamp: 0 })); 9 | return chain.client.chain.sendRpcRequest('importmulti', [request]); 10 | } 11 | 12 | export async function getRandomBitcoinAddress(chain: Chain): Promise
{ 13 | return chain.client.chain.sendRpcRequest('getnewaddress', []); 14 | } 15 | -------------------------------------------------------------------------------- /test/integration/clients/evm/clients.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@chainify/client'; 2 | import * as EVM from '@chainify/evm'; 3 | import { EvmLedgerProvider } from '@chainify/evm-ledger'; 4 | import { Network, WalletOptions } from '@chainify/types'; 5 | import { providers } from 'ethers'; 6 | import { NodeTransportCreator } from '../../environment/NodeTransportCreator'; 7 | import { EVMConfig } from './config'; 8 | import { EIP1559MockFeeProvider } from './mock/EIP1559MockFeeProvider'; 9 | 10 | function getEvmClient(network: Network) { 11 | const config = EVMConfig(network); 12 | const provider = new providers.StaticJsonRpcProvider(network.rpcUrl); 13 | // using mainnet gas fees 14 | const feeProvider = new EIP1559MockFeeProvider(provider); 15 | const chainProvider = new EVM.EvmChainProvider(network, provider, feeProvider); 16 | // we don't have multicall on the common address on Ganache 17 | void chainProvider.multicall.setMulticallAddress('0x08579f8763415cfCEa1B0F0dD583b1A0DEbfBe2b'); 18 | const walletProvider = new EVM.EvmWalletProvider(config.walletOptions as WalletOptions, chainProvider); 19 | const swapProvider = new EVM.EvmSwapProvider(config.swapOptions, walletProvider); 20 | const client = new Client().connect(swapProvider); 21 | return client; 22 | } 23 | 24 | function getEvmLedgerClient(network: Network) { 25 | const config = EVMConfig(network); 26 | const provider = new providers.StaticJsonRpcProvider(network.rpcUrl); 27 | const feeProvider = new EIP1559MockFeeProvider(provider); 28 | const chainProvider = new EVM.EvmChainProvider(network, provider, feeProvider); 29 | // we don't have multicall on the common address on Ganache 30 | void chainProvider.multicall.setMulticallAddress('0x08579f8763415cfCEa1B0F0dD583b1A0DEbfBe2b'); 31 | const walletProvider = new EvmLedgerProvider( 32 | { ...config.walletOptions, transportCreator: new NodeTransportCreator() } as any, 33 | chainProvider 34 | ); 35 | const swapProvider = new EVM.EvmSwapProvider(config.swapOptions, walletProvider); 36 | return new Client(chainProvider, walletProvider, swapProvider); 37 | } 38 | 39 | export const EVMClient = getEvmClient(EVM.EvmNetworks.ganache); 40 | export const EVMLedgerClient = getEvmLedgerClient(EVM.EvmNetworks.ganache); 41 | -------------------------------------------------------------------------------- /test/integration/clients/evm/config.ts: -------------------------------------------------------------------------------- 1 | import { AssetTypes, BigNumber, ChainId, Network } from '@chainify/types'; 2 | import { Wallet } from 'ethers'; 3 | import { IConfig } from '../../types'; 4 | 5 | export const EVMConfig = (network: Network): IConfig => { 6 | return { 7 | network, 8 | 9 | walletOptions: { 10 | mnemonic: 'diary wolf balcony magnet view mosquito settle gym slim target divert all', 11 | derivationPath: `m/44'/${network.coinType}'/0'/0/0`, 12 | network, 13 | }, 14 | 15 | walletExpectedResult: { 16 | address: '0x70B2d0adf991a69FC65eC510A05EC1f7392B6E05', 17 | numberOfUsedAddresses: 1, 18 | unusedAddress: '0x70B2d0adf991a69FC65eC510A05EC1f7392B6E05', 19 | privateKey: 'b2b630d7354d5ffa273b10153c3dade56bc8587d66331c2aaae447eb4daa2065', 20 | signedMessage: 21 | '926abeac6b5698f182d31d2b657f8c3352aa0f92337128fac1741960844c2aa25a7ba8c27112ec2e7c960505edbc9508816f03088d7947f754795b92ba8e8b2e1b', 22 | }, 23 | 24 | swapOptions: { 25 | contractAddress: '0x91441284dfAc14425c9Bf7b3f159CE480d0dd018', 26 | }, 27 | 28 | swapParams: { 29 | value: new BigNumber(1e21), 30 | }, 31 | 32 | sendParams: { 33 | value: new BigNumber(1e21), 34 | }, 35 | 36 | recipientAddress: '0xe862a41cef3bbcc6d85bff8b9c36801a6bc4453e', 37 | multicallAddress: '0x08579f8763415cfCEa1B0F0dD583b1A0DEbfBe2b', 38 | 39 | assets: [ 40 | { 41 | name: 'Ethereum', 42 | code: 'ETH', 43 | chain: ChainId.Ethereum, 44 | type: AssetTypes.native, 45 | decimals: 18, 46 | }, 47 | { 48 | name: 'TestToken', 49 | code: 'TT', 50 | chain: ChainId.Ethereum, 51 | type: AssetTypes.erc20, 52 | decimals: 18, 53 | contractAddress: '0x6ACbD54254da14Db970c7eDE7cFD90784dBeFb6C', 54 | }, 55 | ], 56 | }; 57 | }; 58 | 59 | export const EVMLedgerConfig = (network: Network): IConfig => { 60 | /// NOTE: You have to manually change the mnemonic to match the one in the Ledger to run the tests successfully 61 | const wallet = Wallet.fromMnemonic('indoor dish desk flag debris potato excuse depart ticket judge file exit'); 62 | 63 | const config = EVMConfig(network); 64 | 65 | return { 66 | ...config, 67 | 68 | walletExpectedResult: { 69 | address: wallet.address, 70 | numberOfUsedAddresses: 1, 71 | unusedAddress: wallet.address, 72 | privateKey: null, 73 | signedMessage: null, 74 | }, 75 | 76 | swapParams: { 77 | ...config.swapParams, 78 | expiry: 120, 79 | }, 80 | }; 81 | }; 82 | -------------------------------------------------------------------------------- /test/integration/clients/evm/index.ts: -------------------------------------------------------------------------------- 1 | import { shouldBehaveLikeChainProvider } from '../../chain/chain.test'; 2 | import { Chains, describeExternal, fundAddress } from '../../common'; 3 | import { deploy } from '../../deploy'; 4 | import { shouldBehaveLikeSwapProvider } from '../../swap/swap.test'; 5 | import { shouldBehaveLikeWalletProvider } from '../../wallet/wallet.test'; 6 | import { shouldSignTypedData } from './signTypedData.behavior'; 7 | import { shouldUpdateTransactionFee } from './updateTransactionFee.behavior'; 8 | 9 | export function shouldBehaveLikeEvmClient() { 10 | before(async () => { 11 | await deploy(Chains.evm.hd.client); 12 | }); 13 | 14 | describe('EVM Client - HD Wallet', () => { 15 | const chain = Chains.evm.hd; 16 | shouldBehaveLikeChainProvider(chain); 17 | shouldBehaveLikeWalletProvider(chain); 18 | shouldBehaveLikeWalletProvider(chain, false); 19 | shouldUpdateTransactionFee(chain); 20 | shouldSignTypedData(chain); 21 | shouldBehaveLikeSwapProvider(chain); 22 | shouldBehaveLikeSwapProvider(chain, false); 23 | }); 24 | 25 | describeExternal('EVM Client - Ledger', () => { 26 | before(async () => { 27 | await fundAddress(Chains.evm.hd, Chains.evm.ledger.config.walletExpectedResult.address); 28 | }); 29 | 30 | const chain = Chains.evm.ledger; 31 | shouldBehaveLikeChainProvider(chain); 32 | shouldBehaveLikeWalletProvider(chain); 33 | shouldUpdateTransactionFee(chain); 34 | shouldBehaveLikeSwapProvider(chain); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /test/integration/clients/evm/mock/EIP1559MockFeeProvider.ts: -------------------------------------------------------------------------------- 1 | import { Fee } from '@chainify/client'; 2 | import { EvmUtils } from '@chainify/evm'; 3 | import { FeeDetails } from '@chainify/types'; 4 | import { StaticJsonRpcProvider } from '@ethersproject/providers'; 5 | 6 | export class EIP1559MockFeeProvider extends Fee { 7 | provider: StaticJsonRpcProvider; 8 | 9 | constructor(provider: StaticJsonRpcProvider) { 10 | super(); 11 | this.provider = provider; 12 | } 13 | 14 | async getFees(): Promise { 15 | const feeData = await this.provider.getFeeData(); 16 | 17 | const fees = { 18 | slow: { 19 | fee: { 20 | maxFeePerGas: EvmUtils.toGwei(feeData.maxFeePerGas.toNumber()).toNumber(), 21 | maxPriorityFeePerGas: EvmUtils.toGwei(feeData.maxPriorityFeePerGas.toNumber()).toNumber(), 22 | }, 23 | }, 24 | average: { 25 | fee: { 26 | maxFeePerGas: EvmUtils.toGwei(EvmUtils.calculateFee(feeData.maxFeePerGas.toNumber(), 1.1)).toNumber(), 27 | maxPriorityFeePerGas: EvmUtils.toGwei(EvmUtils.calculateFee(feeData.maxPriorityFeePerGas.toNumber(), 1.1)).toNumber(), 28 | }, 29 | }, 30 | fast: { 31 | fee: { 32 | maxFeePerGas: EvmUtils.toGwei(EvmUtils.calculateFee(feeData.maxFeePerGas.toNumber(), 1.4)).toNumber(), 33 | maxPriorityFeePerGas: EvmUtils.toGwei(EvmUtils.calculateFee(feeData.maxPriorityFeePerGas.toNumber(), 1.4)).toNumber(), 34 | }, 35 | }, 36 | }; 37 | 38 | return fees; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/integration/clients/index.ts: -------------------------------------------------------------------------------- 1 | export { BitcoinHDWalletClient, BitcoinLedgerClient, BitcoinNodeWalletClient } from './bitcoin/clients'; 2 | export { EVMClient, EVMLedgerClient } from './evm/clients'; 3 | export { NearClient } from './near/clients'; 4 | export { SolanaClient } from './solana/clients'; 5 | export { TerraClient } from './terra/clients'; 6 | -------------------------------------------------------------------------------- /test/integration/clients/near/clients.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@chainify/client'; 2 | import * as Near from '@chainify/near'; 3 | import { NearConfig } from './config'; 4 | 5 | function getNearClient(network: Near.NearTypes.NearNetwork) { 6 | const config = NearConfig(network); 7 | const chainProvider = new Near.NearChainProvider(network); 8 | const walletProvider = new Near.NearWalletProvider(config.walletOptions as Near.NearTypes.NearWalletOptions, chainProvider); 9 | const swapProvider = new Near.NearSwapProvider(network.helperUrl, walletProvider); 10 | return new Client(chainProvider, walletProvider, swapProvider); 11 | } 12 | 13 | export const NearClient = getNearClient(Near.NearNetworks.near_testnet); 14 | -------------------------------------------------------------------------------- /test/integration/clients/near/config.ts: -------------------------------------------------------------------------------- 1 | import * as Near from '@chainify/near'; 2 | import { AssetTypes, BigNumber, ChainId } from '@chainify/types'; 3 | import { IConfig } from '../../types'; 4 | 5 | export const NearConfig = (network: Near.NearTypes.NearNetwork): IConfig => { 6 | return { 7 | network, 8 | 9 | walletOptions: { 10 | mnemonic: 'diary wolf balcony magnet view mosquito settle gym slim target divert all', 11 | derivationPath: `m/44'/${network.coinType}'/0'`, 12 | helperUrl: network.helperUrl, 13 | }, 14 | 15 | walletExpectedResult: { 16 | address: '9eed84cfc2ac0068dd8fc10b8b3b71c8d0f74cfd09211e036bdb8561c2647472', 17 | numberOfUsedAddresses: 1, 18 | unusedAddress: '9eed84cfc2ac0068dd8fc10b8b3b71c8d0f74cfd09211e036bdb8561c2647472', 19 | privateKey: 'ed25519:4wRb35gLftuVgYCpNSLAF1SHUQuPFWEsbvh87WX2EDxHWV73vDf4J5sCsEPckeGBHSAf3vbvAyU4CpjidyTNFCcy', 20 | signedMessage: 21 | 'b500437515ea4e3c6ba73c6fb765476d1ae3c8cce58c58ab5f60a0bce7af31c40f06287e448738c8a237450dae9adff04037b7a02b3ada39a6a74f703af5f109', 22 | }, 23 | 24 | swapOptions: { 25 | contractAddress: '0x91441284dfAc14425c9Bf7b3f159CE480d0dd018', 26 | }, 27 | 28 | swapParams: { 29 | value: new BigNumber(5000000000000000000000000), 30 | }, 31 | 32 | sendParams: { 33 | value: new BigNumber(1), 34 | }, 35 | 36 | recipientAddress: '797b73fdaae5f9c4b343a7f8a7334fb56d04dad9a32b5a5e586c503701d537b6', 37 | 38 | assets: [ 39 | { 40 | name: 'Near', 41 | code: 'Near', 42 | chain: ChainId.Near, 43 | type: AssetTypes.native, 44 | decimals: 24, 45 | }, 46 | ], 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /test/integration/clients/near/index.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@chainify/client'; 2 | import { NearTypes, NearWalletProvider } from '@chainify/near'; 3 | import { shouldBehaveLikeChainProvider } from '../../chain/chain.test'; 4 | import { Chains } from '../../common'; 5 | import { shouldBehaveLikeSwapProvider } from '../../swap/swap.test'; 6 | import { shouldBehaveLikeWalletProvider } from '../../wallet/wallet.test'; 7 | 8 | export function shouldBehaveLikeNearClient() { 9 | before('Send funds to Near sender', async () => { 10 | const { client, config } = Chains.near.hd; 11 | const tempClient = new Client( 12 | client.chain, 13 | new NearWalletProvider( 14 | { 15 | ...(config.walletOptions as NearTypes.NearWalletOptions), 16 | mnemonic: 'pet replace kitchen ladder jaguar bleak health horn high fall crush maze', 17 | }, 18 | client.chain 19 | ) 20 | ); 21 | 22 | const nearBalance = (await tempClient.wallet.getBalance(config.assets))[0]; 23 | if (nearBalance.gt(config.swapParams.value)) { 24 | await tempClient.wallet.sendTransaction({ 25 | to: await client.wallet.getAddress(), 26 | value: nearBalance.minus(config.swapParams.value), 27 | }); 28 | } 29 | }); 30 | 31 | describe('Near Client - HD Wallet', () => { 32 | const chain = Chains.near.hd; 33 | shouldBehaveLikeChainProvider(chain); 34 | shouldBehaveLikeWalletProvider(chain); 35 | shouldBehaveLikeSwapProvider(chain); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /test/integration/clients/solana/clients.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@chainify/client'; 2 | import * as Solana from '@chainify/solana'; 3 | import { Network, WalletOptions } from '@chainify/types'; 4 | import { SolanaConfig } from './config'; 5 | 6 | function getSolanaClient(network: Network) { 7 | const config = SolanaConfig(network); 8 | const chainProvider = new Solana.SolanaChainProvider(network); 9 | const walletProvider = new Solana.SolanaWalletProvider(config.walletOptions as WalletOptions, chainProvider); 10 | 11 | return new Client(chainProvider, walletProvider); 12 | } 13 | 14 | export const SolanaClient = getSolanaClient(Solana.SolanaNetworks.solana_testnet); 15 | -------------------------------------------------------------------------------- /test/integration/clients/solana/config.ts: -------------------------------------------------------------------------------- 1 | import { Asset, AssetTypes, BigNumber, ChainId, Network } from '@chainify/types'; 2 | import { IConfig } from '../../types'; 3 | 4 | export const SolanaConfig = (network: Network): IConfig => { 5 | return { 6 | network, 7 | 8 | walletOptions: { 9 | mnemonic: 10 | 'thumb proud solar any north rely grow ceiling pattern dress under illegal relief brief flock ensure tumble green million earth lesson absent horse snap', 11 | derivationPath: `m/44'/${network.coinType}'/0'/0'`, 12 | }, 13 | 14 | walletExpectedResult: { 15 | address: 'CGP6sKHyrZGPJRoUAy8XbyzmX7YD4tVBQG9SEe9ekZM6', 16 | numberOfUsedAddresses: 1, 17 | privateKey: '5xcywSQBBsks8pgWRwRzx6AhZ6cXX2qDtHiFZ8aDZkfiiRA7kTgWvRbgoD1nmTEt8aW1KudN86gPysNXMzuKW6Mr', 18 | }, 19 | 20 | swapOptions: { 21 | contractAddress: null, 22 | }, 23 | 24 | swapParams: { 25 | value: new BigNumber(5000000), 26 | }, 27 | 28 | sendParams: { 29 | value: new BigNumber(5000000), 30 | }, 31 | 32 | recipientAddress: '5r3N8yt7DYgh888Rr7owRoD3Jn6QSNY9sYyisTkT86DU', 33 | 34 | assets: solanaAssets, 35 | }; 36 | }; 37 | 38 | const solanaAssets = [ 39 | { 40 | name: 'SOLANA', 41 | code: 'SOL', 42 | chain: ChainId.Solana, 43 | type: AssetTypes.native, 44 | decimals: 9, 45 | }, 46 | { 47 | name: 'SOLANA ERC 20', 48 | code: 'ERC20', 49 | chain: ChainId.Solana, 50 | type: AssetTypes.erc20, 51 | decimals: 9, 52 | contractAddress: 'Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr', 53 | }, 54 | ] as Asset[]; 55 | -------------------------------------------------------------------------------- /test/integration/clients/solana/index.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@chainify/client'; 2 | import { SolanaChainProvider, SolanaWalletProvider } from '@chainify/solana'; 3 | import { shouldBehaveLikeChainProvider } from '../../chain/chain.test'; 4 | import { Chains } from '../../common'; 5 | import { shouldBehaveLikeWalletProvider } from '../../wallet/wallet.test'; 6 | 7 | export function shouldBehaveLikeSolanaClient() { 8 | before('Send funds to Solana sender', async () => { 9 | const { client, config } = Chains.solana.hd; 10 | const tempClient = new Client( 11 | client.chain, 12 | new SolanaWalletProvider( 13 | { 14 | ...(config.walletOptions as any), 15 | mnemonic: 16 | 'avoid void grid scare guard biology gaze system wine undo tomorrow evoke noble salon income juice stumble myth debate praise kind reflect ketchup fossil', 17 | }, 18 | client.chain as SolanaChainProvider 19 | ) 20 | ); 21 | 22 | const solanaBalance = (await tempClient.wallet.getBalance(config.assets))[0]; 23 | if (solanaBalance.gt(config.swapParams.value)) { 24 | await tempClient.wallet.sendTransaction({ 25 | to: await client.wallet.getAddress(), 26 | value: solanaBalance.minus(config.swapParams.value), 27 | }); 28 | } 29 | }); 30 | 31 | describe('Solana Client - HD Wallet', () => { 32 | const chain = Chains.solana.hd; 33 | shouldBehaveLikeChainProvider(chain); 34 | shouldBehaveLikeWalletProvider(chain); 35 | shouldBehaveLikeWalletProvider(chain, false); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /test/integration/clients/terra/clients.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@chainify/client'; 2 | import * as Terra from '@chainify/terra'; 3 | import { WalletOptions } from '@chainify/types'; 4 | import { TerraConfig } from './config'; 5 | 6 | function getTerraClient(network: Terra.TerraTypes.TerraNetwork) { 7 | const config = TerraConfig(network); 8 | const chainProvider = new Terra.TerraChainProvider(network); 9 | const walletProvider = new Terra.TerraWalletProvider(config.walletOptions as WalletOptions, chainProvider); 10 | const swapProvider = new Terra.TerraSwapProvider(network.helperUrl, walletProvider); 11 | return new Client(chainProvider, walletProvider, swapProvider); 12 | } 13 | 14 | export const TerraClient = getTerraClient(Terra.TerraNetworks.terra_testnet); 15 | -------------------------------------------------------------------------------- /test/integration/clients/terra/config.ts: -------------------------------------------------------------------------------- 1 | import { Asset, AssetTypes, BigNumber, ChainId, Network } from '@chainify/types'; 2 | import { IConfig } from '../../types'; 3 | 4 | export const TerraConfig = (network: Network): IConfig => { 5 | return { 6 | network, 7 | 8 | walletOptions: { 9 | mnemonic: 10 | 'fury motion step civil horn snake engine wage honey already interest fall property nephew jeans true moment weasel village then upset avocado wheat write', 11 | derivationPath: `m/44'/${network.coinType}'/0'/0/0`, 12 | }, 13 | 14 | walletExpectedResult: { 15 | address: 'terra156c6y66lqp7xe9x3hvl3uf0szl7ek44ferg4sg', 16 | numberOfUsedAddresses: 1, 17 | privateKey: '9977cb9d096ad0287d36bf00a67293ac4cf0dc7b9633837ca6383575f93fd888', 18 | }, 19 | 20 | swapOptions: { 21 | contractAddress: null, 22 | }, 23 | 24 | swapParams: { 25 | value: new BigNumber(5000000), 26 | }, 27 | 28 | sendParams: { 29 | value: new BigNumber(5000000), 30 | feeAsset: terraAssets[0], 31 | }, 32 | 33 | recipientAddress: 'terra10c9wv2symnwq72yh8v9xg7ddkcugxq08nhskx9', 34 | 35 | assets: terraAssets, 36 | }; 37 | }; 38 | 39 | const terraAssets = [ 40 | { 41 | name: 'Luna', 42 | code: 'LUNA', 43 | chain: ChainId.Terra, 44 | type: AssetTypes.native, 45 | decimals: 6, 46 | }, 47 | { 48 | name: 'TerraUSD', 49 | code: 'UST', 50 | chain: ChainId.Terra, 51 | type: AssetTypes.native, 52 | decimals: 6, 53 | }, 54 | ] as Asset[]; 55 | -------------------------------------------------------------------------------- /test/integration/clients/terra/index.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@chainify/client'; 2 | import { TerraChainProvider, TerraWalletProvider } from '@chainify/terra'; 3 | import { AssetTypes } from '@chainify/types'; 4 | import { shouldBehaveLikeChainProvider } from '../../chain/chain.test'; 5 | import { Chains } from '../../common'; 6 | import { shouldBehaveLikeSwapProvider } from '../../swap/swap.test'; 7 | import { shouldBehaveLikeWalletProvider } from '../../wallet/wallet.test'; 8 | 9 | export function shouldBehaveLikeTerraClient() { 10 | before('Send funds to Terra sender', async () => { 11 | const { client, config } = Chains.terra.hd; 12 | const tempClient = new Client( 13 | client.chain, 14 | new TerraWalletProvider( 15 | { 16 | ...(config.walletOptions as any), 17 | mnemonic: 18 | 'avoid void grid scare guard biology gaze system wine undo tomorrow evoke noble salon income juice stumble myth debate praise kind reflect ketchup fossil', 19 | }, 20 | client.chain as TerraChainProvider 21 | ) 22 | ); 23 | 24 | const terraBalance = (await tempClient.wallet.getBalance(config.assets))[0]; 25 | if (terraBalance.gt(config.swapParams.value)) { 26 | await tempClient.wallet.sendTransaction({ 27 | to: await client.wallet.getAddress(), 28 | value: terraBalance.minus(config.swapParams.value), 29 | asset: config.assets.find((a) => a.type === AssetTypes.native), 30 | feeAsset: config.sendParams.feeAsset, 31 | }); 32 | } 33 | }); 34 | 35 | describe('Terra Client - HD Wallet', () => { 36 | const chain = Chains.terra.hd; 37 | shouldBehaveLikeChainProvider(chain); 38 | shouldBehaveLikeWalletProvider(chain); 39 | shouldBehaveLikeSwapProvider(chain); 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /test/integration/config.ts: -------------------------------------------------------------------------------- 1 | export { BtcHdWalletConfig, BtcLedgerConfig, BtcNodeConfig } from './clients/bitcoin/config'; 2 | export { EVMConfig, EVMLedgerConfig } from './clients/evm/config'; 3 | export { NearConfig } from './clients/near/config'; 4 | export { SolanaConfig } from './clients/solana/config'; 5 | export { TerraConfig } from './clients/terra/config'; 6 | -------------------------------------------------------------------------------- /test/integration/deploy/evm.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@chainify/client'; 2 | import { Typechain } from '@chainify/evm'; 3 | import { constants, ethers } from 'ethers'; 4 | 5 | /** 6 | * The deploy step should always comes first before executing any tests. 7 | * The order of deployment is also important, because that determines the contract addresses. 8 | * Please make sure that no transactions are executed before the deploy step. 9 | */ 10 | export async function deployEvmContracts(client: Client) { 11 | const signer = client.wallet.getSigner(); 12 | const erc20 = await new Typechain.TestERC20__factory().connect(signer).deploy(); 13 | await new Typechain.Multicall3__factory().connect(signer).deploy(); 14 | const htlc = await new Typechain.LiqualityHTLC__factory().connect(signer).deploy(); 15 | 16 | // Mint tokens to the first 10 addresses 17 | const userAddress = await client.wallet.getAddresses(0, 10); 18 | for (const user of userAddress) { 19 | await erc20.mint(user.toString(), ethers.utils.parseEther('1000000000')); 20 | await erc20.approve(htlc.address, constants.MaxUint256); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/integration/deploy/index.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@chainify/client'; 2 | import { deployEvmContracts } from './evm'; 3 | 4 | export async function deploy(client: Client) { 5 | await deployEvmContracts(client); 6 | } 7 | -------------------------------------------------------------------------------- /test/integration/environment/NodeTransportCreator.ts: -------------------------------------------------------------------------------- 1 | import { TransportCreator } from '@chainify/hw-ledger'; 2 | import Transport from '@ledgerhq/hw-transport'; 3 | import LedgerHwTransportNode from '@ledgerhq/hw-transport-node-hid'; 4 | 5 | export class NodeTransportCreator implements TransportCreator { 6 | private _transport: Transport = null; 7 | private _onDisconnectCallbacks: Array<() => void> = []; 8 | 9 | async create(onDisconnect?: () => void): Promise { 10 | if (!this._transport || !(this._transport as LedgerHwTransportNode)?.device?.opened) { 11 | this._transport = await LedgerHwTransportNode.create(); 12 | this._transport.on('disconnect', async () => { 13 | this._onDisconnectCallbacks.forEach((cb) => { 14 | cb(); 15 | }); 16 | await this._transport?.close(); 17 | this._transport = null; 18 | this._onDisconnectCallbacks = []; 19 | }); 20 | } 21 | 22 | if (onDisconnect) { 23 | this._onDisconnectCallbacks.push(onDisconnect); 24 | } 25 | 26 | return this._transport; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/integration/environment/btc/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | bitcoin: 4 | image: ruimarinho/bitcoin-core:0.18.1 5 | ports: 6 | - '18443:18443' 7 | command: -regtest=1 8 | -txindex 9 | -deprecatedrpc=generate 10 | -rpcuser=bitcoin 11 | -rpcpassword=local321 12 | -rpcallowip=0.0.0.0/0 13 | -rpcbind=0.0.0.0 14 | -server 15 | -rpcworkqueue=400 16 | bitcoin-miner: 17 | build: 18 | context: . 19 | dockerfile: miner.Dockerfile 20 | environment: 21 | - TARGET_HOST=bitcoin 22 | - TARGET_PORT=18443 23 | depends_on: 24 | - bitcoin 25 | -------------------------------------------------------------------------------- /test/integration/environment/btc/mine.sh: -------------------------------------------------------------------------------- 1 | sleep 10 # Wait for wallet to load 2 | 3 | mine () { curl --basic -u bitcoin:local321 -X POST -d "{\"jsonrpc\": \"2.0\", \"id\": \"0\", \"method\": \"generate\", \"params\": [$1]}" http://${TARGET_HOST}:${TARGET_PORT} > /dev/null; } 4 | 5 | mine 105 # Extra few blocks for some mature funds -------------------------------------------------------------------------------- /test/integration/environment/btc/miner.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | 3 | COPY mine.sh /home/mine.sh 4 | 5 | WORKDIR /home 6 | 7 | RUN apk add curl curl-dev 8 | 9 | CMD /bin/sh mine.sh -------------------------------------------------------------------------------- /test/integration/environment/ganache.ts: -------------------------------------------------------------------------------- 1 | import Ganache from 'ganache'; 2 | 3 | let server: any = null; 4 | 5 | export async function startGanache(_options: any = {}) { 6 | const defaultOptions = { 7 | mnemonic: 'diary wolf balcony magnet view mosquito settle gym slim target divert all', 8 | totalAccounts: 10, 9 | port: 8545, 10 | network_id: '1337', 11 | chainId: '1337', 12 | quiet: true, 13 | blockTime: 0, 14 | instamine: 'eager', 15 | defaultGasPrice: 0, 16 | default_balance_ether: 1000000000, 17 | ..._options, 18 | }; 19 | server = Ganache.server(defaultOptions); 20 | 21 | server.listen(defaultOptions.port, async (err: any) => { 22 | if (err) { 23 | throw err; 24 | } 25 | 26 | console.log(`Ganache listening on port ${defaultOptions.port}...`); 27 | const provider = server.provider; 28 | const accounts = await provider.request({ 29 | method: 'eth_accounts', 30 | params: [], 31 | }); 32 | 33 | console.log(accounts); 34 | }); 35 | 36 | return new Promise((resolve) => { 37 | setInterval(async () => { 38 | if ((server.status & ServerStatus.open) !== 0) { 39 | resolve(true); 40 | } 41 | }, 500); 42 | }); 43 | } 44 | 45 | export async function closeGanache() { 46 | if (server) { 47 | await server.close(); 48 | } 49 | } 50 | 51 | export enum ServerStatus { 52 | /** 53 | * The Server is in an unknown state; perhaps construction didn't succeed 54 | */ 55 | unknown = 0, 56 | /** 57 | * The Server has been constructed and is ready to be opened. 58 | */ 59 | ready = 1 << 0, 60 | /** 61 | * The Server has started to open, but has not yet finished initialization. 62 | */ 63 | opening = 1 << 1, 64 | /** 65 | * The Server is open and ready for connection. 66 | */ 67 | open = 1 << 2, 68 | /** 69 | * The Server is either opening or is already open 70 | */ 71 | openingOrOpen = (1 << 1) | (1 << 2), 72 | /** 73 | * The Server is in the process of closing. 74 | */ 75 | closing = 1 << 3, 76 | /** 77 | * The Server is closed and not accepting new connections. 78 | */ 79 | closed = 1 << 4, 80 | /** 81 | * The Server is either opening or is already open 82 | */ 83 | closingOrClosed = (1 << 3) | (1 << 4), 84 | } 85 | -------------------------------------------------------------------------------- /test/integration/environment/index.ts: -------------------------------------------------------------------------------- 1 | import { closeGanache, startGanache } from './ganache'; 2 | 3 | export async function startLocalNetworks() { 4 | await startGanache(); 5 | } 6 | 7 | export async function stopLocalNetworks() { 8 | await closeGanache(); 9 | } 10 | -------------------------------------------------------------------------------- /test/integration/index.ts: -------------------------------------------------------------------------------- 1 | import { Logger, LogLevel } from '@chainify/logger'; 2 | import chai from 'chai'; 3 | import chaiAsPromised from 'chai-as-promised'; 4 | import { shouldBehaveLikeBitcoinClient } from './clients/bitcoin'; 5 | import { shouldBehaveLikeEvmClient } from './clients/evm'; 6 | import { shouldBehaveLikeNearClient } from './clients/near'; 7 | import { shouldBehaveLikeSolanaClient } from './clients/solana'; 8 | import { shouldBehaveLikeTerraClient } from './clients/terra'; 9 | import { describeExternal } from './common'; 10 | import { startLocalNetworks, stopLocalNetworks } from './environment'; 11 | 12 | chai.use(chaiAsPromised); 13 | 14 | // turn off the logger for the tests 15 | Logger.setLogLevel(LogLevel.OFF); 16 | 17 | describe('Integration tests', function () { 18 | before(async () => { 19 | await startLocalNetworks(); 20 | }); 21 | 22 | describe('Clients', () => { 23 | shouldBehaveLikeEvmClient(); 24 | shouldBehaveLikeBitcoinClient(); 25 | }); 26 | 27 | describeExternal('Manual Clients', () => { 28 | shouldBehaveLikeNearClient(); 29 | shouldBehaveLikeTerraClient(); 30 | shouldBehaveLikeSolanaClient(); 31 | }); 32 | 33 | after(async () => { 34 | await stopLocalNetworks(); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/integration/types.ts: -------------------------------------------------------------------------------- 1 | import { Client } from '@chainify/client'; 2 | import { Asset, BigNumber, Network } from '@chainify/types'; 3 | 4 | export interface IConfig { 5 | network: Network; 6 | walletOptions?: Record; 7 | chainOptions?: Record; 8 | walletExpectedResult?: { 9 | address?: string; 10 | privateKey?: string; 11 | privateKeyRegex?: RegExp; 12 | signedMessage?: string; 13 | unusedAddress?: string; 14 | numberOfUsedAddresses?: number; 15 | }; 16 | swapOptions?: { 17 | contractAddress: string; 18 | }; 19 | swapParams: { 20 | value?: BigNumber; 21 | expiry?: number; 22 | }; 23 | sendParams: { 24 | value?: BigNumber; 25 | feeAsset?: Asset; 26 | }; 27 | assets: Asset[]; 28 | recipientAddress?: string; 29 | multicallAddress?: string; 30 | } 31 | 32 | export enum ChainType { 33 | btc = 'btc', 34 | evm = 'evm', 35 | near = 'near', 36 | terra = 'terra', 37 | solana = 'solana', 38 | } 39 | 40 | export enum WalletType { 41 | hd = 'hd', 42 | node = 'node', 43 | ledger = 'ledger', 44 | } 45 | 46 | export interface Chain { 47 | id: string; 48 | name: string; 49 | client: Client; 50 | config: IConfig; 51 | network?: Network; 52 | segwitFeeImplemented?: boolean; 53 | funded?: boolean; 54 | } 55 | -------------------------------------------------------------------------------- /test/integration/wallet/sign.typed.data.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { Chain } from '../types'; 3 | 4 | export function shouldBehaveLikeSignTypedData(chain: Chain, inputs: any[], expected: string[], info: string[]) { 5 | const { client } = chain; 6 | 7 | describe(`${client.chain.getNetwork().name} Sign Typed Data`, function () { 8 | for (let i = 0; i < inputs.length; i++) { 9 | it(info[i], async () => { 10 | const result = await chain.client.wallet.signTypedData(inputs[i]); 11 | expect(result).to.be.equal(expected[i]); 12 | }); 13 | } 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "baseUrl": "./", 6 | "rootDir": "./", 7 | "esModuleInterop": true, 8 | "noImplicitAny": false // we need that for lib/typechain 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "allowSyntheticDefaultImports": true, 6 | "esModuleInterop": true, 7 | "noImplicitAny": true, 8 | "noImplicitThis": true, 9 | "noUnusedLocals": true, 10 | "target": "es6", 11 | "sourceMap": true, 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "skipLibCheck": true, 15 | "lib": ["es2017", "es7", "es6", "dom"], 16 | "baseUrl": "./packages" 17 | }, 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "pipeline": { 3 | "build": { 4 | "dependsOn": ["^build"], 5 | "outputs": ["dist/**"] 6 | }, 7 | "test:integration": { 8 | "dependsOn": ["^build"], 9 | "outputs": [] 10 | }, 11 | "dev": { 12 | "cache": false 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": [ 3 | "**/*.js", 4 | "**/node_modules/**" 5 | ], 6 | "tsconfig": "tsconfig.json", 7 | "entryPoints": ["."], 8 | "entryPointStrategy": "packages", 9 | "out": "docs", 10 | "excludePrivate": "true", 11 | "theme": "github-wiki" 12 | } --------------------------------------------------------------------------------