Skip to content

Commit

Permalink
add burn
Browse files Browse the repository at this point in the history
  • Loading branch information
SwenSchaeferjohann committed Jan 8, 2025
1 parent 1332c2f commit 8b9ac24
Show file tree
Hide file tree
Showing 10 changed files with 684 additions and 6 deletions.
1 change: 1 addition & 0 deletions js/compressed-token/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"test:e2e:compress": "pnpm test-validator && vitest run tests/e2e/compress.test.ts --reporter=verbose",
"test:e2e:compress-spl-token-account": "pnpm test-validator && vitest run tests/e2e/compress-spl-token-account.test.ts --reporter=verbose",
"test:e2e:decompress": "pnpm test-validator && vitest run tests/e2e/decompress.test.ts --reporter=verbose",
"test:e2e:burn": "pnpm test-validator && vitest run tests/e2e/burn.test.ts --reporter=verbose",
"test:e2e:rpc-token-interop": "pnpm test-validator && vitest run tests/e2e/rpc-token-interop.test.ts --reporter=verbose",
"test:e2e:all": "pnpm test-validator && vitest run tests/e2e/create-mint.test.ts && vitest run tests/e2e/mint-to.test.ts && vitest run tests/e2e/transfer.test.ts && vitest run tests/e2e/compress.test.ts && vitest run tests/e2e/compress-spl-token-account.test.ts && vitest run tests/e2e/decompress.test.ts && vitest run tests/e2e/create-token-pool.test.ts && vitest run tests/e2e/approve-and-mint-to.test.ts && vitest run tests/e2e/rpc-token-interop.test.ts && vitest run tests/e2e/layout.test.ts",
"pull-idl": "../../scripts/push-compressed-token-idl.sh",
Expand Down
86 changes: 86 additions & 0 deletions js/compressed-token/src/actions/burn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
ComputeBudgetProgram,
ConfirmOptions,
PublicKey,
Signer,
TransactionSignature,
} from '@solana/web3.js';
import {
bn,
sendAndConfirmTx,
buildAndSignTx,
Rpc,
dedupeSigner,
} from '@lightprotocol/stateless.js';

import BN from 'bn.js';

import { CompressedTokenProgram } from '../program';
import { selectMinCompressedTokenAccountsForTransfer } from './transfer';

