Skip to content

Commit f911155

Browse files
committed
add pausable extension to js-legacy client
1 parent c0c98ee commit f911155

File tree

9 files changed

+372
-0
lines changed

9 files changed

+372
-0
lines changed

clients/js-legacy/src/extensions/extensionType.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { MEMO_TRANSFER_SIZE } from './memoTransfer/index.js';
1717
import { METADATA_POINTER_SIZE } from './metadataPointer/state.js';
1818
import { MINT_CLOSE_AUTHORITY_SIZE } from './mintCloseAuthority.js';
1919
import { NON_TRANSFERABLE_SIZE, NON_TRANSFERABLE_ACCOUNT_SIZE } from './nonTransferable.js';
20+
import { PAUSABLE_CONFIG_SIZE, PAUSABLE_ACCOUNT_SIZE } from './pausable/index.js';
2021
import { PERMANENT_DELEGATE_SIZE } from './permanentDelegate.js';
2122
import { SCALED_UI_AMOUNT_CONFIG_SIZE } from './scaledUiAmount/index.js';
2223
import { TRANSFER_FEE_AMOUNT_SIZE, TRANSFER_FEE_CONFIG_SIZE } from './transferFee/index.js';
@@ -51,6 +52,8 @@ export enum ExtensionType {
5152
TokenGroupMember = 23,
5253
// ConfidentialMintBurn, // Not implemented yet
5354
ScaledUiAmountConfig = 25,
55+
PausableConfig = 26,
56+
PausableAccount = 27,
5457
}
5558

