Skip to content

Commit

Permalink
feat: upgrade to latest futarchy-ts for swap
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasDeco committed May 1, 2024
1 parent 4bd0164 commit 1dc53fd
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 32 deletions.
3 changes: 2 additions & 1 deletion lib/client/indexer/market-clients/ammMarkets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import {
AmmMarketFetchRequest,
LiquidityAddError,
Market,
SwapPreview,
TokenMetadataSource,
TokenProps,
TokenWithBalance,
} from "@/types";
import { PublicKey } from "@solana/web3.js";
import { SwapPreview, SwapType } from "@metadaoproject/futarchy-ts";
import { SwapType } from "@metadaoproject/futarchy-ts";

export class FutarchyIndexerAmmMarketsClient
implements FutarchyAmmMarketsClient
Expand Down
120 changes: 94 additions & 26 deletions lib/client/rpc/market-clients/ammMarkets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@ import {
AmmMarket,
AmmMarketFetchRequest,
LiquidityAddError,
SwapPreview,
} from "@/types/amm";
import { BN, Program, Provider } from "@coral-xyz/anchor";
import {
AMM_PROGRAM_ID,
AmmAccount,
AmmClient,
SwapPreview,
SwapType,
getATA,
getAmmAddr,
} from "@metadaoproject/futarchy-ts";
import { Amm as AmmIDLType } from "@metadaoproject/futarchy-ts/dist/types/amm";
import { unpackMint, getAssociatedTokenAddressSync } from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import { PublicKey, Transaction } from "@solana/web3.js";

