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:
parent
3c7b90c147
commit
0fe7bda4d6
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
export * from "./collect-all-txn";
|
||||
export * from "./resolve-atas-ix";
|
||||
|
|
|
@ -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)
|
||||
),
|
||||
};
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue