Repository: iamnotstatic/multichain-crypto-wallet Branch: main Commit: b17689286327 Files: 42 Total size: 155.8 KB Directory structure: gitextract_vdzrr2bn/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.yml │ │ └── feature-request.yml │ ├── PULL_REQUEST_TEMPLATE.md │ └── workflows/ │ └── main.yml ├── .gitignore ├── .prettierrc.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── jest.config.js ├── jest.setup.js ├── package.json ├── src/ │ ├── abis/ │ │ └── erc20.json │ ├── common/ │ │ ├── apis/ │ │ │ ├── blockchain.ts │ │ │ ├── blockchair.ts │ │ │ ├── blockstream.ts │ │ │ ├── electrumx.ts │ │ │ ├── jsonrpc.ts │ │ │ ├── sochain.ts │ │ │ └── timeout.ts │ │ ├── fallbacks/ │ │ │ └── btc.ts │ │ ├── helpers/ │ │ │ ├── bitcoinHelper.ts │ │ │ ├── ethereumHelper.ts │ │ │ ├── solanaHelper.ts │ │ │ ├── tronHelper.ts │ │ │ └── wavesHelper.ts │ │ ├── libs/ │ │ │ └── bitgoUtxoLib.ts │ │ └── utils/ │ │ ├── ethers.ts │ │ ├── index.ts │ │ ├── retry.ts │ │ ├── solana.ts │ │ ├── types.ts │ │ └── utxo.ts │ ├── index.ts │ ├── services/ │ │ └── wallet/ │ │ └── index.ts │ └── types/ │ └── declarations.d.ts ├── test/ │ ├── wallet.bitcoin.test.ts │ ├── wallet.ethereum.test.ts │ ├── wallet.solana.test.ts │ ├── wallet.tron.test.ts │ └── wallet.waves.test.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.yml ================================================ name: "🐛 Bug Report" description: Report a bug to help us improve the Multichain Crypto Wallet title: "[Bug]: " labels: ["bug", "needs triage"] body: - type: markdown attributes: value: | ## Thanks for taking the time to report a bug! - type: input id: environment attributes: label: Environment description: Provide details about your environment (e.g., OS, browser, Node version) placeholder: "e.g., macOS Ventura, Chrome 118, Node 18.12.1" validations: required: true - type: textarea id: description attributes: label: Bug Description description: A clear and concise description of what the bug is. placeholder: "Describe the issue you're facing..." validations: required: true - type: textarea id: steps attributes: label: Steps to Reproduce description: How can we reproduce the issue? placeholder: | 1. Go to '...' 2. Click on '...' 3. Scroll down to '...' 4. See error validations: required: true - type: textarea id: expected attributes: label: Expected Behavior description: What did you expect to happen? placeholder: "It should have..." validations: required: true - type: textarea id: actual attributes: label: Actual Behavior description: What actually happened? placeholder: "Instead, it..." validations: required: true - type: input id: repo-version attributes: label: Wallet Version / Branch description: Which version or branch are you seeing the issue on? placeholder: "e.g. v1.0.3 or main" validations: required: true - type: textarea id: logs attributes: label: Relevant Logs / Screenshots description: Paste any logs or screenshots that may help us debug. placeholder: "Copy-paste terminal logs or attach screenshots here." render: shell ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.yml ================================================ name: "✨ Feature Request" description: Suggest an idea to improve the Multichain Crypto Wallet title: "[Feature]: " labels: ["enhancement", "needs triage"] body: - type: markdown attributes: value: | ## Thanks for suggesting a feature! Let's make this wallet better together 🙌 - type: textarea id: feature attributes: label: Describe the Feature description: What's the problem or improvement you're proposing? placeholder: "It would be great if..." validations: required: true - type: textarea id: motivation attributes: label: Motivation / Use Case description: Why do you want this feature? What problem does it solve? placeholder: "As a user, I want to..." validations: required: true - type: textarea id: implementation attributes: label: Possible Implementation description: Any thoughts on how this could be built? placeholder: "Maybe we could use..." render: markdown - type: checkboxes id: impact attributes: label: Potential Impact options: - label: This would improve developer experience - label: This would improve end-user experience - label: This would support new chains or protocols - label: This could affect existing functionality ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Description Please include a summary of the changes and be sure to follow our [Contribution Guidelines](../CONTRIBUTING.md). ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) ## Checklist: - [ ] I have selected the correct base branch. - [ ] I have performed a self-review of my own code. - [ ] I have commented my code, particularly in hard-to-understand areas. - [ ] I have made corresponding changes to the documentation. - [ ] My changes generate no new warnings. - [ ] Any dependent changes have been merged and published in downstream modules. - [ ] I ran `npm run test` and my test cases cover all the lines and branches of the added code. - [ ] I ran `npm run build` with success. - [ ] I have tested my code on the live network. - [ ] I have checked the Deploy Preview and it looks correct. ================================================ FILE: .github/workflows/main.yml ================================================ name: Build, lint, and test on: [push] jobs: build: name: Build, lint, and test on Nodejs runs-on: ubuntu-latest strategy: matrix: node-version: [16.x] steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} cache: yarn - name: Install dependencies run: yarn --ignore-engines - name: Build run: yarn build ================================================ FILE: .gitignore ================================================ *.log .DS_Store node_modules dist .vscode ================================================ FILE: .prettierrc.json ================================================ { "trailingComma": "all", "tabWidth": 2, "semi": true, "singleQuote": true } ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Guidelines We're glad to have you want to contribute to this project! This document will help answer common questions you may have during your first contribution. Following these standards demonstrates that you value the time spent by the developers that manage and maintain this open source project. #### Before you contribute... Read this document before you contribute in any form. ## Submitting an Issue Not all contributions comes in the form of code. Feautures, bug fixes, and documentation are all valid contributions. ## Setup In order to create a pull request to contribute, the processes are simple 0. Create a new branch. This would be the name of the feature/issue you are working on. 1. Install dependencies and start the project locally. ``` javascript yarn // npm install yan start // npm run start ``` 2. Run tests. Jest tests are setup to run in watch mode. ``` javascript yarn test // npm run test ``` 3. Run tests for a specific file. Jest tests are setup to run in watch mode. ``` javascript yarn test test/file.test.ts // npm run test test/file.test.ts ``` 4. Make your necessary changes and accompany each function with a test. ✨ Thanks for contributing to **multichain-crypto-wallet**! ✨ ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2022 iamnotstatic Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Multichain Crypto Wallet A Multichain crypto wallet library that supports Ethereum, Bitcoin, Solana, Waves and other EVM compatible blockchains E.g. Binance Smart Chain, Polygon, Avalanche etc. [![Build](https://img.shields.io/github/actions/workflow/status/iamnotstatic/multichain-crypto-wallet/main.yml)](https://github.com/iamnotstatic/multichain-crypto-wallet) [![Version](https://img.shields.io/npm/v/multichain-crypto-wallet)](https://github.com/iamnotstatic/multichain-crypto-wallet) [![GitHub issues](https://img.shields.io/github/issues/iamnotstatic/multichain-crypto-wallet)](https://github.com/iamnotstatic/multichain-crypto-wallet/issues) [![GitHub stars](https://img.shields.io/github/stars/iamnotstatic/multichain-crypto-wallet)](https://github.com/iamnotstatic/multichain-crypto-wallet/stargazers) [![GitHub license](https://img.shields.io/github/license/iamnotstatic/multichain-crypto-wallet)](https://github.com/iamnotstatic/multichain-crypto-wallet) [![Total Downloads](https://img.shields.io/npm/dm/multichain-crypto-wallet)](https://github.com/iamnotstatic/multichain-crypto-wallet) ## Installation ```bash npm install multichain-crypto-wallet ``` Using yarn, ```bash yarn add multichain-crypto-wallet ``` ## Usage ### Javascript ```javascript const multichainWallet = require('multichain-crypto-wallet'); ``` ### TypeScript ```typescript import * as multichainWallet from 'multichain-crypto-wallet'; ``` ## Methods The following methods are available with this SDK: - [Multichain Crypto Wallet](#multichain-crypto-wallet) - [Installation](#installation) - [Usage](#usage) - [Javascript](#javascript) - [TypeScript](#typescript) - [Methods](#methods) - [Generate mnemonic](#generate-mnemonic) - [Response](#response) - [Create Wallet](#create-wallet) - [Response](#response) - [Get Balance](#get-balance) - [Native coins](#native-coins) - [Tokens](#tokens) - [Response](#response-1) - [Generate Wallet from Mnemonic](#generate-wallet-from-mnemonic) - [Response](#response-2) - [Get Address from Private Key](#get-address-from-private-key) - [Response](#response-3) - [Get Transaction](#get-transaction) - [Response](#response-4) - [Transfer](#transfer) - [Ethereum Network](#ethereum-network) - [Response](#response-5) - [Bitcoin Network](#bitcoin-network) - [Response](#response-6) - [Solana Network](#solana-network) - [Response](#response-7) - [Waves Network](#waves-network) - [Response](#response-8) - [Tron Network](#tron-network) - [Response](#response-9) - [Encryptions](#encryptions) - [Encrypt Private Key](#encrypt-private-key) - [Response](#response-10) - [Decrypt Encrypted JSON](#decrypt-encrypted-json) - [Response](#response-11) - [Token Info](#token-info) - [Get ERC20 Token Info](#get-erc20-token-info) - [Response](#response-12) - [Get SPL Token Info](#get-spl-token-info) - [Response](#response-13) - [Get Waves Token Info](#get-waves-token-info) - [Response](#response-14) - [Get TRC20 Token Info](#get-tron-token-info) - [Response](#response-15) - [Smart Contract Call](#smart-contract-call) - [Ethereum network](#ethereum-network-1) - [Waves network](#waves-network-1) - [Tron network](#tron-network-1) - [Response](#response-17) - [Want to contribute?](#want-to-contribute) ### Generate mnemonic This method is used to generate mnemonic. Default number of words is `12` but you can pass a number param if you want to generate more or less. ```javascript const mnemonic = multichainWallet.generateMnemonic(); // Note: Mnemonics with less than 12 words have low entropy and may be guessed by an attacker. ``` #### Response ```javascript net idle lava mango another capable inhale portion blossom fluid discover cruise ``` ### Create Wallet This method creates a new wallet. The method accepts a payload object as the parameter. The parameter of this payload is: ```javascript // Creating an Ethereum wallet. const wallet = multichainWallet.createWallet({ derivationPath: "m/44'/60'/0'/0/0", // Leave empty to use default derivation path network: 'ethereum', }); // NOTE - Address generated will work for EVM compatible blockchains E.g. Binance smart chain, Polygon etc // Creating a Bitcoin wallet. const wallet = multichainWallet.createWallet({ derivationPath: "m/44'/0'/0'/0/0", // Leave empty to use default derivation path network: 'bitcoin', // 'bitcoin' or 'bitcoin-testnet' }); // Creating a Solana wallet. const wallet = multichainWallet.createWallet({ derivationPath: "m/44'/501'/0'/0'", // Leave empty to use default derivation path network: 'solana', }); // Creating a Waves wallet. const wallet = await multichainWallet.createWallet({ cluster: 'testnet' // Can also be mainnet, network: 'waves', }); // Creating a Tron wallet const wallet = await multichainWallet.createWallet({ network: 'tron', }); ``` #### Response ```javascript { address: '0xfBE11AC0258cc8288cA24E818691Eb062f7042E9', privateKey: '0xfdf745f45d1942feea79b4c0a3fc1ca67da366899f7e6cebaa06496806ca8127', mnemonic: 'net idle lava mango another capable inhale portion blossom fluid discover cruise' } ``` ### Get Balance This gets the balance of the address passed in. The method accepts an object as the parameter. The parameters for this object depending on the kind of balance to be gotten is in the form: #### Native coins ```javascript // Get the ETH balance of an address. const data = await multichainWallet.getBalance({ address: '0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22', network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', }); // NOTE - For otherEVM compatible blockchains all you have to do is change the rpcUrl. // Binance Smart chain const data = await multichainWallet.getBalance({ address: '0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22', network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', }); // Get the BTC balance of an address. const data = await multichainWallet.getBalance({ address: '2NAhbS79dEUeqcnbC27UppwnjoVSwET5bat', network: 'bitcoin-testnet', // 'bitcoin' or 'bitcoin-testnet' }); // Get the SOL balance of an address. const data = await multichainWallet.getBalance({ address: 'DYgLjazTY6kMqagbDMsNttRHKQj9o6pNS8D6jMjWbmP7', network: 'solana', rpcUrl: 'https://api.devnet.solana.com', }); // Get the WAVES balance of an address. const data = await multichainWallet.getBalance({ network: 'waves', address: '3NBE5tjbQn9BHczjD6NSSuFDKVHKsBRzTv9', rpcUrl: 'https://nodes-testnet.wavesnodes.com', }); // Get the Tron balance of an address. const data = await multichainWallet.getBalance({ network: 'tron', address: 'TDdHvW9nU1JaX1P7roYtDvjErTTR17GPJJ', rpcUrl: 'https://nile.trongrid.io', }); ``` #### Tokens ```javascript // Get the balance of an ERC20 token. const data = await multichainWallet.getBalance({ address: '0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22', network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', tokenAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7', }); // NOTE - For other EVM compatible blockchains all you have to do is change the rpcUrl. // Get the balance of a token on Solana. const data = await multichainWallet.getBalance({ address: '5PwN5k7hin2XxUUaXveur7jSe5qt2mkWinp1JEiv8xYu', tokenAddress: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', network: 'solana', rpcUrl: 'https://rpc.ankr.com/solana', }); // Get the balance of a token on Waves. const data = await multichainWallet.getBalance({ network: 'waves', address: '3NBE5tjbQn9BHczjD6NSSuFDKVHKsBRzTv9', rpcUrl: 'https://nodes-testnet.wavesnodes.com', tokenAddress: '39pnv8FVf3BX3xwtC6uhFxffy2sE3seXCPsf25eNn6qG', }); // Get the balance of a token on tron. const data = await multichainWallet.getBalance({ network: 'tron', address: 'TDdHvW9nU1JaX1P7roYtDvjErTTR17GPJJ', rpcUrl: 'https://nile.trongrid.io', tokenAddress: 'TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj', }); ``` #### Response ```javascript { balance: '2'; } ``` ### Generate Wallet from Mnemonic This generates a wallet from Mnemonic phrase. The method accepts an object as the parameter. The parameters that this object takes are: ```javascript // Generate an Ethereum wallet from mnemonic. const wallet = multichainWallet.generateWalletFromMnemonic({ mnemonic: 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat', derivationPath: "m/44'/60'/0'/0/0", // Leave empty to use default derivation path network: 'ethereum', }); // NOTE - Address generated will work for EVM compatible blockchains E.g. Binance smart chain, Polygon etc // Generate a Bitcoin wallet from mnemonic. const wallet = multichainWallet.generateWalletFromMnemonic({ mnemonic: 'excess quit spot inspire stereo scrap cave wife narrow era pizza typical', derivationPath: "m/44'/0'/0'/0/0", // Leave empty to use default derivation path network: 'bitcoin', // 'bitcoin' or 'bitcoin-testnet' }); // Generate a Solana wallet from mnemonic. const wallet = multichainWallet.generateWalletFromMnemonic({ mnemonic: 'base dry mango subject neither labor portion weekend range couple right document', derivationPath: "m/44'/501'/0'/0'", // Leave empty to use default derivation path network: 'solana', }); // Generate a Waves wallet from mnemonic. const wallet = multichainWallet.generateWalletFromMnemonic({ mnemonic: 'mushroom deliver work spray hire nuclear wrong deputy march six midnight outside motor differ adult', cluster: 'testnet', network: 'waves', }); // Generate a Waves wallet from mnemonic. const wallet = multichainWallet.generateWalletFromMnemonic({ mnemonic: 'mushroom deliver work spray hire nuclear wrong deputy march six midnight outside motor differ adult', cluster: 'testnet', network: 'waves', }); // Generate a Tron wallet from mnemonic. const wallet = multichainWallet.generateWalletFromMnemonic({ mnemonic: 'mushroom deliver work spray hire nuclear wrong deputy march six midnight outside motor differ adult', network: 'tron', }); ``` #### Response ```javascript { address: '0x627306090abaB3A6e1400e9345bC60c78a8BEf57', privateKey: '0xc87509a1c067bbde78beb793e6fa76530b6382a4c0241e5e4a9ec0a0f44dc0d3', mnemonic: 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat' } ``` ### Get Address from Private Key This gets the address from the private key passed in. The method accepts an object as the parameter. The parameters that this object takes are: ```javascript // Get the address from the private key on the Ethereum network. const address = multichainWallet.getAddressFromPrivateKey({ privateKey: '0f9e5c0bee6c7d06b95204ca22dea8d7f89bb04e8527a2c59e134d185d9af8ad', network: 'ethereum', }); // Get the address from the private key on the Bitcoin network. const data = multichainWallet.getAddressFromPrivateKey({ privateKey: 'KxqTGtCMnX6oL9rxynDKCRJXt64Gm5ame4AEQcYncFhSSUxFBkeJ', network: 'bitcoin', // 'bitcoin' or 'bitcoin-testnet' }); // Get the address from the private key on the Solana network. const address = multichainWallet.getAddressFromPrivateKey({ privateKey: 'bXXgTj2cgXMFAGpLHkF5GhnoNeUpmcJDsxXDhXQhQhL2BDpJumdwMGeC5Cs66stsN3GfkMH8oyHu24dnojKbtfp', network: 'solana', }); // Get the address from the private key on the Tron network. const address = multichainWallet.getAddressFromPrivateKey({ privateKey: 'fa01dc6efd5fd64e4897aadf255ae715cf34138c7ada5f6a7efb0bdd0bd9c8c4', network: 'tron', }); ``` #### Response ```javascript { address: '0x1C082D1052fb44134a408651c01148aDBFcCe7Fe'; } ``` ### Get Transaction This gets the transaction receipt of a transaction from the transaction hash. The method accepts an object as the parameter. The parameters that this object takes are: ```javascript // Get the transaction receipt on Ethereum network. const receipt = await multichainWallet.getTransaction({ hash: '0x5a90cea37e3a5dbee6e10190ff5a3769ad27a0c6f625458682104e26e0491055', network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', }); // NOTE - For other EVM compatible blockchains all you have to do is change the rpcUrl. // Get the transaction receipt on Bitcoin network. const receipt = await getTransaction({ network: 'bitcoin-testnet', // 'bitcoin' or 'bitcoin-testnet' hash: '4f6c3661e0e6d190dbdfb6c0791396fccee653c5bf4a5249b049341c2b539ee1', }); // Get the transaction receipt on Solana network. const receipt = await multichainWallet.getTransaction({ rpcUrl: 'https://api.devnet.solana.com', hash: 'CkG1ynQ2vN8bmNsBUKG8ix3moUUfELWwd8K2f7mmqDd7LifFFfgyFhBux6t22AncbY4NR3PsEU3DbH7mDBMXWk7', network: 'solana', }); // Get the transaction receipt on Waves network. const receipt = await multichainWallet.getTransaction({ rpcUrl: 'https://nodes-testnet.wavesnodes.com', hash: 'Barwuj1gCiQ9wCfLQ1nbdz2CSyQXLnRxnDEubtdTwJpd', network: 'waves', }); // Get the transaction receipt on Tron network. const receipt = await multichainWallet.getTransaction({ hash: '34f27486cbe693d5182c4b5e18c1779d918668f86f396ed62a279d8b519b81cc', network: 'tron', rpcUrl: 'https://nile.trongrid.io', }); ``` #### Response ```javascript { object; } ``` ### Transfer This transfers the amount of tokens from the source address to the destination address It takes in an object as the parameter. It allows for the transfer of the following: #### Ethereum Network Allows for the transfer of ETH, and overriding of transactions. ```javascript // Transferring ETH from one address to another. const transfer = await multichainWallet.transfer({ recipientAddress: '0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22', amount: 1, network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', privateKey: '0f9e5c0bee6c7d06b95204ca22dea8d7f89bb04e8527a2c59e134d185d9af8ad', gasPrice: '10', // Gas price is in Gwei. Leave empty to use default gas price data: 'Money for transportation', // Send a message }); // NOTE - For other EVM compatible blockchains all you have to do is change the rpcUrl. // Transferring ERC20 tokens from one address to another. const transfer = await multichainWallet.transfer({ recipientAddress: '0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22', amount: 10, network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', privateKey: '0f9e5c0bee6c7d06b95204ca22dea8d7f89bb04e8527a2c59e134d185d9af8ad', gasPrice: '10', // Gas price is in Gwei. leave empty to use default gas price tokenAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7', }); // NOTE - For other EVM compatible blockchains all you have to do is change the rpcUrl. ``` The optional parameters that the object takes in are: gas price, nonce, and data. - The gas price is the price of gas in Gwei. The higher the gas price, the faster the transaction will be. It's best to use a higher gas price than the default. - The nonce is the number of transactions that have been sent from the source address and is used to ensure that the transaction is unique. The transaction is unique because the nonce is incremented each time a transaction is sent. - The data is a string parameter used to pass across a message through the transaction. Can only be used on transfer of ETH. ```javascript // Overriding pending ETH transaction. const transfer = await multichainWallet.transfer({ recipientAddress: '0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22', amount: 0, network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', privateKey: '0f9e5c0bee6c7d06b95204ca22dea8d7f89bb04e8527a2c59e134d185d9af8ad', gasPrice: '10', nonce: 1, // The pending transaction nonce data: 'Money for feeding', // Send a message }); // Overriding ERC20 token pending transaction. const transfer = await multichainWallet.transfer({ recipientAddress: '0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22', amount: 0, network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', privateKey: '0f9e5c0bee6c7d06b95204ca22dea8d7f89bb04e8527a2c59e134d185d9af8ad', gasPrice: '10', tokenAddress: '0xdac17f958d2ee523a2206206994597c13d831ec7', nonce: 1, // The pending transaction nonce }); ``` #### Response ```javascript { object; } ``` #### Bitcoin Network Allows for the transfer of BTC from one address to another. ```javascript // Transferring BTC from one address to another. const response = await multichainWallet.transfer({ privateKey: 'L3tSvMViDit1GSp7mbV2xFCGv6M45kDNuSyNY9xyUxmUPBFrBkc4', recipientAddress: '2NAhbS79dEUeqcnbC27UppwnjoVSwET5bat', amount: 0.001, network: 'bitcoin-testnet', // 'bitcoin' or 'bitcoin-testnet' fee: 10000, // Optional param default value is 10000 subtractFee: false, // Optional param default value is false }); ``` #### Response ```javascript { object; } ``` #### Solana Network Allows for the transfer of SOL and tokens. ```javascript // Transferring SOL from one address to another. const transfer = await multichainWallet.transfer({ recipientAddress: '9DSRMyr3EfxPzxZo9wMBPku7mvcazHTHfyjhcfw5yucA', amount: 1, network: 'solana', rpcUrl: 'https://api.devnet.solana.com', privateKey: 'bXXgTj2cgXMFAGpLHkF5GhnoNeUpmcJDsxXDhXQhQhL2BDpJumdwMGeC5Cs66stsN3GfkMH8oyHu24dnojKbtfp', }); // Transferring a token from one address to another. const transfer = await multichainWallet.transfer({ recipientAddress: '9DSRMyr3EfxPzxZo9wMBPku7mvcazHTHfyjhcfw5yucA', tokenAddress: 'DV2exYApRFWEVb9oQkedLRYeSm8ccxNReLfEksEE5FZm', amount: 1, network: 'solana', rpcUrl: 'https://api.devnet.solana.com', privateKey: 'h5KUPKU4z8c9nhMCQsvCLq4q6Xn9XK1B1cKjC9bJVLQLgJDvknKCBtZdHKDoKBHuATnSYaHRvjJSDdBWN8P67hh', }); ``` #### Response ```javascript { hash: '3nGq2yczqCpm8bF2dyvdPtXpnFLJ1oGWkDfD6neLbRay8SjNqYNhWQBKE1ZFunxvFhJ47FyT6igNpYPP293jXCZk'; } ``` #### Waves Network Allows for the transfer of WAVES and tokens. ```javascript // Transferring WAVES from one address to another. const response = await multichainWallet.transfer({ recipientAddress: '3N4x4ML4D6fiU18Tpw86puRoN78FCTs9VQu', amount: 0.0001, network: 'waves', rpcUrl: 'https://nodes-testnet.wavesnodes.com', privateKey: 'mushroom deliver work spray hire nuclear wrong deputy march six midnight outside motor differ adult', }); // Transferring a token from one address to another. const transfer = await multichainWallet.transfer({ recipientAddress: '3N4x4ML4D6fiU18Tpw86puRoN78FCTs9VQu', tokenAddress: '39pnv8FVf3BX3xwtC6uhFxffy2sE3seXCPsf25eNn6qG', amount: 1, network: 'waves', rpcUrl: 'https://nodes-testnet.wavesnodes.com', privateKey: 'mushroom deliver work spray hire nuclear wrong deputy march six midnight outside motor differ adult', }); ``` #### Response ```javascript { type: 4, id: '9CbA3dsyEvbdf52gqeBvVkjEP5zBmCQPANjguNznHryf', fee: 100000, feeAssetId: null, timestamp: 1661781621495, version: 3, chainId: 84, sender: '3NBE5tjbQn9BHczjD6NSSuFDKVHKsBRzTv9', senderPublicKey: '8JEFTsZfqp2Y7HpmaxqgGtiMLfsNAAq3bMkwZwGpUWPV', proofs: [ '5m4DpkkYkVY4xkiMNyrNpiVUHNNAtyJrSH5UCkjWSnLTAabkCefLx6wWTFT1Xcb7K8C31H7ndZAX8mWrJLMrsqxr' ], recipient: '3N4x4ML4D6fiU18Tpw86puRoN78FCTs9VQu', assetId: '39pnv8FVf3BX3xwtC6uhFxffy2sE3seXCPsf25eNn6qG', feeAsset: null, amount: 100000000, attachment: '' } ``` #### Tron Network Allows for the transfer of TRX and TRC20 tokens. ```javascript // Transferring TRX from one address to another. const transfer = await multichainWallet.transfer({ rpcUrl: 'https://nile.trongrid.io', recipientAddress: 'TEVuGfgLkQCVXs7EtjMiQp3ZSSUkEbNnVS', amount: 0.0001, network: 'tron', privateKey: 'fa01dc6efd5fd64e4897aadf255ae715cf34138c7ada5f6a7efb0bdd0bd9c8c4', }); // Transferring TRC20 tokens from one address to another. const transfer = await multichainWallet.transfer({ rpcUrl: 'https://nile.trongrid.io', recipientAddress: 'TEVuGfgLkQCVXs7EtjMiQp3ZSSUkEbNnVS', privateKey: 'fa01dc6efd5fd64e4897aadf255ae715cf34138c7ada5f6a7efb0bdd0bd9c8c4', amount: 0.1, network: 'tron', tokenAddress: 'TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj', }); ``` #### Response ```javascript { txid ..object; } ``` ### Encryptions #### Encrypt Private Key It supports encryption of ethereum and other EVM compatible chains private keys. ```javascript // encrypt private key. const encrypted = await multichainWallet.getEncryptedJsonFromPrivateKey({ network: 'ethereum', privateKey: '0f9e5c0bee6c7d06b95204ca22dea8d7f89bb04e8527a2c59e134d185d9af8ad', password: 'walletpassword', }); ``` #### Response ```javascript { json: '{"address":"1c082d1052fb44134a408651c01148adbfcce7fe","id":"abfb9f10-165a-4b7a-935d-51729f10c310","version":3,"Crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"f3fac53ee2d76c293977d1af3a7d73bb"},"ciphertext":"c5034579cdf32d7a612c9a83801aad899abfebb7436712f363ecf89546bbcbce","kdf":"scrypt","kdfparams":{"salt":"78ff80ece5d681b1aecd829526388472d1889da233229fa5c1416e8f2035b7a8","n":131072,"dklen":32,"p":1,"r":8},"mac":"0f70eca6138ffe60b174308b6ab7a8a81a0d2b662e2cf5d8727443cf12af766c"}}'; } ``` #### Decrypt Encrypted JSON It supports decryption of encrypted JSONs (A.K.A keystore). ```javascript // decrypting encrypted JSON. const decrypted = await multichainWallet.getWalletFromEncryptedJson({ network: 'ethereum', json: '{"address":"1c082d1052fb44134a408651c01148adbfcce7fe","id":"abfb9f10-165a-4b7a-935d-51729f10c310","version":3,"Crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"f3fac53ee2d76c293977d1af3a7d73bb"},"ciphertext":"c5034579cdf32d7a612c9a83801aad899abfebb7436712f363ecf89546bbcbce","kdf":"scrypt","kdfparams":{"salt":"78ff80ece5d681b1aecd829526388472d1889da233229fa5c1416e8f2035b7a8","n":131072,"dklen":32,"p":1,"r":8},"mac":"0f70eca6138ffe60b174308b6ab7a8a81a0d2b662e2cf5d8727443cf12af766c"}}', password: 'walletpassword', }); ``` #### Response ```javascript { privateKey: '0x0f9e5c0bee6c7d06b95204ca22dea8d7f89bb04e8527a2c59e134d185d9af8ad', address: '0x1C082D1052fb44134a408651c01148aDBFcCe7Fe' } ``` ### Token Info #### Get ERC20 Token Info Allows for fetching ERC20 tokens info from compatible blockchains by the token address ```javascript // getting token info. const info = await multichainWallet.getTokenInfo({ address: '0x7fe03a082fd18a80a7dbd55e9b216bcf540557e4', network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', }); // NOTE - For other EVM compatible blockchains all you have to do is change the rpcUrl. ``` #### Response ```javascript { name: 'Mocked USDT', symbol: 'USDT', decimals: 6, address: '0x7fe03a082fd18a80a7dbd55e9b216bcf540557e4', totalSupply: '1000000000000' } ``` #### Get SPL Token Info Allows for fetching SPL tokens info on the solana by the token address. Note: Token has to be available on the solana token list registry ```javascript // getting token info. const info = await multichainWallet.getTokenInfo({ address: '7Xn4mM868daxsGVJmaGrYxg8CZiuqBnDwUse66s5ALmr', network: 'solana', rpcUrl: 'https://api.devnet.solana.com', cluster: 'devnet', }); ``` #### Response ```javascript { object; } ``` #### Get ERC20 Token Info Allows for fetching ERC20 tokens info from compatible blockchains by the token address ```javascript // getting token info. const info = await multichainWallet.getTokenInfo({ address: '0x7fe03a082fd18a80a7dbd55e9b216bcf540557e4', network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', }); // NOTE - For other EVM compatible blockchains all you have to do is change the rpcUrl. ``` #### Response ```javascript { name: 'Mocked USDT', symbol: 'USDT', decimals: 6, address: '0x7fe03a082fd18a80a7dbd55e9b216bcf540557e4', totalSupply: '1000000000000' } ``` #### Get Tron Token Info Allows for fetching Tron token info ```javascript const info = await multichainWallet.getTokenInfo({ address: 'TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj', network: 'tron', rpcUrl: 'https://nile.trongrid.io', }); ``` #### Response ```javascript { name: 'Tether USD', symbol: 'USDT', address: 'TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj', decimals: 6, totalSupply: '2100000000000000' } ``` ### Smart Contract Call This can be used to make custom smart contract interaction by specifying the contract ABI and function types. #### Ethereum network ```javascript // Calling a write smart contract function. const data = await multichainWallet.smartContractCall({ rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', network: 'ethereum', contractAddress: '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa', method: 'transfer', methodType: 'write', params: ['0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22', '1000000000000000000'], contractAbi: [ { constant: false, inputs: [ { name: '_to', type: 'address' }, { name: '_value', type: 'uint256' }, ], name: 'transfer', outputs: [{ name: '', type: 'bool' }], payable: false, stateMutability: 'nonpayable', type: 'function', }, ], privateKey: '0f9e5c0bee6c7d06b95204ca22dea8d7f89bb04e8527a2c59e134d185d9af8ad', }); // NOTE - For other EVM compatible blockchains all you have to do is change the rpcUrl. // calling a read smart contract function. const data = await multichainWallet.smartContractCall({ rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', network: 'ethereum', contractAddress: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', method: 'factory', methodType: 'read', params: [], contractAbi: [ { inputs: [], name: 'factory', outputs: [{ internalType: 'address', name: '', type: 'address' }], stateMutability: 'view', type: 'function', }, ], }); // NOTE - For other EVM compatible blockchains all you have to do is change the rpcUrl. ``` #### Waves network ```javascript // calling a read smart contract function. const data = await multichainWallet.smartContractCall({ network: 'waves', methodType: 'read', rpcUrl: 'https://nodes-testnet.wavesnodes.com', contractAddress: '3N9uzrTiArce1h9VCqK3QUUZmFqBgg5rZSW', method: '3N1gVpA5MVY4WsMpzQ6RfcscpDDdqBbLx6n_balance', params: [], }); // calling a write smart contract function. const data = await multichainWallet.smartContractCall({ network: 'waves', methodType: 'write', rpcUrl: 'https://nodes-testnet.wavesnodes.com', contractAddress: '3N9uzrTiArce1h9VCqK3QUUZmFqBgg5rZSW', privateKey: 'mushroom deliver work spray hire nuclear wrong deputy march six midnight outside motor differ adult', method: 'deposit', payment: [{ assetId: null, amount: 1000 }], params: [], }); ``` #### Tron network ```javascript // Calling a write smart contract function. const data = await multichainWallet.smartContractCall({ network: 'tron', rpcUrl: 'https://nile.trongrid.io', contractAddress: 'TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj', method: 'transfer(address,uint256)', methodType: 'write', contractAbi: [ { constant: false, inputs: [ { name: '_to', type: 'address' }, { name: '_value', type: 'uint256' }, ], name: 'transfer', outputs: [{ name: '', type: 'bool' }], payable: false, stateMutability: 'nonpayable', type: 'function', }, ], params: [ { type: 'address', value: 'TEVuGfgLkQCVXs7EtjMiQp3ZSSUkEbNnVS' }, { type: 'uint256', value: 1000000 }, ], privateKey: 'fa01dc6efd5fd64e4897aadf255ae715cf34138c7ada5f6a7efb0bdd0bd9c8c4', }); // calling a read smart contract function. const data = await multichainWallet.smartContractCall({ network: 'tron', rpcUrl: 'https://nile.trongrid.io', contractAddress: 'TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj', method: 'balanceOf', methodType: 'read', contractAbi: [ { constant: true, inputs: [{ name: '_owner', type: 'address' }], name: 'balanceOf', outputs: [{ name: 'balance', type: 'uint256' }], payable: false, stateMutability: 'view', type: 'function', }, ], params: [{ type: 'address', value: 'TEVuGfgLkQCVXs7EtjMiQp3ZSSUkEbNnVS' }], }); ``` Some of the parameters available in this function are: - The **method** parameter is the name of the smart contract function to be interacted with. - The **method type** is the type of action the method is meant to perform. - The **params** parameter is the parameter of the smart contract function if it requires any. It must be in the same order as the smart contract function. If the smart contract function does not require any parameters, leave it as an empty array. The optional parameters that the object takes in are: value, contractAbi, gas price, gas limit, nonce, and private key. - The **value** is the amount of ETH you want to send while interacting with the function. - The **contractAbi** is the ABI of the smart contract. Every smart contract has an ABI that can be used to interact with the smart contract functions. If this is not specified. You can interact with all the [ERC20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md) token standard functions by default. - The **gas price** is the price of gas in Gwei. The higher the gas price, the faster the transaction gets mined. It's best to use a higher gas price than the default. - The **gas limit** is the maximum amount of gas you are willing to pay for the transaction. - The **nonce** is the number of transactions that have been sent from the source address and is used to ensure that the transaction is unique. The transaction is unique because the nonce is incremented each time a transaction is sent. - The **private key** is a string parameter that can be passed to use as the signer. It is used to sign the transaction. This parameter is not needed when calling a smart contract read function. - The **payment** (only on Waves) payment is the payment (WAVES or Waves Asset) sent to the smart contract while interacting with it. If the smart contract function does not require any payment. - The **feeLimt** (only on Tron) is the max amount of fee you're willing to pay for the transaction #### Response ```javascript { data: object; } ``` ### Want to contribute? Contributions are welcome! Kindly refer to the [contribution guidelines](CONTRIBUTING.md). ================================================ FILE: jest.config.js ================================================ const config = { testTimeout: 100000, moduleNameMapper: { '^axios$': 'axios/dist/axios.js', }, globals: { 'ts-jest': { isolatedModules: true, }, }, setupFiles: ['./jest.setup.js'], }; module.exports = config; ================================================ FILE: jest.setup.js ================================================ const crypto = require('crypto'); Object.defineProperty(global, 'crypto', { value: { getRandomValues: arr => crypto.randomFillSync(arr), }, }); try { const noble = require('@noble/hashes/utils'); if (noble) { noble.randomBytes = length => { return crypto.randomBytes(length); }; } } catch (e) { console.warn('Could not patch @noble/hashes'); } ================================================ FILE: package.json ================================================ { "version": "0.2.10", "license": "MIT", "main": "dist/index.js", "typings": "dist/index.d.ts", "files": [ "dist", "src" ], "engines": { "node": ">=10" }, "scripts": { "start": "tsdx watch", "build": "tsdx build", "test": "tsdx test", "test:btc": "tsdx test --testPathPattern=bitcoin", "test:eth": "tsdx test --testPathPattern=ethereum", "test:sol": "tsdx test --testPathPattern=solana", "test:tron": "tsdx test --testPathPattern=tron", "test:waves": "tsdx test --testPathPattern=waves", "lint": "tsdx lint", "prepare": "tsdx build", "size": "size-limit", "analyze": "size-limit --why" }, "peerDependencies": {}, "husky": { "hooks": { "pre-commit": "tsdx lint" } }, "prettier": { "printWidth": 80, "semi": true, "singleQuote": true, "trailingComma": "es5" }, "name": "multichain-crypto-wallet", "author": "Abdulfatai Suleiman ", "keywords": [ "multichain", "crypto", "wallet", "ethereum", "blockchain", "cryptocurrency", "evm", "solana", "bitcoin", "evm", "binance smart chain", "polygon", "avalanche", "waves", "cryptography", "tron", "tronweb", "TRC20", "ERC20" ], "repository": { "type": "git", "url": "https://github.com/iamnotstatic/multichain-crypto-wallet" }, "homepage": "https://github.com/iamnotstatic/multichain-crypto-wallet/blob/main/README.md", "module": "dist/multichain-crypto-wallet.esm.js", "size-limit": [ { "path": "dist/multichain-crypto-wallet.cjs.production.min.js", "limit": "500 KB" }, { "path": "dist/multichain-crypto-wallet.esm.js", "limit": "500 KB" } ], "devDependencies": { "@size-limit/preset-small-lib": "^7.0.8", "husky": "^7.0.4", "size-limit": "^7.0.8", "tsdx": "^0.14.1", "tslib": "^2.3.1", "typescript": "^4.6.3" }, "dependencies": { "@bitgo/utxo-lib": "https://github.com/ren-forks/bitgo-utxo-lib#b848585e65b42c48b98c207e72d7d3006c9a5da0", "@solana/spl-token": "^0.2.0", "@solana/web3.js": "^1.39.1", "@truffle/hdwallet-provider": "^2.0.5", "@waves/ts-lib-crypto": "^1.4.4-beta.1", "@waves/waves-transactions": "^4.2.10", "axios": "^0.27.2", "bip32": "^3.0.1", "bip39": "^3.1.0", "bitcoinjs-lib": "^6.0.1", "bs58": "^5.0.0", "buffer-layout": "^1.2.2", "ecpair": "^2.0.1", "ed25519-hd-key": "^1.2.0", "ethers": "^5.6.2", "immutable": "^4.1.0", "tiny-secp256k1": "^2.2.1", "tronweb": "^6.0.2", "url": "^0.11.0" }, "browser": { "stream": false } } ================================================ FILE: src/abis/erc20.json ================================================ [ { "constant": true, "inputs": [], "name": "name", "outputs": [{ "name": "", "type": "string" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "approve", "outputs": [{ "name": "", "type": "bool" }], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "totalSupply", "outputs": [{ "name": "", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_from", "type": "address" }, { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transferFrom", "outputs": [{ "name": "", "type": "bool" }], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "decimals", "outputs": [{ "name": "", "type": "uint8" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [{ "name": "_owner", "type": "address" }], "name": "balanceOf", "outputs": [{ "name": "balance", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "symbol", "outputs": [{ "name": "", "type": "string" }], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transfer", "outputs": [{ "name": "", "type": "bool" }], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [ { "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" } ], "name": "allowance", "outputs": [{ "name": "", "type": "uint256" }], "payable": false, "stateMutability": "view", "type": "function" }, { "payable": true, "stateMutability": "payable", "type": "fallback" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "owner", "type": "address" }, { "indexed": true, "name": "spender", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "from", "type": "address" }, { "indexed": true, "name": "to", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" } ] ================================================ FILE: src/common/apis/blockchain.ts ================================================ import axios from 'axios'; import { URLSearchParams } from 'url'; import { sortUTXOs, UTXO } from '../utils/utxo'; import { DEFAULT_TIMEOUT } from './timeout'; export enum BlockchainNetwork { Bitcoin = 'btc', BitcoinCash = 'bch', BitcoinTestnet = 'btc-testnet', BitcoinCashTestnet = 'bch-testnet', } interface BlockchainTransaction { txid: string; // "550b293355f5274e513c65f846311fd5817d13bcfcd492ab94ff2725ba94f21e" size: number; // 124 version: number; // 1 locktime: number; // 0 fee: number; // 0 inputs: [ { coinbase: boolean; // true txid: string; // "0000000000000000000000000000000000000000000000000000000000000000" output: number; // 4294967295 sigscript: string; // "03e3b9162f696d2f" sequence: number; // 4294967295 pkscript: null; value: null; address: null; witness: unknown[]; } ]; outputs: [ { address: string; // "bchtest:qp7k5sm9dcmvse2rgmkj2ktylm9fgqcnv5kp2hrs0h" pkscript: string; // "76a9147d6a43656e36c8654346ed255964feca9403136588ac" value: number; // 39062500 spent: boolean; // false spender: null; }, { address: null; pkscript: string; // "6a14883805620000000000000000faee4177fe240000" value: number; // 0 spent: boolean; // false spender: null; } ]; block: { height?: number; // 1489379 position?: number; // 0 mempool?: number; }; deleted: boolean; // false time: number; // 1646186011 rbf: boolean; // false weight: number; // 496 } const fetchLatestBlock = async ( network: BlockchainNetwork ): Promise => { const statsUrl = `https://api.blockchain.info/haskoin-store/${network}/block/best?notx=true`; const statsResponse = (await axios.get<{ height: number }>(statsUrl)).data; return statsResponse.height; }; const fetchUTXO = (network: BlockchainNetwork) => async ( txHash: string, vOut: number ): Promise => { const url = `https://api.blockchain.info/haskoin-store/${network}/transaction/${txHash}`; const response = ( await axios.get(`${url}`, { timeout: DEFAULT_TIMEOUT, }) ).data; const confirmations = !response.block || !response.block.height ? 0 : Math.max( (await fetchLatestBlock(network)) - response.block.height + 1, 0 ); return { txHash, block: response.block && response.block.height ? response.block.height : 0, amount: response.outputs[vOut].value, confirmations, }; }; const fetchUTXOs = (network: BlockchainNetwork) => async ( address: string, confirmations: number, limit: number = 25, offset: number = 0 ): Promise => fetchTXs(network)(address, confirmations, limit, offset, true); const fetchTXs = (network: BlockchainNetwork) => async ( address: string, confirmations: number = 0, limit: number = 25, offset: number = 0, onlyUnspent: boolean = false ): Promise => { const url = `https://api.blockchain.info/haskoin-store/${network}/address/${address}/transactions/full?limit=${limit}&offset=${offset}`; const response = ( await axios.get(url, { timeout: DEFAULT_TIMEOUT, }) ).data; let latestBlock: number | undefined; const received: UTXO[] = []; for (const tx of response) { latestBlock = latestBlock || (await fetchLatestBlock(network)); const txConfirmations = tx.block && tx.block.height ? Math.max(latestBlock - tx.block.height + 1, 0) : 0; for (let i = 0; i < tx.outputs.length; i++) { const vout = tx.outputs[i]; if ( vout.address === address && // If the onlyUnspent flag is true, check that the tx is unspent. (!onlyUnspent || vout.spent === false) ) { received.push({ txHash: tx.txid, amount: vout.value, vOut: i, confirmations: txConfirmations, }); } } } return received .filter(utxo => confirmations === 0 || utxo.confirmations >= confirmations) .sort(sortUTXOs); }; export const broadcastTransaction = (network: BlockchainNetwork) => async ( txHex: string ): Promise => { if (network !== BlockchainNetwork.Bitcoin) { throw new Error( `Broadcasting ${network} transactions not supported by endpoint.` ); } const url = `https://blockchain.info/pushtx`; const params = new URLSearchParams(); params.append('tx', txHex); const response = await axios.post(url, params, { timeout: DEFAULT_TIMEOUT, }); if ((response.data as any).error) { throw new Error((response.data as any).error); } return response.data; }; export const Blockchain = { networks: BlockchainNetwork, fetchUTXO, fetchUTXOs, broadcastTransaction, fetchTXs, }; ================================================ FILE: src/common/apis/blockchair.ts ================================================ import axios from 'axios'; import { sortUTXOs, UTXO } from '../utils/utxo'; import { DEFAULT_TIMEOUT } from './timeout'; const fetchUTXO = (network: string) => async ( txHash: string, vOut: number ): Promise => { const url = `https://api.blockchair.com/${network}/dashboards/transaction/${txHash}`; const response = ( await axios.get(`${url}`, { timeout: DEFAULT_TIMEOUT, }) ).data; if (!response.data[txHash]) { throw new Error(`Transaction not found.`); } const tx = response.data[txHash]; let latestBlock = response.context.state; if (latestBlock === 0) { const statsUrl = `https://api.blockchair.com/${network}/stats`; const statsResponse = (await axios.get(statsUrl)).data; latestBlock = statsResponse.data.blocks - 1; } const confirmations = tx.transaction.block_id === -1 ? 0 : Math.max(latestBlock - tx.transaction.block_id + 1, 0); return { txHash, block: tx.transaction.block_id === -1 ? 0 : tx.transaction.block_id, amount: tx.outputs[vOut].value, confirmations, }; }; const fetchUTXOs = (network: string) => async ( address: string, confirmations: number ): Promise => { const url = `https://api.blockchair.com/${network}/dashboards/address/${address}?limit=0,100`; const response = ( await axios.get(url, { timeout: DEFAULT_TIMEOUT }) ).data; let latestBlock = response.context.state; if (latestBlock === 0) { const statsUrl = `https://api.blockchair.com/${network}/stats`; const statsResponse = (await axios.get(statsUrl)).data; latestBlock = statsResponse.data.blocks - 1; } return response.data[address].utxo .map(utxo => ({ txHash: utxo.transaction_hash, amount: utxo.value, vOut: utxo.index, confirmations: utxo.block_id === -1 ? 0 : latestBlock - utxo.block_id + 1, })) .filter(utxo => confirmations === 0 || utxo.confirmations >= confirmations) .sort(sortUTXOs); }; const fetchTXs = (network: string) => async ( address: string, confirmations: number = 0, limit: number = 25 ): Promise => { const url = `https://api.blockchair.com/${network}/dashboards/address/${address}?limit=${limit},0`; const response = ( await axios.get(url, { timeout: DEFAULT_TIMEOUT }) ).data; let latestBlock = response.context.state; if (latestBlock === 0) { const statsUrl = `https://api.blockchair.com/${network}/stats`; const statsResponse = (await axios.get(statsUrl)).data; latestBlock = statsResponse.data.blocks - 1; } const txHashes = response.data[address].transactions; let txDetails: { [txHash: string]: TransactionResponse['data'][''] } = {}; // Fetch in sets of 10 for (let i = 0; i < Math.ceil(txHashes.length / 10); i++) { const txUrl = `https://api.blockchair.com/${network}/dashboards/transactions/${txHashes .slice(i * 10, (i + 1) * 10) .join(',')}`; const txResponse = ( await axios.get(txUrl, { timeout: DEFAULT_TIMEOUT, }) ).data; txDetails = { ...txDetails, ...txResponse.data, }; } const received: UTXO[] = []; for (const txHash of txHashes) { const tx = txDetails[txHash]; const txConfirmations = tx.transaction.block_id === -1 ? 0 : Math.max(latestBlock - tx.transaction.block_id + 1, 0); for (let i = 0; i < tx.outputs.length; i++) { const vout = tx.outputs[i]; if (vout.recipient === address) { received.push({ txHash: tx.transaction.hash, amount: vout.value, vOut: i, confirmations: txConfirmations, }); } } } return received .filter(utxo => confirmations === 0 || utxo.confirmations >= confirmations) .sort(sortUTXOs); }; export const broadcastTransaction = (network: string) => async ( txHex: string ): Promise => { const url = `https://api.blockchair.com/${network}/push/transaction`; const response = await axios.post<{ data: { transaction_hash: string } }>( url, { data: txHex }, { timeout: DEFAULT_TIMEOUT } ); if ((response.data as any).error) { throw new Error((response.data as any).error); } return response.data.data.transaction_hash; }; enum Networks { BITCOIN = 'bitcoin', BITCOIN_CASH = 'bitcoin-cash', LITECOIN = 'litecoin', BITCOIN_SV = 'bitcoin-sv', DOGECOIN = 'dogecoin', DASH = 'dash', GROESTLCOIN = 'groestlcoin', BITCOIN_TESTNET = 'bitcoin/testnet', } export const Blockchair = { networks: Networks, fetchUTXO, fetchUTXOs, broadcastTransaction, fetchTXs, }; interface BlockchairContext { code: number; // 200 source: string; // "D" time: number; // 0.2793741226196289 limit: string; // "0,100" offset: string; // "0,0" results: number; // 0 state: number; // 611807 cache: { live: boolean; duration: number; since: string; until: string; time: null; }; api: { version: string; last_major_update: string; next_major_update: null | string; documentation: 'https://blockchair.com/api/docs'; notice?: string; }; } /** TYPES */ interface AddressResponse { data: { [addr: string]: { address: { type: 'pubkey'; script_hex: string; // "4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac" balance: number; // 6820995737 balance_usd: number; // 527582.1930705917 received: number; // 6820995737 received_usd: number; // 15963.6287 spent: number; // 0 spent_usd: number; // 0 output_count: number; // 1924 unspent_output_count: number; // 1924 first_seen_receiving: string; // "2009-01-03 18:15:05" last_seen_receiving: string; // "2020-01-07 22:38:01" first_seen_spending: null; last_seen_spending: null; transaction_count: null; }; transactions: string[]; utxo: Array<{ block_id: number; // 611802, transaction_hash: string; // "f3c8e9b5964703f5634261a6769d6c9d836e3175fbfbebd204837aa15ef382f7" index: number; // 29 value: number; // 7043123 }>; }; }; context: BlockchairContext; } interface InputOrOutput { block_id: number; // 9 transaction_id: number; // 9 index: number; // 0 transaction_hash: string; // "0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9" date: string; // "2009-01-09" time: string; // "2009-01-09 03:54:39" value: number; // 5000000000 value_usd: number; // 0.5 recipient: string; // "12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S" type: string; // "pubkey" script_hex: string; // "410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac" is_from_coinbase: boolean; // true is_spendable: boolean; // true is_spent: boolean; // true spending_block_id: number; // 170, spending_transaction_id: number; // 171, spending_index: number; // 0, spending_transaction_hash: string; // "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16" spending_date: string; // "2009-01-12" spending_time: string; // "2009-01-12 03:30:25" spending_value_usd: number; // 0.5 spending_sequence: number; // 4294967295 spending_signature_hex: string; // "47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901" spending_witness: string; // "" lifespan: number; // 257746 cdd: number; // 149.15856481481 } interface TransactionResponse { data: { [utxo: string]: { transaction: { block_id: number; // 170 id: number; // 171 hash: string; // "f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16" date: string; // "2009-01-12" time: string; // "2009-01-12 03:30:25" size: number; // 275 weight: number; // 1100 version: number; // 1 lock_time: number; // 0 is_coinbase: boolean; // false has_witness: boolean; // false input_count: number; // 1 output_count: number; // 2 input_total: number; // 5000000000 input_total_usd: number; // 0.5 output_total: number; // 5000000000 output_total_usd: number; // 0.5 fee: number; // 0 fee_usd: number; // 0 fee_per_kb: number; // 0 fee_per_kb_usd: number; // 0 fee_per_kwu: number; // 0 fee_per_kwu_usd: number; // 0 cdd_total: number; // 149.15856481481 is_rbf: boolean; }; inputs: InputOrOutput[]; outputs: InputOrOutput[]; }; }; context: BlockchairContext; } ================================================ FILE: src/common/apis/blockstream.ts ================================================ import axios from 'axios'; import { sortUTXOs, UTXO } from '../utils/utxo'; import { DEFAULT_TIMEOUT } from './timeout'; interface BlockstreamUTXO { status: | { confirmed: false; } | { confirmed: true; block_height: number; block_hash: string; block_time: number; }; txid: string; value: number; vout: vout; // vout is a number for utxos, or an array of utxos for a tx } interface BlockstreamTX extends BlockstreamUTXO< Array<{ scriptpubkey: string; scriptpubkey_asm: string; scriptpubkey_type: string; scriptpubkey_address: string; value: number; // e.g. 1034439 }> > { version: number; locktime: number; vin: Array<{ txid: string; vout: number; prevout: any; scriptsig: string; scriptsig_asm: string; is_coinbase: false; sequence: number; }>; size: number; weight: number; fee: number; } const getAPIUrl = (testnet: boolean) => `https://blockstream.info/${testnet ? 'testnet/' : ''}api`; const fetchUTXO = (testnet: boolean) => async ( txHash: string, vOut: number ): Promise => { const apiUrl = getAPIUrl(testnet); const utxo = ( await axios.get(`${apiUrl}/tx/${txHash}`, { timeout: DEFAULT_TIMEOUT, }) ).data; const heightResponse = ( await axios.get(`${apiUrl}/blocks/tip/height`, { timeout: DEFAULT_TIMEOUT, }) ).data; const confirmations = utxo.status.confirmed ? Math.max(1 + parseInt(heightResponse, 10) - utxo.status.block_height, 0) : 0; return { txHash, block: utxo.status.confirmed ? utxo.status.block_height : 0, amount: utxo.vout[vOut].value, confirmations, }; }; const fetchUTXOs = (testnet: boolean) => async ( address: string, confirmations: number ): Promise => { const apiUrl = getAPIUrl(testnet); const response = await axios.get>( `${apiUrl}/address/${address}/utxo`, { timeout: DEFAULT_TIMEOUT } ); const heightResponse = await axios.get( `${apiUrl}/blocks/tip/height`, { timeout: DEFAULT_TIMEOUT } ); return response.data .map(utxo => ({ txHash: utxo.txid, amount: utxo.value, vOut: utxo.vout, confirmations: utxo.status.confirmed ? 1 + parseInt(heightResponse.data, 10) - utxo.status.block_height : 0, })) .filter(utxo => confirmations === 0 || utxo.confirmations >= confirmations) .sort(sortUTXOs); }; const fetchTXs = (testnet: boolean) => async ( address: string, confirmations: number = 0 ): Promise => { const apiUrl = getAPIUrl(testnet); const response = await axios.get>( `${apiUrl}/address/${address}/txs`, { timeout: DEFAULT_TIMEOUT } ); const heightResponse = await axios.get( `${apiUrl}/blocks/tip/height`, { timeout: DEFAULT_TIMEOUT } ); const received: UTXO[] = []; for (const tx of response.data) { for (let i = 0; i < tx.vout.length; i++) { const vout = tx.vout[i]; if (vout.scriptpubkey_address === address) { received.push({ txHash: tx.txid, amount: vout.value, vOut: i, confirmations: tx.status.confirmed ? 1 + parseInt(heightResponse.data, 10) - tx.status.block_height : 0, }); } } } return received .filter(utxo => confirmations === 0 || utxo.confirmations >= confirmations) .sort(sortUTXOs); }; const broadcastTransaction = (testnet: boolean) => async ( txHex: string ): Promise => { const apiUrl = getAPIUrl(testnet); const response = await axios.post(`${apiUrl}/tx`, txHex, { timeout: DEFAULT_TIMEOUT, }); return response.data; }; export const Blockstream = { fetchUTXO, fetchUTXOs, broadcastTransaction, fetchTXs, }; ================================================ FILE: src/common/apis/electrumx.ts ================================================ import axios from 'axios'; import { sortUTXOs, UTXO } from '../utils/utxo'; const getURL = (network: string, testnet: boolean) => `https://multichain-web-proxy.herokuapp.com/electrumx-${network}-${ testnet ? 'testnet' : 'mainnet' }`; const fetchUTXOs = (network: string, testnet: boolean) => async ( address: string, confirmations: number, scriptHash?: string ): Promise => { if (!scriptHash) { throw new Error('Must provide script hash.' + address); } const url = getURL(network, testnet); const latestBlock = ( await axios.post<{ result: { hex: string; height: number; }; error: null; id: number; }>(url, { jsonrpc: '1.0', id: '67', method: 'blockchain.scripthash.listunspent', params: [scriptHash], }) ).data.result.height; const response = await axios.post<{ result: [ { tx_hash: string; // "fd742de094de839845c1c94e4e5d1804b3f869c2b5a777fc33792e68719ce113"; tx_pos: number; // 0; height: number; // 2001083; value: number; // 100000; } ]; error: null; id: '67'; }>(url, { jsonrpc: '1.0', id: '67', method: 'blockchain.scripthash.listunspent', params: [scriptHash], }); return response.data.result .map(utxo => ({ txHash: utxo.tx_hash, amount: utxo.value, vOut: utxo.tx_pos, confirmations: utxo.height ? 1 + latestBlock - utxo.height : 0, })) .filter(utxo => confirmations === 0 || utxo.confirmations >= confirmations) .sort(sortUTXOs); }; export const ElectrumX = { fetchUTXOs, }; ================================================ FILE: src/common/apis/jsonrpc.ts ================================================ import axios from 'axios'; import { DEFAULT_TIMEOUT } from './timeout'; export const MULTICHAIN_URLS = { BTC: 'https://multichain-web-proxy.herokuapp.com/multichain-bitcoin', BTCTEST: 'https://multichain-web-proxy.herokuapp.com/multichain-bitcoin-testnet', ZEC: 'https://multichain-web-proxy.herokuapp.com/multichain-zcash', ZECTEST: 'https://multichain-web-proxy.herokuapp.com/multichain-zcash-testnet', BCH: 'https://multichain-web-proxy.herokuapp.com/multichain-bitcoincash', BCHTEST: 'https://multichain-web-proxy.herokuapp.com/multichain-bitcoincash-testnet', }; const broadcastTransaction = (url: string) => async ( txHex: string ): Promise => { const response = await axios.post<{ result: string; error: null; id: string | number; }>( url, { jsonrpc: '1.0', id: '67', method: 'sendrawtransaction', params: [txHex], }, { timeout: DEFAULT_TIMEOUT } ); return response.data.result; }; export const JSONRPC = { broadcastTransaction, }; ================================================ FILE: src/common/apis/sochain.ts ================================================ import axios from 'axios'; import { fixUTXO, fixUTXOs, sortUTXOs, UTXO } from '../utils/utxo'; import { DEFAULT_TIMEOUT } from './timeout'; export interface SoChainUTXO { txid: string; // hex string without 0x prefix value: number; // satoshis script_asm: string; script_hex: string; // hex string without 0x prefix output_no: number; confirmations: number; time: number; } export interface SoChainTX { network: string; // "BTC"; txid: string; // "756548eb92505a7214b66faa8d1a77116e92d81d40b8d5a5c997dd83d1efb53b"; blockhash: string | null; confirmations: number; // 0; time: number; // 1600217073; inputs: Array<{ input_no: number; // 0; value: string; // "0.06498884"; address: string; // "1JHKKk18HD6bgy4FKkJaVxCZpBx3hhRocf"; type: 'pubkeyhash'; script: string; // "3045022100dccc5915d63e50506c962179cd11e78e94d86b1c6815daf1ad8362e75543196a022053285e92aa92dce92745d59c5a6cafd349c4657f5c1007ca31592fda63c9437d01 0363dd6554e3d3263df30c24beaf3f4fb5b2db3d0679d92615fb2e67d697648085"; witness: null; from_output: { txid: string; // "229833c1bb68984721dba4ccfcfb092ec8ea000fd96de300baa49a2009ae5def"; output_no: number; // 1; }; }>; outputs: Array<{ output_no: 0; value: string; // "0.00980175"; address: string; // "19Hb9HH2QK5v38NAwQuEmLwngu5HJqqRjm"; type: 'pubkeyhash'; script: string; // "OP_DUP OP_HASH160 5ae429a0a453e9d3e4e350717569315092e1f917 OP_EQUALVERIFY OP_CHECKSIG"; }>; tx_hex: string; size: number; version: 1; locktime: 0; } const fetchUTXO = (network: string) => async ( txHash: string, vOut: number ): Promise => { const url = `https://sochain.com/api/v2/get_tx/${network}/${txHash}`; const response = await axios.get<{ readonly data: SoChainTX; }>(url, { timeout: DEFAULT_TIMEOUT }); const tx = response.data.data; return fixUTXO( { txHash: tx.txid, amount: parseInt(tx.outputs[vOut].value, 10), // scriptPubKey: tx.script_hex, vOut, confirmations: tx.confirmations, }, 8 ); }; const fetchUTXOs = (network: string) => async ( address: string, confirmations: number ): Promise => { const url = `https://sochain.com/api/v2/get_tx_unspent/${network}/${address}/${confirmations}`; const response = await axios.get<{ readonly data: { readonly txs: readonly SoChainUTXO[] }; }>(url, { timeout: DEFAULT_TIMEOUT }); return fixUTXOs( response.data.data.txs.map(utxo => ({ txHash: utxo.txid, amount: utxo.value, // scriptPubKey: utxo.script_hex, vOut: utxo.output_no, confirmations: utxo.confirmations, })), 8 ) .filter(utxo => confirmations === 0 || utxo.confirmations >= confirmations) .sort(sortUTXOs); }; const fetchTXs = (network: string) => async ( address: string, confirmations: number = 0 ): Promise => { const url = `https://sochain.com/api/v2/get_tx_received/${network}/${address}/${confirmations}`; const response = await axios.get<{ readonly data: { readonly txs: readonly SoChainUTXO[] }; }>(url, { timeout: DEFAULT_TIMEOUT }); return fixUTXOs( response.data.data.txs.map(utxo => ({ txHash: utxo.txid, amount: utxo.value, // scriptPubKey: utxo.script_hex, vOut: utxo.output_no, confirmations: utxo.confirmations, })), 8 ) .filter(utxo => confirmations === 0 || utxo.confirmations >= confirmations) .sort(sortUTXOs); }; const broadcastTransaction = (network: string) => async ( txHex: string ): Promise => { const response = await axios.post<{ status: 'success'; data: { network: string; txid: string; // Hex without 0x }; }>( `https://sochain.com/api/v2/send_tx/${network}`, { tx_hex: txHex }, { timeout: DEFAULT_TIMEOUT } ); return response.data.data.txid; }; export const Sochain = { fetchUTXOs, broadcastTransaction, fetchUTXO, fetchTXs, }; ================================================ FILE: src/common/apis/timeout.ts ================================================ const SECONDS = 1000; export const DEFAULT_TIMEOUT = 30 * SECONDS; ================================================ FILE: src/common/fallbacks/btc.ts ================================================ import { Blockchain, BlockchainNetwork } from '../../common/apis/blockchain'; import { Blockstream } from '../../common/apis/blockstream'; import { Sochain } from '../../common/apis/sochain'; import { Blockchair } from '../../common/apis/blockchair'; import { ElectrumX } from '../../common/apis/electrumx'; import { JSONRPC, MULTICHAIN_URLS } from '../../common/apis/jsonrpc'; import { shuffleArray } from '../utils/index'; export const _apiFallbacks = { fetchUTXO: (testnet: boolean, txHash: string, vOut: number) => [ ...shuffleArray( () => Blockstream.fetchUTXO(testnet)(txHash, vOut), () => Blockchair.fetchUTXO( testnet ? Blockchair.networks.BITCOIN_TESTNET : Blockchair.networks.BITCOIN )(txHash, vOut) ), () => Blockchain.fetchUTXO( testnet ? BlockchainNetwork.BitcoinTestnet : BlockchainNetwork.Bitcoin )(txHash, vOut), ], fetchUTXOs: ( testnet: boolean, address: string, confirmations: number, scriptHash?: string ) => [ ...shuffleArray( () => Blockstream.fetchUTXOs(testnet)(address, confirmations), () => Blockchair.fetchUTXOs( testnet ? Blockchair.networks.BITCOIN_TESTNET : Blockchair.networks.BITCOIN )(address, confirmations) ), () => Sochain.fetchUTXOs(testnet ? 'BTCTEST' : 'BTC')(address, confirmations), () => Blockchain.fetchUTXOs( testnet ? BlockchainNetwork.BitcoinTestnet : BlockchainNetwork.Bitcoin )(address, confirmations), () => ElectrumX.fetchUTXOs('bitcoin', testnet)( address, confirmations, scriptHash ), ], fetchTXs: (testnet: boolean, address: string, confirmations: number = 0) => [ ...shuffleArray( () => Blockstream.fetchTXs(testnet)(address), () => Blockchair.fetchTXs( testnet ? Blockchair.networks.BITCOIN_TESTNET : Blockchair.networks.BITCOIN )(address, confirmations), () => Sochain.fetchTXs(testnet ? 'BTCTEST' : 'BTC')(address, confirmations), () => Blockchain.fetchUTXOs( testnet ? BlockchainNetwork.BitcoinTestnet : BlockchainNetwork.Bitcoin )(address, confirmations) ), ], broadcastTransaction: (testnet: boolean, hex: string) => [ ...shuffleArray( () => Blockstream.broadcastTransaction(testnet)(hex), () => Blockchair.broadcastTransaction( testnet ? Blockchair.networks.BITCOIN_TESTNET : Blockchair.networks.BITCOIN )(hex) ), () => Sochain.broadcastTransaction(testnet ? 'BTCTEST' : 'BTC')(hex), () => JSONRPC.broadcastTransaction( testnet ? MULTICHAIN_URLS.BTCTEST : MULTICHAIN_URLS.BTC )(hex), testnet ? undefined : () => Blockchain.broadcastTransaction(BlockchainNetwork.Bitcoin)(hex), ], }; ================================================ FILE: src/common/helpers/bitcoinHelper.ts ================================================ import * as bitcoin from 'bitcoinjs-lib'; import * as ecc from 'tiny-secp256k1'; import { BIP32Factory } from 'bip32'; import { ECPairFactory } from 'ecpair'; import * as bip39 from 'bip39'; import { successResponse } from '../utils'; import BigNumber from 'bignumber.js'; import { List } from 'immutable'; import * as utxolib from '@bitgo/utxo-lib'; import { _apiFallbacks } from '../fallbacks/btc'; import { fallback, retryNTimes } from '../utils/retry'; import { BalancePayload, CreateWalletPayload, GenerateWalletFromMnemonicPayload, GetAddressFromPrivateKeyPayload, GetTransactionPayload, IResponse, Network, TransferPayload, } from '../utils/types'; import { BitgoUTXOLib } from '../libs/bitgoUtxoLib'; const bip32 = BIP32Factory(ecc); const ECPair = ECPairFactory(ecc); const createWallet = ({ network, derivationPath, }: CreateWalletPayload): IResponse => { if (derivationPath) { const purpose = derivationPath?.split('/')[1]; if (purpose !== "44'") { throw new Error('Invalid derivation path'); } } const path = derivationPath || "m/44'/0'/0'/0/0"; const mnemonic = bip39.generateMnemonic(); const seed = bip39.mnemonicToSeedSync(mnemonic); const node = bip32.fromSeed(seed); const child = node.derivePath(path); const actualNetwork = getNetwork(network); const { address } = bitcoin.payments.p2pkh({ pubkey: child.publicKey, network: actualNetwork, }); const privateKey = child.toWIF(); return successResponse({ address, privateKey, mnemonic, }); }; const generateWalletFromMnemonic = ({ network, mnemonic, derivationPath, }: GenerateWalletFromMnemonicPayload): IResponse => { if (derivationPath) { const purpose = derivationPath?.split('/')[1]; if (purpose !== "44'") { throw new Error('Invalid derivation path '); } } const seed = bip39.mnemonicToSeedSync(mnemonic); const path = derivationPath || "m/44'/0'/0'/0/0"; const node = bip32.fromSeed(seed); const child = node.derivePath(path); const actualNetwork = getNetwork(network); const { address } = bitcoin.payments.p2pkh({ pubkey: child.publicKey, network: actualNetwork, }); const privateKey = child.toWIF(); return successResponse({ address, privateKey, mnemonic, }); }; const getAddressFromPrivateKey = ({ privateKey, network, }: GetAddressFromPrivateKeyPayload): IResponse => { const actualNetwork = getNetwork(network); const keyPair = ECPair.fromWIF(privateKey); const { address } = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: actualNetwork, }); return successResponse({ address, }); }; const getBalance = async ({ address, network, }: BalancePayload): Promise => { const testnet = isTestnet(network); const endpoints = _apiFallbacks.fetchUTXOs(testnet, address, 0); const utxos = await fallback(endpoints); const bn = utxos .reduce((sum, utxo) => sum.plus(utxo.amount), new BigNumber(0)) .dividedBy(new BigNumber(10).exponentiatedBy(8)); return successResponse({ balance: bn.toNumber(), }); }; const transfer = async (args: TransferPayload): Promise => { const testnet = isTestnet(args.network); const keyPair = ECPair.fromWIF(args.privateKey); const privateKey = utxolib.ECPair.fromPrivateKeyBuffer( keyPair.privateKey, args.network === 'bitcoin' ? utxolib.networks.bitcoin : utxolib.networks.testnet ); const fromAddress = getAddressFromPrivateKey({ privateKey: args.privateKey, network: args.network, }).address; const changeAddress = fromAddress; const endpoints = _apiFallbacks.fetchUTXOs(testnet, fromAddress, 0); const utxos = List(await fallback(endpoints)) .sortBy(utxo => utxo.amount) .reverse() .toArray(); const amount = new BigNumber(args.amount.toString()); const built = await BitgoUTXOLib.buildUTXO( testnet ? utxolib.networks.testnet : utxolib.networks.bitcoin, privateKey, changeAddress, args.recipientAddress, amount.times(new BigNumber(10).exponentiatedBy(8)), utxos, { fee: args.fee, subtractFee: args.subtractFee, } ); const txHash = await retryNTimes( () => fallback(_apiFallbacks.broadcastTransaction(testnet, built.toHex())), 3 ); try { const transaction = await fallback( _apiFallbacks.fetchUTXO(testnet, txHash, 0) ); const bigAmount = new BigNumber(transaction.amount); // Convert amount from Satoshi to Bitcoin const amountToBtc = bigAmount.dividedBy( new BigNumber(10).exponentiatedBy(8) ); return successResponse({ ...transaction, amount: amountToBtc.toNumber(), }); } catch (e) { return successResponse({ txHash, }); } }; const getTransaction = async ({ hash, network, }: GetTransactionPayload): Promise => { const testnet = isTestnet(network); const transaction = await fallback(_apiFallbacks.fetchUTXO(testnet, hash, 0)); const bigAmount = new BigNumber(transaction.amount); const amount = bigAmount.dividedBy(new BigNumber(10).exponentiatedBy(8)); return successResponse({ ...transaction, amount: amount.toNumber(), }); }; function getNetwork(network: Network) { return network === 'bitcoin' ? bitcoin.networks.bitcoin : network === 'bitcoin-testnet' ? bitcoin.networks.testnet : bitcoin.networks.bitcoin; } function isTestnet(network: Network) { return network === 'bitcoin-testnet'; } export default { createWallet, generateWalletFromMnemonic, getAddressFromPrivateKey, getBalance, transfer, getTransaction, }; ================================================ FILE: src/common/helpers/ethereumHelper.ts ================================================ import provider from '../utils/ethers'; import erc20Abi from '../../abis/erc20.json'; import { ethers } from 'ethers'; import { BalancePayload, GetEncryptedJsonFromPrivateKey, GetTransactionPayload, GetWalletFromEncryptedjsonPayload, TransferPayload, IGetTokenInfoPayload, ITokenInfo, ISmartContractCallPayload, CreateWalletPayload, GetAddressFromPrivateKeyPayload, GenerateWalletFromMnemonicPayload, IResponse, } from '../utils/types'; import { successResponse } from '../utils'; interface GetContract { rpcUrl?: string; privateKey?: string; contractAddress?: string; abi?: any[]; } const getContract = async ({ contractAddress, rpcUrl, privateKey, abi, }: GetContract) => { if (!rpcUrl) { throw new Error('RPC URL is required'); } const providerInstance = provider(rpcUrl); const gasPrice = await providerInstance.getGasPrice(); const gas = ethers.BigNumber.from(21000); let nonce; let contract; let signer; const contractAbi = abi || erc20Abi; if (privateKey && contractAddress) { signer = new ethers.Wallet(privateKey, providerInstance); nonce = providerInstance.getTransactionCount(signer.getAddress()); contract = new ethers.Contract(contractAddress, contractAbi, signer); } else if (privateKey && !contractAddress) { signer = new ethers.Wallet(privateKey, providerInstance); nonce = providerInstance.getTransactionCount(signer.getAddress()); } else if (contractAddress && !privateKey) { contract = new ethers.Contract( contractAddress, contractAbi, providerInstance ); } return { contract, signer, gasPrice, gas, nonce, providerInstance, }; }; const createWallet = ({ derivationPath }: CreateWalletPayload): IResponse => { const path = derivationPath || "m/44'/60'/0'/0/0"; const wallet = ethers.Wallet.createRandom({ path, }); return successResponse({ address: wallet.address, privateKey: wallet.privateKey, mnemonic: wallet.mnemonic.phrase, }); }; const getAddressFromPrivateKey = ({ privateKey, }: GetAddressFromPrivateKeyPayload): IResponse => { const wallet = new ethers.Wallet(privateKey); return successResponse({ address: wallet.address, }); }; const generateWalletFromMnemonic = ({ mnemonic, derivationPath, }: GenerateWalletFromMnemonicPayload): IResponse => { const path = derivationPath || "m/44'/60'/0'/0/0"; const wallet = ethers.Wallet.fromMnemonic(mnemonic, path); return successResponse({ address: wallet.address, privateKey: wallet.privateKey, mnemonic: wallet.mnemonic.phrase, }); }; const getBalance = async ({ rpcUrl, tokenAddress, address, }: BalancePayload): Promise => { const { contract, providerInstance } = await getContract({ rpcUrl, contractAddress: tokenAddress, }); try { let balance; if (contract) { const decimals = await contract.decimals(); balance = await contract.balanceOf(address); return successResponse({ balance: parseFloat(ethers.utils.formatUnits(balance, decimals)), }); } balance = await providerInstance.getBalance(address); return successResponse({ balance: parseFloat(ethers.utils.formatEther(balance)), }); } catch (error) { throw error; } }; const transfer = async ({ privateKey, tokenAddress, rpcUrl, ...args }: TransferPayload): Promise => { const { contract, providerInstance, gasPrice, nonce } = await getContract({ rpcUrl, privateKey, contractAddress: tokenAddress, }); let wallet = new ethers.Wallet(privateKey, providerInstance); try { let tx; if (contract) { const decimals = await contract.decimals(); const estimatedGas = await contract.estimateGas.transfer( args.recipientAddress, ethers.utils.parseUnits(args.amount.toString(), decimals) ); tx = await contract.transfer( args.recipientAddress, ethers.utils.parseUnits(args.amount.toString(), decimals), { gasPrice: args.gasPrice ? ethers.utils.parseUnits(args.gasPrice.toString(), 'gwei') : gasPrice, nonce: args.nonce || nonce, gasLimit: args.gasLimit || estimatedGas, } ); } else { tx = await wallet.sendTransaction({ to: args.recipientAddress, value: ethers.utils.parseEther(args.amount.toString()), gasPrice: args.gasPrice ? ethers.utils.parseUnits(args.gasPrice.toString(), 'gwei') : gasPrice, nonce: args.nonce || nonce, data: args.data ? ethers.utils.hexlify(ethers.utils.toUtf8Bytes(args.data as string)) : '0x', }); } return successResponse({ ...tx, }); } catch (error) { throw error; } }; const getTransaction = async ({ hash, rpcUrl }: GetTransactionPayload): Promise => { const { providerInstance } = await getContract({ rpcUrl }); try { const tx = await providerInstance.getTransactionReceipt(hash); return successResponse({ ...tx, }); } catch (error) { throw error; } }; const getEncryptedJsonFromPrivateKey = async ( args: GetEncryptedJsonFromPrivateKey ): Promise => { const wallet = new ethers.Wallet(args.privateKey); const json = await wallet.encrypt(args.password); return successResponse({ json }); }; const getWalletFromEncryptedJson = async ( args: GetWalletFromEncryptedjsonPayload ): Promise => { const wallet = await ethers.Wallet.fromEncryptedJson( args.json, args.password ); return successResponse({ privateKey: wallet.privateKey, address: wallet.address, }); }; const getTokenInfo = async ({ address, rpcUrl }: IGetTokenInfoPayload): Promise => { const { contract } = await getContract({ contractAddress: address, rpcUrl }); if (contract) { const [name, symbol, decimals, totalSupply] = await Promise.all([ contract.name(), contract.symbol(), contract.decimals(), contract.totalSupply(), ]); const data: ITokenInfo = { name, symbol, decimals, address: contract.address, totalSupply: ethers.utils.formatUnits(totalSupply, decimals).toString(), }; return successResponse({ ...data }); } throw new Error('Contract not found'); }; const smartContractCall = async (args: ISmartContractCallPayload): Promise => { const { contract, gasPrice, nonce } = await getContract({ rpcUrl: args.rpcUrl, contractAddress: args.contractAddress, abi: args.contractAbi, privateKey: args.privateKey, }); try { let tx; let overrides = {} as any; if (args.methodType === 'read') { overrides = {}; } else if (args.methodType === 'write') { overrides = { gasPrice: args.gasPrice ? ethers.utils.parseUnits(args.gasPrice, 'gwei') : gasPrice, nonce: args.nonce || nonce, value: args.value ? ethers.utils.parseEther(args.value.toString()) : 0, }; if (args.gasLimit) { overrides.gasLimit = args.gasLimit; } } if (args.params.length > 0) { tx = await contract?.[args.method](...args.params, overrides); } else { tx = await contract?.[args.method](overrides); } return successResponse({ data: tx, }); } catch (error) { throw error; } }; export default { getBalance, createWallet, getAddressFromPrivateKey, generateWalletFromMnemonic, transfer, getTransaction, getEncryptedJsonFromPrivateKey, getWalletFromEncryptedJson, getTokenInfo, smartContractCall, }; ================================================ FILE: src/common/helpers/solanaHelper.ts ================================================ import provider from '../utils/solana'; import * as solanaWeb3 from '@solana/web3.js'; import { getOrCreateAssociatedTokenAccount, transfer as transferToken, getMint, } from '@solana/spl-token'; import { BalancePayload, CreateWalletPayload, GenerateWalletFromMnemonicPayload, GetAddressFromPrivateKeyPayload, GetTransactionPayload, IGetTokenInfoPayload, IResponse, ISplTokenInfo, ITokenInfo, TransferPayload, } from '../utils/types'; import * as bs58 from 'bs58'; import { successResponse } from '../utils'; import * as bip39 from 'bip39'; import { derivePath } from 'ed25519-hd-key'; // @ts-ignore import * as BufferLayout from 'buffer-layout'; import axios from 'axios'; export const ACCOUNT_LAYOUT = BufferLayout.struct([ BufferLayout.blob(32, 'mint'), BufferLayout.blob(32, 'owner'), BufferLayout.nu64('amount'), BufferLayout.blob(93), ]); export const chainId = { 'mainnet-beta': 101, testnet: 102, devnet: 103, }; const getConnection = (rpcUrl?: string) => { const connection = provider(rpcUrl); return connection; }; const createWallet = ({ derivationPath }: CreateWalletPayload) => { const path = derivationPath || "m/44'/501'/0'/0'"; const mnemonic = bip39.generateMnemonic(); const seed = bip39.mnemonicToSeedSync(mnemonic); const derivedSeed = derivePath(path, seed.toString('hex')).key; const keyPair = solanaWeb3.Keypair.fromSeed( (derivedSeed as unknown) as Uint8Array ); return successResponse({ address: keyPair.publicKey.toBase58(), privateKey: bs58.encode(keyPair.secretKey), mnemonic, }); }; const generateWalletFromMnemonic = ({ mnemonic, derivationPath, }: GenerateWalletFromMnemonicPayload): IResponse => { const path = derivationPath || "m/44'/501'/0'/0'"; const seed = bip39.mnemonicToSeedSync(mnemonic); const derivedSeed = derivePath(path, seed.toString('hex')).key; const keyPair = solanaWeb3.Keypair.fromSeed( (derivedSeed as unknown) as Uint8Array ); return successResponse({ address: keyPair.publicKey.toBase58(), privateKey: bs58.encode(keyPair.secretKey), mnemonic, }); }; const getAddressFromPrivateKey = ({ privateKey, }: GetAddressFromPrivateKeyPayload): IResponse => { let secretKey; if (privateKey.split(',').length > 1) { secretKey = new Uint8Array(privateKey.split(',') as any); } else { secretKey = bs58.decode(privateKey); } const keyPair = solanaWeb3.Keypair.fromSecretKey(secretKey, { skipValidation: true, }); return successResponse({ address: keyPair.publicKey.toBase58(), }); }; const getBalance = async (args: BalancePayload): Promise => { const connection = getConnection(args.rpcUrl); try { let balance; if (args.tokenAddress) { const account = await connection.getTokenAccountsByOwner( new solanaWeb3.PublicKey(args.address), { mint: new solanaWeb3.PublicKey(args.tokenAddress), } ); balance = account.value.length > 0 ? ACCOUNT_LAYOUT.decode(account.value[0].account.data).amount : 0; return successResponse({ balance: balance / solanaWeb3.LAMPORTS_PER_SOL, }); } const publicKey = new solanaWeb3.PublicKey(args.address); balance = await connection.getBalance(publicKey); return successResponse({ balance: balance / solanaWeb3.LAMPORTS_PER_SOL, }); } catch (error) { throw error; } }; const transfer = async (args: TransferPayload): Promise => { const connection = getConnection(args.rpcUrl); try { const recipient = new solanaWeb3.PublicKey(args.recipientAddress); let secretKey; let signature; if (args.privateKey.split(',').length > 1) { secretKey = new Uint8Array(args.privateKey.split(',') as any); } else { secretKey = bs58.decode(args.privateKey); } const from = solanaWeb3.Keypair.fromSecretKey(secretKey, { skipValidation: true, }); if (args.tokenAddress) { // Get token mint const mint = await getMint( connection, new solanaWeb3.PublicKey(args.tokenAddress) ); // Get the token account of the from address, and if it does not exist, create it const fromTokenAccount = await getOrCreateAssociatedTokenAccount( connection, from, mint.address, from.publicKey ); // Get the token account of the recipient address, and if it does not exist, create it const recipientTokenAccount = await getOrCreateAssociatedTokenAccount( connection, from, mint.address, recipient ); signature = await transferToken( connection, from, fromTokenAccount.address, recipientTokenAccount.address, from.publicKey, solanaWeb3.LAMPORTS_PER_SOL * args.amount ); } else { const transaction = new solanaWeb3.Transaction().add( solanaWeb3.SystemProgram.transfer({ fromPubkey: from.publicKey, toPubkey: recipient, lamports: solanaWeb3.LAMPORTS_PER_SOL * args.amount, }) ); signature = await solanaWeb3.sendAndConfirmTransaction( connection, transaction, [from] ); } const tx = await connection.getTransaction(signature, { maxSupportedTransactionVersion: 0, }); return successResponse({ ...tx, }); } catch (error) { throw error; } }; const getTransaction = async ( args: GetTransactionPayload ): Promise => { const connection = getConnection(args.rpcUrl); try { const tx = await connection.getTransaction(args.hash, { maxSupportedTransactionVersion: 0, }); return successResponse({ ...tx, }); } catch (error) { throw error; } }; const getTokenInfo = async (args: IGetTokenInfoPayload): Promise => { try { const connection = getConnection(args.rpcUrl); const tokenList = await getTokenList(args.cluster!); const token = tokenList.find(token => token.address === args.address); if (!token) { throw new Error('Token not found'); } const data: ITokenInfo = { name: token.name, symbol: token.symbol, address: token.address, decimals: token.decimals, logoUrl: token.logoURI, totalSupply: '0', }; const tokenSupply = await connection.getTokenSupply( new solanaWeb3.PublicKey(data.address) ); data.totalSupply = tokenSupply.value.uiAmount!.toString(); return successResponse({ ...data }); } catch (error) { throw error; } }; const getTokenList = async ( cluster: 'mainnet-beta' | 'testnet' | 'devnet' ): Promise => { const tokenListUrl = 'https://raw.githubusercontent.com/solana-labs/token-list/main/src/tokens/solana.tokenlist.json'; const response = await axios.get(tokenListUrl); if (response.data && response.data.tokens) { return response.data.tokens.filter( (data: ISplTokenInfo) => data.chainId === chainId[cluster] ); } return []; }; export default { getBalance, createWallet, generateWalletFromMnemonic, transfer, getAddressFromPrivateKey, getTransaction, getTokenInfo, }; ================================================ FILE: src/common/helpers/tronHelper.ts ================================================ import { TronWeb, utils } from 'tronweb'; import { successResponse } from '../utils'; import erc20Abi from '../../abis/erc20.json'; import { BalancePayload, CreateWalletPayload, GenerateWalletFromMnemonicPayload, GetAddressFromPrivateKeyPayload, GetTransactionPayload, IGetTokenInfoPayload, IResponse, ISmartContractCallPayload, TransferPayload, } from '../utils/types'; interface GetContract { rpcUrl?: string; apiKey?: string; privateKey?: string; contractAddress?: string; abi?: any[]; } const getContract = async ({ contractAddress, rpcUrl, apiKey, privateKey, abi, }: GetContract) => { if (!rpcUrl) { throw new Error('RPC URL is required'); } let tronWeb = new TronWeb({ fullHost: rpcUrl, headers: apiKey ? { 'TRON-PRO-API-KEY': apiKey } : undefined, }); if (privateKey) { tronWeb.setPrivateKey(privateKey); } let contract; if (contractAddress) { if (privateKey) { const wallet = TronWeb.address.fromPrivateKey(privateKey); tronWeb.setAddress(wallet as string); } else { tronWeb.setAddress(contractAddress); } contract = tronWeb.contract(abi || erc20Abi, contractAddress); } return { contract, tronWeb, }; }; const createWallet = ({ derivationPath }: CreateWalletPayload): IResponse => { const path = derivationPath || "m/44'/195'/0'/0/0"; const account = TronWeb.createRandom(undefined, path); return successResponse({ address: account.address, privateKey: account.privateKey, mnemonic: account.mnemonic?.phrase, }); }; const getAddressFromPrivateKey = ({ privateKey, }: GetAddressFromPrivateKeyPayload): IResponse => { const account = TronWeb.address.fromPrivateKey(privateKey); return successResponse({ address: account, }); }; const generateWalletFromMnemonic = ({ mnemonic, derivationPath, }: GenerateWalletFromMnemonicPayload): IResponse => { const path = derivationPath || "m/44'/195'/0'/0/0"; const account = TronWeb.fromMnemonic(mnemonic, path); const privateKey = account.privateKey.startsWith('0x') ? account.privateKey.substring(2) : account.privateKey; return successResponse({ address: account.address, privateKey: privateKey, mnemonic: mnemonic, }); }; const getBalance = async ({ rpcUrl, tokenAddress, address, }: BalancePayload): Promise => { const { tronWeb, contract } = await getContract({ rpcUrl, contractAddress: tokenAddress, }); try { if (contract && tokenAddress) { const balance = await contract.methods.balanceOf(address).call(); return successResponse({ balance: TronWeb.fromSun(Number(balance)), }); } const balance = await tronWeb.trx.getBalance(address); return successResponse({ balance: TronWeb.fromSun(balance).toString(), }); } catch (error) { throw error; } }; const transfer = async ({ privateKey, tokenAddress, rpcUrl, recipientAddress, amount, feeLimit, }: TransferPayload): Promise => { const { tronWeb, contract } = await getContract({ rpcUrl, privateKey, contractAddress: tokenAddress, }); try { let tx; if (contract && tokenAddress) { const amountInSun = TronWeb.toSun(amount); const functionSelector = 'transfer(address,uint256)'; const parameter = [ { type: 'address', value: recipientAddress }, { type: 'uint256', value: amountInSun.toString() }, ]; const txRaw = await tronWeb.transactionBuilder.triggerSmartContract( tokenAddress, functionSelector, feeLimit ? { feeLimit } : {}, parameter ); const signedTx = await tronWeb.trx.sign(txRaw.transaction); const result = await tronWeb.trx.sendRawTransaction(signedTx); tx = result; } else { const amountInSun = TronWeb.toSun(amount); tx = await tronWeb.trx.sendTransaction( recipientAddress, Number(amountInSun.toString()) ); } return successResponse({ ...tx, }); } catch (error) { throw error; } }; const getTransaction = async ({ hash, rpcUrl }: GetTransactionPayload): Promise => { const { tronWeb } = await getContract({ rpcUrl }); try { const tx = await tronWeb.trx.getTransactionInfo(hash); return successResponse({ ...tx, }); } catch (error) { throw error; } }; const getTokenInfo = async ({ address, rpcUrl, apiKey, }: IGetTokenInfoPayload): Promise => { const { contract } = await getContract({ contractAddress: address, rpcUrl, apiKey, }); if (contract) { try { const [name, symbol, decimals, totalSupply] = await Promise.all([ contract.methods.name().call(), contract.methods.symbol().call(), contract.methods.decimals().call(), contract.methods.totalSupply().call(), ]); const data = { name, symbol, decimals: Number(decimals), address, totalSupply: TronWeb.fromSun(Number(totalSupply)).toString(), }; return successResponse({ ...data }); } catch (error) { throw error; } } throw new Error('Contract not found'); }; const smartContractCall = async ({ rpcUrl, contractAddress, privateKey, method, params = [], methodType, feeLimit, contractAbi, }: ISmartContractCallPayload): Promise => { const { tronWeb } = await getContract({ rpcUrl, contractAddress, privateKey, }); try { if (!contractAbi) { throw new Error('Contract ABI is required'); } let result; if (methodType === 'read') { const functionAbi = contractAbi.find(abi => abi.name === method); const functionSelector = `${functionAbi.name}(${functionAbi.inputs .map((input: { type: string }) => input.type) .join(',')})`; result = await tronWeb.transactionBuilder.triggerConstantContract( contractAddress, functionSelector, {}, params ); const prefixedHex = result.constant_result[0].startsWith('0x') ? result.constant_result[0] : `0x${result.constant_result[0]}`; const decoded = utils.abi.decodeParamsV2ByABI(functionAbi, prefixedHex); result = decoded.toString(); } else if (methodType === 'write') { const txRaw = await tronWeb.transactionBuilder.triggerSmartContract( contractAddress, method, feeLimit ? { feeLimit } : {}, params ); const signedTx = await tronWeb.trx.sign(txRaw.transaction); result = await tronWeb.trx.sendRawTransaction(signedTx); } return successResponse({ data: result, }); } catch (error) { throw error; } }; export default { getBalance, createWallet, getAddressFromPrivateKey, generateWalletFromMnemonic, transfer, getTransaction, getTokenInfo, smartContractCall, }; ================================================ FILE: src/common/helpers/wavesHelper.ts ================================================ import { successResponse } from '../utils'; import { BalancePayload, CreateWalletPayload, GenerateWalletFromMnemonicPayload, GetAddressFromPrivateKeyPayload, GetTransactionPayload, IGetTokenInfoPayload, IResponse, ISmartContractCallPayload, ITokenInfo, TransferPayload, } from '../utils/types'; import { MAIN_NET_CHAIN_ID, TEST_NET_CHAIN_ID, address, privateKey, randomSeed, } from '@waves/ts-lib-crypto'; import { nodeInteraction, transfer as transferAsset, invokeScript, IInvokeScriptParams, ITransferParams, } from '@waves/waves-transactions'; import axios from 'axios'; const WAVES_DECIMALS = 8; const createWallet = ({ cluster }: CreateWalletPayload): IResponse => { const seed = randomSeed(); const chainId = getChainIdWithCluster(cluster); return successResponse({ address: address(seed, chainId), privateKey: privateKey(seed), mnemonic: seed, }); }; const generateWalletFromMnemonic = ({ mnemonic, cluster, }: GenerateWalletFromMnemonicPayload): IResponse => { const chainId = getChainIdWithCluster(cluster); return successResponse({ address: address(mnemonic, chainId), privateKey: privateKey(mnemonic), mnemonic: mnemonic, }); }; const getAddressFromPrivateKey = ({ privateKey, }: GetAddressFromPrivateKeyPayload): IResponse => { const chainId = getChainIdWithAddress(privateKey); return successResponse({ address: address(privateKey, chainId), }); }; const getBalance = async (args: BalancePayload): Promise => { try { if (!args.rpcUrl) { throw new Error('Error: Node URL is required'); } if (args.tokenAddress) { const balance = await nodeInteraction.assetBalance( args.tokenAddress, args.address, args.rpcUrl ); const tokenInfo = await getTokenInfo({ address: args.tokenAddress, rpcUrl: args.rpcUrl, network: args.network, }); return successResponse({ balance: Number(balance) / Math.pow(10, tokenInfo.decimals), }); } const balance = await nodeInteraction.balance(args.address, args.rpcUrl); return successResponse({ balance: balance / Math.pow(10, WAVES_DECIMALS), }); } catch (error) { throw error; } }; const transfer = async (args: TransferPayload): Promise => { try { if (!args.rpcUrl) { throw new Error('Error: Node URL is required'); } let amount; if (args.tokenAddress) { const tokenInfo = await getTokenInfo({ address: args.tokenAddress, rpcUrl: args.rpcUrl, network: args.network, }); amount = args.amount * Math.pow(10, tokenInfo.decimals); } else { amount = args.amount * Math.pow(10, WAVES_DECIMALS); } const params = { assetId: args.tokenAddress, recipient: args.recipientAddress, amount: parseInt(String(amount)), chainId: getChainIdWithAddress(args.recipientAddress), } as ITransferParams; const signedTx = transferAsset(params, args.privateKey); const broadcastedTx = await nodeInteraction.broadcast( signedTx, args.rpcUrl ); return successResponse({ ...broadcastedTx, }); } catch (error) { throw error; } }; const getTransaction = async ( args: GetTransactionPayload ): Promise => { try { if (!args.rpcUrl) { throw new Error('Error: Node URL is required'); } const tx = await nodeInteraction.transactionById(args.hash, args.rpcUrl); return successResponse({ ...tx, }); } catch (error) { throw error; } }; const getTokenInfo = async (args: IGetTokenInfoPayload): Promise => { try { const url = new URL(`assets/details/${args.address}`, args.rpcUrl); const { data } = await axios.get(url.toString()); const info: ITokenInfo = { name: data.name, symbol: data.name, address: data.assetId, decimals: data.decimals, totalSupply: data.quantity.toString(), }; return successResponse({ ...info }); } catch (error) { throw error; } }; const smartContractCall = async ( args: ISmartContractCallPayload ): Promise => { let data; if (args.methodType === 'write') { const params = { dApp: args.contractAddress, call: { function: args.method, args: [...(args.params || [])], }, payment: [...(args.payment || [])], chainId: getChainIdWithAddress(args.contractAddress), } as IInvokeScriptParams; const signedTx = invokeScript(params, args.privateKey!); data = await nodeInteraction.broadcast(signedTx, args.rpcUrl); } else if (args.methodType === 'read') { const response = await nodeInteraction.accountDataByKey( args.method, args.contractAddress, args.rpcUrl ); data = response?.value; } return successResponse({ data, }); }; function getChainIdWithCluster(cluster?: string) { return cluster === 'testnet' ? TEST_NET_CHAIN_ID : cluster === 'mainnet' ? MAIN_NET_CHAIN_ID : undefined; } function getChainIdWithAddress(address: string) { return address.startsWith('3P') ? MAIN_NET_CHAIN_ID : TEST_NET_CHAIN_ID; } export default { getBalance, createWallet, generateWalletFromMnemonic, getAddressFromPrivateKey, transfer, getTransaction, getTokenInfo, smartContractCall, }; ================================================ FILE: src/common/libs/bitgoUtxoLib.ts ================================================ import BigNumber from 'bignumber.js'; import * as bitcoin from '@bitgo/utxo-lib'; import { UTXO } from '../utils/utxo'; const buildUTXO = async ( network: typeof bitcoin.networks.bitcoin, privateKey: any, changeAddress: string, toAddress: string, valueIn: BigNumber, utxos: UTXO[], options?: { subtractFee?: boolean; fee?: number; signFlag?: number; version?: number; versionGroupID?: number; expiryHeight?: number; lockTime?: number; consensusBranchId?: number; } ): Promise<{ toHex: () => string }> => { const fees = new BigNumber( options && options.fee !== undefined ? options.fee : 10000 ); const value = options && options.subtractFee ? valueIn.minus(fees) : valueIn; if (value.lt(0)) { throw new Error( `Unable to include fee in value, fee exceeds value (${fees.toFixed()} > ${valueIn.toFixed()})` ); } const tx = new bitcoin.TransactionBuilder(network); if (options && options.version) { tx.setVersion(options.version); } if (options && options.versionGroupID) { tx.setVersionGroupId(options.versionGroupID); } if (options && options.expiryHeight) { tx.setExpiryHeight(options.expiryHeight); } if (options && options.lockTime) { tx.setLockTime(options.lockTime); } if (options && options.consensusBranchId) { tx.setConsensusBranchId(options.consensusBranchId); } // Only use the required utxos const [usedUTXOs, sum] = utxos.reduce( ([utxoAcc, total], utxo) => total.lt(value.plus(fees)) ? [[...utxoAcc, utxo], total.plus(utxo.amount)] : [utxoAcc, total], [[] as UTXO[], new BigNumber(0)] ); if (sum.lt(value.plus(fees))) { throw new Error('Insufficient balance to broadcast transaction'); } // Add all inputs usedUTXOs.map(utxo => tx.addInput(utxo.txHash, utxo.vOut)); const change = sum.minus(value).minus(fees); // Add outputs tx.addOutput(toAddress, value.toNumber()); if (change.gt(0)) { tx.addOutput(changeAddress, change.toNumber()); } // Sign inputs usedUTXOs.map((utxo, i) => tx.sign( i, privateKey, null, options && options.signFlag !== undefined ? options.signFlag : null, utxo.amount ) ); return tx.build(); }; export const BitgoUTXOLib = { buildUTXO, }; ================================================ FILE: src/common/utils/ethers.ts ================================================ import { ethers } from 'ethers'; const provider = (rpcUrl?: string) => { return new ethers.providers.JsonRpcProvider(rpcUrl); }; export default provider; ================================================ FILE: src/common/utils/index.ts ================================================ import { IResponse } from './types'; import * as base64 from 'base64-js'; export const successResponse = (args: IResponse): IResponse => { return args; }; /** * Remove 0x prefix from a hex string. If the input doesn't have a 0x prefix, * it's returned unchanged. * * @param hex The hex value to be prefixed. */ export const strip0x = (hex: string): string => { return hex.substring(0, 2) === '0x' ? hex.slice(2) : hex; }; /** * Convert a Uint8Array to a hex string (with no "0x"-prefix). */ export const toHex = (array: Uint8Array): string => array.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), ''); /** * Add a 0x prefix to a hex value, converting to a string first. If the input * is already prefixed, it's returned unchanged. * * @param hexInput The hex value to be prefixed. */ export const Ox = ( hexInput: Uint8Array | string | number, { prefix } = { prefix: '0x' } ): string => { let hexString: string = hexInput instanceof Uint8Array ? toHex(hexInput) : typeof hexInput === 'number' ? hexInput.toString(16) : hexInput; if (hexString.length % 2 === 1) { hexString = '0' + hexString; } return hexString.substring(0, 2) === prefix ? hexString : `${prefix}${hexString}`; }; /** * Convert a hex string to a Uint8Array. */ export const fromHex = (hexString: string): Uint8Array => { // Strip "0x" prefix. hexString = strip0x(hexString); // Pad the hex string. if (hexString.length % 2) { hexString = '0' + hexString; } // Split the string into bytes. const match = hexString.match(/.{1,2}/g); if (!match) { return new Uint8Array(); } // Parse each byte and create a Uint8Array. return new Uint8Array(match.map(byte => parseInt(byte, 16))); }; /** * https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array/12646864#12646864 * Randomize array element order in-place. * Using Durstenfeld shuffle algorithm. */ export const shuffleArray = (...arrayIn: T[] | T[][]): T[] => { const array: T[] = arrayIn.length === 1 && Array.isArray(arrayIn[0]) ? (arrayIn[0] as T[]) : (arrayIn as T[]); for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; }; export const toUTF8String = (input: Uint8Array): string => { let TextDecoderConstructor = (window || {}).TextDecoder || require('util').TextDecoder; const textDecoder = new TextDecoderConstructor(); return textDecoder.decode(input); }; export const fromUTF8String = (input: string): Uint8Array => { let TextEncoderConstructor = (window || {}).TextEncoder || require('util').TextEncoder; const textEncoder = new TextEncoderConstructor(); return textEncoder.encode(input); }; /** * Convert a base64 string to a Uint8Array. */ export const fromBase64 = (base64String: string): Uint8Array => { return base64.toByteArray(base64String); }; export const toBase64 = (input: Uint8Array): string => { return base64.fromByteArray(input); }; ================================================ FILE: src/common/utils/retry.ts ================================================ export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); export const extractError = (error: any): string => { if (error && typeof error === 'object') { if (error.response) { return extractError(error.response); } if (error.error) { return extractError(error.error); } if (error.message) { return extractError(error.message); } if (error.data) { return extractError(error.data); } if (error.context) { return extractError(error.context); } if (error.statusText) { return extractError(error.statusText); } try { return JSON.stringify(error); } catch (error) { // Ignore JSON error } } // Remove `Error: ` prefix. try { if (typeof error === 'string') { if (error.slice(0, 7).toLowerCase() === 'Error: ') { // tslint:disable-next-line: no-parameter-reassignment error = error.slice(7); } return error; } return JSON.stringify(error); } catch (error) { // Ignore JSON error } return String(error); }; // export const onlyMainnet = (x: any, testnet: boolean) => // testnet ? undefined : x; // export const onlyTestnet = (x: any, testnet: boolean) => // testnet ? x : undefined; export const fallback = async ( fallbacks: Array Promise)> ): Promise => { let firstError: Error | undefined; for (const fn of fallbacks) { if (!fn) { continue; } try { return await fn(); } catch (error) { firstError = firstError || (error as Error); } } throw firstError || new Error('No result returned'); }; export const retryNTimes = async ( fnCall: () => Promise, retries: number ) => { let returnError; for (let i = 0; i < retries; i++) { try { return await fnCall(); } catch (error) { if (String(error).match(/timeout of .* exceeded/)) { returnError = error; } else { const errorMessage = extractError(error); if (errorMessage) { // tslint:disable-next-line: no-object-mutation error.message += ` (${errorMessage})`; } throw error; } } await sleep(500); } throw returnError; }; ================================================ FILE: src/common/utils/solana.ts ================================================ import * as solanaWeb3 from '@solana/web3.js'; const provider = (rpcUrl?: string) => { return new solanaWeb3.Connection(rpcUrl as string); }; export default provider; ================================================ FILE: src/common/utils/types.ts ================================================ import bitcoinHelper from '../../common/helpers/bitcoinHelper'; import ethereumHelper from '../../common/helpers/ethereumHelper'; import solanaHelper from '../../common/helpers/solanaHelper'; import wavesHelper from '../../common/helpers/wavesHelper'; import tronHelper from '../../common/helpers/tronHelper'; export type Network = | 'ethereum' | 'solana' | 'tron' | 'waves' | 'bitcoin' | 'bitcoin-testnet'; export type NetworkHelper = { [key in T]: | typeof bitcoinHelper | typeof ethereumHelper | typeof solanaHelper | typeof wavesHelper | typeof tronHelper; }; export interface TransferPayload { recipientAddress: string; amount: number; network: Network; rpcUrl?: string; apiKey?: string; privateKey: string; gasPrice?: string; tokenAddress?: string; nonce?: number; data?: string; gasLimit?: number; fee?: number; // defaults to 10000 feeLimit?: number; subtractFee?: boolean; // defaults to false } export interface BalancePayload { address: string; network: Network; rpcUrl?: string; apiKey?: string; tokenAddress?: string; } export interface CreateWalletPayload { derivationPath?: string; cluster?: string; network: Network; } export interface GetAddressFromPrivateKeyPayload { privateKey: string; network: Network; } export interface GetTransactionPayload { rpcUrl?: string; apiKey?: string; hash: string; network: Network; } export interface GenerateWalletFromMnemonicPayload { mnemonic: string; derivationPath?: string; cluster?: string; network: Network; } export interface IResponse { [key: string]: any; } export interface GetEncryptedJsonFromPrivateKey { password: string; privateKey: string; network: Network; } export interface GetWalletFromEncryptedjsonPayload { json: string; password: string; network: Network; } export interface IGetTokenInfoPayload { network: Network; rpcUrl: string; address: string; cluster?: 'mainnet-beta' | 'testnet' | 'devnet'; apiKey?: string; } export interface ITokenInfo { name: string; symbol: string; address: string; decimals: number; totalSupply: string; logoUrl?: string; } export interface ISplTokenInfo { chainId: number; address: string; symbol: string; name: string; decimals: number; logoURI?: string; tags: string[]; extensions: any; } export interface ISmartContractCallPayload { rpcUrl: string; apiKey?: string; network: Network; contractAddress: string; method: string; methodType: 'read' | 'write'; params: any[]; payment?: any[]; value?: number; contractAbi?: any[]; gasPrice?: string; gasLimit?: number; feeLimit?: number; nonce?: number; privateKey?: string; } export interface INetworkHelper { getAddressFromPrivateKey: (args: GetAddressFromPrivateKeyPayload) => IResponse; generateWalletFromMnemonic: (args: GenerateWalletFromMnemonicPayload) => IResponse; createWallet: (args: CreateWalletPayload) => IResponse; getBalance: (args: BalancePayload) => Promise; transfer: (args: TransferPayload) => Promise; getTransaction: (args: GetTransactionPayload) => Promise; getEncryptedJsonFromPrivateKey?: (args: GetEncryptedJsonFromPrivateKey) => Promise; getWalletFromEncryptedJson?: (args: GetWalletFromEncryptedjsonPayload) => Promise; getTokenInfo?: (args: IGetTokenInfoPayload) => Promise; smartContractCall?: (args: ISmartContractCallPayload) => Promise; } ================================================ FILE: src/common/utils/utxo.ts ================================================ import BigNumber from 'bignumber.js'; export interface UTXO { readonly txHash: string; // hex string without 0x prefix readonly vOut?: number; readonly amount: number; // in sats readonly scriptPubKey?: string; // hex string without 0x prefix readonly confirmations: number; readonly block?: number; } /** * sortUTXOs compares two UTXOs by amount, then confirmations and then hash. * * @example * sortUTXOs({amount: 1, confirmations: 1}, {amount: 2, confirmations: 0}); * // -1, representing that the first parameter should be ordered first. * * @returns a negative value to represent that a should come before b or a * positive value to represent that b should come before a. */ export const sortUTXOs = (a: UTXO, b: UTXO): number => { // Sort greater values first if (a.amount !== b.amount) { return b.amount - a.amount; } // Sort older UTXOs first if (a.confirmations !== b.confirmations) { return a.confirmations - b.confirmations; } return a.txHash <= b.txHash ? -1 : 1; }; /** * fixValue turns a readable value, e.g. `0.0001` BTC, to the value in the smallest * unit, e.g. `10000` sats. * * @example * fixValue(0.0001, 8) = 10000; * * @param value Value in the readable representation, e.g. `0.0001` BTC. * @param decimals The number of decimals to shift by, e.g. 8. */ export const fixValue = (value: number, decimals: number) => new BigNumber(value) .multipliedBy(new BigNumber(10).exponentiatedBy(decimals)) .decimalPlaces(0) .toNumber(); /** * fixUTXO calls {{fixValue}} on the value of the UTXO. */ export const fixUTXO = (utxo: UTXO, decimals: number): UTXO => ({ ...utxo, amount: fixValue(utxo.amount, decimals), }); /** * fixUTXOs maps over an array of UTXOs and calls {{fixValue}}. */ export const fixUTXOs = (utxos: readonly UTXO[], decimals: number) => { return utxos.map(utxo => fixUTXO(utxo, decimals)); }; ================================================ FILE: src/index.ts ================================================ export * from './services/wallet'; ================================================ FILE: src/services/wallet/index.ts ================================================ import * as bip39 from 'bip39'; import bitcoinHelper from '../../common/helpers/bitcoinHelper'; import ethereumHelper from '../../common/helpers/ethereumHelper'; import solanaHelper from '../../common/helpers/solanaHelper'; import wavesHelper from '../../common/helpers/wavesHelper'; import tronHelper from '../../common/helpers/tronHelper'; import { TransferPayload, BalancePayload, CreateWalletPayload, GetAddressFromPrivateKeyPayload, GenerateWalletFromMnemonicPayload, GetTransactionPayload, GetWalletFromEncryptedjsonPayload, GetEncryptedJsonFromPrivateKey, IGetTokenInfoPayload, ISmartContractCallPayload, Network, INetworkHelper, } from '../../common/utils/types'; /** * Mapping of supported networks to their corresponding helper implementations. */ const networkHelpers: Record = { ethereum: ethereumHelper, solana: solanaHelper, tron: tronHelper, waves: wavesHelper, bitcoin: bitcoinHelper, 'bitcoin-testnet': bitcoinHelper, }; /** * Core features available for all networks. */ const baseFeatures = [ 'createWallet', 'getAddressFromPrivateKey', 'generateWalletFromMnemonic', 'getBalance', 'transfer', 'getTransaction', ]; /** * Feature support matrix for each network. */ const supportedFeatures: Record = { ethereum: [ ...baseFeatures, 'getEncryptedJsonFromPrivateKey', 'getWalletFromEncryptedJson', 'getTokenInfo', 'smartContractCall', ], solana: [...baseFeatures, 'getTokenInfo'], bitcoin: [...baseFeatures], 'bitcoin-testnet': [...baseFeatures], waves: [...baseFeatures, 'getTokenInfo', 'smartContractCall'], tron: [...baseFeatures, 'getTokenInfo', 'smartContractCall'], }; /** * Retrieves the helper for the specified network or throws if unsupported. * * @param network - The blockchain network identifier. * @returns The network helper implementation. * @throws {Error} When an unsupported network is provided. */ function getNetworkHelper(network: Network): INetworkHelper { const helper = networkHelpers[network]; if (!helper) { throw new Error(`Unsupported network: ${network}`); } return helper; } /** * Checks if a given feature is supported on the specified network. * * @param network - The blockchain network identifier. * @param feature - The feature to check support for. * @returns True if supported; otherwise, false. */ function isFeatureSupported(network: Network, feature: string): boolean { return supportedFeatures[network]?.includes(feature) || false; } /** * Generates a mnemonic phrase. * * @param numWords - The number of words in the mnemonic (default is 12). * @returns A mnemonic phrase as a string. * @remarks The strength is calculated as (numWords / 3) * 32. */ function generateMnemonic(numWords: number = 12): string { const strength = (numWords / 3) * 32; return bip39.generateMnemonic(strength); } /** * Retrieves the blockchain address from a private key. * * @param args - The payload containing the private key and network information. * @returns The derived blockchain address. * @throws {Error} When an unsupported or invalid network is provided. * @remarks Supported networks: Ethereum, Solana, and Bitcoin variants. */ function getAddressFromPrivateKey( args: GetAddressFromPrivateKeyPayload ) { if (!isFeatureSupported(args.network, 'getAddressFromPrivateKey')) { throw new Error( `getAddressFromPrivateKey is not supported for ${args.network}` ); } const helper = getNetworkHelper(args.network); return helper.getAddressFromPrivateKey(args); } /** * Generates a wallet from a mnemonic phrase. * * @param args - The payload containing the mnemonic, derivation path, and network details. * @returns A wallet object generated from the provided mnemonic. * @throws {Error} When an unsupported or invalid network is provided. * @remarks Supported networks: Ethereum, Solana, Bitcoin variants, and Waves. */ function generateWalletFromMnemonic( args: GenerateWalletFromMnemonicPayload ) { if (!isFeatureSupported(args.network, 'generateWalletFromMnemonic')) { throw new Error( `generateWalletFromMnemonic is not supported for ${args.network}` ); } const helper = getNetworkHelper(args.network); return helper.generateWalletFromMnemonic(args); } /** * Creates a new wallet for the specified network. * * @param args - The payload containing network information and configuration parameters * (such as derivation path or cluster details). * @returns The newly created wallet. * @throws {Error} When an unsupported or invalid network is provided. * @remarks Supported networks: Ethereum, Solana, Bitcoin variants, and Waves. */ function createWallet(args: CreateWalletPayload) { if (!isFeatureSupported(args.network, 'createWallet')) { throw new Error(`createWallet is not supported for ${args.network}`); } const helper = getNetworkHelper(args.network); return helper.createWallet(args); } /** * Retrieves the balance for a given address or wallet. * * @param args - The payload containing the address and network information. * @returns The balance of the specified address or wallet. * @throws {Error} When an unsupported or invalid network is provided. * @remarks Supported networks: Ethereum, Solana, Bitcoin variants, and Waves. */ async function getBalance(args: BalancePayload) { if (!isFeatureSupported(args.network, 'getBalance')) { throw new Error(`getBalance is not supported for ${args.network}`); } const helper = getNetworkHelper(args.network); return helper.getBalance(args); } /** * Executes a transfer transaction on the specified network. * * @param args - The payload containing transfer details, including sender, recipient, amount, and network. * @returns The result of the transfer operation. * @throws {Error} When an unsupported or invalid network is provided. * @remarks Supported networks: Ethereum, Solana, Bitcoin variants, and Waves. */ async function transfer(args: TransferPayload) { if (!isFeatureSupported(args.network, 'transfer')) { throw new Error(`transfer is not supported for ${args.network}`); } const helper = getNetworkHelper(args.network); return helper.transfer(args); } /** * Retrieves details of a specific transaction. * * @param args - The payload containing transaction identification and network details. * @returns The transaction details. * @throws {Error} When an unsupported or invalid network is provided. * @remarks Supported networks: Ethereum, Solana, Bitcoin variants, and Waves. */ async function getTransaction(args: GetTransactionPayload) { if (!isFeatureSupported(args.network, 'getTransaction')) { throw new Error(`getTransaction is not supported for ${args.network}`); } const helper = getNetworkHelper(args.network); return helper.getTransaction(args); } /** * Generates an encrypted JSON wallet from a private key for Ethereum. * * @param args - The payload containing the private key and network information. * @returns A promise that resolves to the encrypted JSON wallet. * @throws {Error} When an unsupported or invalid network is provided. * @remarks Currently, only the Ethereum network is supported. */ async function getEncryptedJsonFromPrivateKey( args: GetEncryptedJsonFromPrivateKey ) { if (!isFeatureSupported(args.network, 'getEncryptedJsonFromPrivateKey')) { throw new Error( `getEncryptedJsonFromPrivateKey is not supported for ${args.network}` ); } const helper = getNetworkHelper(args.network); return helper.getEncryptedJsonFromPrivateKey?.(args); } /** * Retrieves a wallet from its encrypted JSON representation for Ethereum. * * @param args - The payload containing the encrypted JSON and network details. * @returns A promise that resolves to the decrypted wallet. * @throws {Error} When an unsupported or invalid network is provided. * @remarks Currently, only the Ethereum network is supported. */ async function getWalletFromEncryptedJson( args: GetWalletFromEncryptedjsonPayload ) { if (!isFeatureSupported(args.network, 'getWalletFromEncryptedJson')) { throw new Error( `getWalletFromEncryptedJson is not supported for ${args.network}` ); } const helper = getNetworkHelper(args.network); return helper.getWalletFromEncryptedJson?.(args); } /** * Fetches token information from the specified network. * * @param args - The payload containing token and network details. * @returns The token information. * @throws {Error} When an unsupported or invalid network is provided. * @remarks Supported networks: Ethereum, Solana, and Waves. */ async function getTokenInfo(args: IGetTokenInfoPayload) { if (!isFeatureSupported(args.network, 'getTokenInfo')) { throw new Error(`getTokenInfo is not supported for ${args.network}`); } const helper = getNetworkHelper(args.network); return helper.getTokenInfo?.(args); } /** * Executes a smart contract call on the specified network. * * @param args - The payload containing smart contract call details and network information. * @returns The result of the smart contract call. * @throws {Error} When an unsupported or invalid network is provided, or if the network is not Ethereum or Waves. * @remarks Currently, only Ethereum and Waves networks are supported. */ async function smartContractCall(args: ISmartContractCallPayload) { if (!isFeatureSupported(args.network, 'smartContractCall')) { throw new Error(`smartContractCall is not supported for ${args.network}`); } const helper = getNetworkHelper(args.network); return helper.smartContractCall?.(args); } export { generateMnemonic, getAddressFromPrivateKey, generateWalletFromMnemonic, createWallet, getBalance, transfer, getTransaction, getEncryptedJsonFromPrivateKey, getWalletFromEncryptedJson, getTokenInfo, smartContractCall, }; ================================================ FILE: src/types/declarations.d.ts ================================================ declare module '@bitgo/utxo-lib'; ================================================ FILE: test/wallet.bitcoin.test.ts ================================================ import { generateMnemonic, createWallet, generateWalletFromMnemonic, getAddressFromPrivateKey, getBalance, transfer, getTransaction, } from '../src'; describe('MultichainCryptoWallet Bitcoin tests', () => { it('generateMnemonic', () => { const mnemonic = generateMnemonic(); // default is 12 expect(typeof mnemonic).toBe('string'); }); it('createWallet', () => { const wallet = createWallet({ derivationPath: "m/44'/0'/0'/0/0", // Leave empty to use default derivation path network: 'bitcoin', // 'bitcoin' or 'bitcoin-testnet' }); expect(typeof wallet).toBe('object'); }); it('generateWalletFromMnemonic', () => { const wallet = generateWalletFromMnemonic({ mnemonic: 'excess quit spot inspire stereo scrap cave wife narrow era pizza typical', derivationPath: "m/44'/0'/0'/0/0", // Leave empty to use default derivation path network: 'bitcoin', // 'bitcoin' or 'bitcoin-testnet' }); expect(wallet.address).toBe('1NV8FPKDW1hxJFxc2dNVZDAp7iCqxCLeFu'); expect(wallet.privateKey).toBe( 'KxqTGtCMnX6oL9rxynDKCRJXt64Gm5ame4AEQcYncFhSSUxFBkeJ' ); expect(wallet.mnemonic).toBe( 'excess quit spot inspire stereo scrap cave wife narrow era pizza typical' ); }); it('getAddressFromPrivateKey', () => { const data = getAddressFromPrivateKey({ privateKey: 'KxqTGtCMnX6oL9rxynDKCRJXt64Gm5ame4AEQcYncFhSSUxFBkeJ', network: 'bitcoin', // 'bitcoin' or 'bitcoin-testnet' }); expect(data.address).toBe('1NV8FPKDW1hxJFxc2dNVZDAp7iCqxCLeFu'); }); it('getBalance', async () => { const data = await getBalance({ address: 'bc1q7yh99tgvqnpuzgja4etahdgznxldwu3flrf2fl', network: 'bitcoin', // 'bitcoin' or 'bitcoin-testnet' }); expect(typeof data.balance).toBe('number'); }); it('Get transaction', async () => { const receipt = await getTransaction({ network: 'bitcoin', // 'bitcoin' or 'bitcoin-testnet' hash: 'e499940336c523ed7bb6dce45f3e6fc9a68442cb814ca2f84c2c0c1cdca37c6d', }); expect(typeof receipt).toBe('object'); }); it('Transfer', async () => { const response = await transfer({ privateKey: 'L3tSvMViDit1GSp7mbV2xFCGv6M45kDNuSyNY9xyUxmUPBFrBkc4', recipientAddress: '2NAhbS79dEUeqcnbC27UppwnjoVSwET5bat', amount: 0.0000001, network: 'bitcoin-testnet', // 'bitcoin' or 'bitcoin-testnet' fee: 10000, // Optional param default value is 10000 subtractFee: false, // Optional param default value is false }); expect(typeof response).toBe('object'); }); }); ================================================ FILE: test/wallet.ethereum.test.ts ================================================ import { createWallet, generateMnemonic, generateWalletFromMnemonic, getAddressFromPrivateKey, getBalance, getTransaction, transfer, getWalletFromEncryptedJson, getEncryptedJsonFromPrivateKey, getTokenInfo, smartContractCall, } from '../src'; describe('MultichainCryptoWallet Ethereum tests', () => { it('createWallet', () => { const wallet = createWallet({ derivationPath: "m/44'/60'/0'/0/0", // Leave empty to use default derivation path network: 'ethereum', }); expect(typeof wallet).toBe('object'); }); it('generateMnemonic', () => { const mnemonic = generateMnemonic(); // default is 12 expect(typeof mnemonic).toBe('string'); }); it('generateWalletFromMnemonic', () => { const wallet = generateWalletFromMnemonic({ mnemonic: 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat', derivationPath: "m/44'/60'/0'/0/0", // Leave empty to use default derivation path network: 'ethereum', }); expect(typeof wallet).toBe('object'); }); it('getAddressFromPrivateKey', () => { const address = getAddressFromPrivateKey({ privateKey: '0f9e5c0bee6c7d06b95204ca22dea8d7f89bb04e8527a2c59e134d185d9af8ad', network: 'ethereum', }); expect(typeof address).toBe('object'); }); it('getBalance ETH balance', async () => { const data = await getBalance({ address: '0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22', network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', }); expect(typeof data).toBe('object'); }); it('getBalance ERC20 token balance', async () => { const data = await getBalance({ address: '0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22', network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', tokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', }); expect(typeof data).toBe('object'); }); it('transfer', async () => { const data = await transfer({ recipientAddress: '0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22', amount: 0.0001, network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', privateKey: '0f9e5c0bee6c7d06b95204ca22dea8d7f89bb04e8527a2c59e134d185d9af8ad', }); const tx = await getTransaction({ hash: data.hash, network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', }); expect(typeof data).toBe('object'); expect(typeof tx).toBe('object'); }); it('transfer ERC20 Token', async () => { const response = await transfer({ recipientAddress: '0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22', amount: 0.00001, network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', privateKey: '0f9e5c0bee6c7d06b95204ca22dea8d7f89bb04e8527a2c59e134d185d9af8ad', tokenAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', }); expect(typeof response).toBe('object'); }); it('Get transaction', async () => { const receipt = await getTransaction({ rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', hash: '0x45aa3634a7a15a7f5e23265fc98b229adba9ffa89ad68c1b48d6b0a27ef51398', network: 'ethereum', }); expect(typeof receipt).toBe('object'); }); it('encrypts ethereum address privatekey and returns the encrypted json', async () => { const data = await getEncryptedJsonFromPrivateKey({ privateKey: '0xdc062e5c5013699c844ee942b517b0ee663bd22786e186e6e437db45e8790d2c', network: 'ethereum', password: 'walletpassword', }); expect(typeof data).toBe('object'); expect(typeof (data && data.json)).toBe('string'); }); it('decrypts ethereum address and returns the wallet details', async () => { const data = await getWalletFromEncryptedJson({ json: '{"address":"1c082d1052fb44134a408651c01148adbfcce7fe","id":"ca8b45b0-e69d-4e5e-8003-8f3dbb1082cf","version":3,"Crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"fe66bd308ad315126ae6f09f0d6599f4"},"ciphertext":"5f2abe02e49868c36df36f884680b132333e541f89cd7eb375247ff7c8a6ccdd","kdf":"scrypt","kdfparams":{"salt":"2570bf687cb7d9cd694e1c79f6e817c9c66467e81b04013104620670f0664bf5","n":131072,"dklen":32,"p":1,"r":8},"mac":"35f69fb7283c65d75c000a0c93042c063d2903efe9b9e6f03b05d842f47ed1e9"}}', network: 'ethereum', password: 'walletpassword', }); expect(typeof data).toBe('object'); expect(typeof (data && data.privateKey)).toBe('string'); expect(typeof (data && data.address)).toBe('string'); }); it('get ERC20 token info', async () => { const data = await getTokenInfo({ address: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', network: 'ethereum', rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', }); expect(typeof data).toBe('object'); expect(typeof (data && data.name)).toBe('string'); expect(typeof (data && data.symbol)).toBe('string'); expect(typeof (data && data.address)).toBe('string'); expect(typeof (data && data.decimals)).toBe('number'); expect(typeof (data && data.totalSupply)).toBe('string'); }); it('smart contract call (get token Balance)', async () => { const data = await smartContractCall({ rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', network: 'ethereum', contractAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', method: 'balanceOf', methodType: 'read', params: ['0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22'], }); expect(typeof data).toBe('object'); }); it('smart contract call (ERC20 token transfer)', async () => { const data = await smartContractCall({ rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', network: 'ethereum', contractAddress: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', method: 'transfer', methodType: 'write', params: [ '0x2455eC6700092991Ce0782365A89d5Cd89c8Fa22', '1000000000000000000', ], contractAbi: [ { constant: false, inputs: [ { name: '_to', type: 'address' }, { name: '_value', type: 'uint256' }, ], name: 'transfer', outputs: [{ name: '', type: 'bool' }], payable: false, stateMutability: 'nonpayable', type: 'function', }, ], privateKey: '0f9e5c0bee6c7d06b95204ca22dea8d7f89bb04e8527a2c59e134d185d9af8ad', }); expect(typeof data).toBe('object'); }); it('smart contract call (get factory Uniswap)', async () => { const data = await smartContractCall({ rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com', network: 'ethereum', contractAddress: '0xeE567Fe1712Faf6149d80dA1E6934E354124CfE3', method: 'factory', methodType: 'read', params: [], contractAbi: [ { inputs: [], name: 'factory', outputs: [{ internalType: 'address', name: '', type: 'address' }], stateMutability: 'view', type: 'function', }, ], }); expect(typeof data).toBe('object'); }); }); ================================================ FILE: test/wallet.solana.test.ts ================================================ import { createWallet, generateMnemonic, generateWalletFromMnemonic, getAddressFromPrivateKey, getBalance, getTransaction, transfer, getTokenInfo, } from '../src'; describe('MultichainCryptoWallet Solana tests', () => { it('generateMnemonic', () => { const mnemonic = generateMnemonic(); expect(typeof mnemonic).toBe('string'); }); it('createWallet', () => { const wallet = createWallet({ derivationPath: "m/44'/501'/0'/0'", // Leave empty to use default derivation path network: 'solana', }); expect(typeof wallet).toBe('object'); }); it('generateWalletFromMnemonic', () => { const wallet = generateWalletFromMnemonic({ mnemonic: 'base dry mango subject neither labor portion weekend range couple right document', derivationPath: "m/44'/501'/0'/0'", // Leave empty to use default derivation path network: 'solana', }); expect(typeof wallet).toBe('object'); }); it('getAddressFromPrivateKey', () => { const address = getAddressFromPrivateKey({ privateKey: 'bXXgTj2cgXMFAGpLHkF5GhnoNeUpmcJDsxXDhXQhQhL2BDpJumdwMGeC5Cs66stsN3GfkMH8oyHu24dnojKbtfp', network: 'solana', }); expect(typeof address).toBe('object'); }); it('getBalance SOL', async () => { const data = await getBalance({ address: '9DSRMyr3EfxPzxZo9wMBPku7mvcazHTHfyjhcfw5yucA', network: 'solana', rpcUrl: 'https://api.devnet.solana.com', }); expect(typeof data).toBe('object'); }); it('getBalance token', async () => { const data = await getBalance({ address: '9DSRMyr3EfxPzxZo9wMBPku7mvcazHTHfyjhcfw5yucA', tokenAddress: '6xRPFqbtpkS7iVd9SysZDXdYn6iWceXF7p3T91N3EcAc', network: 'solana', rpcUrl: 'https://api.devnet.solana.com', }); expect(typeof data).toBe('object'); }); it('transfer', async () => { const response = await transfer({ recipientAddress: '9DSRMyr3EfxPzxZo9wMBPku7mvcazHTHfyjhcfw5yucA', amount: 0.0001, network: 'solana', rpcUrl: 'https://api.devnet.solana.com', privateKey: 'qUfgDqNZ8EmZtG7FCdvo8ETTQb8crmzcYUdrVdpjfxZiVkrwSjQ9L2ov55oRt25ZSJXCjHw6hqtKJnxdnoGtp1M', }); expect(typeof response).toBe('object'); }); it('transfer Token on Solana', async () => { const response = await transfer({ recipientAddress: '9DSRMyr3EfxPzxZo9wMBPku7mvcazHTHfyjhcfw5yucA', tokenAddress: '6xRPFqbtpkS7iVd9SysZDXdYn6iWceXF7p3T91N3EcAc', amount: 1, network: 'solana', rpcUrl: 'https://api.devnet.solana.com', privateKey: 'qUfgDqNZ8EmZtG7FCdvo8ETTQb8crmzcYUdrVdpjfxZiVkrwSjQ9L2ov55oRt25ZSJXCjHw6hqtKJnxdnoGtp1M', }); expect(typeof response).toBe('object'); }); it('Get transaction', async () => { const receipt = await getTransaction({ rpcUrl: 'https://api.devnet.solana.com', hash: 'CkG1ynQ2vN8bmNsBUKG8ix3moUUfELWwd8K2f7mmqDd7LifFFfgyFhBux6t22AncbY4NR3PsEU3DbH7mDBMXWk7', network: 'solana', }); expect(typeof receipt).toBe('object'); }); it('get SPL token info', async () => { const data = await getTokenInfo({ address: '7Xn4mM868daxsGVJmaGrYxg8CZiuqBnDwUse66s5ALmr', network: 'solana', rpcUrl: 'https://api.devnet.solana.com', cluster: 'devnet', }); expect(typeof data).toBe('object'); expect(typeof (data && data.name)).toBe('string'); expect(typeof (data && data.symbol)).toBe('string'); expect(typeof (data && data.address)).toBe('string'); expect(typeof (data && data.decimals)).toBe('number'); expect(typeof (data && data.totalSupply)).toBe('string'); }); }); ================================================ FILE: test/wallet.tron.test.ts ================================================ import { createWallet, generateMnemonic, generateWalletFromMnemonic, getAddressFromPrivateKey, getBalance, transfer, getTransaction, getTokenInfo, smartContractCall, } from '../src'; describe('MultichainCryptoWallet Tron tests', () => { it('generateMnemonic', () => { const mnemonic = generateMnemonic(); expect(mnemonic).toBeDefined(); }); it('createWallet', () => { const wallet = createWallet({ network: 'tron', derivationPath: "m/44'/195'/0'/0/0", }); expect(wallet).toBeDefined(); }); it('generateWalletFromMnemonic', () => { const wallet = generateWalletFromMnemonic({ mnemonic: 'crop traffic saddle addict foster split make luxury scissors daughter bike exit', network: 'tron', }); expect(wallet).toBeDefined(); }); it('getAddressFromPrivateKey', () => { const address = getAddressFromPrivateKey({ privateKey: 'fa01dc6efd5fd64e4897aadf255ae715cf34138c7ada5f6a7efb0bdd0bd9c8c4', network: 'tron', }); expect(address).toBeDefined(); }); it('getBalance TRX balance', async () => { const balance = await getBalance({ rpcUrl: 'https://nile.trongrid.io', address: 'TDdHvW9nU1JaX1P7roYtDvjErTTR17GPJJ', network: 'tron', }); expect(balance).toBeDefined(); expect(typeof balance.balance).toBe('string'); }); it('getBalance TRC20 token balance', async () => { const balance = await getBalance({ rpcUrl: 'https://nile.trongrid.io', address: 'TEVuGfgLkQCVXs7EtjMiQp3ZSSUkEbNnVS', network: 'tron', tokenAddress: 'TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj', }); expect(balance).toBeDefined(); expect(typeof balance.balance).toBe('string'); }); it('transfer TRX', async () => { const tx = await transfer({ rpcUrl: 'https://nile.trongrid.io', recipientAddress: 'TEVuGfgLkQCVXs7EtjMiQp3ZSSUkEbNnVS', amount: 0.0001, network: 'tron', privateKey: 'fa01dc6efd5fd64e4897aadf255ae715cf34138c7ada5f6a7efb0bdd0bd9c8c4', }); expect(tx).toBeDefined(); expect(tx.txid).toBeDefined(); }); it('transfer TRC20 token', async () => { const tx = await transfer({ rpcUrl: 'https://nile.trongrid.io', recipientAddress: 'TEVuGfgLkQCVXs7EtjMiQp3ZSSUkEbNnVS', privateKey: 'fa01dc6efd5fd64e4897aadf255ae715cf34138c7ada5f6a7efb0bdd0bd9c8c4', amount: 0.1, network: 'tron', tokenAddress: 'TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj', }); expect(tx).toBeDefined(); expect(tx.txid).toBeDefined(); }); it('getTransaction by hash', async () => { const tx = await getTransaction({ hash: '34f27486cbe693d5182c4b5e18c1779d918668f86f396ed62a279d8b519b81cc', network: 'tron', rpcUrl: 'https://nile.trongrid.io', }); expect(tx).toBeDefined(); }); it('getTokenInfo TRC20 token', async () => { const data = await getTokenInfo({ address: 'TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj', network: 'tron', rpcUrl: 'https://nile.trongrid.io', }); expect(typeof data).toBe('object'); expect(typeof (data && data.name)).toBe('string'); expect(typeof (data && data.symbol)).toBe('string'); expect(typeof (data && data.address)).toBe('string'); expect(typeof (data && data.decimals)).toBe('number'); expect(typeof (data && data.totalSupply)).toBe('string'); }); it('smartContractCall write TRC20 token transfer', async () => { const { data } = await smartContractCall({ network: 'tron', rpcUrl: 'https://nile.trongrid.io', contractAddress: 'TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj', method: 'transfer(address,uint256)', methodType: 'write', contractAbi: [ { constant: false, inputs: [ { name: '_to', type: 'address' }, { name: '_value', type: 'uint256' }, ], name: 'transfer', outputs: [{ name: '', type: 'bool' }], payable: false, stateMutability: 'nonpayable', type: 'function', }, ], params: [ { type: 'address', value: 'TEVuGfgLkQCVXs7EtjMiQp3ZSSUkEbNnVS' }, { type: 'uint256', value: 1000000 }, ], privateKey: 'fa01dc6efd5fd64e4897aadf255ae715cf34138c7ada5f6a7efb0bdd0bd9c8c4', }); expect(data).toBeDefined(); expect(data.txid).toBeDefined(); }); it('smartContractCall read TRC20 token balance', async () => { const { data } = await smartContractCall({ network: 'tron', rpcUrl: 'https://nile.trongrid.io', contractAddress: 'TXLAQ63Xg1NAzckPwKHvzw7CSEmLMEqcdj', method: 'balanceOf', methodType: 'read', contractAbi: [ { constant: true, inputs: [{ name: '_owner', type: 'address' }], name: 'balanceOf', outputs: [{ name: 'balance', type: 'uint256' }], payable: false, stateMutability: 'view', type: 'function', }, ], params: [ { type: 'address', value: 'TEVuGfgLkQCVXs7EtjMiQp3ZSSUkEbNnVS' }, ], }); expect(data).toBeDefined(); expect(typeof data).toBe('string'); }); }); ================================================ FILE: test/wallet.waves.test.ts ================================================ import { generateMnemonic, createWallet, generateWalletFromMnemonic, getBalance, getTransaction, getTokenInfo, transfer, smartContractCall, getAddressFromPrivateKey, } from '../src'; describe('MultichainCryptoWallet Waves tests', () => { it('generateMnemonic', () => { const mnemonic = generateMnemonic(); expect(typeof mnemonic).toBe('string'); }); it('createWallet', () => { const wallet = createWallet({ cluster: 'mainnet', network: 'waves' }); expect(typeof wallet).toBe('object'); expect(typeof wallet.address).toBe('string'); expect(typeof wallet.mnemonic).toBe('string'); expect(typeof wallet.privateKey).toBe('string'); }); it('generateWalletFromMnemonic', () => { const wallet = generateWalletFromMnemonic({ mnemonic: 'mushroom deliver work spray hire nuclear wrong deputy march six midnight outside motor differ adult', cluster: 'testnet', network: 'waves', }); expect(typeof wallet).toBe('object'); expect(typeof wallet.address).toBe('string'); expect(typeof wallet.mnemonic).toBe('string'); expect(typeof wallet.privateKey).toBe('string'); expect(wallet.address).toBe('3NBE5tjbQn9BHczjD6NSSuFDKVHKsBRzTv9'); }); it('getAddressFromPrivateKey', () => { const address = getAddressFromPrivateKey({ privateKey: 'mushroom deliver work spray hire nuclear wrong deputy march six midnight outside motor differ adult', network: 'waves', }); expect(typeof address).toBe('object'); expect(typeof address.address).toBe('string'); expect(address.address).toBe('3NBE5tjbQn9BHczjD6NSSuFDKVHKsBRzTv9'); }); it('getBalance WAVES', async () => { const data = await getBalance({ network: 'waves', address: '3NBE5tjbQn9BHczjD6NSSuFDKVHKsBRzTv9', rpcUrl: 'https://nodes-testnet.wavesnodes.com', }); expect(typeof data).toBe('object'); expect(typeof data.balance).toBe('number'); }); it('getBalance token', async () => { const data = await getBalance({ network: 'waves', address: '3NBE5tjbQn9BHczjD6NSSuFDKVHKsBRzTv9', rpcUrl: 'https://nodes-testnet.wavesnodes.com', tokenAddress: '39pnv8FVf3BX3xwtC6uhFxffy2sE3seXCPsf25eNn6qG', }); expect(typeof data).toBe('object'); expect(typeof data.balance).toBe('number'); }); it('transfer WAVES', async () => { const response = await transfer({ recipientAddress: '3N4x4ML4D6fiU18Tpw86puRoN78FCTs9VQu', amount: 0.0001, network: 'waves', rpcUrl: 'https://nodes-testnet.wavesnodes.com', privateKey: 'mushroom deliver work spray hire nuclear wrong deputy march six midnight outside motor differ adult', }); expect(typeof response).toBe('object'); expect(response.assetId).toBe(null); expect(typeof response.id).toBe('string'); expect(typeof response.sender).toBe('string'); expect(typeof response.recipient).toBe('string'); }); it('transfer Token on Waves', async () => { const response = await transfer({ recipientAddress: '3N4x4ML4D6fiU18Tpw86puRoN78FCTs9VQu', tokenAddress: '39pnv8FVf3BX3xwtC6uhFxffy2sE3seXCPsf25eNn6qG', amount: 1, network: 'waves', rpcUrl: 'https://nodes-testnet.wavesnodes.com', privateKey: 'mushroom deliver work spray hire nuclear wrong deputy march six midnight outside motor differ adult', }); expect(typeof response).toBe('object'); expect(typeof response.id).toBe('string'); expect(typeof response.sender).toBe('string'); expect(typeof response.assetId).toBe('string'); expect(typeof response.recipient).toBe('string'); }); it('Get transaction', async () => { const receipt = await getTransaction({ rpcUrl: 'https://nodes-testnet.wavesnodes.com', hash: 'Barwuj1gCiQ9wCfLQ1nbdz2CSyQXLnRxnDEubtdTwJpd', network: 'waves', }); expect(typeof receipt).toBe('object'); expect(typeof receipt.id).toBe('string'); expect(typeof receipt.amount).toBe('number'); expect(typeof receipt.height).toBe('number'); expect(typeof receipt.sender).toBe('string'); expect(typeof receipt.chainId).toBe('number'); expect(typeof receipt.recipient).toBe('string'); expect(typeof receipt.timestamp).toBe('number'); expect(Array.isArray(receipt.proofs)).toBeTruthy(); }); it('get WAVES token info', async () => { const data = await getTokenInfo({ network: 'waves', rpcUrl: 'https://nodes-testnet.wavesnodes.com', address: '39pnv8FVf3BX3xwtC6uhFxffy2sE3seXCPsf25eNn6qG', }); expect(typeof data).toBe('object'); }); it('call (write) to smart contract', async () => { const data = await smartContractCall({ network: 'waves', methodType: 'write', rpcUrl: 'https://nodes-testnet.wavesnodes.com', contractAddress: '3N9uzrTiArce1h9VCqK3QUUZmFqBgg5rZSW', privateKey: 'mushroom deliver work spray hire nuclear wrong deputy march six midnight outside motor differ adult', method: 'deposit', payment: [{ assetId: null, amount: 1000 }], params: [], }); expect(typeof data).toBe('object'); }); it('call (read) smart contract data', async () => { const data = await smartContractCall({ network: 'waves', methodType: 'read', rpcUrl: 'https://nodes-testnet.wavesnodes.com', contractAddress: '3N9uzrTiArce1h9VCqK3QUUZmFqBgg5rZSW', method: '3N1gVpA5MVY4WsMpzQ6RfcscpDDdqBbLx6n_balance', params: [], }); expect(typeof data).toBe('object'); }); }); ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ "module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ "lib": ["DOM","ESNext"], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ "declaration": true, /* Generates corresponding '.d.ts' file. */ // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ // "outDir": "./", /* Redirect output structure to the directory. */ "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ // "removeComments": true, /* Do not emit comments to output. */ "noEmit": true, /* Do not emit outputs. */ "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* Enable strict null checks. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ "noUnusedLocals": true, /* Report errors on unused locals. */ "noUnusedParameters": true, /* Report errors on unused parameters. */ "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ /* Module Resolution Options */ "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ /* Advanced Options */ "skipLibCheck": true, /* Skip type checking of declaration files. */ "forceConsistentCasingInFileNames": true , /* Disallow inconsistently-cased references to the same file. */ "resolveJsonModule": true, "useUnknownInCatchVariables": false, /* Allow using unknown as a type in a catch clause. */ }, "include": ["src", "types"], }