export class FutarchyAmmMarketsRPCClient implements FutarchyAmmMarketsClient {
private rpcProvider: Provider;
Expand Down Expand Up @@ -189,51 +190,49 @@ export class FutarchyAmmMarketsRPCClient implements FutarchyAmmMarketsClient {
inputAmount: number,
outputAmountMin: number
): Promise<string[]> {
const quoteMintInfo = await this.rpcProvider.connection.getAccountInfo(
ammMarket.quoteMint
);
const quoteDecimals = unpackMint(
ammMarket.quoteMint,
quoteMintInfo
).decimals;

const baseMintInfo = await this.rpcProvider.connection.getAccountInfo(
ammMarket.quoteMint
);
const baseDecimals = unpackMint(ammMarket.baseMint, baseMintInfo).decimals;

if (!this.transactionSender) return [];
let inputAmountScaled: BN;
let outputAmountMinScaled: BN;
if (swapType.buy) {
inputAmountScaled = new BN(inputAmount * 10 ** quoteDecimals);
outputAmountMinScaled = new BN(outputAmountMin * 10 ** baseDecimals);
inputAmountScaled = new BN(
inputAmount * 10 ** ammMarket.quoteToken.decimals
);
outputAmountMinScaled = new BN(
outputAmountMin * 10 ** ammMarket.baseToken.decimals
);
} else {
inputAmountScaled = new BN(inputAmount * 10 ** baseDecimals);
outputAmountMinScaled = new BN(outputAmountMin * 10 ** quoteDecimals);
inputAmountScaled = new BN(
inputAmount * 10 ** ammMarket.baseToken.decimals
);
outputAmountMinScaled = new BN(
outputAmountMin * 10 ** ammMarket.quoteToken.decimals
);
}
const tx = await this.amm.methods
const ix = await this.amm.methods
.swap({
swapType,
inputAmount: inputAmountScaled,
outputAmountMin: outputAmountMinScaled,
})
.accounts({
user: this.rpcProvider.publicKey,
user: this.transactionSender.owner,
amm: ammMarket.publicKey,
baseMint: ammMarket.baseMint,
quoteMint: ammMarket.quoteMint,
userAtaBase: getATA(
ammMarket.baseMint,
this.rpcProvider.publicKey!!
this.transactionSender.owner
)[0],
userAtaQuote: getATA(
ammMarket.quoteMint,
this.rpcProvider.publicKey!!
this.transactionSender.owner
)[0],
vaultAtaBase: getATA(ammMarket.baseMint, ammMarket.publicKey)[0],
vaultAtaQuote: getATA(ammMarket.quoteMint, ammMarket.publicKey)[0],
})
.transaction();
.instruction();

const tx = new Transaction().add(ix);

return (
this.transactionSender?.send([tx], this.rpcProvider.connection) ?? []
Expand All @@ -251,14 +250,83 @@ export class FutarchyAmmMarketsRPCClient implements FutarchyAmmMarketsClient {
const inputAmountLots = isBuyBase
? inputAmount * 10 ** ammMarket.baseToken.decimals
: inputAmount * 10 ** ammMarket.quoteToken.decimals;
const resp = this.ammClient.getSwapPreview(
const resp = this.calculateSwapPreview(
ammAccount,
new BN(inputAmountLots),
isBuyBase
);
return resp;
}

calculateSwapPreview(
amm: AmmAccount,
inputAmount: BN,
isBuyBase: boolean
): SwapPreview {
const quoteAmount = amm.quoteAmount;
const baseAmount = amm.baseAmount;
const startPrice =
quoteAmount.toNumber() /
Math.pow(10, amm.quoteMintDecimals) /
(baseAmount.toNumber() / Math.pow(10, amm.baseMintDecimals));
const k = quoteAmount.mul(baseAmount);
const inputMinusFee = inputAmount
.mul(new BN(10000).sub(new BN(100)))
.div(new BN(10000));

if (isBuyBase) {
const tempQuoteAmount = quoteAmount.add(inputMinusFee);
const tempBaseAmount = k.div(tempQuoteAmount);
const finalPrice =
tempQuoteAmount.toNumber() /
Math.pow(10, amm.quoteMintDecimals) /
(tempBaseAmount.toNumber() / Math.pow(10, amm.baseMintDecimals));
const outputAmountBase = baseAmount.sub(tempBaseAmount);
const inputUnits =
inputAmount.toNumber() / Math.pow(10, amm.quoteMintDecimals);
const outputUnits =
outputAmountBase.toNumber() / Math.pow(10, amm.baseMintDecimals);
const priceImpact = Math.abs(finalPrice - startPrice) / startPrice;

return {
isBuyBase,
inputAmount,
outputAmount: outputAmountBase,
inputUnits,
outputUnits,
startPrice,
finalPrice,
avgSwapPrice: inputUnits / outputUnits,
priceImpact,
};
} else {
const tempBaseAmount = baseAmount.add(inputMinusFee);
const tempQuoteAmount = k.div(tempBaseAmount);
const finalPrice =
tempQuoteAmount.toNumber() /
Math.pow(10, amm.quoteMintDecimals) /
(tempBaseAmount.toNumber() / Math.pow(10, amm.baseMintDecimals));
const outputAmountQuote = quoteAmount.sub(tempQuoteAmount);
const inputUnits =
inputAmount.toNumber() / Math.pow(10, amm.baseMintDecimals);
const outputUnits =
outputAmountQuote.toNumber() / Math.pow(10, amm.quoteMintDecimals);
const priceImpact = Math.abs(finalPrice - startPrice) / startPrice;

return {
isBuyBase,
inputAmount,
outputAmount: outputAmountQuote,
inputUnits,
outputUnits,
startPrice,
finalPrice,
avgSwapPrice: outputUnits / inputUnits,
priceImpact,
};
}
}

async getPoolLiquidity(ammAddr: PublicKey) {
const ammAccount = await this.ammClient.getAmm(ammAddr);

Expand Down
13 changes: 13 additions & 0 deletions lib/types/amm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { PublicKey } from "@solana/web3.js";
import { Market, MarketFetchRequest } from "./markets";
import { BN } from "@coral-xyz/anchor";

export class AmmMarketFetchRequest implements MarketFetchRequest {
public marketKey: PublicKey;
Expand All @@ -17,3 +18,15 @@ export type AmmMarket = Market & {
};

export type LiquidityAddError = "AddLiquidityMaxBaseExceeded";

export type SwapPreview = {
isBuyBase: boolean;
inputAmount: BN;
outputAmount: BN;
inputUnits: number;
outputUnits: number;
startPrice: number;
finalPrice: number;
avgSwapPrice: number;
priceImpact: number;
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
],
"dependencies": {
"@coral-xyz/anchor": "^0.28.1-beta.2",
"@metadaoproject/futarchy-ts": "^1.0.1",
"@metadaoproject/futarchy-ts": "^1.3.0",
"@metaplex-foundation/js": "^0.20.1",
"@metaplex-foundation/mpl-token-metadata": "^3.2.1",
"@metaplex-foundation/umi": "^0.9.1",
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 1dc53fd

Please sign in to comment.