Simplify resolveATA in the Whirlpool space (#60)

- Remove resolve-ata-ix from the Instruction set and classify it as a util fn
- Add getTokenMintsFromWhirlpools function to get all token mints from a set of whirlpools
- Convert original fn to resolveAtaForMints that depends on the common-sdk resolveOrCreateATA
This commit is contained in:
meep 2022-11-24 19:20:46 +07:00 committed by GitHub
parent 3c7b90c147
commit 0fe7bda4d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 128 additions and 150 deletions

View File

@ -5,7 +5,7 @@ import {
resolveOrCreateATAs,
TokenUtil,
TransactionBuilder,
ZERO,
ZERO
} from "@orca-so/common-sdk";
import { Address, BN, translateAddress } from "@project-serum/anchor";
import { Keypair, PublicKey } from "@solana/web3.js";
@ -21,7 +21,7 @@ import {
openPositionIx,
openPositionWithMetadataIx,
SwapInput,
swapIx,
swapIx
} from "../instructions";
import { decreaseLiquidityQuoteByLiquidityWithParams } from "../quotes/public";
import { TokenAccountInfo, TokenInfo, WhirlpoolData, WhirlpoolRewardInfo } from "../types/public";
@ -436,7 +436,7 @@ export class WhirlpoolImpl implements Whirlpool {
// Check if all the tick arrays have been initialized.
const tickArrayAddresses = [input.tickArray0, input.tickArray1, input.tickArray2];
const tickArrays = await this.fetcher.listTickArrays(tickArrayAddresses, true);
const tickArrays = await this.ctx.fetcher.listTickArrays(tickArrayAddresses, true);
const uninitializedIndices = TickArrayUtil.getUninitializedArrays(tickArrays);
if (uninitializedIndices.length > 0) {
const uninitializedArrays = uninitializedIndices

View File

@ -3,7 +3,6 @@ import { createWSOLAccountInstructions } from "@orca-so/common-sdk/dist/helpers/
import { Address } from "@project-serum/anchor";
import { NATIVE_MINT } from "@solana/spl-token";
import { PACKET_DATA_SIZE, PublicKey } from "@solana/web3.js";
import { updateFeesAndRewardsIx } from "..";
import { WhirlpoolContext } from "../..";
import { PositionImpl } from "../../impl/position-impl";
import { WhirlpoolIx } from "../../ix";
@ -11,8 +10,9 @@ import { WhirlpoolData } from "../../types/public";
import { PDAUtil, PoolUtil, TickUtil } from "../../utils/public";
import { getAssociatedTokenAddressSync } from "../../utils/spl-token-utils";
import { convertListToMap } from "../../utils/txn-utils";
import { getTokenMintsFromWhirlpools, resolveAtaForMints } from "../../utils/whirlpool-ata-utils";
import { Position } from "../../whirlpool-client";
import { resolveAtaForWhirlpoolsIxs } from "./resolve-atas-ix";
import { updateFeesAndRewardsIx } from "../update-fees-and-rewards-ix";
/**
* Parameters to collect all fees and rewards from a list of positions.
@ -102,23 +102,20 @@ export async function collectAllForPositionsTxns(
const whirlpoolDatas = await ctx.fetcher.listPools(whirlpoolAddrs, false);
const whirlpools = convertListToMap(whirlpoolDatas, whirlpoolAddrs);
// TODO: Payer is not configurable here. Forced to use wallet
const accountExemption = await ctx.fetcher.getAccountRentExempt();
const { ataTokenAddresses: affliatedTokenAtaMap, resolveAtaIxs } =
await resolveAtaForWhirlpoolsIxs(ctx, {
whirlpools: whirlpoolDatas,
await resolveAtaForMints(ctx, {
mints: getTokenMintsFromWhirlpools(whirlpoolDatas),
accountExemption,
receiver: receiverKey,
payer: payerKey,
});
const latestBlockhash = await ctx.connection.getLatestBlockhash("singleGossip");
const accountExemption = await ctx.fetcher.getAccountRentExempt();
const txBuilders: TransactionBuilder[] = [];
let pendingTxBuilder = new TransactionBuilder(ctx.connection, ctx.wallet).addInstructions(
resolveAtaIxs
);
let pendingTxBuilder = new TransactionBuilder(ctx.connection, ctx.wallet).addInstructions(resolveAtaIxs);
let pendingTxBuilderTxSize = await pendingTxBuilder.txnSize({ latestBlockhash });
let posIndex = 0;
let reattempt = false;

View File

@ -1,2 +1 @@
export * from "./collect-all-txn";
export * from "./resolve-atas-ix";

View File

@ -1,136 +0,0 @@
import { Instruction, TokenUtil } from "@orca-so/common-sdk";
import { ASSOCIATED_TOKEN_PROGRAM_ID, Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { PublicKey } from "@solana/web3.js";
import { WhirlpoolContext } from "../..";
import { WhirlpoolData } from "../../types/public";
import { getAssociatedTokenAddressSync } from "../../utils/spl-token-utils";
import { convertListToMap } from "../../utils/txn-utils";
/**
* Parameters to resolve ATAs for affliated tokens in a list of Whirlpools
*
* @category Instruction Types
* @param whirlpools - The list of WhirlpoolData to generate affliated tokens for.
* @param destinationWallet - the wallet to generate ATAs against
* @param payer - The payer address that would pay for the creation of ATA addresses
*/
export type ResolveAtaInstructionParams = {
whirlpools: (WhirlpoolData | null)[];
receiver?: PublicKey;
payer?: PublicKey;
};
/**
* An interface of mapping between tokenMint & ATA & the instruction set to initialize them.
*
* @category Instruction Types
* @param ataTokenAddresses - A record between the token mint & generated ATA addresses
* @param resolveAtaIxs - An array of instructions to initialize all uninitialized ATA token accounts for the list above.
*/
export type ResolvedATAInstructionSet = {
ataTokenAddresses: Record<string, PublicKey>;
resolveAtaIxs: Instruction[];
};
/**
* Build instructions to resolve ATAs (Associated Tokens Addresses) for affliated tokens in a list of Whirlpools.
* Affliated tokens are tokens that are part of the trade pair or reward in a Whirlpool.
*
* SOL tokens does not use the ATA program and therefore not handled.
*
* @param ctx - WhirlpoolContext object for the current environment.
* @returns a ResolvedTokenAddressesIxSet containing the derived ATA addresses & ix set to initialize the accounts.
*/
export async function resolveAtaForWhirlpoolsIxs(
ctx: WhirlpoolContext,
params: ResolveAtaInstructionParams
): Promise<ResolvedATAInstructionSet> {
const { whirlpools, receiver, payer } = params;
const receiverKey = receiver ?? ctx.wallet.publicKey;
const payerKey = payer ?? ctx.wallet.publicKey;
const { affliatedTokenAtaMap, affliatedTokensInfoMap } = await getAffliatedTokenAtas(
ctx,
whirlpools,
receiverKey
);
const tokensRequiringAtaResolve = Object.fromEntries(
Object.entries(affliatedTokensInfoMap)
.filter(([, account]) => !account)
.map(([mint]) => [mint, affliatedTokenAtaMap[mint]])
);
const resolveAtaIxs: Instruction[] = [];
Object.entries(tokensRequiringAtaResolve).forEach(([mint, ataKey]) => {
const createAtaInstruction = Token.createAssociatedTokenAccountInstruction(
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID,
new PublicKey(mint),
ataKey,
receiverKey,
payerKey
);
resolveAtaIxs.push({
instructions: [createAtaInstruction],
cleanupInstructions: [],
signers: [],
});
});
return {
ataTokenAddresses: affliatedTokenAtaMap,
resolveAtaIxs,
};
}
async function getAffliatedTokenAtas(
ctx: WhirlpoolContext,
whirlpoolDatas: (WhirlpoolData | null)[],
wallet: PublicKey
) {
const affliatedTokens = Array.from(
whirlpoolDatas.reduce<Set<string>>((accu, whirlpoolData) => {
if (whirlpoolData) {
const { tokenMintA, tokenMintB } = whirlpoolData;
if (!TokenUtil.isNativeMint(tokenMintA)) {
accu.add(tokenMintA.toBase58());
}
if (!TokenUtil.isNativeMint(tokenMintB)) {
accu.add(tokenMintB.toBase58());
}
const rewardInfos = whirlpoolData.rewardInfos;
rewardInfos.forEach((reward) => {
if (!reward.mint.equals(PublicKey.default)) {
accu.add(reward.mint.toBase58());
}
});
}
return accu;
}, new Set<string>())
);
const tokenMintInfoMap = convertListToMap(
await ctx.fetcher.listMintInfos(affliatedTokens, false),
affliatedTokens
);
// Derive associated addresses for all affliated spl-tokens
const affliatedTokenAtaMap = Object.fromEntries(
Object.keys(tokenMintInfoMap).map((addr) => [
addr,
getAssociatedTokenAddressSync(addr, wallet.toBase58()),
])
);
return {
affliatedTokenAtaMap: affliatedTokenAtaMap,
affliatedTokensInfoMap: convertListToMap(
await ctx.fetcher.listTokenInfos(Object.values(affliatedTokenAtaMap), false),
Object.keys(affliatedTokenAtaMap)
),
};
}

View File

@ -0,0 +1,118 @@
import { Instruction, resolveOrCreateATAs, TokenUtil } from "@orca-so/common-sdk";
import { PublicKey } from "@solana/web3.js";
import { WhirlpoolContext } from "..";
import { WhirlpoolData } from "../types/public";
import { convertListToMap } from "./txn-utils";
/**
* Fetch a list of affliated tokens from a list of whirlpools
*
* SOL tokens does not use the ATA program and therefore not handled.
* @param whirlpoolDatas An array of whirlpoolData (from fetcher.listPools)
* @returns All the whirlpool, reward token mints in the given set of whirlpools
*/
export function getTokenMintsFromWhirlpools(whirlpoolDatas: (WhirlpoolData | null)[]) {
return Array.from(
whirlpoolDatas.reduce<Set<PublicKey>>((accu, whirlpoolData) => {
if (whirlpoolData) {
const { tokenMintA, tokenMintB } = whirlpoolData;
if (!TokenUtil.isNativeMint(tokenMintA)) {
accu.add(tokenMintA);
}
if (!TokenUtil.isNativeMint(tokenMintB)) {
accu.add(tokenMintB);
}
const rewardInfos = whirlpoolData.rewardInfos;
rewardInfos.forEach((reward) => {
if (!reward.mint.equals(PublicKey.default)) {
accu.add(reward.mint);
}
});
}
return accu;
}, new Set<PublicKey>())
);
}
/**
* Parameters to resolve ATAs for affliated tokens in a list of Whirlpools
*
* @category Instruction Types
* @param mints - The list of mints to generate affliated tokens for.
* @param accountExemption - The value from the most recent getMinimumBalanceForRentExemption().
* @param destinationWallet - the wallet to generate ATAs against
* @param payer - The payer address that would pay for the creation of ATA addresses
*/
export type ResolveAtaInstructionParams = {
mints: PublicKey[];
accountExemption: number;
receiver?: PublicKey;
payer?: PublicKey;
};
/**
* An interface of mapping between tokenMint & ATA & the instruction set to initialize them.
*
* @category Instruction Types
* @param ataTokenAddresses - A record between the token mint & generated ATA addresses
* @param resolveAtaIxs - An array of instructions to initialize all uninitialized ATA token accounts for the list above.
*/
export type ResolvedATAInstructionSet = {
ataTokenAddresses: Record<string, PublicKey>;
resolveAtaIxs: Instruction[];
};
/**
* Build instructions to resolve ATAs (Associated Tokens Addresses) for affliated tokens in a list of Whirlpools.
* Affliated tokens are tokens that are part of the trade pair or reward in a Whirlpool.
*
* @param ctx - WhirlpoolContext object for the current environment.
* @param params - ResolveAtaInstructionParams
* @returns a ResolvedTokenAddressesIxSet containing the derived ATA addresses & ix set to initialize the accounts.
*/
export async function resolveAtaForMints(
ctx: WhirlpoolContext,
params: ResolveAtaInstructionParams
): Promise<ResolvedATAInstructionSet> {
const { mints, receiver, payer, accountExemption } = params;
const receiverKey = receiver ?? ctx.wallet.publicKey;
const payerKey = payer ?? ctx.wallet.publicKey;
const resolvedAtaResults = await resolveOrCreateATAs(
ctx.connection,
receiverKey,
mints.map((tokenMint) => {
return { tokenMint };
}),
async () => accountExemption,
payerKey
);
// Convert the results back into the specified format
const { resolveAtaIxs, resolvedAtas } = resolvedAtaResults.reduce<{
resolvedAtas: PublicKey[];
resolveAtaIxs: Instruction[];
}>(
(accu, curr) => {
const { address, ...ix } = curr;
accu.resolvedAtas.push(address);
// TODO: common-sdk needs to have an easier way to check for empty instruction
if (ix.instructions.length) {
accu.resolveAtaIxs.push(ix);
}
return accu;
},
{ resolvedAtas: [], resolveAtaIxs: [] }
);
const affliatedTokenAtaMap = convertListToMap(resolvedAtas, mints.map((mint) => mint.toBase58()));
return {
ataTokenAddresses: affliatedTokenAtaMap,
resolveAtaIxs,
};
}