Skip to content

Commit

Permalink
add Token-2022 support step 2
Browse files Browse the repository at this point in the history
  • Loading branch information
yugure-orca committed Jan 9, 2024
1 parent 72b81ad commit a36945f
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 18 deletions.
2 changes: 1 addition & 1 deletion src/components/AccountDefinition.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
export let href: string;
</script>

<a target="_blank" rel="noreferrer" href={href} style="font-size:small; font-weight:normal; text-decolation: none;">🔡</a>
<a target="_blank" rel="noreferrer" href={href} style="font-size:small; font-weight:normal; text-decoration: none;">🔡</a>
9 changes: 9 additions & 0 deletions src/components/ExtensionData.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script lang="ts">
import AccountDefinition from "./AccountDefinition.svelte";
export let name: string;
export let desc: string;
export let href: string;
</script>

<dt><b title={desc}>{name}</b> <AccountDefinition href={href} /></dt><dd style="margin-left: 0px;"><slot></slot></dd>
56 changes: 48 additions & 8 deletions src/libs/token2022.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import {
unpackMint,
Account,
unpackAccount,
ExtensionType,
getExtensionTypes,
getExtensionData,
// mint
getTransferFeeConfig, TransferFeeConfig,
getMintCloseAuthority, MintCloseAuthority,
Expand All @@ -24,7 +27,7 @@ import {
getMemoTransfer, MemoTransfer,
getCpiGuard, CpiGuard,
getNonTransferableAccount, NonTransferableAccount,
getTransferHookAccount, TransferHookAccount, getExtensionData, ExtensionType,
getTransferHookAccount, TransferHookAccount,
} from "@solana/spl-token-2022";
import { TokenMetadata, unpack as unpackTokenMetadata } from '@solana/spl-token-metadata';
import { AccountMetaInfo, getAccountInfo, toMeta } from "./account";
Expand Down Expand Up @@ -58,6 +61,7 @@ export type TokenAccount2022Info = {
parsed: {
base: Account,
extensions: TokenAccount2022Extensions,
unknownExtensions: ExtensionType[],
},
derived: TokenAccount2022DerivedInfo,
}
Expand All @@ -70,9 +74,9 @@ type Mint2022Extensions = {
transferFeeConfig: TransferFeeConfig | null,
mintCloseAuthority: MintCloseAuthority | null,
// confidentialTransferMint
defaultAccount: DefaultAccountState | null,
defaultAccountState: DefaultAccountState | null,
nonTransferable: NonTransferable | null,
interestBearingMintConfig: InterestBearingMintConfigState | null,
interestBearingConfig: InterestBearingMintConfigState | null,
permanentDelegate: PermanentDelegate | null,
transferHook: TransferHook | null,
metadataPointer: Partial<MetadataPointer> | null,
Expand All @@ -84,6 +88,7 @@ export type Mint2022Info = {
parsed: {
base: Mint,
extensions: Mint2022Extensions,
unknownExtensions: ExtensionType[],
},
derived: Mint2022DerivedInfo,
}
Expand All @@ -107,6 +112,21 @@ export async function getTokenAccount2022Info(addr: Address): Promise<TokenAccou
const nonTransferableAccount = getNonTransferableAccount(account);
const transferHookAccount = getTransferHookAccount(account);

const extensions = getExtensionTypes(account.tlvData);
const unknownExtensions = extensions.sort().filter((e) => {
switch (e) {
case ExtensionType.TransferFeeAmount:
case ExtensionType.ImmutableOwner:
case ExtensionType.MemoTransfer:
case ExtensionType.CpiGuard:
case ExtensionType.NonTransferableAccount:
case ExtensionType.TransferHookAccount:
return false;
default:
return true;
}
});

// isATA ?
const ataAddress = await utils.token.associatedAddress({ mint: account.mint, owner: account.owner });
const isATA = ataAddress.equals(pubkey);
Expand All @@ -122,7 +142,8 @@ export async function getTokenAccount2022Info(addr: Address): Promise<TokenAccou
cpiGuard,
nonTransferableAccount,
transferHookAccount,
}
},
unknownExtensions,
},
derived: {
decimals: mint.decimals,
Expand All @@ -142,9 +163,9 @@ export async function getMint2022Info(addr: Address): Promise<Mint2022Info> {
const transferFeeConfig = getTransferFeeConfig(mint);
const mintCloseAuthority = getMintCloseAuthority(mint);
// const confidentialTransferMint = ?
const defaultAccount = getDefaultAccountState(mint);
const defaultAccountState = getDefaultAccountState(mint);
const nonTransferable = getNonTransferable(mint);
const interestBearingMintConfig = getInterestBearingMintConfigState(mint);
const interestBearingConfig = getInterestBearingMintConfigState(mint);
const permanentDelegate = getPermanentDelegate(mint);
const transferHook = getTransferHook(mint);
const metadataPointer = getMetadataPointerState(mint);
Expand All @@ -154,21 +175,40 @@ export async function getMint2022Info(addr: Address): Promise<Mint2022Info> {
return unpackTokenMetadata(data);
})();

const extensions = getExtensionTypes(mint.tlvData);
const unknownExtensions = extensions.sort().filter((e) => {
switch (e) {
case ExtensionType.TransferFeeConfig:
case ExtensionType.MintCloseAuthority:
case ExtensionType.DefaultAccountState:
case ExtensionType.NonTransferable:
case ExtensionType.InterestBearingConfig:
case ExtensionType.PermanentDelegate:
case ExtensionType.TransferHook:
case ExtensionType.MetadataPointer:
case ExtensionType.TokenMetadata:
return false;
default:
return true;
}
});

return {
meta: toMeta(pubkey, accountInfo, slotContext),
parsed: {
base: mint,
extensions: {
transferFeeConfig,
mintCloseAuthority,
defaultAccount,
defaultAccountState,
nonTransferable,
interestBearingMintConfig,
interestBearingConfig,
permanentDelegate,
transferHook,
metadataPointer,
tokenMetadata,
},
unknownExtensions,
},
derived: {
supply: DecimalUtil.fromBN(new BN(mint.supply.toString()), mint.decimals),
Expand Down
10 changes: 10 additions & 0 deletions src/libs/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,13 @@ function parseStringToBN(amount: string, decimals: number): BN {
const base = new BN(10).pow(new BN(decimals));
return integers.mul(base).add(fractions);
}

export function toJsonStringWithoutTopBracket(obj: any): string | null {
if (!obj) return null;

const result = Object.entries(obj).map(([key, value]) => {
return `"${key}": ${JSON.stringify(value, null, 4)}`;
}).join(",\n");

return result;
}
36 changes: 30 additions & 6 deletions src/pages/token2022/Account.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
export let params;
import { getTokenAccount2022Info, ACCOUNT_DEFINITION } from "../../libs/token2022";
import ExtensionData from "../../components/ExtensionData.svelte";
import { toJsonStringWithoutTopBracket } from "../../libs/utils";
$: tokenAccountInfoPromise = getTokenAccount2022Info(params.pubkey);
</script>

Expand All @@ -31,12 +33,34 @@
<Data name="delegate" type="COption<PublicKey>" offset="COption(72)+PublicKey(76)"><Pubkey address={tokenAccountInfo.parsed.base.delegate} /></Data>
<Data name="delegatedAmount" type="u64" offset="121">{tokenAccountInfo.parsed.base.delegatedAmount}</Data>
<Data name="closeAuthority" type="COption<PublicKey>" offset="COption(129)+PublicKey(133)"><Pubkey address={tokenAccountInfo.parsed.base.closeAuthority} /></Data>
<Data name="extensions">
<pre>
{JSON.stringify(tokenAccountInfo.parsed.extensions, null, 2)}
</pre>
</Data>
</ParsedData>
<Data name="extensions">
{#if tokenAccountInfo.parsed.unknownExtensions.length > 0}
<ExtensionData name="Unknown ExtensionType🚨" href="https://github.com/solana-labs/solana-program-library/blob/master/token/program-2022/src/extension/mod.rs#L906" desc="Please be careful. Account microscope detected unknown extensions.">
<pre>{JSON.stringify(tokenAccountInfo.parsed.unknownExtensions)}</pre>
</ExtensionData>
{/if}

<!-- desc is quoted from: https://github.com/solana-labs/solana-program-library/blob/master/token/program-2022/src/extension/mod.rs#L906 -->
<ExtensionData name="TransferFeeAmount(2)" href="https://spl.solana.com/token-2022/extensions#transfer-fees" desc="Includes withheld transfer fees">
<pre>{toJsonStringWithoutTopBracket(tokenAccountInfo.parsed.extensions.transferFeeAmount)}</pre>
</ExtensionData>
<ExtensionData name="ImmutableOwner(7)" href="https://spl.solana.com/token-2022/extensions#immutable-owner" desc="Indicates that the Account owner authority cannot be changed">
<pre>{toJsonStringWithoutTopBracket(tokenAccountInfo.parsed.extensions.immutableOwner)}</pre>
</ExtensionData>
<ExtensionData name="MemoTransfer(8)" href="https://spl.solana.com/token-2022/extensions#required-memo-on-transfer" desc="Require inbound transfers to have memo">
<pre>{toJsonStringWithoutTopBracket(tokenAccountInfo.parsed.extensions.memoTransfer)}</pre>
</ExtensionData>
<ExtensionData name="CpiGuard(11)" href="https://spl.solana.com/token-2022/extensions#cpi-guard" desc="Locks privileged token operations from happening via CPI">
<pre>{toJsonStringWithoutTopBracket(tokenAccountInfo.parsed.extensions.cpiGuard)}</pre>
</ExtensionData>
<ExtensionData name="NonTransferableAccount(13)" href="https://spl.solana.com/token-2022/extensions#non-transferable-tokens" desc="Indicates that the tokens in this account belong to a non-transferable mint">
<pre>{toJsonStringWithoutTopBracket(tokenAccountInfo.parsed.extensions.nonTransferableAccount)}</pre>
</ExtensionData>
<ExtensionData name="TransferHookAccount(15)" href="https://spl.solana.com/token-2022/extensions#transfer-hook" desc="Indicates that the tokens in this account belong to a mint with a transfer hook">
<pre>{toJsonStringWithoutTopBracket(tokenAccountInfo.parsed.extensions.transferHookAccount)}</pre>
</ExtensionData>
</Data>
</ParsedData>

<DerivedData>
<Data name="decimals">{tokenAccountInfo.derived.decimals}</Data>
Expand Down
39 changes: 36 additions & 3 deletions src/pages/token2022/Mint.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
export let params;
import { getMint2022Info, ACCOUNT_DEFINITION } from "../../libs/token2022";
import ExtensionData from "../../components/ExtensionData.svelte";
import { toJsonStringWithoutTopBracket } from "../../libs/utils";
// import Laboratory from "../../components/Laboratory.svelte";
// import TokenMintCreateAta from "../../components/TokenMintCreateATA.svelte";
// import TokenMintRewriteAuthority from "../../components/TokenMintRewriteAuthority.svelte";
Expand All @@ -31,9 +33,40 @@
<Data name="mintAuthority" type="COption<PublicKey>" offset="COption(0)+PublicKey(4)"><Pubkey address={mintInfo.parsed.base.mintAuthority} /></Data>
<Data name="freezeAuthority" type="COption<PublicKey>" offset="COption(46)+PublicKey(50)"><Pubkey address={mintInfo.parsed.base.freezeAuthority} /></Data>
<Data name="extensions">
<pre>
{JSON.stringify(mintInfo.parsed.extensions, null, 2)}
</pre>
{#if mintInfo.parsed.unknownExtensions.length > 0}
<ExtensionData name="Unknown ExtensionType🚨" href="https://github.com/solana-labs/solana-program-library/blob/master/token/program-2022/src/extension/mod.rs#L906" desc="Please be careful. Account microscope detected unknown extensions.">
<pre>{JSON.stringify(mintInfo.parsed.unknownExtensions)}</pre>
</ExtensionData>
{/if}

<!-- desc is quoted from: https://github.com/solana-labs/solana-program-library/blob/master/token/program-2022/src/extension/mod.rs#L906 -->
<ExtensionData name="TransferFeeConfig(1)" href="https://spl.solana.com/token-2022/extensions#transfer-fees" desc="Includes transfer fee rate info and accompanying authorities to withdraw and set the fee">
<pre>{toJsonStringWithoutTopBracket(mintInfo.parsed.extensions.transferFeeConfig)}</pre>
</ExtensionData>
<ExtensionData name="MintCloseAuthority(3)" href="https://spl.solana.com/token-2022/extensions#mint-close-authority" desc="Includes an optional mint close authority">
<pre>{toJsonStringWithoutTopBracket(mintInfo.parsed.extensions.mintCloseAuthority)}</pre>
</ExtensionData>
<ExtensionData name="DefaultAccountState(6)" href="https://spl.solana.com/token-2022/extensions#default-account-state" desc="Specifies the default Account::state for new Accounts">
<pre>{toJsonStringWithoutTopBracket(mintInfo.parsed.extensions.defaultAccountState)}</pre>
</ExtensionData>
<ExtensionData name="NonTransferable(9)" href="https://spl.solana.com/token-2022/extensions#non-transferable-tokens" desc="Indicates that the tokens from this mint can't be transfered">
<pre>{toJsonStringWithoutTopBracket(mintInfo.parsed.extensions.nonTransferable)}</pre>
</ExtensionData>
<ExtensionData name="InterestBearingConfig(10)" href="https://spl.solana.com/token-2022/extensions#interest-bearing-tokens" desc="Tokens accrue interest over time">
<pre>{toJsonStringWithoutTopBracket(mintInfo.parsed.extensions.interestBearingConfig)}</pre>
</ExtensionData>
<ExtensionData name="PermanentDelegate(12)" href="https://spl.solana.com/token-2022/extensions#permanent-delegate" desc="Includes an optional permanent delegate">
<pre>{toJsonStringWithoutTopBracket(mintInfo.parsed.extensions.permanentDelegate)}</pre>
</ExtensionData>
<ExtensionData name="TransferHook(14)" href="https://spl.solana.com/token-2022/extensions#transfer-hook" desc="Mint requires a CPI to a program implementing the 'transfer hook' interface">
<pre>{toJsonStringWithoutTopBracket(mintInfo.parsed.extensions.transferHook)}</pre>
</ExtensionData>
<ExtensionData name="MetadataPointer(18)" href="https://spl.solana.com/token-2022/extensions#metadata-pointer" desc="Mint contains a pointer to another account (or the same account) that holds metadata">
<pre>{toJsonStringWithoutTopBracket(mintInfo.parsed.extensions.metadataPointer)}</pre>
</ExtensionData>
<ExtensionData name="TokenMetadata(19)" href="https://spl.solana.com/token-2022/extensions#metadata" desc="Mint contains token-metadata">
<pre>{toJsonStringWithoutTopBracket(mintInfo.parsed.extensions.tokenMetadata)}</pre>
</ExtensionData>
</Data>
</ParsedData>

Expand Down

0 comments on commit a36945f

Please sign in to comment.