Skip to content

Instantly share code, notes, and snippets.

@fersilva16
Created February 4, 2022 20:10
Show Gist options
  • Save fersilva16/02dccfd9497ab128fb72e89c13de5ec2 to your computer and use it in GitHub Desktop.
Save fersilva16/02dccfd9497ab128fb72e89c13de5ec2 to your computer and use it in GitHub Desktop.
Get tokens of the owner from an given wallet
import web3 from '@solana/web3.js';
import base58 from 'bs58';
import { asyncFilter } from './asyncFilter'; // From https://gist.github.com/fersilva16/dea9bb85ca69de435d01b8793d44948a
const TOKEN_PROGRAM_ID = new web3.PublicKey(
'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'
);
const TOKEN_METADATA_PROGRAM_ID = new web3.PublicKey(
'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s'
);
/**
* Creator Struct Offsets
* See https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state.rs#L400-L405
* See https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state.rs#L58-L60
*
* 0 - Public key
* 32 - Verified
* 33 - Share
* 34 - End of Creator
*/
/**
* Data Struct Offsets
* See https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state.rs#L95-L106
* See https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state.rs#L40-L49
*
* 0 - Name's length
* 4 - Name
* 36 - Symbol's length
* 40 - Symbol
* 50 - Uri's length
* 54 - Uri
* 254 - Seller Fee Basis Points
* 256 - If the Array of Creator exists
* 257 - Length of the Array of Creator
* 261 - Array of Creator (max of 5)
* 431 - End of Data
*/
/**
* Metadata Struct Offsets
* See https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state.rs#L221-L238
* See https://github.com/metaplex-foundation/metaplex-program-library/blob/master/token-metadata/program/src/state.rs#L27-L38
*
* 0 - Key
* 1 - Update Authority Public Key
* 33 - Mint Public Key
* 65 - Data
* 496 - primary sale
* 497 - mutable
* 498 - nonce (pretty sure this only needs to be 2)
* 507 - collection
* 541 - uses
* 559 - token standard
* 561 - Padding
* 679 - End of Metadata
*/
const TOKEN_ADDRESS_OFFSET = 0;
const UPDATE_AUTHORITY_OFFSET = 1;
const PUBLIC_KEY_LENGTH = 32;
const readPublicKey = (buffer: Buffer, offset: number) =>
base58.encode(buffer.slice(offset, offset + PUBLIC_KEY_LENGTH));
const connection = new web3.Connection(
web3.clusterApiUrl('devnet'),
'confirmed'
);
async function getTokenProgramTokenAccountsByOwners(
walletPublicKey: web3.PublicKey
) {
const tokens = await connection.getTokenAccountsByOwner(walletPublicKey, {
programId: TOKEN_PROGRAM_ID
});
return tokens.value;
}
const getTokenAddress = (buffer: Buffer) =>
readPublicKey(buffer, TOKEN_ADDRESS_OFFSET);
async function getTokenAddressFromAssociatedToken(
associatedToken: web3.PublicKey
) {
const accountInfo = await connection.getAccountInfo(associatedToken);
if (!accountInfo) return undefined;
const buffer = Buffer.from(accountInfo.data);
const tokenAddress = getTokenAddress(buffer);
return new web3.PublicKey(tokenAddress);
}
const getUpdateAuthority = (buffer: Buffer) =>
readPublicKey(buffer, UPDATE_AUTHORITY_OFFSET);
async function getUpdateAuthorityFromTokenAddress(token: web3.PublicKey) {
const [programAddress] = await web3.PublicKey.findProgramAddress(
[
Buffer.from('metadata'),
TOKEN_METADATA_PROGRAM_ID.toBuffer(),
token.toBuffer()
],
TOKEN_METADATA_PROGRAM_ID
);
const accountInfo = await connection.getAccountInfo(programAddress);
if (!accountInfo) return undefined;
const buffer = Buffer.from(accountInfo.data);
return getUpdateAuthority(buffer);
}
async function getTokensOfOwnerFromWallet(
walletPublicKey: web3.PublicKey,
ownerPublicKey: web3.PublicKey
) {
const tokens = await getTokenProgramTokenAccountsByOwners(walletPublicKey);
return asyncFilter(tokens, async ({ pubkey }) => {
try {
const tokenAddress = await getTokenAddressFromAssociatedToken(pubkey);
if (!tokenAddress) return false;
const updateAuthority = await getUpdateAuthorityFromTokenAddress(
tokenAddress
);
return ownerPublicKey.toString() === updateAuthority;
} catch (e) {
return false;
}
});
}
getTokensOfOwnerFromWallet(
new web3.PublicKey('...'), // Wallet Public Key
new web3.PublicKey('...'), // Owner Public Key
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment