Skip to content

Instantly share code, notes, and snippets.

@arcticmatt
Created April 2, 2022 17:35
Show Gist options
  • Save arcticmatt/0f90aaa2a416aa0a02ab520372b55750 to your computer and use it in GitHub Desktop.
Save arcticmatt/0f90aaa2a416aa0a02ab520372b55750 to your computer and use it in GitHub Desktop.
import {
Connection,
ParsedInstruction,
ParsedTransactionWithMeta,
PartiallyDecodedInstruction,
PublicKey,
} from "@solana/web3.js";
import { Maybe } from "formfn-shared/dist/types/UtilityTypes";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
type TokenAccountInfo = {
delegate: string;
delegatedAmount: {
amount: string;
decimals: number;
uiAmount: number;
uiAmountString: string;
};
isNative: boolean;
mint: string;
owner: string;
state: string;
tokenAmount: {
amount: string;
decimals: number;
uiAmount: number;
uiAmountString: string;
};
};
async function getTokenAccountInfo(
connection: Connection,
tokenAccount: PublicKey
): Promise<Maybe<TokenAccountInfo>> {
const parsedAccountInfo = await connection.getParsedAccountInfo(tokenAccount);
const data = parsedAccountInfo?.value?.data;
if (data != null && "parsed" in data) {
return data.parsed.info;
}
return null;
}
function getAllInnerIxs(parsedTx: ParsedTransactionWithMeta) {
return parsedTx.meta?.innerInstructions?.reduce(
(acc: Array<ParsedInstruction | PartiallyDecodedInstruction>, currVal) => [
...acc,
...currVal.instructions,
],
[]
);
}
function isInitializeAccountIx(
ix: ParsedInstruction | PartiallyDecodedInstruction,
account?: string
) {
return (
ix.programId.toString() === TOKEN_PROGRAM_ID.toString() &&
(ix as ParsedInstruction).parsed?.type === "initializeAccount" &&
(account == null ||
(ix as ParsedInstruction).parsed?.info?.account?.toString() === account)
);
}
export default async function getTokenAccountOwner(
connection: Connection,
tokenAccount: PublicKey
): Promise<Maybe<string>> {
// First, try to get the token account info. May return null if account has been closed.
const tokenAccountInfo = await getTokenAccountInfo(connection, tokenAccount);
if (tokenAccountInfo != null) {
return tokenAccountInfo.owner;
}
const signatures = await connection.getConfirmedSignaturesForAddress2(
tokenAccount
);
if (signatures.length === 0) {
return null;
}
// Parse the first tx, which should have created the token account
const firstSignature = signatures[signatures.length - 1];
const parsedTx = await connection.getParsedTransaction(
firstSignature.signature,
"confirmed"
);
if (parsedTx == null) {
return null;
}
const ixs = parsedTx.transaction.message.instructions;
const allInnerIxs = getAllInnerIxs(parsedTx) ?? [];
const initializeAccountIx = [...ixs, ...allInnerIxs].find((ix) =>
isInitializeAccountIx(ix, tokenAccount.toString())
);
if (initializeAccountIx == null) {
return null;
}
return (
initializeAccountIx as ParsedInstruction
).parsed?.info?.owner?.toString();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment