Skip to content

Commit b5f5b2e

Browse files
committed
add confidential transfer InitializeMint and UpdateMint instructions to js-legacy client
1 parent 7644591 commit b5f5b2e

File tree

9 files changed

+425
-2
lines changed

9 files changed

+425
-2
lines changed

clients/js-legacy/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"@solana/buffer-layout-utils": "^0.2.0",
5757
"@solana/spl-token-group": "^0.0.7",
5858
"@solana/spl-token-metadata": "^0.1.6",
59+
"@solana/zk-sdk": "0.1.0",
5960
"buffer": "^6.0.3"
6061
},
6162
"devDependencies": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { ConfirmOptions, Connection, PublicKey, Signer, TransactionSignature } from '@solana/web3.js';
2+
import { sendAndConfirmTransaction, Transaction } from '@solana/web3.js';
3+
import { TOKEN_2022_PROGRAM_ID } from '../../constants.js';
4+
import { createConfidentialTransferUpdateMintInstruction } from './instructions.js';
5+
import type { PodElGamalPubkey } from '@solana/zk-sdk';
6+
7+
/**
8+
* Update confidential transfer mint
9+
*
10+
* @param connection Connection to use
11+
* @param payer Payer of the transaction fees
12+
* @param mint The token mint
13+
* @param autoApproveNewAccounts New auto-approve account policy
14+
* @param auditorElGamalPubkey New Auditor ElGamal public key
15+
* @param confirmOptions Options for confirming the transaction
16+
* @param programId SPL Token program account
17+
*
18+
* @return Signature of the confirmed transaction
19+
*/
20+
export async function updateMint(
21+
connection: Connection,
22+
payer: Signer,
23+
mint: PublicKey,
24+
autoApproveNewAccounts: boolean,
25+
auditorElGamalPubkey: PodElGamalPubkey | null,
26+
authority: Signer,
27+
confirmOptions?: ConfirmOptions,
28+
programId = TOKEN_2022_PROGRAM_ID,
29+
): Promise<TransactionSignature> {
30+
const transaction = new Transaction().add(
31+
createConfidentialTransferUpdateMintInstruction(
32+
mint,
33+
authority.publicKey,
34+
autoApproveNewAccounts,
35+
auditorElGamalPubkey,
36+
programId,
37+
),
38+
);
39+
40+
return await sendAndConfirmTransaction(connection, transaction, [payer, authority], confirmOptions);
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { blob } from '@solana/buffer-layout';
2+
import type { Layout } from '@solana/buffer-layout';
3+
import { encodeDecode } from '@solana/buffer-layout-utils';
4+
import { PodElGamalPubkey, PodElGamalCiphertext, PodAeCiphertext } from '@solana/zk-sdk';
5+
6+
export const elgamalPublicKey = (property?: string): Layout<PodElGamalPubkey> => {
7+
const layout = blob(32, property);
8+
const { encode, decode } = encodeDecode(layout);
9+
10+
const elgamalPublicKeyLayout = layout as Layout<unknown> as Layout<PodElGamalPubkey>;
11+
12+
elgamalPublicKeyLayout.decode = (buffer: Buffer, offset: number) => {
13+
const src = decode(buffer, offset);
14+
return new PodElGamalPubkey(src);
15+
};
16+
17+
elgamalPublicKeyLayout.encode = (elgamalPublicKey: PodElGamalPubkey, buffer: Buffer, offset: number) => {
18+
const src = elgamalPublicKey.toBytes();
19+
return encode(src, buffer, offset);
20+
};
21+
22+
return elgamalPublicKeyLayout;
23+
};
24+
25+
export const elgamalCiphertext = (property?: string): Layout<PodElGamalCiphertext> => {
26+
const layout = blob(32, property);
27+
const { encode, decode } = encodeDecode(layout);
28+
29+
const elgamalCiphertextLayout = layout as Layout<unknown> as Layout<PodElGamalCiphertext>;
30+
31+
elgamalCiphertextLayout.decode = (buffer: Buffer, offset: number) => {
32+
const src = decode(buffer, offset);
33+
return new PodElGamalCiphertext(src);
34+
};
35+
36+
elgamalCiphertextLayout.encode = (elgamalCiphertext: PodElGamalCiphertext, buffer: Buffer, offset: number) => {
37+
const src = elgamalCiphertext.toBytes();
38+
return encode(src, buffer, offset);
39+
};
40+
41+
return elgamalCiphertextLayout;
42+
};
43+
44+
export const aeCiphertext = (property?: string): Layout<PodAeCiphertext> => {
45+
const layout = blob(36, property);
46+
const { encode, decode } = encodeDecode(layout);
47+
48+
const aeCiphertextLayout = layout as Layout<unknown> as Layout<PodAeCiphertext>;
49+
50+
aeCiphertextLayout.decode = (buffer: Buffer, offset: number) => {
51+
const src = decode(buffer, offset);
52+
return new PodAeCiphertext(src);
53+
};
54+
55+
aeCiphertextLayout.encode = (aeCiphertext: PodAeCiphertext, buffer: Buffer, offset: number) => {
56+
const src = aeCiphertext.toBytes();
57+
return encode(src, buffer, offset);
58+
};
59+
60+
return aeCiphertextLayout;
61+
};
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';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import { struct, u8 } from '@solana/buffer-layout';
2+
import { bool, publicKey } from '@solana/buffer-layout-utils';
3+
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
4+
import { programSupportsExtensions, TOKEN_2022_PROGRAM_ID } from '../../constants.js';
5+
import { TokenUnsupportedInstructionError } from '../../errors.js';
6+
import { TokenInstruction } from '../../instructions/types.js';
7+
import { elgamalPublicKey } from './elgamal.js';
8+
import { PodElGamalPubkey } from '@solana/zk-sdk';
9+
10+
export enum ConfidentialTransferInstruction {
11+
InitializeMint = 0,
12+
UpdateMint = 1,
13+
}
14+
15+
export interface InitializeMintData {
16+
instruction: TokenInstruction.ConfidentialTransferExtension;
17+
confidentialTransferInstruction: ConfidentialTransferInstruction.InitializeMint;
18+
confidentialTransferMintAuthority: PublicKey | null;
19+
autoApproveNewAccounts: boolean;
20+
auditorElGamalPubkey: PodElGamalPubkey | null;
21+
}
22+
23+
export const initializeMintData = struct<InitializeMintData>([
24+
u8('instruction'),
25+
u8('confidentialTransferInstruction'),
26+
publicKey('confidentialTransferMintAuthority'),
27+
bool('autoApproveNewAccounts'),
28+
elgamalPublicKey('auditorElGamalPubkey'),
29+
]);
30+
31+
/**
32+
* Construct an InitializeConfidentialTransferInitializeMint instruction
33+
*
34+
* @param mint Token mint account
35+
* @param confidentialTransferMintAuthority Authority that can update confidential transfer mint
36+
* @param autoApproveNewAccounts Auto-approve account policy
37+
* @param auditorElGamalPubkey Optional auditor ElGamal public key
38+
* @param programId SPL Token program account
39+
*
40+
* @return Instruction to add to a transaction
41+
*/
42+
export function createConfidentialTransferInitializeMintInstruction(
43+
mint: PublicKey,
44+
confidentialTransferMintAuthority: PublicKey | null,
45+
autoApproveNewAccounts: boolean,
46+
auditorElGamalPubkey: PodElGamalPubkey | null,
47+
programId = TOKEN_2022_PROGRAM_ID,
48+
): TransactionInstruction {
49+
if (!programSupportsExtensions(programId)) {
50+
throw new TokenUnsupportedInstructionError();
51+
}
52+
const keys = [{ pubkey: mint, isSigner: false, isWritable: true }];
53+
54+
const data = Buffer.alloc(initializeMintData.span);
55+
56+
initializeMintData.encode(
57+
{
58+
instruction: TokenInstruction.ConfidentialTransferExtension,
59+
confidentialTransferInstruction: ConfidentialTransferInstruction.InitializeMint,
60+
confidentialTransferMintAuthority: confidentialTransferMintAuthority ?? PublicKey.default,
61+
autoApproveNewAccounts: autoApproveNewAccounts,
62+
auditorElGamalPubkey: auditorElGamalPubkey ?? PodElGamalPubkey.zeroed(),
63+
},
64+
data,
65+
);
66+
67+
return new TransactionInstruction({ keys, programId, data });
68+
}
69+
70+
export interface UpdateMintData {
71+
instruction: TokenInstruction.ConfidentialTransferExtension;
72+
confidentialTransferInstruction: ConfidentialTransferInstruction.UpdateMint;
73+
autoApproveNewAccounts: boolean;
74+
auditorElGamalPubkey: PodElGamalPubkey | null;
75+
}
76+
77+
export const updateMintData = struct<UpdateMintData>([
78+
u8('instruction'),
79+
u8('confidentialTransferInstruction'),
80+
bool('autoApproveNewAccounts'),
81+
elgamalPublicKey('auditorElGamalPubkey'),
82+
]);
83+
84+
/**
85+
* Construct an UpdateMint instruction
86+
*
87+
* @param mint Token mint account
88+
* @param confidentialTransferMintAuthority Authority that can update confidential transfer mint
89+
* @param autoApproveNewAccounts New auto-approve account policy
90+
* @param auditorElGamalPubkey New auditor ElGamal public key
91+
* @param programId SPL Token program account
92+
*
93+
* @return Instruction to add to a transaction
94+
*/
95+
export function createConfidentialTransferUpdateMintInstruction(
96+
mint: PublicKey,
97+
confidentialTransferMintAuthority: PublicKey,
98+
autoApproveNewAccounts: boolean,
99+
auditorElGamalPubkey: PodElGamalPubkey | null,
100+
programId = TOKEN_2022_PROGRAM_ID,
101+
): TransactionInstruction {
102+
if (!programSupportsExtensions(programId)) {
103+
throw new TokenUnsupportedInstructionError();
104+
}
105+
const keys = [
106+
{ pubkey: mint, isSigner: false, isWritable: true },
107+
{ pubkey: confidentialTransferMintAuthority, isSigner: true, isWritable: false },
108+
];
109+
110+
const data = Buffer.alloc(updateMintData.span);
111+
updateMintData.encode(
112+
{
113+
instruction: TokenInstruction.ConfidentialTransferExtension,
114+
confidentialTransferInstruction: ConfidentialTransferInstruction.UpdateMint,
115+
autoApproveNewAccounts: autoApproveNewAccounts,
116+
auditorElGamalPubkey: auditorElGamalPubkey ?? PodElGamalPubkey.zeroed(),
117+
},
118+
data,
119+
);
120+
121+
return new TransactionInstruction({ keys, programId, data });
122+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { struct } from '@solana/buffer-layout';
2+
import { publicKey, bool, u64 } from '@solana/buffer-layout-utils';
3+
import type { PublicKey } from '@solana/web3.js';
4+
import type { Mint } from '../../state/mint.js';
5+
import type { Account } from '../../state/account.js';
6+
import { ExtensionType, getExtensionData } from '../extensionType.js';
7+
import type { PodElGamalPubkey, PodElGamalCiphertext, PodAeCiphertext } from '@solana/zk-sdk';
8+
import { elgamalPublicKey, elgamalCiphertext, aeCiphertext } from './elgamal.js';
9+
10+
/** ConfidentialTransferMint as stored by the program */
11+
export interface ConfidentialTransferMint {
12+
confidentialTransferMintAuthority: PublicKey;
13+
autoApproveNewAccounts: boolean;
14+
auditorElGamalPubkey: PodElGamalPubkey;
15+
}
16+
17+
/** Buffer layout for de/serializing a confidential transfer mint */
18+
export const ConfidentialTransferMintLayout = struct<ConfidentialTransferMint>([
19+
publicKey('confidentialTransferMintAuthority'),
20+
bool('autoApproveNewAccounts'),
21+
elgamalPublicKey('auditorElGamalPubkey'),
22+
]);
23+
24+
export const CONFIDENTIAL_TRANSFER_MINT_SIZE = ConfidentialTransferMintLayout.span;
25+
26+
export function getConfidentialTransferMint(mint: Mint): ConfidentialTransferMint | null {
27+
const extensionData = getExtensionData(ExtensionType.ConfidentialTransferMint, mint.tlvData);
28+
if (extensionData !== null) {
29+
return ConfidentialTransferMintLayout.decode(extensionData);
30+
} else {
31+
return null;
32+
}
33+
}
34+
35+
/** ConfidentialTransferAccount as stored by the program */
36+
export interface ConfidentialTransferAccount {
37+
approved: boolean;
38+
elgamalPubkey: PodElGamalPubkey;
39+
pendingBalanceLo: PodElGamalCiphertext;
40+
pendingBalanceHi: PodElGamalCiphertext;
41+
availableBalance: PodElGamalCiphertext;
42+
decryptableAvailableBalance: PodAeCiphertext;
43+
allowConfidentialCredits: boolean;
44+
allowNonConfidentialCredits: boolean;
45+
pendingBalanceCreditCounter: bigint;
46+
maximumPendingBalanceCreditCounter: bigint;
47+
expectedPendingBalanceCreditCounter: bigint;
48+
actualPendingBalanceCreditCounter: bigint;
49+
}
50+
51+
/** Buffer layout for de/serializing a confidential transfer account */
52+
export const ConfidentialTransferAccountLayout = struct<ConfidentialTransferAccount>([
53+
bool('approved'),
54+
elgamalPublicKey('elgamalPubkey'),
55+
elgamalCiphertext('pendingBalanceLo'),
56+
elgamalCiphertext('pendingBalanceLo'),
57+
elgamalCiphertext('availableBalance'),
58+
aeCiphertext('decryptableAvailableBalance'),
59+
bool('allowConfidentialCredits'),
60+
bool('allowNonConfidentialCredits'),
61+
u64('pendingBalanceCreditCounter'),
62+
u64('maximumPendingBalanceCreditCounter'),
63+
u64('expectedPendingBalanceCreditCounter'),
64+
u64('actualPendingBalanceCreditCounter'),
65+
]);
66+
67+
export const CONFIDENTIAL_TRANSFER_ACCOUNT_SIZE = ConfidentialTransferAccountLayout.span;
68+
69+
export function getConfidentialTransferAccount(account: Account): ConfidentialTransferAccount | null {
70+
const extensionData = getExtensionData(ExtensionType.ConfidentialTransferAccount, account.tlvData);
71+
if (extensionData !== null) {
72+
return ConfidentialTransferAccountLayout.decode(extensionData);
73+
} else {
74+
return null;
75+
}
76+
}

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { MINT_SIZE, unpackMint } from '../state/mint.js';
66
import { MULTISIG_SIZE } from '../state/multisig.js';
77
import { ACCOUNT_TYPE_SIZE } from './accountType.js';
88
import { CPI_GUARD_SIZE } from './cpiGuard/index.js';
9+
import { CONFIDENTIAL_TRANSFER_MINT_SIZE, CONFIDENTIAL_TRANSFER_ACCOUNT_SIZE } from './confidentialTransfer/index.js';
910
import { DEFAULT_ACCOUNT_STATE_SIZE } from './defaultAccountState/index.js';
1011
import { TOKEN_GROUP_SIZE, TOKEN_GROUP_MEMBER_SIZE } from './tokenGroup/index.js';
1112
import { GROUP_MEMBER_POINTER_SIZE } from './groupMemberPointer/state.js';
@@ -78,9 +79,9 @@ export function getTypeLen(e: ExtensionType): number {
7879
case ExtensionType.MintCloseAuthority:
7980
return MINT_CLOSE_AUTHORITY_SIZE;
8081
case ExtensionType.ConfidentialTransferMint:
81-
return 65;
82+
return CONFIDENTIAL_TRANSFER_MINT_SIZE;
8283
case ExtensionType.ConfidentialTransferAccount:
83-
return 295;
84+
return CONFIDENTIAL_TRANSFER_ACCOUNT_SIZE;
8485
case ExtensionType.CpiGuard:
8586
return CPI_GUARD_SIZE;
8687
case ExtensionType.DefaultAccountState:

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

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './accountType.js';
22
export * from './cpiGuard/index.js';
3+
export * from './confidentialTransfer/index.js';
34
export * from './defaultAccountState/index.js';
45
export * from './extensionType.js';
56
export * from './groupMemberPointer/index.js';

0 commit comments

Comments
 (0)