Skip to content

Commit

Permalink
feat: add js support for arbitrary state trees (#1490)
Browse files Browse the repository at this point in the history
  • Loading branch information
SwenSchaeferjohann authored Jan 22, 2025
1 parent 391c5ee commit d10db3c
Show file tree
Hide file tree
Showing 48 changed files with 1,419 additions and 125 deletions.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions cli/src/commands/approve-and-mint-to/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ import {
CustomLoader,
defaultSolanaWalletKeypair,
generateSolanaTransactionURL,
getSolanaRpcUrl,
rpc,
} from "../../utils/utils";
import { getKeypairFromFile } from "@solana-developers/helpers";
import { Keypair, PublicKey } from "@solana/web3.js";
import { approveAndMintTo } from "@lightprotocol/compressed-token";
import { getTestRpc } from "@lightprotocol/stateless.js";
import { WasmFactory } from "@lightprotocol/hasher.rs";

class ApproveAndMintToCommand extends Command {
static summary =
Expand Down
3 changes: 0 additions & 3 deletions cli/src/commands/compress-spl/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ import {
CustomLoader,
defaultSolanaWalletKeypair,
generateSolanaTransactionURL,
getSolanaRpcUrl,
rpc,
} from "../../utils/utils";
import { PublicKey } from "@solana/web3.js";
import { getTestRpc } from "@lightprotocol/stateless.js";
import { compress } from "@lightprotocol/compressed-token";
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
import { WasmFactory } from "@lightprotocol/hasher.rs";
import { CompressedTokenProgram } from "@lightprotocol/compressed-token";

/// TODO: add ability to compress from non-fee payer
Expand Down
4 changes: 1 addition & 3 deletions cli/src/commands/decompress-sol/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ import {
CustomLoader,
defaultSolanaWalletKeypair,
generateSolanaTransactionURL,
getSolanaRpcUrl,
rpc,
} from "../../utils/utils";
import { PublicKey } from "@solana/web3.js";
import { decompress, getTestRpc } from "@lightprotocol/stateless.js";
import { WasmFactory } from "@lightprotocol/hasher.rs";
import { decompress } from "@lightprotocol/stateless.js";

class DecompressSolCommand extends Command {
static summary = "Decompress SOL.";
Expand Down
5 changes: 3 additions & 2 deletions js/compressed-token/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lightprotocol/compressed-token",
"version": "0.18.0",
"version": "0.20.0",
"description": "JS client to interact with the compressed-token program",
"sideEffects": false,
"main": "dist/cjs/node/index.cjs",
Expand Down Expand Up @@ -93,7 +93,8 @@
"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: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",
"test:e2e:rpc-multi-trees": "pnpm test-validator && vitest run tests/e2e/rpc-multi-trees.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/rpc-multi-trees.test.ts && vitest run tests/e2e/layout.test.ts",
"pull-idl": "../../scripts/push-compressed-token-idl.sh",
"build": "rimraf dist && pnpm build:bundle",
"build:bundle": "rollup -c",
Expand Down
9 changes: 8 additions & 1 deletion js/compressed-token/src/actions/approve-and-mint-to.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
buildAndSignTx,
Rpc,
dedupeSigner,
pickRandomTreeAndQueue,
} from '@lightprotocol/stateless.js';
import { CompressedTokenProgram } from '../program';
import { getOrCreateAssociatedTokenAccount } from '@solana/spl-token';
Expand All @@ -25,7 +26,7 @@ import { getOrCreateAssociatedTokenAccount } from '@solana/spl-token';
* @param authority Minting authority
* @param amount Amount to mint
* @param merkleTree State tree account that the compressed tokens should be
* part of. Defaults to the default state tree account.
* part of. Defaults to random public state tree account.
* @param confirmOptions Options for confirming the transaction
*
* @return Signature of the confirmed transaction
Expand Down Expand Up @@ -56,6 +57,12 @@ export async function approveAndMintTo(
tokenProgramId,
);

if (!merkleTree) {
const stateTreeInfo = await rpc.getCachedActiveStateTreeInfo();
const { tree } = pickRandomTreeAndQueue(stateTreeInfo);
merkleTree = tree;
}

const ixs = await CompressedTokenProgram.approveAndMintTo({
feePayer: payer.publicKey,
mint,
Expand Down
7 changes: 7 additions & 0 deletions js/compressed-token/src/actions/compress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
buildAndSignTx,
Rpc,
dedupeSigner,
pickRandomTreeAndQueue,
} from '@lightprotocol/stateless.js';

import BN from 'bn.js';
Expand Down Expand Up @@ -50,6 +51,12 @@ export async function compress(
? tokenProgramId
: await CompressedTokenProgram.get_mint_program_id(mint, rpc);

if (!merkleTree) {
const stateTreeInfo = await rpc.getCachedActiveStateTreeInfo();
const { tree } = pickRandomTreeAndQueue(stateTreeInfo);
merkleTree = tree;
}

const compressIx = await CompressedTokenProgram.compress({
payer: payer.publicKey,
owner: owner.publicKey,
Expand Down
21 changes: 16 additions & 5 deletions js/compressed-token/src/actions/create-mint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ import {
* @param decimals Location of the decimal place
* @param keypair Optional keypair, defaulting to a new random one
* @param confirmOptions Options for confirming the transaction
* @param isToken22 Whether to create a Token 2022 mint. Defaults to false.
* @param tokenProgramId Program ID for the token. Defaults to
* TOKEN_PROGRAM_ID. You can pass in a boolean to
* automatically resolve to TOKEN_2022_PROGRAM_ID if
* true, or TOKEN_PROGRAM_ID if false.
* @param freezeAuthority Account that will control freeze and thaw. Defaults to null.
*
* @return Address of the new mint and the transaction signature
*/
Expand All @@ -38,21 +42,28 @@ export async function createMint(
decimals: number,
keypair = Keypair.generate(),
confirmOptions?: ConfirmOptions,
isToken22 = false,
tokenProgramId?: PublicKey | boolean,
freezeAuthority?: PublicKey,
): Promise<{ mint: PublicKey; transactionSignature: TransactionSignature }> {
const rentExemptBalance =
await rpc.getMinimumBalanceForRentExemption(MINT_SIZE);

const tokenProgramId = isToken22 ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
// If true, uses TOKEN_2022_PROGRAM_ID.
// If false or undefined, defaults to TOKEN_PROGRAM_ID.
// Otherwise, uses the provided tokenProgramId.
const resolvedTokenProgramId =
tokenProgramId === true
? TOKEN_2022_PROGRAM_ID
: tokenProgramId || TOKEN_PROGRAM_ID;

const ixs = await CompressedTokenProgram.createMint({
feePayer: payer.publicKey,
mint: keypair.publicKey,
decimals,
authority: mintAuthority,
freezeAuthority: null, // TODO: add feature
freezeAuthority: freezeAuthority || null,
rentExemptBalance,
tokenProgramId,
tokenProgramId: resolvedTokenProgramId,
});

const { blockhash } = await rpc.getLatestBlockhash();
Expand Down
1 change: 0 additions & 1 deletion js/compressed-token/src/actions/decompress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ export async function decompress(
amount: number | BN,
owner: Signer,
toAddress: PublicKey,
/// TODO: allow multiple
merkleTree?: PublicKey,
confirmOptions?: ConfirmOptions,
tokenProgramId?: PublicKey,
Expand Down
7 changes: 7 additions & 0 deletions js/compressed-token/src/actions/mint-to.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
buildAndSignTx,
Rpc,
dedupeSigner,
pickRandomTreeAndQueue,
} from '@lightprotocol/stateless.js';
import { CompressedTokenProgram } from '../program';

Expand Down Expand Up @@ -48,6 +49,12 @@ export async function mintTo(

const additionalSigners = dedupeSigner(payer, [authority]);

if (!merkleTree) {
const stateTreeInfo = await rpc.getCachedActiveStateTreeInfo();
const { tree } = pickRandomTreeAndQueue(stateTreeInfo);
merkleTree = tree;
}

const ix = await CompressedTokenProgram.mintTo({
feePayer: payer.publicKey,
mint,
Expand Down
1 change: 0 additions & 1 deletion js/compressed-token/src/actions/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ export async function transfer(
amount: number | BN,
owner: Signer,
toAddress: PublicKey,
/// TODO: allow multiple
merkleTree?: PublicKey,
confirmOptions?: ConfirmOptions,
): Promise<TransactionSignature> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export type PackCompressedTokenAccountsParams = {
tokenTransferOutputs: TokenTransferOutputData[];
};

// TODO: include owner and lamports in packing.
/**
* Packs Compressed Token Accounts.
*/
Expand Down
1 change: 1 addition & 0 deletions js/compressed-token/src/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ export class CompressedTokenProgram {
'Amount and toPubkey arrays must have the same length',
);
}

const keys = mintToAccountsLayout({
mint,
feePayer,
Expand Down
3 changes: 1 addition & 2 deletions js/compressed-token/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ export type CompressedCpiContext = {
firstSetContext: boolean;
cpiContextAccountIndex: number; // u8
};
/// TODO: remove index_mt_account on-chain. passed as part of
/// CompressedTokenInstructionDataInvoke

export type TokenTransferOutputData = {
/**
* The owner of the output token account
Expand Down
3 changes: 3 additions & 0 deletions js/compressed-token/tests/e2e/approve-and-mint-to.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
newAccountWithLamports,
sendAndConfirmTx,
getTestRpc,
defaultTestStateTreeAccounts,
} from '@lightprotocol/stateless.js';
import { WasmFactory } from '@lightprotocol/hasher.rs';
import BN from 'bn.js';
Expand Down Expand Up @@ -90,6 +91,7 @@ describe('approveAndMintTo', () => {
bob,
mintAuthority,
1000000000,
defaultTestStateTreeAccounts().merkleTree,
);

await assertApproveAndMintTo(rpc, mint, bn(1000000000), bob);
Expand Down Expand Up @@ -123,6 +125,7 @@ describe('approveAndMintTo', () => {
bob,
token22MintAuthority,
1000000000,
defaultTestStateTreeAccounts().merkleTree,
);

await assertApproveAndMintTo(rpc, token22Mint, bn(1000000000), bob);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ describe('compressSplTokenAccount', () => {
alice.publicKey,
mintAuthority,
bn(1000),
defaultTestStateTreeAccounts().merkleTree,
);

await decompress(rpc, payer, mint, bn(1000), alice, aliceAta);
Expand Down Expand Up @@ -330,6 +331,7 @@ describe('compressSplTokenAccount', () => {
alice.publicKey,
mintAuthority,
bn(1000),
defaultTestStateTreeAccounts().merkleTree,
);

await decompress(rpc, payer, mint, bn(1000), alice, aliceAta);
Expand Down
23 changes: 16 additions & 7 deletions js/compressed-token/tests/e2e/compress.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,15 @@ describe('compress', () => {
bob.publicKey,
);

await mintTo(rpc, payer, mint, bob.publicKey, mintAuthority, bn(10000));
await mintTo(
rpc,
payer,
mint,
bob.publicKey,
mintAuthority,
bn(10000),
defaultTestStateTreeAccounts().merkleTree,
);

await decompress(rpc, payer, mint, bn(9000), bob, bobAta);

Expand Down Expand Up @@ -151,7 +159,7 @@ describe('compress', () => {
bob,
bobAta,
charlie.publicKey,
merkleTree,
defaultTestStateTreeAccounts().merkleTree,
);
await assertCompress(
rpc,
Expand Down Expand Up @@ -188,7 +196,7 @@ describe('compress', () => {
bob,
bobAta,
recipients.slice(0, 11),
merkleTree,
defaultTestStateTreeAccounts().merkleTree,
);

for (let i = 0; i < recipients.length; i++) {
Expand Down Expand Up @@ -224,7 +232,7 @@ describe('compress', () => {
bob,
bobAta,
recipients.slice(0, 11),
merkleTree,
defaultTestStateTreeAccounts().merkleTree,
),
).rejects.toThrow(
'Amount and toAddress arrays must have the same length',
Expand All @@ -239,7 +247,7 @@ describe('compress', () => {
bob,
bobAta,
recipients,
merkleTree,
defaultTestStateTreeAccounts().merkleTree,
),
).rejects.toThrow(
'Both amount and toAddress must be arrays or both must be single values',
Expand All @@ -259,7 +267,7 @@ describe('compress', () => {
toAddress: recipients,
amount: amounts,
mint,
outputStateTree: merkleTree,
outputStateTree: defaultTestStateTreeAccounts().merkleTree,
});

const { blockhash } = await rpc.getLatestBlockhash();
Expand Down Expand Up @@ -316,6 +324,7 @@ describe('compress', () => {
bob.publicKey,
mintAuthority,
bn(10000),
defaultTestStateTreeAccounts().merkleTree,
);

await decompress(
Expand All @@ -341,7 +350,7 @@ describe('compress', () => {
bob,
bobToken2022Ata,
charlie.publicKey,
merkleTree,
defaultTestStateTreeAccounts().merkleTree,
);
await assertCompress(
rpc,
Expand Down
10 changes: 9 additions & 1 deletion js/compressed-token/tests/e2e/decompress.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,15 @@ describe('decompress', () => {
charlie.publicKey,
);

await mintTo(rpc, payer, mint, bob.publicKey, mintAuthority, bn(1000));
await mintTo(
rpc,
payer,
mint,
bob.publicKey,
mintAuthority,
bn(1000),
defaultTestStateTreeAccounts().merkleTree,
);
});

const LOOP = 10;
Expand Down
17 changes: 15 additions & 2 deletions js/compressed-token/tests/e2e/merge-token-accounts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe('mergeTokenAccounts', () => {
owner.publicKey,
mintAuthority,
bn(100),
defaultTestStateTreeAccounts().merkleTree,
);
}
});
Expand All @@ -59,7 +60,13 @@ describe('mergeTokenAccounts', () => {
);
expect(preAccounts.items.length).to.be.greaterThan(1);

await mergeTokenAccounts(rpc, payer, mint, owner, merkleTree);
await mergeTokenAccounts(
rpc,
payer,
mint,
owner,
defaultTestStateTreeAccounts().merkleTree,
);

const postAccounts = await rpc.getCompressedTokenAccountsByOwner(
owner.publicKey,
Expand Down Expand Up @@ -93,7 +100,13 @@ describe('mergeTokenAccounts', () => {

// Second merge attempt
try {
await mergeTokenAccounts(rpc, payer, mint, owner, merkleTree);
await mergeTokenAccounts(
rpc,
payer,
mint,
owner,
defaultTestStateTreeAccounts().merkleTree,
);
console.log('Second merge succeeded');
} catch (error) {
console.error('Second merge failed:', error);
Expand Down
Loading

0 comments on commit d10db3c

Please sign in to comment.