Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 56b083d

Browse files
qiweiiibuffalojoec
andauthored
feat: add token group extension in token (#6295)
* feat: add token group extension in token * wip: add e2e tests * fix: token group member * feat: init group and groupMember with rent transfer * fix: token group e2e tests * fix: remove single pool changes * fix: pnpm install * fix instructions * rework tests * rename directory to `tokenGroup` --------- Co-authored-by: Joe C <[email protected]>
1 parent ce0389b commit 56b083d

File tree

12 files changed

+3251
-1548
lines changed

12 files changed

+3251
-1548
lines changed

pnpm-lock.yaml

Lines changed: 2513 additions & 1545 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

token-group/js/src/state/tokenGroup.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const tokenGroupCodec = getStructCodec([
88
['maxSize', getU32Codec()],
99
]);
1010

11+
export const TOKEN_GROUP_SIZE = tokenGroupCodec.fixedSize;
12+
1113
export interface TokenGroup {
1214
/** The authority that can sign to update the group */
1315
updateAuthority?: PublicKey;

token-group/js/src/state/tokenGroupMember.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ const tokenGroupMemberCodec = getStructCodec([
77
['memberNumber', getU32Codec()],
88
]);
99

10+
export const TOKEN_GROUP_MEMBER_SIZE = tokenGroupMemberCodec.fixedSize;
11+
1012
export interface TokenGroupMember {
1113
/** The associated mint, used to counter spoofing to be sure that member belongs to a particular mint */
1214
mint: PublicKey;

token/js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"dependencies": {
5757
"@solana/buffer-layout": "^4.0.0",
5858
"@solana/buffer-layout-utils": "^0.2.0",
59+
"@solana/spl-token-group": "^0.0.1",
5960
"@solana/spl-token-metadata": "^0.1.2",
6061
"buffer": "^6.0.3"
6162
},
@@ -82,7 +83,6 @@
8283
"process": "^0.11.10",
8384
"shx": "^0.3.4",
8485
"start-server-and-test": "^2.0.3",
85-
"tslib": "^2.3.1",
8686
"ts-node": "^10.9.2",
8787
"typedoc": "^0.25.12",
8888
"typescript": "^5.4.3"

token/js/src/extensions/extensionType.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { MULTISIG_SIZE } from '../state/multisig.js';
77
import { ACCOUNT_TYPE_SIZE } from './accountType.js';
88
import { CPI_GUARD_SIZE } from './cpiGuard/index.js';
99
import { DEFAULT_ACCOUNT_STATE_SIZE } from './defaultAccountState/index.js';
10+
import { TOKEN_GROUP_SIZE, TOKEN_GROUP_MEMBER_SIZE } from './tokenGroup/index.js';
1011
import { GROUP_MEMBER_POINTER_SIZE } from './groupMemberPointer/state.js';
1112
import { GROUP_POINTER_SIZE } from './groupPointer/state.js';
1213
import { IMMUTABLE_OWNER_SIZE } from './immutableOwner.js';
@@ -43,9 +44,9 @@ export enum ExtensionType {
4344
MetadataPointer = 18, // Remove number once above extensions implemented
4445
TokenMetadata = 19, // Remove number once above extensions implemented
4546
GroupPointer = 20,
46-
// TokenGroup = 21, // Not implemented yet
47+
TokenGroup = 21,
4748
GroupMemberPointer = 22,
48-
// TokenGroupMember = 23, // Not implemented yet
49+
TokenGroupMember = 23,
4950
}
5051

5152
export const TYPE_SIZE = 2;
@@ -106,6 +107,10 @@ export function getTypeLen(e: ExtensionType): number {
106107
return GROUP_POINTER_SIZE;
107108
case ExtensionType.GroupMemberPointer:
108109
return GROUP_MEMBER_POINTER_SIZE;
110+
case ExtensionType.TokenGroup:
111+
return TOKEN_GROUP_SIZE;
112+
case ExtensionType.TokenGroupMember:
113+
return TOKEN_GROUP_MEMBER_SIZE;
109114
case ExtensionType.TokenMetadata:
110115
throw Error(`Cannot get type length for variable extension type: ${e}`);
111116
default:
@@ -127,6 +132,8 @@ export function isMintExtension(e: ExtensionType): boolean {
127132
case ExtensionType.TokenMetadata:
128133
case ExtensionType.GroupPointer:
129134
case ExtensionType.GroupMemberPointer:
135+
case ExtensionType.TokenGroup:
136+
case ExtensionType.TokenGroupMember:
130137
return true;
131138
case ExtensionType.Uninitialized:
132139
case ExtensionType.TransferFeeAmount:
@@ -165,6 +172,8 @@ export function isAccountExtension(e: ExtensionType): boolean {
165172
case ExtensionType.TokenMetadata:
166173
case ExtensionType.GroupPointer:
167174
case ExtensionType.GroupMemberPointer:
175+
case ExtensionType.TokenGroup:
176+
case ExtensionType.TokenGroupMember:
168177
return false;
169178
default:
170179
throw Error(`Unknown extension type: ${e}`);
@@ -197,6 +206,8 @@ export function getAccountTypeOfMintType(e: ExtensionType): ExtensionType {
197206
case ExtensionType.TransferHookAccount:
198207
case ExtensionType.GroupPointer:
199208
case ExtensionType.GroupMemberPointer:
209+
case ExtensionType.TokenGroup:
210+
case ExtensionType.TokenGroupMember:
200211
return ExtensionType.Uninitialized;
201212
}
202213
}

token/js/src/extensions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export * from './immutableOwner.js';
88
export * from './interestBearingMint/index.js';
99
export * from './memoTransfer/index.js';
1010
export * from './metadataPointer/index.js';
11+
export * from './tokenGroup/index.js';
1112
export * from './tokenMetadata/index.js';
1213
export * from './mintCloseAuthority.js';
1314
export * from './nonTransferable.js';
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
import type { ConfirmOptions, Connection, PublicKey, Signer, TransactionSignature } from '@solana/web3.js';
2+
import { sendAndConfirmTransaction, SystemProgram, Transaction } from '@solana/web3.js';
3+
import {
4+
createInitializeGroupInstruction,
5+
createUpdateGroupMaxSizeInstruction,
6+
createUpdateGroupAuthorityInstruction,
7+
createInitializeMemberInstruction,
8+
TOKEN_GROUP_SIZE,
9+
TOKEN_GROUP_MEMBER_SIZE,
10+
} from '@solana/spl-token-group';
11+
12+
import { TOKEN_2022_PROGRAM_ID } from '../../constants.js';
13+
import { getSigners } from '../../actions/internal.js';
14+
15+
/**
16+
* Initialize a new `Group`
17+
*
18+
* Assumes one has already initialized a mint for the group.
19+
*
20+
* @param connection Connection to use
21+
* @param payer Payer of the transaction fee
22+
* @param mint Group mint
23+
* @param mintAuthority Group mint authority
24+
* @param updateAuthority Group update authority
25+
* @param maxSize Maximum number of members in the group
26+
* @param multiSigners Signing accounts if `authority` is a multisig
27+
* @param confirmOptions Options for confirming the transaction
28+
* @param programId SPL Token program account
29+
*
30+
* @return Signature of the confirmed transaction
31+
*/
32+
export async function tokenGroupInitializeGroup(
33+
connection: Connection,
34+
payer: Signer,
35+
mint: PublicKey,
36+
mintAuthority: PublicKey | Signer,
37+
updateAuthority: PublicKey | null,
38+
maxSize: number,
39+
multiSigners: Signer[] = [],
40+
confirmOptions?: ConfirmOptions,
41+
programId = TOKEN_2022_PROGRAM_ID
42+
): Promise<TransactionSignature> {
43+
const [mintAuthorityPublicKey, signers] = getSigners(mintAuthority, multiSigners);
44+
45+
const transaction = new Transaction().add(
46+
createInitializeGroupInstruction({
47+
programId,
48+
group: mint,
49+
mint,
50+
mintAuthority: mintAuthorityPublicKey,
51+
updateAuthority,
52+
maxSize,
53+
})
54+
);
55+
56+
return await sendAndConfirmTransaction(connection, transaction, [payer, ...signers], confirmOptions);
57+
}
58+
59+
/**
60+
* Initialize a new `Group` with rent transfer.
61+
*
62+
* Assumes one has already initialized a mint for the group.
63+
*
64+
* @param connection Connection to use
65+
* @param payer Payer of the transaction fee
66+
* @param mint Group mint
67+
* @param mintAuthority Group mint authority
68+
* @param updateAuthority Group update authority
69+
* @param maxSize Maximum number of members in the group
70+
* @param multiSigners Signing accounts if `authority` is a multisig
71+
* @param confirmOptions Options for confirming the transaction
72+
* @param programId SPL Token program account
73+
*
74+
* @return Signature of the confirmed transaction
75+
*/
76+
export async function tokenGroupInitializeGroupWithRentTransfer(
77+
connection: Connection,
78+
payer: Signer,
79+
mint: PublicKey,
80+
mintAuthority: PublicKey | Signer,
81+
updateAuthority: PublicKey | null,
82+
maxSize: number,
83+
multiSigners: Signer[] = [],
84+
confirmOptions?: ConfirmOptions,
85+
programId = TOKEN_2022_PROGRAM_ID
86+
): Promise<TransactionSignature> {
87+
const [mintAuthorityPublicKey, signers] = getSigners(mintAuthority, multiSigners);
88+
89+
const lamports = await connection.getMinimumBalanceForRentExemption(TOKEN_GROUP_SIZE);
90+
91+
const transaction = new Transaction().add(
92+
SystemProgram.transfer({
93+
fromPubkey: payer.publicKey,
94+
toPubkey: mint,
95+
lamports,
96+
}),
97+
createInitializeGroupInstruction({
98+
programId,
99+
group: mint,
100+
mint,
101+
mintAuthority: mintAuthorityPublicKey,
102+
updateAuthority,
103+
maxSize,
104+
})
105+
);
106+
107+
return await sendAndConfirmTransaction(connection, transaction, [payer, ...signers], confirmOptions);
108+
}
109+
110+
/**
111+
* Update the max size of a `Group`
112+
*
113+
* @param connection Connection to use
114+
* @param payer Payer of the transaction fee
115+
* @param mint Group mint
116+
* @param updateAuthority Group update authority
117+
* @param maxSize Maximum number of members in the group
118+
* @param multiSigners Signing accounts if `authority` is a multisig
119+
* @param confirmOptions Options for confirming the transaction
120+
* @param programId SPL Token program account
121+
*
122+
* @return Signature of the confirmed transaction
123+
*/
124+
export async function tokenGroupUpdateGroupMaxSize(
125+
connection: Connection,
126+
payer: Signer,
127+
mint: PublicKey,
128+
updateAuthority: PublicKey | Signer,
129+
maxSize: number,
130+
multiSigners: Signer[] = [],
131+
confirmOptions?: ConfirmOptions,
132+
programId = TOKEN_2022_PROGRAM_ID
133+
): Promise<TransactionSignature> {
134+
const [updateAuthorityPublicKey, signers] = getSigners(updateAuthority, multiSigners);
135+
136+
const transaction = new Transaction().add(
137+
createUpdateGroupMaxSizeInstruction({
138+
programId,
139+
group: mint,
140+
updateAuthority: updateAuthorityPublicKey,
141+
maxSize,
142+
})
143+
);
144+
145+
return await sendAndConfirmTransaction(connection, transaction, [payer, ...signers], confirmOptions);
146+
}
147+
148+
/**
149+
* Update the authority of a `Group`
150+
*
151+
* @param connection Connection to use
152+
* @param payer Payer of the transaction fee
153+
* @param mint Group mint
154+
* @param updateAuthority Group update authority
155+
* @param newAuthority New authority for the token group, or unset
156+
* @param multiSigners Signing accounts if `authority` is a multisig
157+
* @param confirmOptions Options for confirming the transaction
158+
* @param programId SPL Token program account
159+
*
160+
* @return Signature of the confirmed transaction
161+
*/
162+
export async function tokenGroupUpdateGroupAuthority(
163+
connection: Connection,
164+
payer: Signer,
165+
mint: PublicKey,
166+
updateAuthority: PublicKey | Signer,
167+
newAuthority: PublicKey | null,
168+
multiSigners: Signer[] = [],
169+
confirmOptions?: ConfirmOptions,
170+
programId = TOKEN_2022_PROGRAM_ID
171+
): Promise<TransactionSignature> {
172+
const [updateAuthorityPublicKey, signers] = getSigners(updateAuthority, multiSigners);
173+
174+
const transaction = new Transaction().add(
175+
createUpdateGroupAuthorityInstruction({
176+
programId,
177+
group: mint,
178+
currentAuthority: updateAuthorityPublicKey,
179+
newAuthority,
180+
})
181+
);
182+
183+
return await sendAndConfirmTransaction(connection, transaction, [payer, ...signers], confirmOptions);
184+
}
185+
186+
/**
187+
* Initialize a new `Member` of a `Group`
188+
*
189+
* Assumes the `Group` has already been initialized,
190+
* as well as the mint for the member.
191+
*
192+
* @param connection Connection to use
193+
* @param payer Payer of the transaction fee
194+
* @param mint Member mint
195+
* @param mintAuthority Member mint authority
196+
* @param group Group mint
197+
* @param groupUpdateAuthority Group update authority
198+
* @param multiSigners Signing accounts if `authority` is a multisig
199+
* @param confirmOptions Options for confirming the transaction
200+
* @param programId SPL Token program account
201+
*
202+
* @return Signature of the confirmed transaction
203+
*/
204+
export async function tokenGroupMemberInitialize(
205+
connection: Connection,
206+
payer: Signer,
207+
mint: PublicKey,
208+
mintAuthority: PublicKey | Signer,
209+
group: PublicKey,
210+
groupUpdateAuthority: PublicKey,
211+
multiSigners: Signer[] = [],
212+
confirmOptions?: ConfirmOptions,
213+
programId = TOKEN_2022_PROGRAM_ID
214+
): Promise<TransactionSignature> {
215+
const [mintAuthorityPublicKey, signers] = getSigners(mintAuthority, multiSigners);
216+
217+
const transaction = new Transaction().add(
218+
createInitializeMemberInstruction({
219+
programId,
220+
member: mint,
221+
memberMint: mint,
222+
memberMintAuthority: mintAuthorityPublicKey,
223+
group,
224+
groupUpdateAuthority,
225+
})
226+
);
227+
228+
return await sendAndConfirmTransaction(connection, transaction, [payer, ...signers], confirmOptions);
229+
}
230+
231+
/**
232+
* Initialize a new `Member` of a `Group` with rent transfer.
233+
*
234+
* Assumes the `Group` has already been initialized,
235+
* as well as the mint for the member.
236+
*
237+
* @param connection Connection to use
238+
* @param payer Payer of the transaction fee
239+
* @param mint Member mint
240+
* @param mintAuthority Member mint authority
241+
* @param group Group mint
242+
* @param groupUpdateAuthority Group update authority
243+
* @param multiSigners Signing accounts if `authority` is a multisig
244+
* @param confirmOptions Options for confirming the transaction
245+
* @param programId SPL Token program account
246+
*
247+
* @return Signature of the confirmed transaction
248+
*/
249+
export async function tokenGroupMemberInitializeWithRentTransfer(
250+
connection: Connection,
251+
payer: Signer,
252+
mint: PublicKey,
253+
mintAuthority: PublicKey | Signer,
254+
group: PublicKey,
255+
groupUpdateAuthority: PublicKey,
256+
multiSigners: Signer[] = [],
257+
confirmOptions?: ConfirmOptions,
258+
programId = TOKEN_2022_PROGRAM_ID
259+
): Promise<TransactionSignature> {
260+
const [mintAuthorityPublicKey, signers] = getSigners(mintAuthority, multiSigners);
261+
262+
const lamports = await connection.getMinimumBalanceForRentExemption(TOKEN_GROUP_MEMBER_SIZE);
263+
264+
const transaction = new Transaction().add(
265+
SystemProgram.transfer({
266+
fromPubkey: payer.publicKey,
267+
toPubkey: mint,
268+
lamports,
269+
}),
270+
createInitializeMemberInstruction({
271+
programId,
272+
member: mint,
273+
memberMint: mint,
274+
memberMintAuthority: mintAuthorityPublicKey,
275+
group,
276+
groupUpdateAuthority,
277+
})
278+
);
279+
280+
return await sendAndConfirmTransaction(connection, transaction, [payer, ...signers], confirmOptions);
281+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './actions.js';
2+
export * from './state.js';

0 commit comments

Comments
 (0)