5659
export const TYPE_SIZE = 2;
@@ -117,6 +120,10 @@ export function getTypeLen(e: ExtensionType): number {
117120
return TOKEN_GROUP_MEMBER_SIZE;
118121
case ExtensionType.ScaledUiAmountConfig:
119122
return SCALED_UI_AMOUNT_CONFIG_SIZE;
123+
case ExtensionType.PausableConfig:
124+
return PAUSABLE_CONFIG_SIZE;
125+
case ExtensionType.PausableAccount:
126+
return PAUSABLE_ACCOUNT_SIZE;
120127
case ExtensionType.TokenMetadata:
121128
throw Error(`Cannot get type length for variable extension type: ${e}`);
122129
default:
@@ -141,6 +148,7 @@ export function isMintExtension(e: ExtensionType): boolean {
141148
case ExtensionType.TokenGroup:
142149
case ExtensionType.TokenGroupMember:
143150
case ExtensionType.ScaledUiAmountConfig:
151+
case ExtensionType.PausableConfig:
144152
return true;
145153
case ExtensionType.Uninitialized:
146154
case ExtensionType.TransferFeeAmount:
@@ -150,6 +158,7 @@ export function isMintExtension(e: ExtensionType): boolean {
150158
case ExtensionType.CpiGuard:
151159
case ExtensionType.NonTransferableAccount:
152160
case ExtensionType.TransferHookAccount:
161+
case ExtensionType.PausableAccount:
153162
return false;
154163
default:
155164
throw Error(`Unknown extension type: ${e}`);
@@ -165,6 +174,7 @@ export function isAccountExtension(e: ExtensionType): boolean {
165174
case ExtensionType.CpiGuard:
166175
case ExtensionType.NonTransferableAccount:
167176
case ExtensionType.TransferHookAccount:
177+
case ExtensionType.PausableAccount:
168178
return true;
169179
case ExtensionType.Uninitialized:
170180
case ExtensionType.TransferFeeConfig:
@@ -182,6 +192,7 @@ export function isAccountExtension(e: ExtensionType): boolean {
182192
case ExtensionType.TokenGroup:
183193
case ExtensionType.TokenGroupMember:
184194
case ExtensionType.ScaledUiAmountConfig:
195+
case ExtensionType.PausableConfig:
185196
return false;
186197
default:
187198
throw Error(`Unknown extension type: ${e}`);
@@ -198,6 +209,8 @@ export function getAccountTypeOfMintType(e: ExtensionType): ExtensionType {
198209
return ExtensionType.NonTransferableAccount;
199210
case ExtensionType.TransferHook:
200211
return ExtensionType.TransferHookAccount;
212+
case ExtensionType.PausableAccount:
213+
return ExtensionType.PausableConfig;
201214
case ExtensionType.TransferFeeAmount:
202215
case ExtensionType.ConfidentialTransferAccount:
203216
case ExtensionType.CpiGuard:
@@ -217,6 +230,7 @@ export function getAccountTypeOfMintType(e: ExtensionType): ExtensionType {
217230
case ExtensionType.TokenGroup:
218231
case ExtensionType.TokenGroupMember:
219232
case ExtensionType.ScaledUiAmountConfig:
233+
case ExtensionType.PausableConfig:
220234
return ExtensionType.Uninitialized;
221235
}
222236
}

clients/js-legacy/src/extensions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export * from './nonTransferable.js';
1717
export * from './transferFee/index.js';
1818
export * from './permanentDelegate.js';
1919
export * from './transferHook/index.js';
20+
export * from './pausable/index.js';
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import type { ConfirmOptions, Connection, PublicKey, Signer, TransactionSignature } from '@solana/web3.js';
2+
import { sendAndConfirmTransaction, Transaction } from '@solana/web3.js';
3+
import { getSigners } from '../../actions/internal.js';
4+
import { TOKEN_2022_PROGRAM_ID } from '../../constants.js';
5+
import { createPauseInstruction, createResumeInstruction } from './instructions.js';
6+
7+
/**
8+
* Pause a pausable mint
9+
*
10+
* @param connection Connection to use
11+
* @param payer Payer of the transaction fees
12+
* @param mint Public key of the mint
13+
* @param owner The pausable config authority
14+
* @param multiSigners Signing accounts if `owner` is a multisig
15+
* @param confirmOptions Options for confirming the transaction
16+
* @param programId SPL Token program account
17+
*
18+
* @return Public key of the mint
19+
*/
20+
export async function pause(
21+
connection: Connection,
22+
payer: Signer,
23+
mint: PublicKey,
24+
owner: Signer | PublicKey,
25+
multiSigners: Signer[] = [],
26+
confirmOptions?: ConfirmOptions,
27+
programId = TOKEN_2022_PROGRAM_ID,
28+
): Promise<TransactionSignature> {
29+
const [ownerPublicKey, signers] = getSigners(owner, multiSigners);
30+
31+
const transaction = new Transaction().add(createPauseInstruction(mint, ownerPublicKey, multiSigners, programId));
32+
33+
return await sendAndConfirmTransaction(connection, transaction, [payer, ...signers], confirmOptions);
34+
}
35+
36+
/**
37+
* Resume a pausable mint
38+
*
39+
* @param connection Connection to use
40+
* @param payer Payer of the transaction fees
41+
* @param mint Public key of the mint
42+
* @param owner The pausable config authority
43+
* @param multiSigners Signing accounts if `owner` is a multisig
44+
* @param confirmOptions Options for confirming the transaction
45+
* @param programId SPL Token program account
46+
*
47+
* @return Public key of the mint
48+
*/
49+
export async function resume(
50+
connection: Connection,
51+
payer: Signer,
52+
mint: PublicKey,
53+
owner: Signer | PublicKey,
54+
multiSigners: Signer[] = [],
55+
confirmOptions?: ConfirmOptions,
56+
programId = TOKEN_2022_PROGRAM_ID,
57+
): Promise<TransactionSignature> {
58+
const [ownerPublicKey, signers] = getSigners(owner, multiSigners);
59+
60+
const transaction = new Transaction().add(createResumeInstruction(mint, ownerPublicKey, multiSigners, programId));
61+
62+
return await sendAndConfirmTransaction(connection, transaction, [payer, ...signers], confirmOptions);
63+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from './actions.js';
2+
export * from './instructions.js';
3+
export * from './state.js';
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { struct, u8 } from '@solana/buffer-layout';
2+
import { publicKey } from '@solana/buffer-layout-utils';
3+
import type { Signer } from '@solana/web3.js';
4+
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
5+
import { TOKEN_2022_PROGRAM_ID, programSupportsExtensions } from '../../constants.js';
6+
import { TokenUnsupportedInstructionError } from '../../errors.js';
7+
import { TokenInstruction } from '../../instructions/types.js';
8+
import { addSigners } from '../../instructions/internal.js';
9+
10+
export enum PausableInstruction {
11+
Initialize = 0,
12+
Pause = 1,
13+
Resume = 2,
14+
}
15+
16+
export interface InitializePausableConfigInstructionData {
17+
instruction: TokenInstruction.PausableExtension;
18+
pausableInstruction: PausableInstruction.Initialize;
19+
authority: PublicKey;
20+
}
21+
22+
export const initializePausableConfigInstructionData = struct<InitializePausableConfigInstructionData>([
23+
u8('instruction'),
24+
u8('pausableInstruction'),
25+
publicKey('authority'),
26+
]);
27+
28+
/**
29+
* Construct a InitializePausableConfig instruction
30+
*
31+
* @param mint Token mint account
32+
* @param authority Optional authority that can pause or resume mint
33+
* @param programId SPL Token program account
34+
*/
35+
export function createInitializePausableConfigInstruction(
36+
mint: PublicKey,
37+
authority: PublicKey | null,
38+
programId: PublicKey = TOKEN_2022_PROGRAM_ID,
39+
): TransactionInstruction {
40+
if (!programSupportsExtensions(programId)) {
41+
throw new TokenUnsupportedInstructionError();
42+
}
43+
const keys = [{ pubkey: mint, isSigner: false, isWritable: true }];
44+
45+
const data = Buffer.alloc(initializePausableConfigInstructionData.span);
46+
initializePausableConfigInstructionData.encode(
47+
{
48+
instruction: TokenInstruction.PausableExtension,
49+
pausableInstruction: PausableInstruction.Initialize,
50+
authority: authority ?? PublicKey.default,
51+
},
52+
data,
53+
);
54+
55+
return new TransactionInstruction({ keys, programId, data: data });
56+
}
57+
58+
export interface PauseInstructionData {
59+
instruction: TokenInstruction.PausableExtension;
60+
pausableInstruction: PausableInstruction.Pause;
61+
}
62+
63+
export const pauseInstructionData = struct<PauseInstructionData>([u8('instruction'), u8('pausableInstruction')]);
64+
65+
/**
66+
* Construct a Pause instruction
67+
*
68+
* @param mint Token mint account
69+
* @param authority The pausable mint's authority
70+
* @param multiSigners Signing accounts if authority is a multisig
71+
* @param programId SPL Token program account
72+
*/
73+
export function createPauseInstruction(
74+
mint: PublicKey,
75+
authority: PublicKey,
76+
multiSigners: (Signer | PublicKey)[] = [],
77+
programId: PublicKey = TOKEN_2022_PROGRAM_ID,
78+
): TransactionInstruction {
79+
if (!programSupportsExtensions(programId)) {
80+
throw new TokenUnsupportedInstructionError();
81+
}
82+
const keys = addSigners([{ pubkey: mint, isSigner: false, isWritable: true }], authority, multiSigners);
83+
84+
const data = Buffer.alloc(pauseInstructionData.span);
85+
pauseInstructionData.encode(
86+
{
87+
instruction: TokenInstruction.PausableExtension,
88+
pausableInstruction: PausableInstruction.Pause,
89+
},
90+
data,
91+
);
92+
93+
return new TransactionInstruction({ keys, programId, data: data });
94+
}
95+
96+
export interface ResumeInstructionData {
97+
instruction: TokenInstruction.PausableExtension;
98+
pausableInstruction: PausableInstruction.Resume;
99+
}
100+
101+
export const resumeInstructionData = struct<ResumeInstructionData>([u8('instruction'), u8('pausableInstruction')]);
102+
103+
/**
104+
* Construct a Resume instruction
105+
*
106+
* @param mint Token mint account
107+
* @param authority The pausable mint's authority
108+
* @param multiSigners Signing accounts if authority is a multisig
109+
* @param programId SPL Token program account
110+
*/
111+
export function createResumeInstruction(
112+
mint: PublicKey,
113+
authority: PublicKey,
114+
multiSigners: (Signer | PublicKey)[] = [],
115+
programId: PublicKey = TOKEN_2022_PROGRAM_ID,
116+
): TransactionInstruction {
117+
if (!programSupportsExtensions(programId)) {
118+
throw new TokenUnsupportedInstructionError();
119+
}
120+
const keys = addSigners([{ pubkey: mint, isSigner: false, isWritable: true }], authority, multiSigners);
121+
122+
const data = Buffer.alloc(resumeInstructionData.span);
123+
resumeInstructionData.encode(
124+
{
125+
instruction: TokenInstruction.PausableExtension,
126+
pausableInstruction: PausableInstruction.Resume,
127+
},
128+
data,
129+
);
130+
131+
return new TransactionInstruction({ keys, programId, data: data });
132+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { struct } from '@solana/buffer-layout';
2+
import { publicKey, bool } from '@solana/buffer-layout-utils';
3+
import type { PublicKey } from '@solana/web3.js';
4+
import type { Account } from '../../state/account.js';
5+
import type { Mint } from '../../state/mint.js';
6+
import { ExtensionType, getExtensionData } from '../extensionType.js';
7+
8+
/** PausableConfig as stored by the program */
9+
export interface PausableConfig {
10+
/** Authority that can pause or resume activity on the mint */
11+
authority: PublicKey;
12+
/** Whether minting / transferring / burning tokens is paused */
13+
paused: boolean;
14+
}
15+
16+
/** Buffer layout for de/serializing a pausable config */
17+
export const PausableConfigLayout = struct<PausableConfig>([publicKey('authority'), bool('paused')]);
18+
19+
export const PAUSABLE_CONFIG_SIZE = PausableConfigLayout.span;
20+
21+
export function getPausableConfig(mint: Mint): PausableConfig | null {
22+
const extensionData = getExtensionData(ExtensionType.PausableConfig, mint.tlvData);
23+
if (extensionData !== null) {
24+
return PausableConfigLayout.decode(extensionData);
25+
} else {
26+
return null;
27+
}
28+
}
29+
30+
/** Pausable token account state as stored by the program */
31+
export interface PausableAccount {} // eslint-disable-line
32+
33+
/** Buffer layout for de/serializing a pausable account */
34+
export const PausableAccountLayout = struct<PausableAccount>([]); // esline-disable-line
35+
36+
export const PAUSABLE_ACCOUNT_SIZE = PausableAccountLayout.span;
37+
38+
export function getPausableAccount(account: Account): PausableAccount | null {
39+
const extensionData = getExtensionData(ExtensionType.PausableAccount, account.tlvData);
40+
if (extensionData !== null) {
41+
return PausableAccountLayout.decode(extensionData);
42+
} else {
43+
return null;
44+
}
45+
}

clients/js-legacy/src/instructions/setAuthority.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export enum AuthorityType {
3131
GroupPointer = 13,
3232
GroupMemberPointer = 14,
3333
ScaledUiAmountConfig = 15,
34+
PausableConfig = 16,
3435
}
3536

3637
/** TODO: docs */

clients/js-legacy/src/instructions/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,5 @@ export enum TokenInstruction {
4444
GroupMemberPointerExtension = 41,
4545
// ConfidentialMintBurnExtension = 42,
4646
ScaledUiAmountExtension = 43,
47+
PausableExtension = 44,
4748
}

0 commit comments

Comments
 (0)