Welcome to the Bithoven, Gambit's Algorithmic Trading System. Bithoven lets you run your own customized investment strategy on the Gambit contract deployed on the Base network. With Bithoven, you can buy and sell player bits (represented by their web3 address in the contract) based on numerous signals and trends (e.g., bits in circulation, win rate, trading activity on the most recent block, and much more!). All of this can be done from your own desktop (or in the cloud) with very minimal system requirements.
By following the instructions in this guide, you will be able to set up and run your own Bithoven bot for trading on the Gambit contract. Happy trading!
- OS: A Linux-based system (e.g., Debian, Ubuntu, etc.). This will also work from a Mac shell!
- Software: Node.js (v18.11 or greater), npm, pm2
- Storage: Bithoven uses the local file system to persist state. It is safe to wipe out the state and start from scratch any time, as the blockchain (BASE) on which the Gambit contract is deployed is used as the source of truth. All positions and trades made by the Bithoven operator are rebuilt by the Bithoven blockchain indexer component that processes all trade events emitted by the Gambit contract.
Clone the repository: Change the git url below to the actual git url when made
git clone https://github.com/ethlas-1/bithoven.git
cd bithoven
npm installInstall pm2 globally:
npm install -g pm2To connect to the Base network, you will need an RPC URL. Here is an example of the URL:
BASE_PROVIDER_URL=https://base-mainnet.core.chainstack.com/your-api-key
This setup has been tested and confirmed to work with Chainstack. To use Chainstack nodes, you will need to purchase the Growth plan to enable the necessary calls. The capacity in terms of the number of requests per month covered by this plan (20 million) should suffice for Bithoven blockchain indexing requirements.
For Alchemy nodes, you will need to purchase their premium plan as well to run it on there.
List of calls made during indexing:
eth_chainId
eth_getLogs
eth_getBlockByNumber
eth_call
eth_blockNumberThe Gambit contract used by Bithoven is deployed to the following address: 0x6b4819f78D886eF37d4f9972FD55B0302c947277. The contract source code for Gambit can be viewed here.
You will need one or more Base addresses (and corresponding private keys) to trade from. While you can trade with a single key, Bithoven will not initiate a new buy/sell transaction from a key until the current one has been mined. To trade on multiple buying and selling opportunities simultaneously, Bithoven enables you to set up a key fleet to trade from. To do this, set up your .env file as shown below: (The .env file needs to be set in the config directory)
WALLET_0x4c9fBe5b9e6a1fBc5dC2E8270EdB54b64437bAB2="xxxxx"
WALLET_{WALLET_2}="your-private-key-2"
WALLET_{WALLET_3}="your-private-key-3"
BASE_PROVIDER_URL=https://base-mainnet.core.chainstack.com/your-api-keyYou will need to purchase WELS. The address for WELS is: 0x7F62ac1e974D65Fab4A81821CA6AF659A5F46298 You can get WELS by following the instructions provided on aerodrome.
You will also need Base ETH. You can obtain Base ETH from major cryptocurrency exchanges like Binance or Coinbase.
When one of your fleet addresses buys bits from the Gambit contract, the Gambit contract must have allowance to transfer WELS tokens from the key fleet address to the Gambit contract address. To grant allowance:
- Navigate to the WELS token contract on Etherscan.
- Click on the "Write Contract" tab.
- Connect your wallet.
- Find the "increaseAllowance" function and enter the following details:
spender: The Gambit contract address (0x6b4819f78D886eF37d4f9972FD55B0302c947277).addedValue: Enter a high amount, e.g.,100000000000000000000000(ensure to set a value high enough to cover multiple transactions).
- Click "Write" and confirm the transaction.
Audited by FailSafe
Ensure your .env file is correctly set up with your wallet keys and provider URL.
CAUTION: Running your bot scripts manually is purely for initial experimentation. For actual trading, you should use the PM2 approach described below. There are conditions, such as faulty provider connectivity/errors, where the indexer (investor.js) will intentionally shut down, counting on PM2 to automatically restart, so that it can pick up indexing from the last successfully persisted contract event.
To start your bot manually:
Start the investor script:
node ./scripts/tradesGenaration/investor.jsThis starts up the indexer and once it has caught up with the latest emitted events, it begins proposing trades based on ./rules/buy/buyRules.json and ./rules/sell/sellRules.json. Note that these rules are intended for you to change and customize to form your strategy! The rules need to stick to the schema format defined in the .schema directory and use the functions defined in trade/functions.js. You can do further customization by implementing and plugging in your own. For more details, see Trading Strategies
Start the trade gofer script:
node ./scripts/tradesExecution/tradeGofer.jsThis script processes, schedules, and executes the trades proposed by the investor.js script. The throughput of the trades depends on the size of your keyFleet. The pending trades are temporarily tracked in the ./data/orders directory.
First, install PM2:
npm install -g pm2Then run:
pm2 start node --time --log /tmp/investorLog.log --name investor -- --trace-uncaught ./scripts/tradesGenaration/investor.js
pm2 start node --time --log /tmp/tradeGoferLog.log --name tradeGofer -- --trace-uncaught ./scripts/tradesExecution/tradeGofer.js
pm2 saveTo see traced script execution, use:
tail -F /tmp/investorLog.log
tail -F /tmp/tradeGoferLog.logTo view the outcome of proposed and executed trades, check the logs directory at the root of your project (logs/logs.info, logs/info.log, logs/warnings.log, logs/errors.log). Logs are automatically rolled over when they reach the maximum size (as specified in config/cloudConfig.js). To receive these trade events on Slack, see the Slack configuration section below.
pm2 stop investor
pm2 stop tradeGoferFor more details on how to use PM2, refer to the PM2 Documentation.
This setup is optional (this information is also logged to the logs directory). However, if you would like to receive trade status/warnings and any errors on Slack, do the following:
-
Create a Slack App:
- Go to the Slack API: Applications page.
- Click on "Create New App".
- Choose "From scratch" and provide a name for your app and select the Slack workspace where you want to install the app.
-
Configure OAuth & Permissions:
- Navigate to "OAuth & Permissions" under "Features".
- Scroll down to "Scopes" and add the following bot token scopes:
chat:writechannels:readgroups:readim:readmpim:read
- Click "Save Changes".
-
Install App to Workspace:
- Go back to "OAuth & Permissions".
- Scroll up to "OAuth Tokens for Your Workspace" and click "Install App to Workspace".
- Complete the installation and copy the "Bot User OAuth Token".
-
Add Your Slack API Key to .env File:
- In your
.envfile, add the following line with your copied token:slack_key="your-slack-api-key"
- In your
This will enable the bot to send notifications and updates directly to your Slack channel.
Here is an example of a complete .env file:
(The .env file needs to be set in the config directory)
slack_key="xxxxx"
WALLET_0x4c9fBe5b9e6a1fBc5dC2E8270EdB54b64437bAB2="xxxxx"
WALLET_{WALLET_2}="your-private-key-2"
WALLET_{WALLET_3}="your-private-key-3"
BASE_PROVIDER_URL="https://base-mainnet.core.chainstack.com/your-api-key"This section explains various buy and sell strategies that can be implemented to optimize your bot's trading performance. To implement a given strategy, you should paste the corresponding rules into the rules/buy/buyRules.json for buy strategies and rules/sell/sellRules.json for sell strategies.
All strategies shown below can be found in rules/buy/exampleStrategies and rules/sell/exampleStrategies.
-
New Gamer Strategies
[ { "ruleID": "newUser", "invokeBy": ["cloudNewGamerFeed"], "conditions": [ { "expression": "gamerWithinMaxAge(60)" }, { "expression": "gamerTotalBitsInCirculation('<', 5)" } ], "action": "buyUpTo(2)" } ]This strategy buys bits for users who have registered within the last 60 minutes and have fewer than 5 bits in circulation. It aims to capitalize on new users entering the market. Note that if the conditions evaluate to true, the
buyUpTofunction ensures that the entire key fleet owns no more than 2 bits of the gamer that met the above conditions.The configuration-driven strategy makes it easy to compose your own conditions, specify your own parameter values, and more without writing any additional code.
-
Skilled Gamer Strategy
[ { "ruleID": "experiencedGamer", "invokeBy": ["chainIndexer"], "conditions": [ { "expression": "gamesPlayed('>', 20)" }, { "expression": "gamerTotalBitsInCirculationExcludeOwnStake('>', 20)" } ], "action": "buyUpTo(3)" }, { "ruleID": "triggerhappy", "invokeBy": ["chainIndexer"], "conditions": [ { "expression": "gamerSumKills('>=', 15)" }, { "expression": "gamerBitWithinMaxBuyPrice(6)" } ], "action": "buyUpTo(2)" }, { "ruleID": "skilledGamer", "invokeBy": ["chainIndexer"], "conditions": [ { "expression": "gamerTotalBitsInCirculation('<', 10)" }, { "expression": "gamerWinRate('>', 40)" } ], "action": "buyUpTo(1)" } ]This set of rules targets experienced gamers by considering factors like the number of games played, total bits in circulation excluding their own stake, sum of kills, and win rate. It aims to invest in gamers who show consistent and high performance.
-
Trending Gamer Strategy
[ { "ruleID": "trendingUpUser", "invokeBy": ["chainIndexer"], "conditions": [ { "expression": "gamerSupplyUpTick(3)" }, { "expression": "gamerTotalBitsInCirculationExcludeOwnStake('>', 5)" }, { "expression": "gamerTotalBitsInCirculationExcludeOwnStake('<', 10)" } ], "action": "buyUpTo(3)" } ]This strategy buys bits when a gamer's bit supply is trending upwards within a specified range. It aims to take advantage of positive trends in a gamer's performance and bit accumulation.
-
Close Out All Positions Strategy
[ { "ruleID": "portfolioRefresh", "invokeBy": ["chainHolderInvestmentsFullSweep"], "quantity": "holderOwnedBitAge('>', 1)", "action": "sellBit()" } ]This strategy sells all bits owned by the keyFleet that are older than one minute. It is used to quickly liquidate the entire portfolio. This strategy illustrates the use of quantity instead of conditions. The function in the
quantityfield (holderOwnedBitAge) retrieves the number of bits owned by the keyFleet that are older than one minute and passes this value to thesellBitfunction. This applies to any gamer owned by any one of the keyFleet keys. If the value is 0, meaning no criteria match, thensellBitwill not sell anything. -
Profit Taking Example
[ { "ruleID": "profitTaking", "invokeBy": ["chainHolderInvestmentsFullSweep"], "quantity": "bitProfitThreshold(10)", "action": "sellBit()" } ]This strategy sells bits that have reached a target profit threshold (e.g., 10%). It is designed to take profits when the bits have appreciated in value.
-
Trending Down User Example
[ { "ruleID": "trendingDownUser", "invokeBy": ["chainIndexer"], "conditions": [ { "expression": "gamerSupplyDownTick(2)" }, { "expression": "gamerTotalBitsInCirculationExcludeOwnStake('<', 5)" } ], "action": "sellBitFromAutoSelectedFleetKey(1000000)" } ]This strategy sells all bits owned by the keyFleet if a gamer's bit supply is trending downwards. It aims to cut losses by liquidating positions when negative trends are detected.
ruleIDis an identifier used for logging purposes and has no actual meaning in the execution of the rules.conditionsare boolean expressions that must be met for the actions to be executed.actionsare the operations that will be carried out if the conditions are met.- You can specify any number of rule objects in the
buyRulesandsellRulesfiles. - The action function is only executed if all conditions evaluate to true.
- In a condition, you can enumerate any number of expressions.
invokedByrepresents the system component that will execute your rule.- There are three system components for this:
cloudNewGamerFeed- pulls off-chain new Gambit player records.chainIndexer- processes all new blocks looking for events emitted by the Gambit contract.chainHolderInvestmentsFullSweep- periodically triggers rules on all gamer bits owned by the key fleet.
- There are three system components for this:
- Note that either
conditionsor thequantityfield needs to be specified (quantity examples shown in the sell section). - The complete list of supported functions and valid parameter values are defined in the next section, followed by how to extend and implement your own custom function.
To generate the profit and loss (P&L) report for the fleet addresses:
-
Navigate to the
bithovendirectory. -
Run the following command:
node scripts/generatePandL.js
This script retrieves the full store for the fleet addresses, computes the P&L, and prints the results.
You can also provide a specific block number to compute the P&L from that point onward. For example:
node scripts/generatePandL.js 14543534
This will calculate profit and loss starting from the block number
14543534, which is the point in time when your strategy was deployed.
Once you have configured, tested, and are ready to deploy your trading strategy with Bithoven, it's essential to manage the amount of WELS that your strategy can utilize effectively. The simplest way to control this is by limiting the total funds you transfer into your key fleet. For instance, you could allocate 200 WELS across 4 keys.
The trading system is designed to handle situations where it fully invests all allocated WELS into gamer bits. In such scenarios, the system will only execute sell bit orders until additional WELS are available. This ensures that the strategy continues to operate smoothly without exceeding the available budget.
To evaluate the performance of your strategy, you can use the scripts/generatePandL.js tool. This tool helps you analyze profits and losses, enabling you to make informed decisions about your strategy.
Based on the evaluation, you have several options:
- Stop the Scripts: If the strategy is not performing as expected, you can halt the execution and consider developing a new strategy.
- Lock in Profits: If the strategy is profitable, you can withdraw funds from the key fleet to secure your gains.
- Continue Execution: You may choose to let the strategy run if it's meeting your expectations. Any WELS earned from sales will be reinvested into the key fleet, allowing the strategy to continue its trading activities.
Below is a description of the functions that you can use in the rules, along with their parameters:
Evaluates if the bits held by the gamer meet a specified profit threshold.
Parameters:
value: The profit threshold percentage.
Proposes to buy up to a specified amount of bits.
Parameters:
value: The maximum amount of bits to buy.
Evaluates the number of games played by the gamer and checks if it meets the specified condition.
Parameters:
operator: The comparison operator (e.g., '>', '>=', '<', '<=', '==').gamesPlayed: The number of games played.
Checks if the current buy price of bits for a gamer is within a specified maximum price.
Parameters:
value: The maximum buy price.
Checks if the gamer's bits have been idle within a specified maximum time.
Parameters:
value: The maximum idle time in minutes.
Evaluates the gamer's sum of kills and checks if it meets the specified condition.
Parameters:
operator: The comparison operator (e.g., '>', '>=', '<', '<=', '==').sumKills: The sum of kills.
Checks if the gamer's supply has decreased by a specified tick amount.
Parameters:
value: The number of bits that were sold on a recent block.
Checks if the gamer's supply has increased by a specified tick amount.
Parameters:
value: The number of bits that were bought on a recent block.
Evaluates the total number of bits in circulation for the gamer and checks if it meets the specified condition.
Parameters:
operator: The comparison operator (e.g., '>', '>=', '<', '<=', '==').value: The target number of bits.
Evaluates the total number of bits in circulation for the gamer, excluding their own stake, and checks if it meets the specified condition.
Parameters:
operator: The comparison operator (e.g., '>', '>=', '<', '<=', '==').value: The target number of bits.
Checks if the gamer's wallet was created within the specified number of minutes.
Parameters:
value: The maximum age of the wallet in minutes.
Evaluates the gamer's win rate and checks if it meets the specified condition.
Parameters:
operator: The comparison operator (e.g., '>', '>=', '<', '<=', '==').value: The win rate.
Evaluates if the holder-owned bits' age meets the specified condition.
Parameters:
operator: The comparison operator (e.g., '>', '>=', '<', '<=', '==').value: The age in minutes.
Checks if a recent purchase event has reached or exceeded the target amount. For example, this could be used to determine if 3 or more bits were bought recently. Bit movements belonging to the key fleet are excluded.
Parameters:
amount: The target number of bits as a string representation of an integer.value: The time period in minutes over which to evaluate the purchases.
Returns:
- A
Promise<boolean>that resolves totrueif the condition is met, otherwisefalse.
Checks if a recent sell event has reached or exceeded the target amount. For example, this could be used to determine if 3 or more bits were sold recently. Bit movements belonging to the key fleet are excluded.
Parameters:
amount: The target number of bits as a string representation of an integer.value: The time period in minutes over which to evaluate the sales.
Returns:
- A
Promise<boolean>that resolves totrueif the condition is met, otherwisefalse.
Checks if the current gamer's address exists in the specified whitelist.
Whitelist file directory located in /bithoven/data/whitelist
Parameters:
value: The name of the whitelist file (without the.jsonextension).
Returns:
trueif the gamer's address is in the whitelist, otherwisefalse.
Checks if the current gamer's address does not exist in the specified whitelist.
Whitelist file directory located in /bithoven/data/whitelist
Parameters:
value: The name of the whitelist file (without the.jsonextension).
Proposes to sell bits based on current context.
Proposes to sell bits using an automatically selected key from the key fleet.
If you need to implement your own custom function, you must add support for it in common/functions.js, common/conditions/conditions.js, and common/actions/actions.js, following the same pattern used for the existing functions.
This section describes the usage and purpose of the scripts/deleteFileStore.js script, which is a tool designed to reset the local data store used by the Bithoven trading system.
The deleteFileStore.js script removes all directories in the data directory. This can be used to start clean and re-sync the data store from scratch.
The deleteFileStore.js script is used to clear all directories within the data directory. This is particularly useful when you need to reset and re-sync your local data store from scratch. This can be necessary if you encounter issues with the data store or simply want to start fresh.
To use the scripts/deleteFileStore.js script, follow these steps:
-
Stop Running Scripts: Ensure that both the
investor.jsandtradeGofer.jsscripts are stopped. This prevents any potential data corruption or conflicts while clearing the data store. -
Run the Script: Execute the script using the command:
node scripts/deleteFileStore.js
-
Restart the Scripts: Once the data directory has been cleared, restart the
investor.jsandtradeGofer.jsscripts. Theinvestor.jsscript will rebuild the local store by re-indexing all events emitted by the Gambit contract, ensuring that the local data store is fully synchronized and up-to-date.
By following these steps, you can effectively manage and maintain the integrity of your Bithoven trading system's local data store.
-
Lambda Signature Call for Smart Contract Buy Method:
The purpose of this signature is to validate the purchase of whitelisted bits based on the amount of trading volume you have. This ensures that only eligible traders can participate in purchasing certain bits, making the process more secure and regulated.
We introduced a feature that calls the Lambda signature function and passes the signature into the new
buymethod on the Gambit smart contract. This enables smoother and more secure execution when buying bits through the Bithoven platform. Thebuymethod now supports signature-based validation, allowing for a secure and streamlined purchase process.
-
New
gamerBuysandgamerSellsFunctions:
These functions allow the system to track and execute buy/sell operations based on recent trends in a gamer's performance. ThegamerBuysfunction tracks the number of bits recently bought for a specific gamer and evaluates whether the number exceeds a certain threshold within a given time period. Similarly,gamerSellstracks the number of bits sold. These new functions empower your trading strategy to capitalize on trending gamers by purchasing or selling bits when they exhibit strong positive or negative trends.Example Use:
If a gamer's bit supply starts trending up, the system can trigger a purchase (usinggamerBuys), and if the bit supply trends down, it can trigger a sale (usinggamerSells).
-
Whitelist Trading Feature:
We've added a whitelist functionality to allow for more controlled trading. This allows you to define a whitelist rule so that Bithoven will only trade bits related to certain games, such as Dota. The whitelist ensures that only bits from the specified players or games can be traded, providing tighter control over your strategy.Example:
You can configure the system to trade only Dota-related bits by adding a rule that restricts the bot to execute trades solely for Dota players.
Bithoven now supports multiple game contracts, each requiring its own bot instance:
-
Dota (WELS) Contract:
0x6b4819f78D886eF37d4f9972FD55B0302c947277- Used exclusively for Dota bits
-
Multi-Game (USDC) Contract:
0x5739EdBcc6916fB1A68991F7A6e3951334d2898b- Used for all other game bits (Valorant, CS2, etc.)
To configure your bot for a specific contract, update the chainConfig.js file with:
- The appropriate
contractAddressfrom above - The correct contract type (
"WELS"or"USDC")
Each contract requires a separate bot instance, so you'll need to run two separate setups if you wish to trade on both contracts simultaneously.
The Bithoven bot can trade bits for different games (e.g., BSD, Dota, Valorant, CS2). However, not all metadata fields are available or relevant for every game. For instance, gamerWinRate was available for BSD but not used for Dota.
Use the reference below to see which signals/functions are currently supported by each game. Attempting to use a function that is not supported by a game will generally cause your rule's condition to evaluate to false (or be ignored).
| Signal / Function | BSD | Dota | Valorant | CS2 | Notes |
|---|---|---|---|---|---|
| gamerWinRate | ✓ | ✗ | ✗ | ✗ | Only populated for BSD at this time. |
| gamerSumKills | ✓ | ✗ | ✗ | ✗ | Available for most MOBA/FPS games. (Dota → kills; Valorant/CS2 → kills, however functionaility currently not built in) |
| gamesPlayed | ✓ | ✗ | ✗ | ✗ | Universal field indicating the total number of matches (or rounds) played by the gamer. |
| gamerSupplyUpTick / gamerSupplyDownTick | ✓ | ✓ | ✓ | ✓ | Universal signals indicating a significant increase/decrease in the gamer's bit supply (regardless of game). |
| gamerTotalBitsInCirculation | ✓ | ✓ | ✓ | ✓ | Universal field indicating how many bits (total) are currently owned by all market participants for this gamer. |
| gamerWithinMaxAge | ✓ | ✓ | ✓ | ✓ | Universal field indicating how "new" the gamer is (based on wallet registration/first-play data). |
| gamerBitWithinMaxBuyPrice | ✓ | ✓ | ✓ | ✓ | Universal check for the gamer's current bit buy price vs. your specified maximum buy limit. |
| gamerBuys / gamerSells | ✓ | ✓ | ✓ | ✓ | Universal functions checking how many new buys/sells occurred (excluding your own keyFleet's trades) in a given period. |
| holderOwnedBitAge | ✓ | ✓ | ✓ | ✓ | Universal function checking how long your keyFleet has held bits for a given gamer. |
| isGamerInWhitelist / isGamerNotInWhitelist | ✓ | ✓ | ✓ | ✓ | Universal logic for restricting trades to (or excluding) certain addresses. |