This repository has been archived by the owner on Oct 14, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 66
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adding auto swapping and targeting specific markets
- Loading branch information
Showing
9 changed files
with
322 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/* eslint-disable no-lonely-if */ | ||
/* eslint-disable no-continue */ | ||
/* eslint-disable no-restricted-syntax */ | ||
/* eslint-disable no-param-reassign */ | ||
import { findWhere } from 'underscore'; | ||
import BigNumber from 'bignumber.js'; | ||
import { Token, TOKEN_PROGRAM_ID } from '@solana/spl-token'; | ||
import { PublicKey } from '@solana/web3.js'; | ||
import { TokenCount } from 'global'; | ||
import swap from './swap'; | ||
|
||
// Padding so we rebalance only when abs(target-actual)/target is greater than PADDING | ||
const PADDING = Number(process.env.REBALANCE_PADDING) || 0.2; | ||
|
||
export async function rebalanceWallet(connection, payer, jupiter, tokensOracle, walletBalances, target) { | ||
const info = await aggregateInfo(tokensOracle, walletBalances, connection, payer, target); | ||
// calculate token diff between current & target value | ||
info.forEach((tokenInfo) => { | ||
tokenInfo.diff = tokenInfo.balance - tokenInfo.target; | ||
tokenInfo.diffUSD = tokenInfo.diff * tokenInfo.price; | ||
}); | ||
|
||
// Sort in decreasing order so we sell first then buy | ||
info.sort((a, b) => b.diffUSD - a.diffUSD); | ||
|
||
for (const tokenInfo of info) { | ||
// skip usdc since it is our base currency | ||
if (tokenInfo.symbol === 'USDC') { | ||
continue; | ||
} | ||
|
||
// skip if exchange amount is too little | ||
if (Math.abs(tokenInfo.diff) <= PADDING * tokenInfo.target) { | ||
continue; | ||
} | ||
|
||
let fromTokenInfo; | ||
let toTokenInfo; | ||
let amount; | ||
|
||
const USDCTokenInfo = findWhere(info, { symbol: 'USDC' }); | ||
if (!USDCTokenInfo) { | ||
console.error('failed to find USDC token info'); | ||
} | ||
|
||
// negative diff means we need to buy | ||
if (tokenInfo.diff < 0) { | ||
fromTokenInfo = USDCTokenInfo; | ||
toTokenInfo = tokenInfo; | ||
amount = (new BigNumber(tokenInfo.diffUSD).multipliedBy(fromTokenInfo.decimals)).abs(); | ||
|
||
// positive diff means we sell | ||
} else { | ||
fromTokenInfo = tokenInfo; | ||
toTokenInfo = USDCTokenInfo; | ||
amount = new BigNumber(tokenInfo.diff).multipliedBy(fromTokenInfo.decimals); | ||
} | ||
|
||
try { | ||
await swap(connection, payer, jupiter, fromTokenInfo, toTokenInfo, Math.floor(amount.toNumber())); | ||
} catch (error) { | ||
console.error({ error }, 'failed to swap tokens'); | ||
} | ||
} | ||
} | ||
|
||
function aggregateInfo(tokensOracle, walletBalances, connection, wallet, target) { | ||
const info: any = []; | ||
target.forEach(async (tokenDistribution: TokenCount) => { | ||
const { symbol, target } = tokenDistribution; | ||
const tokenOracle = findWhere(tokensOracle, { symbol }); | ||
const walletBalance = findWhere(walletBalances, { symbol }); | ||
|
||
if (walletBalance) { | ||
// -1 as sentinel value for account not available | ||
if (walletBalance.balance === -1) { | ||
const token = new Token( | ||
connection, | ||
new PublicKey(tokenOracle.mintAddress), | ||
TOKEN_PROGRAM_ID, | ||
wallet, | ||
); | ||
|
||
// create missing ATA for token | ||
const ata = await token.createAssociatedTokenAccount(wallet.publicKey); | ||
walletBalance.ata = ata.toString(); | ||
walletBalance.balance = 0; | ||
} | ||
|
||
const usdValue = new BigNumber(walletBalance.balance).multipliedBy(tokenOracle.price); | ||
info.push({ | ||
symbol, | ||
target, | ||
mintAddress: tokenOracle.mintAddress, | ||
ata: walletBalance.ata?.toString(), | ||
balance: walletBalance.balance, | ||
usdValue: usdValue.toNumber(), | ||
price: tokenOracle.price.toNumber(), | ||
decimals: tokenOracle.decimals, | ||
reserveAddress: tokenOracle.reserveAddress, | ||
}); | ||
} | ||
}); | ||
|
||
return info; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* eslint-disable prefer-promise-reject-errors */ | ||
import { Jupiter } from '@jup-ag/core'; | ||
import { | ||
Connection, Keypair, PublicKey, | ||
} from '@solana/web3.js'; | ||
|
||
const SLIPPAGE = 2; | ||
const SWAP_TIMEOUT_SEC = 20; | ||
|
||
export default async function swap(connection: Connection, wallet: Keypair, jupiter: Jupiter, fromTokenInfo, toTokenInfo, amount: number) { | ||
console.log({ | ||
fromToken: fromTokenInfo.symbol, | ||
toToken: toTokenInfo.symbol, | ||
amount: amount.toString(), | ||
}, 'swapping tokens'); | ||
|
||
const inputMint = new PublicKey(fromTokenInfo.mintAddress); | ||
const outputMint = new PublicKey(toTokenInfo.mintAddress); | ||
const routes = await jupiter.computeRoutes({ | ||
inputMint, // Mint address of the input token | ||
outputMint, // Mint address of the output token | ||
inputAmount: amount, // raw input amount of tokens | ||
slippage: SLIPPAGE, // The slippage in % terms | ||
}); | ||
|
||
// Prepare execute exchange | ||
const { execute } = await jupiter.exchange({ | ||
routeInfo: routes.routesInfos[0], | ||
}); | ||
|
||
// Execute swap | ||
await new Promise((resolve, reject) => { | ||
// sometime jup hangs hence the timeout here. | ||
let timedOut = false; | ||
const timeoutHandle = setTimeout(() => { | ||
timedOut = true; | ||
console.error(`Swap took longer than ${SWAP_TIMEOUT_SEC} seconds to complete.`); | ||
reject('Swap timed out'); | ||
}, SWAP_TIMEOUT_SEC * 1000); | ||
|
||
execute().then((swapResult: any) => { | ||
if (!timedOut) { | ||
clearTimeout(timeoutHandle); | ||
|
||
console.log({ | ||
tx: swapResult.txid, | ||
inputAddress: swapResult.inputAddress.toString(), | ||
outputAddress: swapResult.outputAddress.toString(), | ||
inputAmount: swapResult.inputAmount / fromTokenInfo.decimals, | ||
outputAmount: swapResult.outputAmount / toTokenInfo.decimals, | ||
inputToken: fromTokenInfo.symbol, | ||
outputToken: toTokenInfo.symbol, | ||
}, 'successfully swapped token'); | ||
resolve(swapResult); | ||
} | ||
}).catch((swapError) => { | ||
if (!timedOut) { | ||
clearTimeout(timeoutHandle); | ||
console.error({ | ||
err: swapError.error, | ||
tx: swapError.txid, | ||
fromToken: fromTokenInfo.symbol, | ||
toToken: toTokenInfo.symbol, | ||
}, 'error swapping'); | ||
resolve(swapError); | ||
} | ||
}); | ||
}); | ||
} |
Oops, something went wrong.