omega/ui/src/utils/pools.tsx

1157 lines
29 KiB
TypeScript

import {
Account,
Connection,
PublicKey,
SystemProgram,
TransactionInstruction,
} from "@solana/web3.js";
import { sendTransaction, useConnection } from "./connection";
import { useEffect, useMemo, useState } from "react";
import { Token, MintLayout, AccountLayout } from "@solana/spl-token";
import { notify } from "./notifications";
import {
cache,
getCachedAccount,
useUserAccounts,
useCachedPool,
getMultipleAccounts,
} from "./accounts";
import {
programIds,
SWAP_HOST_FEE_ADDRESS,
SWAP_PROGRAM_OWNER_FEE_ADDRESS,
WRAPPED_SOL_MINT,
} from "./ids";
import {
LiquidityComponent,
PoolInfo,
TokenAccount,
createInitSwapInstruction,
TokenSwapLayout,
depositInstruction,
withdrawInstruction,
TokenSwapLayoutLegacyV0,
swapInstruction,
PoolConfig,
} from "./../models";
const LIQUIDITY_TOKEN_PRECISION = 8;
export const LIQUIDITY_PROVIDER_FEE = 0.003;
export const SERUM_FEE = 0.0005;
export const removeLiquidity = async (
connection: Connection,
wallet: any,
liquidityAmount: number,
account: TokenAccount,
pool?: PoolInfo
) => {
if (!pool) {
return;
}
notify({
message: "Removing Liquidity...",
description: "Please review transactions to approve.",
type: "warn",
});
// TODO get min amounts based on total supply and liquidity
const minAmount0 = 0;
const minAmount1 = 0;
const poolMint = await cache.queryMint(connection, pool.pubkeys.mint);
const accountA = await cache.queryAccount(
connection,
pool.pubkeys.holdingAccounts[0]
);
const accountB = await cache.queryAccount(
connection,
pool.pubkeys.holdingAccounts[1]
);
if (!poolMint.mintAuthority) {
throw new Error("Mint doesnt have authority");
}
const authority = poolMint.mintAuthority;
const signers: Account[] = [];
const instructions: TransactionInstruction[] = [];
const cleanupInstructions: TransactionInstruction[] = [];
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
AccountLayout.span
);
// TODO: check if one of to accounts needs to be native sol ... if yes unwrap it ...
const toAccounts: PublicKey[] = [
await findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
cleanupInstructions,
accountRentExempt,
accountA.info.mint,
signers
),
await findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
cleanupInstructions,
accountRentExempt,
accountB.info.mint,
signers
),
];
instructions.push(
Token.createApproveInstruction(
programIds().token,
account.pubkey,
authority,
wallet.publicKey,
[],
liquidityAmount
)
);
// withdraw
instructions.push(
withdrawInstruction(
pool.pubkeys.account,
authority,
pool.pubkeys.mint,
pool.pubkeys.feeAccount,
account.pubkey,
pool.pubkeys.holdingAccounts[0],
pool.pubkeys.holdingAccounts[1],
toAccounts[0],
toAccounts[1],
pool.pubkeys.program,
programIds().token,
liquidityAmount,
minAmount0,
minAmount1
)
);
const deleteAccount = liquidityAmount === account.info.amount.toNumber();
if (deleteAccount) {
instructions.push(
Token.createCloseAccountInstruction(
programIds().token,
account.pubkey,
authority,
wallet.publicKey,
[]
)
);
}
let tx = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),
signers
);
if(deleteAccount) {
cache.deleteAccount(account.pubkey);
}
notify({
message: "Liquidity Returned. Thank you for your support.",
type: "success",
description: `Transaction - ${tx}`,
});
};
export const swap = async (
connection: Connection,
wallet: any,
components: LiquidityComponent[],
SLIPPAGE: number,
pool?: PoolInfo
) => {
if (!pool || !components[0].account) {
notify({
type: "error",
message: `Pool doesn't exsist.`,
description: `Swap trade cancelled`,
});
return;
}
// Uniswap whitepaper: https://uniswap.org/whitepaper.pdf
// see: https://uniswap.org/docs/v2/advanced-topics/pricing/
// as well as native uniswap v2 oracle: https://uniswap.org/docs/v2/core-concepts/oracles/
const amountIn = components[0].amount; // these two should include slippage
const minAmountOut = components[1].amount * (1 - SLIPPAGE);
const holdingA =
pool.pubkeys.holdingMints[0]?.toBase58() ===
components[0].account.info.mint.toBase58()
? pool.pubkeys.holdingAccounts[0]
: pool.pubkeys.holdingAccounts[1];
const holdingB =
holdingA === pool.pubkeys.holdingAccounts[0]
? pool.pubkeys.holdingAccounts[1]
: pool.pubkeys.holdingAccounts[0];
const poolMint = await cache.queryMint(connection, pool.pubkeys.mint);
if (!poolMint.mintAuthority || !pool.pubkeys.feeAccount) {
throw new Error("Mint doesnt have authority");
}
const authority = poolMint.mintAuthority;
const instructions: TransactionInstruction[] = [];
const cleanupInstructions: TransactionInstruction[] = [];
const signers: Account[] = [];
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
AccountLayout.span
);
const fromAccount = getWrappedAccount(
instructions,
cleanupInstructions,
components[0].account,
wallet.publicKey,
amountIn + accountRentExempt,
signers
);
let toAccount = findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
cleanupInstructions,
accountRentExempt,
new PublicKey(components[1].mintAddress),
signers
);
// create approval for transfer transactions
instructions.push(
Token.createApproveInstruction(
programIds().token,
fromAccount,
authority,
wallet.publicKey,
[],
amountIn
)
);
let hostFeeAccount = SWAP_HOST_FEE_ADDRESS
? findOrCreateAccountByMint(
wallet.publicKey,
SWAP_HOST_FEE_ADDRESS,
instructions,
cleanupInstructions,
accountRentExempt,
pool.pubkeys.mint,
signers
)
: undefined;
// swap
instructions.push(
swapInstruction(
pool.pubkeys.account,
authority,
fromAccount,
holdingA,
holdingB,
toAccount,
pool.pubkeys.mint,
pool.pubkeys.feeAccount,
pool.pubkeys.program,
programIds().token,
amountIn,
minAmountOut,
hostFeeAccount
)
);
let tx = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),
signers
);
notify({
message: "Trade executed.",
type: "success",
description: `Transaction - ${tx}`,
});
};
export const addLiquidity = async (
connection: Connection,
wallet: any,
components: LiquidityComponent[],
slippage: number,
pool?: PoolInfo,
options?: PoolConfig
) => {
if (!pool) {
if (!options) {
throw new Error("Options are required to create new pool.");
}
await _addLiquidityNewPool(wallet, connection, components, options);
} else {
await _addLiquidityExistingPool(pool, components, connection, wallet);
}
};
const getHoldings = (connection: Connection, accounts: string[]) => {
return accounts.map((acc) =>
cache.queryAccount(connection, new PublicKey(acc))
);
};
const toPoolInfo = (item: any, program: PublicKey) => {
const mint = new PublicKey(item.data.tokenPool);
return {
pubkeys: {
account: item.pubkey,
program: program,
mint,
holdingMints: [] as PublicKey[],
holdingAccounts: [item.data.tokenAccountA, item.data.tokenAccountB].map(
(a) => new PublicKey(a)
),
},
legacy: false,
raw: item,
} as PoolInfo;
};
export const usePools = () => {
const connection = useConnection();
const [pools, setPools] = useState<PoolInfo[]>([]);
// initial query
useEffect(() => {
setPools([]);
const queryPools = async (swapId: PublicKey, isLegacy = false) => {
let poolsArray: PoolInfo[] = [];
console.log('queryPools', swapId.toString());
(await connection.getProgramAccounts(swapId))
.filter(
(item) =>
item.account.data.length === TokenSwapLayout.span ||
item.account.data.length === TokenSwapLayoutLegacyV0.span
)
.map((item) => {
let result = {
data: undefined as any,
account: item.account,
pubkey: item.pubkey,
init: async () => { },
};
// handling of legacy layout can be removed soon...
if (item.account.data.length === TokenSwapLayoutLegacyV0.span) {
result.data = TokenSwapLayoutLegacyV0.decode(item.account.data);
let pool = toPoolInfo(result, swapId);
pool.legacy = isLegacy;
poolsArray.push(pool as PoolInfo);
result.init = async () => {
try {
// TODO: this is not great
// Ideally SwapLayout stores hash of all the mints to make finding of pool for a pair easier
const holdings = await Promise.all(
getHoldings(connection, [
result.data.tokenAccountA,
result.data.tokenAccountB,
])
);
pool.pubkeys.holdingMints = [
holdings[0].info.mint,
holdings[1].info.mint,
] as PublicKey[];
} catch (err) {
console.log(err);
}
};
} else {
result.data = TokenSwapLayout.decode(item.account.data);
let pool = toPoolInfo(result, swapId);
pool.legacy = isLegacy;
pool.pubkeys.feeAccount = new PublicKey(result.data.feeAccount);
pool.pubkeys.holdingMints = [
new PublicKey(result.data.mintA),
new PublicKey(result.data.mintB),
] as PublicKey[];
poolsArray.push(pool as PoolInfo);
}
return result;
});
const toQuery = poolsArray
.map(
(p) =>
[
...p.pubkeys.holdingAccounts.map((h) => h.toBase58()),
...p.pubkeys.holdingMints.map((h) => h.toBase58()),
p.pubkeys.feeAccount?.toBase58(), // used to calculate volume aproximation
p.pubkeys.mint.toBase58(),
].filter((p) => p) as string[]
)
.flat();
// This will pre-cache all accounts used by pools
// All those accounts are updated whenever there is a change
await getMultipleAccounts(connection, toQuery, "single").then(
({ keys, array }) => {
return array.map((obj, index) => {
const pubKey = new PublicKey(keys[index]);
if (obj.data.length === AccountLayout.span) {
return cache.addAccount(pubKey, obj);
} else if (obj.data.length === MintLayout.span) {
return cache.addMint(pubKey, obj);
}
return obj;
}) as any[];
}
);
return poolsArray;
};
console.log('queryPools', programIds());
Promise.all([
queryPools(programIds().swap),
...programIds().swap_legacy.map((leg) => queryPools(leg, true)),
]).then((all) => {
setPools(all.flat());
});
}, [connection]);
useEffect(() => {
const subID = connection.onProgramAccountChange(
programIds().swap,
async (info) => {
const id = (info.accountId as unknown) as string;
if (info.accountInfo.data.length === TokenSwapLayout.span) {
const account = info.accountInfo;
const updated = {
data: TokenSwapLayout.decode(account.data),
account: account,
pubkey: new PublicKey(id),
};
const index =
pools &&
pools.findIndex((p) => p.pubkeys.account.toBase58() === id);
if (index && index >= 0 && pools) {
// TODO: check if account is empty?
const filtered = pools.filter((p, i) => i !== index);
setPools([...filtered, toPoolInfo(updated, programIds().swap)]);
} else {
let pool = toPoolInfo(updated, programIds().swap);
pool.pubkeys.feeAccount = new PublicKey(updated.data.feeAccount);
pool.pubkeys.holdingMints = [
new PublicKey(updated.data.mintA),
new PublicKey(updated.data.mintB),
] as PublicKey[];
setPools([...pools, pool]);
}
}
},
"singleGossip"
);
return () => {
connection.removeProgramAccountChangeListener(subID);
};
}, [connection, pools]);
return { pools };
};
export const usePoolForBasket = (mints: (string | undefined)[]) => {
const connection = useConnection();
const { pools } = useCachedPool();
const [pool, setPool] = useState<PoolInfo>();
const sortedMints = useMemo(() => [...mints].sort(), [...mints]); // eslint-disable-line
useEffect(() => {
(async () => {
// reset pool during query
setPool(undefined);
let matchingPool = pools
.filter((p) => !p.legacy)
.filter((p) =>
p.pubkeys.holdingMints
.map((a) => a.toBase58())
.sort()
.every((address, i) => address === sortedMints[i])
);
for (let i = 0; i < matchingPool.length; i++) {
const p = matchingPool[i];
const account = await cache.queryAccount(
connection,
p.pubkeys.holdingAccounts[0]
);
if (!account.info.amount.eqn(0)) {
setPool(p);
return;
}
}
})();
}, [connection, sortedMints, pools]);
return pool;
};
export const useOwnedPools = () => {
const { pools } = useCachedPool();
const { userAccounts } = useUserAccounts();
const ownedPools = useMemo(() => {
const map = userAccounts.reduce((acc, item) => {
const key = item.info.mint.toBase58();
acc.set(key, [...(acc.get(key) || []), item]);
return acc;
}, new Map<string, TokenAccount[]>());
return pools
.filter((p) => map.has(p.pubkeys.mint.toBase58()))
.map((item) => {
let feeAccount = item.pubkeys.feeAccount?.toBase58();
return map.get(item.pubkeys.mint.toBase58())?.map((a) => {
return {
account: a as TokenAccount,
isFeeAccount: feeAccount === a.pubkey.toBase58(),
pool: item,
};
}) as {
account: TokenAccount;
isFeeAccount: boolean;
pool: PoolInfo;
}[];
})
.flat();
}, [pools, userAccounts]);
return ownedPools;
};
// Allow for this much price movement in the pool before adding liquidity to the pool aborts
const SLIPPAGE = 0.005;
async function _addLiquidityExistingPool(
pool: PoolInfo,
components: LiquidityComponent[],
connection: Connection,
wallet: any
) {
notify({
message: "Adding Liquidity...",
description: "Please review transactions to approve.",
type: "warn",
});
const poolMint = await cache.queryMint(connection, pool.pubkeys.mint);
if (!poolMint.mintAuthority) {
throw new Error("Mint doesnt have authority");
}
if (!pool.pubkeys.feeAccount) {
throw new Error("Invald fee account");
}
const accountA = await cache.queryAccount(
connection,
pool.pubkeys.holdingAccounts[0]
);
const accountB = await cache.queryAccount(
connection,
pool.pubkeys.holdingAccounts[1]
);
const reserve0 = accountA.info.amount.toNumber();
const reserve1 = accountB.info.amount.toNumber();
const fromA =
accountA.info.mint.toBase58() === components[0].mintAddress
? components[0]
: components[1];
const fromB = fromA === components[0] ? components[1] : components[0];
if (!fromA.account || !fromB.account) {
throw new Error("Missing account info.");
}
const supply = poolMint.supply.toNumber();
const authority = poolMint.mintAuthority;
// Uniswap whitepaper: https://uniswap.org/whitepaper.pdf
// see: https://uniswap.org/docs/v2/advanced-topics/pricing/
// as well as native uniswap v2 oracle: https://uniswap.org/docs/v2/core-concepts/oracles/
const amount0 = fromA.amount;
const amount1 = fromB.amount;
const liquidity = Math.min(
(amount0 * (1 - SLIPPAGE) * supply) / reserve0,
(amount1 * (1 - SLIPPAGE) * supply) / reserve1
);
const instructions: TransactionInstruction[] = [];
const cleanupInstructions: TransactionInstruction[] = [];
const signers: Account[] = [];
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
AccountLayout.span
);
const fromKeyA = getWrappedAccount(
instructions,
cleanupInstructions,
fromA.account,
wallet.publicKey,
amount0 + accountRentExempt,
signers
);
const fromKeyB = getWrappedAccount(
instructions,
cleanupInstructions,
fromB.account,
wallet.publicKey,
amount1 + accountRentExempt,
signers
);
let toAccount = findOrCreateAccountByMint(
wallet.publicKey,
wallet.publicKey,
instructions,
[],
accountRentExempt,
pool.pubkeys.mint,
signers,
new Set<string>([pool.pubkeys.feeAccount.toBase58()])
);
// create approval for transfer transactions
instructions.push(
Token.createApproveInstruction(
programIds().token,
fromKeyA,
authority,
wallet.publicKey,
[],
amount0
)
);
instructions.push(
Token.createApproveInstruction(
programIds().token,
fromKeyB,
authority,
wallet.publicKey,
[],
amount1
)
);
// depoist
instructions.push(
depositInstruction(
pool.pubkeys.account,
authority,
fromKeyA,
fromKeyB,
pool.pubkeys.holdingAccounts[0],
pool.pubkeys.holdingAccounts[1],
pool.pubkeys.mint,
toAccount,
pool.pubkeys.program,
programIds().token,
liquidity,
amount0,
amount1
)
);
let tx = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),
signers
);
notify({
message: "Pool Funded. Happy trading.",
type: "success",
description: `Transaction - ${tx}`,
});
}
function findOrCreateAccountByMint(
payer: PublicKey,
owner: PublicKey,
instructions: TransactionInstruction[],
cleanupInstructions: TransactionInstruction[],
accountRentExempt: number,
mint: PublicKey, // use to identify same type
signers: Account[],
excluded?: Set<string>
): PublicKey {
const accountToFind = mint.toBase58();
const account = getCachedAccount(
(acc) =>
acc.info.mint.toBase58() === accountToFind &&
acc.info.owner.toBase58() === owner.toBase58() &&
(excluded === undefined || !excluded.has(acc.pubkey.toBase58()))
);
const isWrappedSol = accountToFind === WRAPPED_SOL_MINT.toBase58();
let toAccount: PublicKey;
if (account && !isWrappedSol) {
toAccount = account.pubkey;
} else {
// creating depositor pool account
const newToAccount = createSplAccount(
instructions,
payer,
accountRentExempt,
mint,
owner,
AccountLayout.span
);
toAccount = newToAccount.publicKey;
signers.push(newToAccount);
if (isWrappedSol) {
cleanupInstructions.push(
Token.createCloseAccountInstruction(
programIds().token,
toAccount,
payer,
payer,
[]
)
);
}
}
return toAccount;
}
function estimateProceedsFromInput(
inputQuantityInPool: number,
proceedsQuantityInPool: number,
inputAmount: number
): number {
return (
(proceedsQuantityInPool * inputAmount) / (inputQuantityInPool + inputAmount)
);
}
function estimateInputFromProceeds(
inputQuantityInPool: number,
proceedsQuantityInPool: number,
proceedsAmount: number
): number | string {
if (proceedsAmount >= proceedsQuantityInPool) {
return "Not possible";
}
return (
(inputQuantityInPool * proceedsAmount) /
(proceedsQuantityInPool - proceedsAmount)
);
}
export enum PoolOperation {
Add,
SwapGivenInput,
SwapGivenProceeds,
}
export async function calculateDependentAmount(
connection: Connection,
independent: string,
amount: number,
pool: PoolInfo,
op: PoolOperation
): Promise<number | string | undefined> {
const poolMint = await cache.queryMint(connection, pool.pubkeys.mint);
const accountA = await cache.queryAccount(
connection,
pool.pubkeys.holdingAccounts[0]
);
const accountB = await cache.queryAccount(
connection,
pool.pubkeys.holdingAccounts[1]
);
if (!poolMint.mintAuthority) {
throw new Error("Mint doesnt have authority");
}
if (poolMint.supply.eqn(0)) {
return;
}
const mintA = await cache.queryMint(connection, accountA.info.mint);
const mintB = await cache.queryMint(connection, accountB.info.mint);
if (!mintA || !mintB) {
return;
}
const isFirstIndependent = accountA.info.mint.toBase58() === independent;
const depPrecision = Math.pow(
10,
isFirstIndependent ? mintB.decimals : mintA.decimals
);
const indPrecision = Math.pow(
10,
isFirstIndependent ? mintA.decimals : mintB.decimals
);
const indAdjustedAmount = amount * indPrecision;
let indBasketQuantity = (isFirstIndependent
? accountA
: accountB
).info.amount.toNumber();
let depBasketQuantity = (isFirstIndependent
? accountB
: accountA
).info.amount.toNumber();
var depAdjustedAmount;
switch (+op) {
case PoolOperation.Add:
depAdjustedAmount =
(depBasketQuantity / indBasketQuantity) * indAdjustedAmount;
break;
case PoolOperation.SwapGivenProceeds:
depAdjustedAmount = estimateInputFromProceeds(
depBasketQuantity,
indBasketQuantity,
indAdjustedAmount
);
break;
case PoolOperation.SwapGivenInput:
depAdjustedAmount = estimateProceedsFromInput(
indBasketQuantity,
depBasketQuantity,
indAdjustedAmount
);
break;
}
if (typeof depAdjustedAmount === "string") {
return depAdjustedAmount;
}
if (depAdjustedAmount === undefined) {
return undefined;
}
return depAdjustedAmount / depPrecision;
}
// TODO: add ui to customize curve type
async function _addLiquidityNewPool(
wallet: any,
connection: Connection,
components: LiquidityComponent[],
options: PoolConfig
) {
notify({
message: "Creating new pool...",
description: "Please review transactions to approve.",
type: "warn",
});
if (components.some((c) => !c.account)) {
notify({
message: "You need to have balance for all legs in the basket...",
description: "Please review inputs.",
type: "error",
});
return;
}
let instructions: TransactionInstruction[] = [];
let cleanupInstructions: TransactionInstruction[] = [];
const liquidityTokenAccount = new Account();
// Create account for pool liquidity token
instructions.push(
SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: liquidityTokenAccount.publicKey,
lamports: await connection.getMinimumBalanceForRentExemption(
MintLayout.span
),
space: MintLayout.span,
programId: programIds().token,
})
);
const tokenSwapAccount = new Account();
const [authority, nonce] = await PublicKey.findProgramAddress(
[tokenSwapAccount.publicKey.toBuffer()],
programIds().swap
);
// create mint for pool liquidity token
instructions.push(
Token.createInitMintInstruction(
programIds().token,
liquidityTokenAccount.publicKey,
LIQUIDITY_TOKEN_PRECISION,
// pass control of liquidity mint to swap program
authority,
// swap program can freeze liquidity token mint
null
)
);
// Create holding accounts for
const accountRentExempt = await connection.getMinimumBalanceForRentExemption(
AccountLayout.span
);
const holdingAccounts: Account[] = [];
let signers: Account[] = [];
components.forEach((leg) => {
if (!leg.account) {
return;
}
const mintPublicKey = leg.account.info.mint;
// component account to store tokens I of N in liquidity poll
holdingAccounts.push(
createSplAccount(
instructions,
wallet.publicKey,
accountRentExempt,
mintPublicKey,
authority,
AccountLayout.span
)
);
});
// creating depositor pool account
const depositorAccount = createSplAccount(
instructions,
wallet.publicKey,
accountRentExempt,
liquidityTokenAccount.publicKey,
wallet.publicKey,
AccountLayout.span
);
// creating fee pool account its set from env variable or to creater of the pool
// creater of the pool is not allowed in some versions of token-swap program
const feeAccount = createSplAccount(
instructions,
wallet.publicKey,
accountRentExempt,
liquidityTokenAccount.publicKey,
SWAP_PROGRAM_OWNER_FEE_ADDRESS || wallet.publicKey,
AccountLayout.span
);
// create all accounts in one transaction
let tx = await sendTransaction(connection, wallet, instructions, [
liquidityTokenAccount,
depositorAccount,
feeAccount,
...holdingAccounts,
...signers,
]);
notify({
message: "Accounts created",
description: `Transaction ${tx}`,
type: "success",
});
notify({
message: "Adding Liquidity...",
description: "Please review transactions to approve.",
type: "warn",
});
signers = [];
instructions = [];
cleanupInstructions = [];
instructions.push(
SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: tokenSwapAccount.publicKey,
lamports: await connection.getMinimumBalanceForRentExemption(
TokenSwapLayout.span
),
space: TokenSwapLayout.span,
programId: programIds().swap,
})
);
components.forEach((leg, i) => {
if (!leg.account) {
return;
}
// create temporary account for wrapped sol to perform transfer
const from = getWrappedAccount(
instructions,
cleanupInstructions,
leg.account,
wallet.publicKey,
leg.amount + accountRentExempt,
signers
);
instructions.push(
Token.createTransferInstruction(
programIds().token,
from,
holdingAccounts[i].publicKey,
wallet.publicKey,
[],
leg.amount
)
);
});
instructions.push(
createInitSwapInstruction(
tokenSwapAccount,
authority,
holdingAccounts[0].publicKey,
holdingAccounts[1].publicKey,
liquidityTokenAccount.publicKey,
feeAccount.publicKey,
depositorAccount.publicKey,
programIds().token,
programIds().swap,
nonce,
options.curveType,
options.tradeFeeNumerator,
options.tradeFeeDenominator,
options.ownerTradeFeeNumerator,
options.ownerTradeFeeDenominator,
options.ownerWithdrawFeeNumerator,
options.ownerWithdrawFeeDenominator
)
);
// All instructions didn't fit in single transaction
// initialize and provide inital liquidity to swap in 2nd (this prevents loss of funds)
tx = await sendTransaction(
connection,
wallet,
instructions.concat(cleanupInstructions),
[tokenSwapAccount, ...signers]
);
notify({
message: "Pool Funded. Happy trading.",
type: "success",
description: `Transaction - ${tx}`,
});
}
function getWrappedAccount(
instructions: TransactionInstruction[],
cleanupInstructions: TransactionInstruction[],
toCheck: TokenAccount,
payer: PublicKey,
amount: number,
signers: Account[]
) {
if (!toCheck.info.isNative) {
return toCheck.pubkey;
}
const account = new Account();
instructions.push(
SystemProgram.createAccount({
fromPubkey: payer,
newAccountPubkey: account.publicKey,
lamports: amount,
space: AccountLayout.span,
programId: programIds().token,
})
);
instructions.push(
Token.createInitAccountInstruction(
programIds().token,
WRAPPED_SOL_MINT,
account.publicKey,
payer
)
);
cleanupInstructions.push(
Token.createCloseAccountInstruction(
programIds().token,
account.publicKey,
payer,
payer,
[]
)
);
signers.push(account);
return account.publicKey;
}
function createSplAccount(
instructions: TransactionInstruction[],
payer: PublicKey,
accountRentExempt: number,
mint: PublicKey,
owner: PublicKey,
space: number
) {
const account = new Account();
instructions.push(
SystemProgram.createAccount({
fromPubkey: payer,
newAccountPubkey: account.publicKey,
lamports: accountRentExempt,
space,
programId: programIds().token,
})
);
instructions.push(
Token.createInitAccountInstruction(
programIds().token,
mint,
account.publicKey,
owner
)
);
return account;
}