/**
* Decompress compressed tokens
*
* @param rpc Rpc to use
* @param payer Payer of the transaction fees
* @param mint Mint of the compressed token
* @param amount Number of tokens to burn
* @param owner Owner of the compressed tokens
* @param merkleTree State tree account that any change compressed tokens should be
* inserted into. Defaults to a default state tree
* account.
* @param confirmOptions Options for confirming the transaction
* @param tokenProgramId Optional: The token program ID. Default: SPL Token Program ID
*
* @return Signature of the confirmed transaction
*/
export async function burn(
rpc: Rpc,
payer: Signer,
mint: PublicKey,
amount: number | BN,
owner: Signer,
merkleTree?: PublicKey,
confirmOptions?: ConfirmOptions,
tokenProgramId?: PublicKey,
): Promise<TransactionSignature> {
amount = bn(amount);

const compressedTokenAccounts = await rpc.getCompressedTokenAccountsByOwner(
owner.publicKey,
{
mint,
},
);

/// TODO: consider using a different selection algorithm
const [inputAccounts] = selectMinCompressedTokenAccountsForTransfer(
compressedTokenAccounts.items,
amount,
);

const proof = await rpc.getValidityProof(
inputAccounts.map(account => bn(account.compressedAccount.hash)),
);

const ix = await CompressedTokenProgram.burn({
payer: payer.publicKey,
inputCompressedTokenAccounts: inputAccounts,
amount,
outputStateTree: merkleTree,
recentInputStateRootIndices: proof.rootIndices,
recentValidityProof: proof.compressedProof,
tokenProgramId,
});

const { blockhash } = await rpc.getLatestBlockhash();
const additionalSigners = dedupeSigner(payer, [owner]);
const signedTx = buildAndSignTx(
[ComputeBudgetProgram.setComputeUnitLimit({ units: 500_000 }), ix],
payer,
blockhash,
additionalSigners,
);
const txId = await sendAndConfirmTx(rpc, signedTx, confirmOptions);
return txId;
}
1 change: 1 addition & 0 deletions js/compressed-token/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export * from './create-token-pool';
export * from './transfer';
export * from './create-token-program-lookup-table';
export * from './compress-spl-token-account';
export * from './burn';
4 changes: 4 additions & 0 deletions js/compressed-token/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ export const TRANSFER_DISCRIMINATOR = Buffer.from([
export const COMPRESS_SPL_TOKEN_ACCOUNT_DISCRIMINATOR = Buffer.from([
112, 230, 105, 101, 145, 202, 157, 97,
]);

export const BURN_DISCRIMINATOR = Buffer.from([
116, 110, 29, 56, 107, 219, 42, 93,
]);
92 changes: 92 additions & 0 deletions js/compressed-token/src/idl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,52 @@ export type LightCompressedToken = {
];
};
},
{
name: 'BurnInstructionData';
type: {
kind: 'struct';
fields: [
{
name: 'proof';
type: {
defined: 'CompressedProof';
};
},
{
name: 'inputTokenDataWithContext';
type: {
vec: {
defined: 'InputTokenDataWithContext';
};
};
},
{
name: 'cpiContext';
type: {
option: {
defined: 'CompressedCpiContext';
};
};
},
{
name: 'burnAmount';
type: 'u64';
},
{
name: 'changeAccountMerkleTreeIndex';
type: 'u8';
},
{
name: 'delegatedTransfer';
type: {
option: {
defined: 'DelegatedTransfer';
};
};
},
];
};
},
{
name: 'DelegatedTransfer';
docs: [
Expand Down Expand Up @@ -2827,6 +2873,52 @@ export const IDL: LightCompressedToken = {
],
},
},
{
name: 'BurnInstructionData',
type: {
kind: 'struct',
fields: [
{
name: 'proof',
type: {
defined: 'CompressedProof',
},
},
{
name: 'inputTokenDataWithContext',
type: {
vec: {
defined: 'InputTokenDataWithContext',
},
},
},
{
name: 'cpiContext',
type: {
option: {
defined: 'CompressedCpiContext',
},
},
},
{
name: 'burnAmount',
type: 'u64',
},
{
name: 'changeAccountMerkleTreeIndex',
type: 'u8',
},
{
name: 'delegatedTransfer',
type: {
option: {
defined: 'DelegatedTransfer',
},
},
},
],
},
},
{
name: 'DelegatedTransfer',
docs: [
Expand Down
85 changes: 80 additions & 5 deletions js/compressed-token/src/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,22 @@ import { Buffer } from 'buffer';
import { AccountMeta, PublicKey } from '@solana/web3.js';
import { CompressedTokenProgram } from './program';
import {
BurnInstructionData,
CompressedTokenInstructionDataTransfer,
CompressSplTokenAccountInstructionData,
MintToInstructionData,
} from './types';
import {
BURN_DISCRIMINATOR,
COMPRESS_SPL_TOKEN_ACCOUNT_DISCRIMINATOR,
MINT_TO_DISCRIMINATOR,
TRANSFER_DISCRIMINATOR,
} from './constants';

const CompressedProofLayout = struct([
array(u8(), 32, 'a'),
array(u8(), 64, 'b'),
array(u8(), 32, 'c'),
]);
export const CompressedProofLayout = struct(
[array(u8(), 32, 'a'), array(u8(), 64, 'b'), array(u8(), 32, 'c')],
'proof',
);

const PackedTokenTransferOutputDataLayout = struct([
publicKey('owner'),
Expand Down Expand Up @@ -69,6 +70,14 @@ export const CpiContextLayout = struct([
u8('cpiContextAccountIndex'),
]);

export const BurnInstructionDataLayout = struct([
CompressedProofLayout,
vec(InputTokenDataWithContextLayout, 'inputTokenDataWithContext'),
option(CpiContextLayout, 'cpiContext'),
u64('burnAmount'),
u8('changeAccountMerkleTreeIndex'),
option(DelegatedTransferLayout, 'delegatedTransfer'),
]);
export const CompressedTokenInstructionDataTransferLayout = struct([
option(CompressedProofLayout, 'proof'),
publicKey('mint'),
Expand All @@ -93,6 +102,23 @@ export const compressSplTokenAccountInstructionDataLayout = struct([
option(CpiContextLayout, 'cpiContext'),
]);

export function encodeBurnInstructionData(data: BurnInstructionData): Buffer {
const buffer = Buffer.alloc(1000);
const len = BurnInstructionDataLayout.encode(data, buffer);
const lengthBuffer = Buffer.alloc(4);
lengthBuffer.writeUInt32LE(len, 0);
return Buffer.concat([
BURN_DISCRIMINATOR,
lengthBuffer,
buffer.slice(0, len),
]);
}
export function decodeBurnInstructionData(buffer: Buffer): BurnInstructionData {
return BurnInstructionDataLayout.decode(
buffer.slice(BURN_DISCRIMINATOR.length + 4),
) as BurnInstructionData;
}

export function encodeMintToInstructionData(
data: MintToInstructionData,
): Buffer {
Expand Down Expand Up @@ -218,6 +244,11 @@ export type revokeAccountsLayoutParams = approveAccountsLayoutParams;
export type freezeAccountsLayoutParams = BaseAccountsLayoutParams & {
mint: PublicKey;
};
export type burnAccountsLayoutParams = BaseAccountsLayoutParams & {
mint: PublicKey;
tokenPoolPda: PublicKey;
tokenProgram: PublicKey;
};
export type thawAccountsLayoutParams = freezeAccountsLayoutParams;

export const createTokenPoolAccountsLayout = (
Expand Down Expand Up @@ -355,6 +386,50 @@ export const transferAccountsLayout = (
return accountsList;
};

export const burnAccountsLayout = (
accounts: burnAccountsLayoutParams,
): AccountMeta[] => {
const {
feePayer,
authority,
cpiAuthorityPda,
mint,
tokenPoolPda,
tokenProgram,
lightSystemProgram,
registeredProgramPda,
noopProgram,
accountCompressionAuthority,
accountCompressionProgram,
selfProgram,
systemProgram,
} = accounts;

return [
{ pubkey: feePayer, isWritable: true, isSigner: true },
{ pubkey: authority, isWritable: false, isSigner: true },
{ pubkey: cpiAuthorityPda, isWritable: false, isSigner: false },
{ pubkey: mint, isWritable: true, isSigner: false },
{ pubkey: tokenPoolPda, isWritable: true, isSigner: false },
{ pubkey: tokenProgram, isWritable: false, isSigner: false },
{ pubkey: lightSystemProgram, isWritable: false, isSigner: false },
{ pubkey: registeredProgramPda, isWritable: false, isSigner: false },
{ pubkey: noopProgram, isWritable: false, isSigner: false },
{
pubkey: accountCompressionAuthority,
isWritable: false,
isSigner: false,
},
{
pubkey: accountCompressionProgram,
isWritable: false,
isSigner: false,
},
{ pubkey: selfProgram, isWritable: false, isSigner: false },
{ pubkey: systemProgram, isWritable: false, isSigner: false },
];
};

// TODO: use this layout for approve/revoke/freeze/thaw once we add them
// export const approveAccountsLayout = (
// accounts: approveAccountsLayoutParams,
Expand Down
Loading

0 comments on commit 8b9ac24

Please sign in to comment.