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

Commit ae2e000

Browse files
InitializeImmutableOwner Instruction (#3068)
* initializeImmutableOwner Instruction * Immutable Owner Testing * immutable owner tests * Creating Mint and Initializing Account * Parameter Synchronization * Documentation Update Co-authored-by: Ronald Hood <[email protected]>
1 parent 76f0532 commit ae2e000

File tree

6 files changed

+232
-1
lines changed

6 files changed

+232
-1
lines changed

token/js/src/extensions/extensionType.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Mint, MINT_SIZE } from '../state/mint';
33
import { MULTISIG_SIZE } from '../state/multisig';
44
import { ACCOUNT_TYPE_SIZE } from './accountType';
55
import { MINT_CLOSE_AUTHORITY_SIZE } from './mintCloseAuthority';
6+
import { IMMUTABLE_OWNER_SIZE } from './immutableOwner';
67

78
export enum ExtensionType {
89
Uninitialized,
@@ -38,7 +39,7 @@ export function getTypeLen(e: ExtensionType): number {
3839
case ExtensionType.DefaultAccountState:
3940
return 1;
4041
case ExtensionType.ImmutableOwner:
41-
return 0;
42+
return IMMUTABLE_OWNER_SIZE;
4243
case ExtensionType.MemoTransfer:
4344
return 1;
4445
default:
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { struct } from '@solana/buffer-layout';
2+
import { Account } from '../state/account';
3+
import { ExtensionType, getExtensionData } from './extensionType';
4+
5+
/** ImmutableOwner as stored by the program */
6+
export interface ImmutableOwner {} // eslint-disable-line
7+
8+
/** Buffer layout for de/serializing an account */
9+
export const ImmutableOwnerLayout = struct<ImmutableOwner>([]);
10+
11+
export const IMMUTABLE_OWNER_SIZE = ImmutableOwnerLayout.span;
12+
13+
export function getImmutableOwner(account: Account): ImmutableOwner | null {
14+
const extensionData = getExtensionData(ExtensionType.ImmutableOwner, account.tlvData);
15+
if (extensionData !== null) {
16+
return ImmutableOwnerLayout.decode(extensionData);
17+
} else {
18+
return null;
19+
}
20+
}

token/js/src/extensions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './accountType';
22
export * from './extensionType';
33
export * from './mintCloseAuthority';
4+
export * from './immutableOwner';

token/js/src/instructions/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export * from './syncNative'; // 17
2121
export * from './initializeAccount3'; // 18
2222
export * from './initializeMultisig2'; // 19
2323
export * from './initializeMint2'; // 20
24+
export * from './initializeImmutableOwner'; // 22
2425
export * from './initializeMintCloseAuthority'; // 23
2526
export * from './createNativeMint'; // 29
2627

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { struct, u8 } from '@solana/buffer-layout';
2+
import { AccountMeta, PublicKey, TransactionInstruction } from '@solana/web3.js';
3+
import {
4+
TokenInvalidInstructionDataError,
5+
TokenInvalidInstructionKeysError,
6+
TokenInvalidInstructionProgramError,
7+
TokenInvalidInstructionTypeError,
8+
} from '../errors';
9+
import { TokenInstruction } from './types';
10+
11+
/** Deserialized instruction for the initiation of an immutable owner account */
12+
export interface InitializeImmutableOwnerInstructionData {
13+
instruction: TokenInstruction.InitializeImmutableOwner;
14+
}
15+
16+
/** The struct that represents the instruction data as it is read by the program */
17+
export const initializeImmutableOwnerInstructionData = struct<InitializeImmutableOwnerInstructionData>([
18+
u8('instruction'),
19+
]);
20+
21+
/**
22+
* Construct an InitializeImmutableOwner instruction
23+
*
24+
* @param account Immutable Owner Account
25+
* @param programId SPL Token program account
26+
*
27+
* @return Instruction to add to a transaction
28+
*/
29+
export function createInitializeImmutableOwnerInstruction(
30+
account: PublicKey,
31+
programId: PublicKey
32+
): TransactionInstruction {
33+
const keys = [{ pubkey: account, isSigner: false, isWritable: true }];
34+
35+
const data = Buffer.alloc(initializeImmutableOwnerInstructionData.span);
36+
initializeImmutableOwnerInstructionData.encode(
37+
{
38+
instruction: TokenInstruction.InitializeImmutableOwner,
39+
},
40+
data
41+
);
42+
43+
return new TransactionInstruction({ keys, programId, data });
44+
}
45+
46+
/** A decoded, valid InitializeImmutableOwner instruction */
47+
export interface DecodedInitializeImmutableOwnerInstruction {
48+
programId: PublicKey;
49+
keys: {
50+
account: AccountMeta;
51+
};
52+
data: {
53+
instruction: TokenInstruction.InitializeImmutableOwner;
54+
};
55+
}
56+
57+
/**
58+
* Decode an InitializeImmutableOwner instruction and validate it
59+
*
60+
* @param instruction InitializeImmutableOwner instruction to decode
61+
* @param programId SPL Token program account
62+
*
63+
* @return Decoded, valid instruction
64+
*/
65+
export function decodeInitializeImmutableOwnerInstruction(
66+
instruction: TransactionInstruction,
67+
programId: PublicKey
68+
): DecodedInitializeImmutableOwnerInstruction {
69+
if (!instruction.programId.equals(programId)) throw new TokenInvalidInstructionProgramError();
70+
if (instruction.data.length !== initializeImmutableOwnerInstructionData.span)
71+
throw new TokenInvalidInstructionDataError();
72+
73+
const {
74+
keys: { account },
75+
data,
76+
} = decodeInitializeImmutableOwnerInstructionUnchecked(instruction);
77+
if (data.instruction !== TokenInstruction.InitializeImmutableOwner) throw new TokenInvalidInstructionTypeError();
78+
if (!account) throw new TokenInvalidInstructionKeysError();
79+
80+
return {
81+
programId,
82+
keys: {
83+
account,
84+
},
85+
data,
86+
};
87+
}
88+
89+
/** A decoded, non-validated InitializeImmutableOwner instruction */
90+
export interface DecodedInitializeImmutableOwnerInstructionUnchecked {
91+
programId: PublicKey;
92+
keys: {
93+
account: AccountMeta | undefined;
94+
};
95+
data: {
96+
instruction: number;
97+
};
98+
}
99+
100+
/**
101+
* Decode an InitializeImmutableOwner instruction without validating it
102+
*
103+
* @param instruction Transaction instruction to decode
104+
*
105+
* @return Decoded, non-validated instruction
106+
*/
107+
export function decodeInitializeImmutableOwnerInstructionUnchecked({
108+
programId,
109+
keys: [account],
110+
data,
111+
}: TransactionInstruction): DecodedInitializeImmutableOwnerInstructionUnchecked {
112+
const { instruction } = initializeImmutableOwnerInstructionData.decode(data);
113+
114+
return {
115+
programId,
116+
keys: {
117+
account: account,
118+
},
119+
data: {
120+
instruction,
121+
},
122+
};
123+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import chai, { expect } from 'chai';
2+
import chaiAsPromised from 'chai-as-promised';
3+
chai.use(chaiAsPromised);
4+
5+
import {
6+
Connection,
7+
Keypair,
8+
PublicKey,
9+
Signer,
10+
SystemProgram,
11+
Transaction,
12+
sendAndConfirmTransaction,
13+
} from '@solana/web3.js';
14+
15+
import {
16+
AuthorityType,
17+
setAuthority,
18+
ExtensionType,
19+
createInitializeImmutableOwnerInstruction,
20+
createInitializeAccountInstruction,
21+
createMint,
22+
getAccountLen,
23+
} from '../../src';
24+
25+
import { TEST_PROGRAM_ID, newAccountWithLamports, getConnection } from '../common';
26+
const TEST_TOKEN_DECIMALS = 2;
27+
const EXTENSIONS = [ExtensionType.ImmutableOwner];
28+
describe('immutableOwner', () => {
29+
let connection: Connection;
30+
let payer: Signer;
31+
let owner: Keypair;
32+
let account: PublicKey;
33+
let mint: PublicKey;
34+
before(async () => {
35+
connection = await getConnection();
36+
payer = await newAccountWithLamports(connection, 1000000000);
37+
});
38+
beforeEach(async () => {
39+
const mintAuthority = Keypair.generate();
40+
const mintKeypair = Keypair.generate();
41+
mint = await createMint(
42+
connection,
43+
payer,
44+
mintAuthority.publicKey,
45+
mintAuthority.publicKey,
46+
TEST_TOKEN_DECIMALS,
47+
mintKeypair,
48+
undefined,
49+
TEST_PROGRAM_ID
50+
);
51+
owner = Keypair.generate();
52+
const accountLen = getAccountLen(EXTENSIONS);
53+
const lamports = await connection.getMinimumBalanceForRentExemption(accountLen);
54+
const accountKeypair = Keypair.generate();
55+
account = accountKeypair.publicKey;
56+
const transaction = new Transaction().add(
57+
SystemProgram.createAccount({
58+
fromPubkey: payer.publicKey,
59+
newAccountPubkey: account,
60+
space: accountLen,
61+
lamports,
62+
programId: TEST_PROGRAM_ID,
63+
}),
64+
createInitializeImmutableOwnerInstruction(account, TEST_PROGRAM_ID),
65+
createInitializeAccountInstruction(account, mint, owner.publicKey, TEST_PROGRAM_ID)
66+
);
67+
await sendAndConfirmTransaction(connection, transaction, [payer, accountKeypair], undefined);
68+
});
69+
it('AccountOwner', async () => {
70+
const newOwner = Keypair.generate();
71+
expect(
72+
setAuthority(
73+
connection,
74+
payer,
75+
account,
76+
newOwner,
77+
AuthorityType.AccountOwner,
78+
owner.publicKey,
79+
[],
80+
undefined,
81+
TEST_PROGRAM_ID
82+
)
83+
).to.be.rejected;
84+
});
85+
});

0 commit comments

Comments
 (0)