├── .env ├── .eslintrc.js ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── README.md ├── contracts └── FlashbotsCheckAndSend.sol ├── package-lock.json ├── package.json ├── src ├── abi.ts ├── engine │ ├── Approval721.ts │ ├── Base.ts │ ├── CryptoKitties.ts │ └── TransferERC20.ts ├── index.ts └── utils.ts └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY_EXECUTOR= 2 | PRIVATE_KEY_SPONSOR= 3 | FLASHBOTS_RELAY_SIGNING_KEY= 4 | RECIPIENT= -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:@typescript-eslint/recommended" 9 | ], 10 | "parser": "@typescript-eslint/parser", 11 | "parserOptions": { 12 | "ecmaVersion": 12, 13 | "sourceType": "module" 14 | }, 15 | "plugins": [ 16 | "@typescript-eslint" 17 | ], 18 | "rules": { 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [10.x, 12.x, 14.x, 15.x] 20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/ 21 | 22 | steps: 23 | - uses: actions/checkout@v2 24 | - name: Use Node.js ${{ matrix.node-version }} 25 | uses: actions/setup-node@v1 26 | with: 27 | node-version: ${{ matrix.node-version }} 28 | - run: npm install 29 | - run: npm run lint 30 | - run: npm run build 31 | - run: npm test 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /node_modules 3 | /build 4 | /tsconfig.tsbuildinfo 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | searcher-sponsored-tx 2 | ======================= 3 | This repository contains a simple Flashbots "searcher" for submitting a transaction from an `executor` account, but paying for the transaction from a `sponsor` account. This is accomplished by submitting a Flashbots transaction bundle, with the first "sponsor" transaction paying the "executor" wallet in ETH, followed by a series of `executor` transactions that spend this newly received ETH on gas fees. 4 | 5 | We hope you will use this repository as an example of how to integrate Flashbots into your own Flashbot searcher (bot). For more information, see the [Flashbots Searcher Quick Start](https://docs.flashbots.net/flashbots-auction/searchers/quick-start/) 6 | 7 | Use case 8 | ======== 9 | The use case for this multi-transaction setup is to make calls from an account that has a compromised private key. Since transferring in any ETH to this compromised wallet will immediately be swept by bots that monitor that account, transferring in funds will also give any attacker the ability to withdraw tokens that are held by that account. 10 | 11 | Using this searcher, you can create a bundle of transaction that execute against the compromised account, spending ETH that was received in the same block. 12 | 13 | With the activation of EIP-1559, the old method of using `gasPrice = 0` is no longer functional. Transactions must pay at least `baseFee`. 14 | 15 | 16 | Environment Variables 17 | ===================== 18 | - ETHEREUM_RPC_URL - Ethereum RPC endpoint. Can not be the same as FLASHBOTS_RPC_URL 19 | - PRIVATE_KEY_EXECUTOR - Private key for the compromised Ethereum EOA that owns assets that needs to be transferred 20 | - PRIVATE_KEY_SPONSOR - Private key for an account that has ETH that will be used to fund the miner for the "ZERO_GAS" transactions 21 | - RECIPIENT - Ethereum EOA to receive assets from ZERO_GAS account 22 | - FLASHBOTS_RELAY_SIGNING_KEY - Optional param, private key used to sign messages to Flashbots to establish reputation of profitability 23 | 24 | Setting Miner Reward 25 | ==================== 26 | Inside `src/index.ts` is : 27 | ``` 28 | const PRIORITY_GAS_PRICE = GWEI.mul(31) 29 | ``` 30 | 31 | This is the priority fee, on top of baseFee, sent to the miner for *all* transactions in the bundle, including the sponsor-funding transaction. All transactions use the same gasPrice, with no coinbase transfers in any transaction. In the case of a block re-organization, hopefully all transactions will appear in the next block as well, preventing sweeper bots from gaining access to the incoming ETH before it is spent on gas fees. 32 | 33 | Selecting a different "engine" 34 | ============================== 35 | This system can operate against different protocols by swapping a new "engine" class that adheres to "Base" functionality in the `main()` function. Available engines: 36 | - `TransferERC20` 37 | - `CryptoKitties` 38 | - `Approval721` 39 | 40 | 41 | An engine accepts relevant parameters during construction and provides functions to retrieve transaction descriptions to be passed in to Flashbots. Selecting and configuring a different engine requires directly modifying the source, uncommenting the engine and setting the necessary variables. 42 | 43 | 44 | Usage 45 | ====================== 46 | ``` 47 | $ npm install 48 | $ PRIVATE_KEY_EXECUTOR=__COMPROMISED_PRIVATE_KEY__ \ 49 | PRIVATE_KEY_SPONSOR=__FUNDED_PRIVATE_KEY__ \ 50 | RECIPIENT=__ADDRESS_THAT_RECEIVES_ASSETS__ \ 51 | FLASHBOTS_SECRET=__YOUR_PERSONAL_SECRET__ \ 52 | npm run start 53 | ``` 54 | -------------------------------------------------------------------------------- /contracts/FlashbotsCheckAndSend.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: MIT 2 | pragma solidity 0.6.12; 3 | pragma experimental ABIEncoderV2; 4 | 5 | /* 6 | Copyright 2021 Flashbots: Scott Bigelow (scott@flashbots.net). 7 | */ 8 | 9 | // This contract performs one or many staticcall's, compares their output, and pays 10 | // the miner directly if all calls exactly match the specified result 11 | // For how to use this script, read the Flashbots searcher docs: https://github.com/flashbots/pm/blob/main/guides/searcher-onboarding.md 12 | contract FlashbotsCheckAndSend { 13 | function check32BytesAndSend(address _target, bytes memory _payload, bytes32 _resultMatch) external payable { 14 | _check32Bytes(_target, _payload, _resultMatch); 15 | block.coinbase.transfer(msg.value); 16 | } 17 | 18 | function check32BytesAndSendMulti(address[] memory _targets, bytes[] memory _payloads, bytes32[] memory _resultMatches) external payable { 19 | require (_targets.length == _payloads.length); 20 | require (_targets.length == _resultMatches.length); 21 | for (uint256 i = 0; i < _targets.length; i++) { 22 | _check32Bytes(_targets[i], _payloads[i], _resultMatches[i]); 23 | } 24 | block.coinbase.transfer(msg.value); 25 | } 26 | 27 | function checkBytesAndSend(address _target, bytes memory _payload, bytes memory _resultMatch) external payable { 28 | _checkBytes(_target, _payload, _resultMatch); 29 | block.coinbase.transfer(msg.value); 30 | } 31 | 32 | function checkBytesAndSendMulti(address[] memory _targets, bytes[] memory _payloads, bytes[] memory _resultMatches) external payable { 33 | require (_targets.length == _payloads.length); 34 | require (_targets.length == _resultMatches.length); 35 | for (uint256 i = 0; i < _targets.length; i++) { 36 | _checkBytes(_targets[i], _payloads[i], _resultMatches[i]); 37 | } 38 | block.coinbase.transfer(msg.value); 39 | } 40 | 41 | // ======== INTERNAL ======== 42 | 43 | function _check32Bytes(address _target, bytes memory _payload, bytes32 _resultMatch) internal view { 44 | (bool _success, bytes memory _response) = _target.staticcall(_payload); 45 | require(_success, "!success"); 46 | require(_response.length >= 32, "response less than 32 bytes"); 47 | bytes32 _responseScalar; 48 | assembly { 49 | _responseScalar := mload(add(_response, 0x20)) 50 | } 51 | require(_responseScalar == _resultMatch, "response mismatch"); 52 | } 53 | 54 | function _checkBytes(address _target, bytes memory _payload, bytes memory _resultMatch) internal view { 55 | (bool _success, bytes memory _response) = _target.staticcall(_payload); 56 | require(_success, "!success"); 57 | require(keccak256(_resultMatch) == keccak256(_response), "response bytes mismatch"); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@flashbots/searcher-sponsored-tx", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "build/index.js", 6 | "types": "build/index.d.ts", 7 | "scripts": { 8 | "start": "npx ts-node --project tsconfig.json src/index.ts", 9 | "build": "npx tsc", 10 | "prepare": "npm run build", 11 | "lint": "npx eslint src" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "devDependencies": { 16 | "@types/lodash": "^4.14.165", 17 | "@types/node": "^14.14.10", 18 | "@typescript-eslint/eslint-plugin": "^4.9.1", 19 | "@typescript-eslint/parser": "^4.9.1", 20 | "eslint": "^7.15.0" 21 | }, 22 | "dependencies": { 23 | "@flashbots/ethers-provider-bundle": "^0.4.0", 24 | "lodash": "^4.17.20", 25 | "log-timestamp": "^0.3.0", 26 | "ts-node": "^9.1.0", 27 | "typescript": "^4.1.2", 28 | "ethers": "^5.4.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/abi.ts: -------------------------------------------------------------------------------- 1 | export const CHECK_AND_SEND_ABI = [{ 2 | "inputs": [{ 3 | "internalType": "address", 4 | "name": "_target", 5 | "type": "address" 6 | }, {"internalType": "bytes", "name": "_payload", "type": "bytes"}, { 7 | "internalType": "bytes32", 8 | "name": "_resultMatch", 9 | "type": "bytes32" 10 | }], "name": "check32BytesAndSend", "outputs": [], "stateMutability": "payable", "type": "function" 11 | }, { 12 | "inputs": [{"internalType": "address[]", "name": "_targets", "type": "address[]"}, { 13 | "internalType": "bytes[]", 14 | "name": "_payloads", 15 | "type": "bytes[]" 16 | }, {"internalType": "bytes32[]", "name": "_resultMatches", "type": "bytes32[]"}], 17 | "name": "check32BytesAndSendMulti", 18 | "outputs": [], 19 | "stateMutability": "payable", 20 | "type": "function" 21 | }, { 22 | "inputs": [{"internalType": "address", "name": "_target", "type": "address"}, { 23 | "internalType": "bytes", 24 | "name": "_payload", 25 | "type": "bytes" 26 | }, {"internalType": "bytes", "name": "_resultMatch", "type": "bytes"}], 27 | "name": "checkBytesAndSend", 28 | "outputs": [], 29 | "stateMutability": "payable", 30 | "type": "function" 31 | }, { 32 | "inputs": [{"internalType": "address[]", "name": "_targets", "type": "address[]"}, { 33 | "internalType": "bytes[]", 34 | "name": "_payloads", 35 | "type": "bytes[]" 36 | }, {"internalType": "bytes[]", "name": "_resultMatches", "type": "bytes[]"}], 37 | "name": "checkBytesAndSendMulti", 38 | "outputs": [], 39 | "stateMutability": "payable", 40 | "type": "function" 41 | }] 42 | -------------------------------------------------------------------------------- /src/engine/Approval721.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, Contract } from "ethers"; 2 | import { isAddress } from "ethers/lib/utils"; 3 | import { TransactionRequest } from "@ethersproject/abstract-provider"; 4 | import { Base } from "./Base"; 5 | 6 | const ERC721_ABI = [{ 7 | "constant": true, 8 | "inputs": [{"internalType": "address", "name": "owner", "type": "address"}, { 9 | "internalType": "address", 10 | "name": "operator", 11 | "type": "address" 12 | }], 13 | "name": "isApprovedForAll", 14 | "outputs": [{"internalType": "bool", "name": "", "type": "bool"}], 15 | "payable": false, 16 | "stateMutability": "view", 17 | "type": "function" 18 | }, { 19 | "constant": false, 20 | "inputs": [{"internalType": "address", "name": "to", "type": "address"}, { 21 | "internalType": "bool", 22 | "name": "approved", 23 | "type": "bool" 24 | }], 25 | "name": "setApprovalForAll", 26 | "outputs": [], 27 | "payable": false, 28 | "stateMutability": "nonpayable", 29 | "type": "function" 30 | }, { 31 | "inputs": [{"internalType": "address", "name": "from", "type": "address"}, { 32 | "internalType": "address", 33 | "name": "to", 34 | "type": "address" 35 | }, {"internalType": "uint256", "name": "tokenId", "type": "uint256"}], 36 | "name": "safeTransferFrom", 37 | "outputs": [], 38 | "stateMutability": "nonpayable", 39 | "type": "function" 40 | }, { 41 | "inputs": [{"internalType": "address", "name": "from", "type": "address"}, { 42 | "internalType": "address", 43 | "name": "to", 44 | "type": "address" 45 | }, {"internalType": "uint256", "name": "tokenId", "type": "uint256"}], 46 | "name": "transferFrom", 47 | "outputs": [], 48 | "stateMutability": "nonpayable", 49 | "type": "function" 50 | }] 51 | 52 | 53 | export class Approval721 extends Base { 54 | private _recipient: string; 55 | private _contractAddresses721: string[]; 56 | 57 | constructor(recipient: string, contractAddresses721: string[]) { 58 | super() 59 | if (!isAddress(recipient)) throw new Error("Bad Address") 60 | this._recipient = recipient; 61 | this._contractAddresses721 = contractAddresses721; 62 | } 63 | 64 | async description(): Promise { 65 | return `Giving ${this._recipient} approval for: ${this._contractAddresses721.join(", ")}` 66 | } 67 | 68 | getSponsoredTransactions(): Promise> { 69 | return Promise.all(this._contractAddresses721.map(async (contractAddress721) => { 70 | const erc721Contract = new Contract(contractAddress721, ERC721_ABI); 71 | return { 72 | ...(await erc721Contract.populateTransaction.setApprovalForAll(this._recipient, true)), 73 | gasPrice: BigNumber.from(0), 74 | } 75 | })) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/engine/Base.ts: -------------------------------------------------------------------------------- 1 | import { TransactionRequest } from "@ethersproject/abstract-provider"; 2 | 3 | export abstract class Base { 4 | abstract getSponsoredTransactions(): Promise>; 5 | abstract description(): Promise; 6 | } 7 | -------------------------------------------------------------------------------- /src/engine/CryptoKitties.ts: -------------------------------------------------------------------------------- 1 | import { Contract, providers } from "ethers"; 2 | import { isAddress } from "ethers/lib/utils"; 3 | import { TransactionRequest } from "@ethersproject/abstract-provider"; 4 | import { Base } from "./Base"; 5 | 6 | const CRYPTO_KITTIES_ADDRESS = "0x06012c8cf97bead5deae237070f9587f8e7a266d" 7 | const CRYPTO_KITTIES_ABI = [{"constant":true,"inputs":[{"name":"_interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"cfoAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"},{"name":"_preferredTransport","type":"string"}],"name":"tokenMetadata","outputs":[{"name":"infoUrl","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"promoCreatedCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"ceoAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"GEN0_STARTING_PRICE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setSiringAuctionAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"pregnantKitties","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_kittyId","type":"uint256"}],"name":"isPregnant","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"GEN0_AUCTION_DURATION","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"siringAuction","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setGeneScienceAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newCEO","type":"address"}],"name":"setCEO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newCOO","type":"address"}],"name":"setCOO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_kittyId","type":"uint256"},{"name":"_startingPrice","type":"uint256"},{"name":"_endingPrice","type":"uint256"},{"name":"_duration","type":"uint256"}],"name":"createSaleAuction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"sireAllowedToAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_matronId","type":"uint256"},{"name":"_sireId","type":"uint256"}],"name":"canBreedWith","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"kittyIndexToApproved","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_kittyId","type":"uint256"},{"name":"_startingPrice","type":"uint256"},{"name":"_endingPrice","type":"uint256"},{"name":"_duration","type":"uint256"}],"name":"createSiringAuction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"val","type":"uint256"}],"name":"setAutoBirthFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"},{"name":"_sireId","type":"uint256"}],"name":"approveSiring","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_newCFO","type":"address"}],"name":"setCFO","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_genes","type":"uint256"},{"name":"_owner","type":"address"}],"name":"createPromoKitty","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"secs","type":"uint256"}],"name":"setSecondsPerBlock","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"withdrawBalance","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"name":"owner","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"GEN0_CREATION_LIMIT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"newContractAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_address","type":"address"}],"name":"setSaleAuctionAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"count","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_v2Address","type":"address"}],"name":"setNewAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"secondsPerBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"tokensOfOwner","outputs":[{"name":"ownerTokens","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_matronId","type":"uint256"}],"name":"giveBirth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"withdrawAuctionBalances","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"cooldowns","outputs":[{"name":"","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"kittyIndexToOwner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"cooAddress","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"autoBirthFee","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"erc721Metadata","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_genes","type":"uint256"}],"name":"createGen0Auction","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_kittyId","type":"uint256"}],"name":"isReadyToBreed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PROMO_CREATION_LIMIT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_contractAddress","type":"address"}],"name":"setMetadataAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"saleAuction","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"}],"name":"getKitty","outputs":[{"name":"isGestating","type":"bool"},{"name":"isReady","type":"bool"},{"name":"cooldownIndex","type":"uint256"},{"name":"nextActionAt","type":"uint256"},{"name":"siringWithId","type":"uint256"},{"name":"birthTime","type":"uint256"},{"name":"matronId","type":"uint256"},{"name":"sireId","type":"uint256"},{"name":"generation","type":"uint256"},{"name":"genes","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sireId","type":"uint256"},{"name":"_matronId","type":"uint256"}],"name":"bidOnSiringAuction","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"gen0CreatedCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"geneScience","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_matronId","type":"uint256"},{"name":"_sireId","type":"uint256"}],"name":"breedWithAuto","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"matronId","type":"uint256"},{"indexed":false,"name":"sireId","type":"uint256"},{"indexed":false,"name":"cooldownEndBlock","type":"uint256"}],"name":"Pregnant","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"from","type":"address"},{"indexed":false,"name":"to","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"approved","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"owner","type":"address"},{"indexed":false,"name":"kittyId","type":"uint256"},{"indexed":false,"name":"matronId","type":"uint256"},{"indexed":false,"name":"sireId","type":"uint256"},{"indexed":false,"name":"genes","type":"uint256"}],"name":"Birth","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"newContract","type":"address"}],"name":"ContractUpgrade","type":"event"}] 8 | 9 | export class CryptoKitties extends Base { 10 | private _sender: string; 11 | private _recipient: string; 12 | private _kittyIds: number[]; 13 | private _cryptoKittiesContract: Contract; 14 | 15 | constructor(provider: providers.JsonRpcProvider, sender: string, recipient: string, kittyIds: number[]) { 16 | super() 17 | if (!isAddress(sender) ) throw new Error("Bad Address") 18 | if (!isAddress(recipient) ) throw new Error("Bad Address") 19 | this._sender = sender; 20 | this._recipient = recipient; 21 | this._kittyIds = kittyIds; 22 | this._cryptoKittiesContract = new Contract(CRYPTO_KITTIES_ADDRESS, CRYPTO_KITTIES_ABI, provider); 23 | } 24 | 25 | async description(): Promise { 26 | return "Sending these kitties " + this._kittyIds.join(",") + " from " + this._sender + " to " + this._recipient 27 | } 28 | 29 | async getSponsoredTransactions(): Promise> { 30 | await Promise.all(this._kittyIds.map(async (kittyId) => { 31 | const owner = await this._cryptoKittiesContract.functions.ownerOf(kittyId); 32 | if (owner.owner !== this._sender) throw new Error("Kitty: " + kittyId + " is not owned by " + this._sender) 33 | })) 34 | return Promise.all(this._kittyIds.map(async (kittyId) => { 35 | return { 36 | ...(await this._cryptoKittiesContract.populateTransaction.transfer(this._recipient, kittyId)), 37 | } 38 | })) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/engine/TransferERC20.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, Contract, providers } from "ethers"; 2 | import { isAddress } from "ethers/lib/utils"; 3 | import { TransactionRequest } from "@ethersproject/abstract-provider"; 4 | import { Base } from "./Base"; 5 | 6 | const ERC20_ABI = [{"constant":true,"inputs":[],"name":"mintingFinished","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"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":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MAXIMUM_SUPPLY","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseApproval","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"finishMinting","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"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":false,"inputs":[{"name":"_token","type":"address"},{"name":"_to","type":"address"}],"name":"salvageTokens","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseApproval","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DipTokensale","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[],"name":"MintFinished","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"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"}] 7 | 8 | export class TransferERC20 extends Base { 9 | private _sender: string; 10 | private _recipient: string; 11 | private _tokenContract: Contract; 12 | 13 | constructor(provider: providers.JsonRpcProvider, sender: string, recipient: string, _tokenAddress: string) { 14 | super() 15 | if (!isAddress(sender)) throw new Error("Bad Address") 16 | if (!isAddress(recipient)) throw new Error("Bad Address") 17 | this._sender = sender; 18 | this._recipient = recipient; 19 | this._tokenContract = new Contract(_tokenAddress, ERC20_ABI, provider); 20 | } 21 | 22 | async description(): Promise { 23 | return "Transfer ERC20 balance " + (await this.getTokenBalance(this._sender)).toString() + " @ " + this._tokenContract.address + " from " + this._sender + " to " + this._recipient 24 | } 25 | 26 | async getSponsoredTransactions(): Promise> { 27 | const tokenBalance = await this.getTokenBalance(this._sender) 28 | if (tokenBalance.eq(0)) { 29 | throw new Error(`No Token Balance: ${this._sender} does not have any balance of ${this._tokenContract.address}`) 30 | } 31 | return [{ 32 | ...(await this._tokenContract.populateTransaction.transfer(this._recipient, tokenBalance)), 33 | }] 34 | } 35 | 36 | private async getTokenBalance(tokenHolder: string): Promise { 37 | return (await this._tokenContract.functions.balanceOf(tokenHolder))[0]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FlashbotsBundleProvider, FlashbotsBundleRawTransaction, 3 | FlashbotsBundleResolution, 4 | FlashbotsBundleTransaction 5 | } from "@flashbots/ethers-provider-bundle"; 6 | import { BigNumber, providers, Wallet } from "ethers"; 7 | import { Base } from "./engine/Base"; 8 | import { checkSimulation, gasPriceToGwei, printTransactions } from "./utils"; 9 | import {TransferERC20} from "./engine/TransferERC20"; 10 | 11 | require('log-timestamp'); 12 | 13 | const BLOCKS_IN_FUTURE = 2; 14 | 15 | const GWEI = BigNumber.from(10).pow(9); 16 | const PRIORITY_GAS_PRICE = GWEI.mul(31) 17 | 18 | const PRIVATE_KEY_EXECUTOR = process.env.PRIVATE_KEY_EXECUTOR || "" 19 | const PRIVATE_KEY_SPONSOR = process.env.PRIVATE_KEY_SPONSOR || "" 20 | const FLASHBOTS_RELAY_SIGNING_KEY = process.env.FLASHBOTS_RELAY_SIGNING_KEY || ""; 21 | const RECIPIENT = process.env.RECIPIENT || "" 22 | 23 | if (PRIVATE_KEY_EXECUTOR === "") { 24 | console.warn("Must provide PRIVATE_KEY_EXECUTOR environment variable, corresponding to Ethereum EOA with assets to be transferred") 25 | process.exit(1) 26 | } 27 | if (PRIVATE_KEY_SPONSOR === "") { 28 | console.warn("Must provide PRIVATE_KEY_SPONSOR environment variable, corresponding to an Ethereum EOA with ETH to pay miner") 29 | process.exit(1) 30 | } 31 | if (FLASHBOTS_RELAY_SIGNING_KEY === "") { 32 | console.warn("Must provide FLASHBOTS_RELAY_SIGNING_KEY environment variable. Please see https://github.com/flashbots/pm/blob/main/guides/flashbots-alpha.md") 33 | process.exit(1) 34 | } 35 | if (RECIPIENT === "") { 36 | console.warn("Must provide RECIPIENT environment variable, an address which will receive assets") 37 | process.exit(1) 38 | } 39 | 40 | async function main() { 41 | const walletRelay = new Wallet(FLASHBOTS_RELAY_SIGNING_KEY) 42 | 43 | // ======= UNCOMMENT FOR GOERLI ========== 44 | // const provider = new providers.InfuraProvider(5, process.env.INFURA_API_KEY || ''); 45 | // const flashbotsProvider = await FlashbotsBundleProvider.create(provider, walletRelay, 'https://relay-goerli.epheph.com/'); 46 | // ======= UNCOMMENT FOR GOERLI ========== 47 | 48 | // ======= UNCOMMENT FOR MAINNET ========== 49 | const ETHEREUM_RPC_URL = process.env.ETHEREUM_RPC_URL || "http://127.0.0.1:8545" 50 | const provider = new providers.StaticJsonRpcProvider(ETHEREUM_RPC_URL); 51 | const flashbotsProvider = await FlashbotsBundleProvider.create(provider, walletRelay); 52 | // ======= UNCOMMENT FOR MAINNET ========== 53 | 54 | const walletExecutor = new Wallet(PRIVATE_KEY_EXECUTOR); 55 | const walletSponsor = new Wallet(PRIVATE_KEY_SPONSOR); 56 | 57 | const block = await provider.getBlock("latest") 58 | 59 | // ======= UNCOMMENT FOR ERC20 TRANSFER ========== 60 | const tokenAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"; 61 | const engine: Base = new TransferERC20(provider, walletExecutor.address, RECIPIENT, tokenAddress); 62 | // ======= UNCOMMENT FOR ERC20 TRANSFER ========== 63 | 64 | // ======= UNCOMMENT FOR 721 Approval ========== 65 | // const HASHMASKS_ADDRESS = "0xC2C747E0F7004F9E8817Db2ca4997657a7746928"; 66 | // const engine: Base = new Approval721(RECIPIENT, [HASHMASKS_ADDRESS]); 67 | // ======= UNCOMMENT FOR 721 Approval ========== 68 | 69 | const sponsoredTransactions = await engine.getSponsoredTransactions(); 70 | 71 | const gasEstimates = await Promise.all(sponsoredTransactions.map(tx => 72 | provider.estimateGas({ 73 | ...tx, 74 | from: tx.from === undefined ? walletExecutor.address : tx.from 75 | })) 76 | ) 77 | const gasEstimateTotal = gasEstimates.reduce((acc, cur) => acc.add(cur), BigNumber.from(0)) 78 | 79 | const gasPrice = PRIORITY_GAS_PRICE.add(block.baseFeePerGas || 0); 80 | const bundleTransactions: Array = [ 81 | { 82 | transaction: { 83 | to: walletExecutor.address, 84 | gasPrice: gasPrice, 85 | value: gasEstimateTotal.mul(gasPrice), 86 | gasLimit: 21000, 87 | }, 88 | signer: walletSponsor 89 | }, 90 | ...sponsoredTransactions.map((transaction, txNumber) => { 91 | return { 92 | transaction: { 93 | ...transaction, 94 | gasPrice: gasPrice, 95 | gasLimit: gasEstimates[txNumber], 96 | }, 97 | signer: walletExecutor, 98 | } 99 | }) 100 | ] 101 | const signedBundle = await flashbotsProvider.signBundle(bundleTransactions) 102 | await printTransactions(bundleTransactions, signedBundle); 103 | const simulatedGasPrice = await checkSimulation(flashbotsProvider, signedBundle); 104 | 105 | console.log(await engine.description()) 106 | 107 | console.log(`Executor Account: ${walletExecutor.address}`) 108 | console.log(`Sponsor Account: ${walletSponsor.address}`) 109 | console.log(`Simulated Gas Price: ${gasPriceToGwei(simulatedGasPrice)} gwei`) 110 | console.log(`Gas Price: ${gasPriceToGwei(gasPrice)} gwei`) 111 | console.log(`Gas Used: ${gasEstimateTotal.toString()}`) 112 | 113 | provider.on('block', async (blockNumber) => { 114 | const simulatedGasPrice = await checkSimulation(flashbotsProvider, signedBundle); 115 | const targetBlockNumber = blockNumber + BLOCKS_IN_FUTURE; 116 | console.log(`Current Block Number: ${blockNumber}, Target Block Number:${targetBlockNumber}, gasPrice: ${gasPriceToGwei(simulatedGasPrice)} gwei`) 117 | const bundleResponse = await flashbotsProvider.sendBundle(bundleTransactions, targetBlockNumber); 118 | if ('error' in bundleResponse) { 119 | throw new Error(bundleResponse.error.message) 120 | } 121 | const bundleResolution = await bundleResponse.wait() 122 | if (bundleResolution === FlashbotsBundleResolution.BundleIncluded) { 123 | console.log(`Congrats, included in ${targetBlockNumber}`) 124 | process.exit(0) 125 | } else if (bundleResolution === FlashbotsBundleResolution.BlockPassedWithoutInclusion) { 126 | console.log(`Not included in ${targetBlockNumber}`) 127 | } else if (bundleResolution === FlashbotsBundleResolution.AccountNonceTooHigh) { 128 | console.log("Nonce too high, bailing") 129 | process.exit(1) 130 | } 131 | }) 132 | } 133 | 134 | main() 135 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FlashbotsBundleProvider, FlashbotsBundleRawTransaction, 3 | FlashbotsBundleTransaction, 4 | } from "@flashbots/ethers-provider-bundle"; 5 | import { BigNumber } from "ethers"; 6 | import { parseTransaction } from "ethers/lib/utils"; 7 | 8 | export const ETHER = BigNumber.from(10).pow(18); 9 | export const GWEI = BigNumber.from(10).pow(9); 10 | 11 | export async function checkSimulation( 12 | flashbotsProvider: FlashbotsBundleProvider, 13 | signedBundle: Array 14 | ): Promise { 15 | const simulationResponse = await flashbotsProvider.simulate( 16 | signedBundle, 17 | "latest" 18 | ); 19 | 20 | if ("results" in simulationResponse) { 21 | for (let i = 0; i < simulationResponse.results.length; i++) { 22 | const txSimulation = simulationResponse.results[i]; 23 | if ("error" in txSimulation) { 24 | throw new Error( 25 | `TX #${i} : ${txSimulation.error} ${txSimulation.revert}` 26 | ); 27 | } 28 | } 29 | 30 | if (simulationResponse.coinbaseDiff.eq(0)) { 31 | throw new Error("Does not pay coinbase"); 32 | } 33 | 34 | const gasUsed = simulationResponse.results.reduce( 35 | (acc: number, txSimulation) => acc + txSimulation.gasUsed, 36 | 0 37 | ); 38 | 39 | const gasPrice = simulationResponse.coinbaseDiff.div(gasUsed); 40 | return gasPrice; 41 | } 42 | 43 | console.error( 44 | `Similuation failed, error code: ${simulationResponse.error.code}` 45 | ); 46 | console.error(simulationResponse.error.message); 47 | throw new Error("Failed to simulate response"); 48 | } 49 | 50 | export async function printTransactions( 51 | bundleTransactions: Array, 52 | signedBundle: Array 53 | ): Promise { 54 | console.log("--------------------------------"); 55 | console.log( 56 | ( 57 | await Promise.all( 58 | bundleTransactions.map( 59 | async (bundleTx, index) => { 60 | const tx = 'signedTransaction' in bundleTx ? parseTransaction(bundleTx.signedTransaction) : bundleTx.transaction 61 | const from = 'signer' in bundleTx ? await bundleTx.signer.getAddress() : tx.from 62 | 63 | return `TX #${index}: ${from} => ${tx.to} : ${tx.data}` 64 | }) 65 | ) 66 | ).join("\n") 67 | ); 68 | 69 | console.log("--------------------------------"); 70 | console.log( 71 | ( 72 | await Promise.all( 73 | signedBundle.map(async (signedTx, index) => `TX #${index}: ${signedTx}`) 74 | ) 75 | ).join("\n") 76 | ); 77 | 78 | console.log("--------------------------------"); 79 | } 80 | 81 | export function gasPriceToGwei(gasPrice: BigNumber): number { 82 | return gasPrice.mul(100).div(GWEI).toNumber() / 100; 83 | } 84 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | "lib": [ "es2018" ], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./build", /* Redirect output structure to the directory. */ 16 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | "composite": true, /* Enable project compilation */ 18 | "tsBuildInfoFile": "./build/tsconfig.tsbuildinfo", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "src/" /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | }, 63 | "include": [ 64 | "src/**/*", 65 | ], 66 | "exclude": [ 67 | "test", 68 | "build", 69 | "node_modules", 70 | ] 71 | } 72 | --------------------------------------------------------------------------------