Bonfida-bot JS library
A JavaScript client library for interacting with the bonfida-bot on-chain program. This library can be used for :
- Creating pools.
- Trading on behalf of pools by sending signals.
- Depositing assets into a pool in exchange for pool tokens.
- Redeeming pool tokens to retrieve an investement from the pool.
Installation
This library provides bindings for different pool operations. Adding it to a project is quite easy.
Using npm:
npm install bonfida-bot
Using yarn:
yarn add bonfida-bot
Concepts
Pool state and locking
There are two scenarios in which a pool can find itself in a locked state :
- There are pending orders : some Serum orders have not yet been settled with the
settleFunds
instruction. - There are fees to collect. At intervals greater than a week, signal provider and Bonfida fees must be extracted from the pool.
However, a locked pool can be unlocked with a sequence of permissionless operations. When the pool has pending orders, it is often possible to
resolve the situation by running a settleFunds
instruction. This is due to the fact that orders are either in the event queue and waiting to
be consumed by Serum permissionless crankers, or waiting to be settled. When the pool has fees to collect, the permissionless collectFees
instruction extracts all accrued fees from the pool and transfers those to defined signal provider (50% of fee) and Bonfida Bots Insurance Fund (25% of fee), as well as a FIDA buy and burn address (25% of fee).
TLDR: It is always possible for users to unlock the pool when they want to exit or even just enter into it.
Usage
Creating a pool
import { Connection, Account, PublicKey } from '@solana/web3.js';
import { createPool } from 'bonfida-bot';
import { signAndSendTransactionInstructions, Numberu64 } from 'bonfida-bot';
import { ENDPOINTS } from 'bonfida-bot';
const connection = new Connection(ENDPOINTS.mainnet);
const sourceOwnerAccount = new Account(['<MY PRIVATE KEY ARRAY>']);
const signalProviderAccount = new Account(['<MY PRIVATE KEY ARRAY AGAIN>']);
const payerAccount = sourceOwnerAccount;
const sourceAssetKeys = [
new PublicKey('<First asset Key>'),
new PublicKey('<Second asset Key>'),
];
const deposit_amounts = [1000000, 1000000];
export const decimalsFromMint = async (
connection: Connection,
mint: PublicKey,
) => {
const result = await connection.getParsedAccountInfo(mint);
const decimals = result?.value?.data?.parsed?.info?.decimals;
if (!decimals) {
throw new Error('Invalid mint');
}
return decimals;
};
const maxNumberOfAsset = 10;
const allowedMarkets = [
new PublicKey('E14BKBhDWD4EuTkWj1ooZezesGxMW8LPCps4W5PuzZJo'),
];
let marketAddress =
MARKETS[
MARKETS.map(m => {
return m.name;
}).lastIndexOf('<Market name, FIDA/USDC for instance>')
].address;
const feeCollectionPeriod = new Numberu64(604800);
const feePercentage = 0.1;
const pool = async () => {
let [poolSeed, createInstructions] = await createPool(
connection,
sourceOwnerAccount.publicKey,
sourceAssetKeys,
signalProviderAccount.publicKey,
deposit_amounts,
maxNumberOfAsset,
allowedMarkets,
payerAccount.publicKey,
feeCollectionPeriod,
feePercentage,
);
await signAndSendTransactionInstructions(
connection,
[sourceOwnerAccount],
payerAccount,
createInstructions,
);
console.log('Created Pool');
};
pool();
Signal provider operations
Sending an order to the pool as a signal provider
import { MARKETS } from '@project-serum/serum';
import { Connection, Account } from '@solana/web3.js';
import {
createOrder,
ENDPOINTS,
OrderSide,
OrderType,
SelfTradeBehavior,
} from 'bonfida-bot';
import { signAndSendTransactionInstructions, Numberu64 } from 'bonfida-bot';
import bs58 from 'bs58';
const connection = new Connection(ENDPOINTS.mainnet);
let marketInfo =
MARKETS[
MARKETS.map(m => {
return m.name;
}).lastIndexOf('<Market name, FIDA/USDC for instance>')
];
let poolSeed = bs58.decode('<poolSeeds>');
const signalProviderAccount = new Account('<MY PRIVATE KEY ARRAY>');
const payerAccount = signalProviderAccount;
let side = OrderSide.Ask;
let limitPrice = new Numberu64(100000);
let maxQuantityPercentage = 20;
let orderType = OrderType.ImmediateOrCancel;
let clientId = new Numberu64(0);
let selfTradeBehavior = SelfTradeBehavior.DecrementTake;
const order = async () => {
let [openOrderAccount, createPoolTxInstructions] = await createOrder(
connection,
poolSeed,
marketInfo.address,
side,
limitPrice,
maxQuantityPercentage,
orderType,
clientId,
selfTradeBehavior,
null,
payerAccount.publicKey,
);
await signAndSendTransactionInstructions(
connection,
[openOrderAccount, signalProviderAccount],
payerAccount,
createPoolTxInstructions,
);
console.log('Created Order for Pool');
};
order();
Non-privileged operations
Depositing funds into a pool
In exchange for a distribution of tokens which is proportional to the current asset holdings of the pool, a pool will issue pool tokens which
are redeemable for a commensurate proportion of pool assets at a later date. This operation represents the fundamental investment mechanism.
import { deposit } from 'bonfida-bot';
import { signAndSendTransactionInstructions, Numberu64 } from 'bonfida-bot';
import { BONFIDABOT_PROGRAM_ID } from 'bonfida-bot';
const poolTokenAmount = new Numberu64(3000000);
const depositIntoPool = async () => {
let depositTxInstructions = await deposit(
connection,
sourceOwnerAccount.publicKey,
sourceAssetKeys,
poolTokenAmount,
[poolSeed],
payerAccount.publicKey,
);
await signAndSendTransactionInstructions(
connection,
[sourceOwnerAccount],
payerAccount,
depositTxInstructions,
);
console.log('Deposited into Pool');
};
depositIntoPool();
Retrieving funds from a pool
import { redeem } from 'bonfida-bot';
import { signAndSendTransactionInstructions, Numberu64 } from 'bonfida-bot';
const poolTokenAmount = new Numberu64(1000000);
const redeemFromPool = async () => {
let redeemTxInstruction = await redeem(
connection,
sourceOwnerAccount.publicKey,
sourcePoolTokenKey,
sourceAssetKeys,
[poolSeed],
poolTokenAmount,
);
await signAndSendTransactionInstructions(
connection,
[sourceOwnerAccount],
payerAccount,
redeemTxInstruction,
);
console.log('Redeemed out of Pool');
};
redeemFromPool();
Settling funds from an order
Once a Serum order has gone through, it is necessary to retrieve the funds from the openOrder account in order to unlock the pool for all deposit and
redeem operations. Thankfully, this operation is permissionless which means that a locked pool is unlockable by anyone. This means that in order to make sure that a deposit or redeem instruction will go through, it is interesting to precede it with a settle instruction in the same transaction.
import { Account } from '@solana/web3.js';
import { settleFunds } from 'bonfida-bot';
import { signAndSendTransactionInstructions } from 'bonfida-bot';
const payerAccount = Account('<MY PRIVATE KEY ARRAY>');
let settleFundsTxInstructions = await settleFunds(
connection,
poolSeed,
marketAddress,
OpenOrderAccount.address,
null,
);
await signAndSendTransactionInstructions(
connection,
[],
payerAccount,
settleFundsTxInstructions,
);
console.log('Settled Funds');
Triggering a fee collection operation
In order to unlock a potentially unlocked pool, or in order to trigger fee collection as a signal provider, it is necesary to
activate the collectFees
permissionless crank.
Beneficiary | Fee Proportion |
---|
Signal provider | 50% |
Bonfida | 25% |
FIDA buy and burn | 25% |
import { collectFees } from 'bonfida-bot';
import { signAndSendTransactionInstructions } from 'bonfida-bot';
let collectFeesTxInstruction = await collectFees(connection, [poolSeed]);
await signAndSendTransactionInstructions(
connection,
[],
payerAccount,
collectFeesTxInstruction,
);
console.log('Redeemed out of Pool');