From 0a30fb6c4cb0483e59766c625cc6b7c9fe201150 Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Sun, 12 Jan 2025 18:58:19 +0000 Subject: [PATCH] get-light-state-tree-info.ts --- .../src/actions/create-mint.ts | 2 +- js/compressed-token/src/types.ts | 3 +- js/stateless.js/src/test-helpers/index.ts | 1 + .../src/{utils => test-helpers}/test-utils.ts | 0 .../src/utils/get-light-state-tree-info.ts | 211 ++++++++++++++++++ js/stateless.js/src/utils/index.ts | 1 - 6 files changed, 214 insertions(+), 4 deletions(-) rename js/stateless.js/src/{utils => test-helpers}/test-utils.ts (100%) create mode 100644 js/stateless.js/src/utils/get-light-state-tree-info.ts diff --git a/js/compressed-token/src/actions/create-mint.ts b/js/compressed-token/src/actions/create-mint.ts index 6ca94e2fe6..8d1110b70f 100644 --- a/js/compressed-token/src/actions/create-mint.ts +++ b/js/compressed-token/src/actions/create-mint.ts @@ -31,7 +31,7 @@ import { * 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 freezing. Defaults to null. + * @param freezeAuthority Account that will control freeze and thaw. Defaults to null. * * @return Address of the new mint and the transaction signature */ diff --git a/js/compressed-token/src/types.ts b/js/compressed-token/src/types.ts index a8016585ed..208ea5b849 100644 --- a/js/compressed-token/src/types.ts +++ b/js/compressed-token/src/types.ts @@ -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 diff --git a/js/stateless.js/src/test-helpers/index.ts b/js/stateless.js/src/test-helpers/index.ts index 7a5a1110e1..74a4ee8eaa 100644 --- a/js/stateless.js/src/test-helpers/index.ts +++ b/js/stateless.js/src/test-helpers/index.ts @@ -1,2 +1,3 @@ export * from './merkle-tree'; export * from './test-rpc'; +export * from './test-utils'; diff --git a/js/stateless.js/src/utils/test-utils.ts b/js/stateless.js/src/test-helpers/test-utils.ts similarity index 100% rename from js/stateless.js/src/utils/test-utils.ts rename to js/stateless.js/src/test-helpers/test-utils.ts diff --git a/js/stateless.js/src/utils/get-light-state-tree-info.ts b/js/stateless.js/src/utils/get-light-state-tree-info.ts new file mode 100644 index 0000000000..3b41ab16ac --- /dev/null +++ b/js/stateless.js/src/utils/get-light-state-tree-info.ts @@ -0,0 +1,211 @@ +import { + AddressLookupTableProgram, + Connection, + Keypair, + PublicKey, +} from '@solana/web3.js'; +import { buildAndSignTx, sendAndConfirmTx } from './send-and-confirm'; +import { Rpc } from '../rpc'; + +/** + * Create two lookup tables storing all public state tree and queue addresses + * returns lookup table addresses and txId + * [stateTreeLookupTable, nullifyTable] + * @internal + * @param connection - Connection to the Solana network + * @param payer - Keypair of the payer + * @param authority - Keypair of the authority + * @param recentSlot - Slot of the recent block + */ +export async function createStateTreeLookupTables( + connection: Rpc, + payer: Keypair, + authority: Keypair, + recentSlot: number, +) { + const [createInstruction1, lookupTableAddress1] = + AddressLookupTableProgram.createLookupTable({ + payer: payer.publicKey, + authority: authority.publicKey, + recentSlot, + }); + + const [createInstruction2, lookupTableAddress2] = + AddressLookupTableProgram.createLookupTable({ + payer: payer.publicKey, + authority: authority.publicKey, + recentSlot, + }); + + const blockhash = await connection.getLatestBlockhash(); + + const tx = buildAndSignTx( + [createInstruction1, createInstruction2], + payer, + blockhash.blockhash, + ); + const txId = await sendAndConfirmTx(connection, tx); + + return { + addresses: [lookupTableAddress1, lookupTableAddress2], + txId, + }; +} + +/** + * Extend state tree lookup table with new state tree and queue addresses + * @internal + * @param connection - Connection to the Solana network + * @param tableAddress - Address of the lookup table to extend + * @param newStateTreeAddress - Address of the new state tree to add + * @param newQueueAddress - Address of the new queue to add + * @param payer - Keypair of the payer + * @param authority - Keypair of the authority + */ +export async function extendStateTreeLookupTable( + connection: Rpc, + tableAddress: PublicKey, + newStateTreeAddress: PublicKey, + newQueueAddress: PublicKey, + payer: Keypair, + authority: Keypair, +) { + const instructions = AddressLookupTableProgram.extendLookupTable({ + payer: payer.publicKey, + authority: authority.publicKey, + lookupTable: tableAddress, + addresses: [newStateTreeAddress, newQueueAddress], + }); + + const blockhash = await connection.getLatestBlockhash(); + + const tx = buildAndSignTx([instructions], payer, blockhash.blockhash); + const txId = await sendAndConfirmTx(connection, tx); + + return { + tableAddress, + txId, + }; +} + +/** + * Adds full state tree address to lookup table. Acts as nullifier + * @internal + * @param connection - Connection to the Solana network + * @param stateTreeAddress - Address of the state tree to nullify + * @param nullifyTableAddress - Address nullifier lookup table to store address in + * @param stateTreeLookupTableAddress - lookup table storing all state tree addresses + * @param payer - Keypair of the payer + * @param authority - Keypair of the authority + */ +export async function nullifyLookupTable({ + connection, + fullStateTreeAddress, + nullifyTableAddress, + stateTreeLookupTableAddress, + payer, + authority, +}: { + connection: Rpc; + fullStateTreeAddress: PublicKey; + nullifyTableAddress: PublicKey; + stateTreeLookupTableAddress: PublicKey; + payer: Keypair; + authority: Keypair; +}) { + // to be nullified address must be part of stateTreeLookupTable set + const stateTreeLookupTable = await connection.getAddressLookupTable( + stateTreeLookupTableAddress, + ); + + if (!stateTreeLookupTable) { + throw new Error('State tree lookup table not found'); + } + + if ( + !stateTreeLookupTable.value?.state.addresses.includes( + fullStateTreeAddress, + ) + ) { + throw new Error( + 'State tree address not found in lookup table. Pass correct address or stateTreeLookupTable', + ); + } + + const nullifyTable = + await connection.getAddressLookupTable(nullifyTableAddress); + + if (!nullifyTable) { + throw new Error('Nullify table not found'); + } + if (nullifyTable.value?.state.addresses.includes(fullStateTreeAddress)) { + throw new Error('State tree address already in nullify table'); + } + + const instructions = AddressLookupTableProgram.extendLookupTable({ + payer: payer.publicKey, + authority: authority.publicKey, + lookupTable: nullifyTableAddress, + addresses: [fullStateTreeAddress], + }); + + const blockhash = await connection.getLatestBlockhash(); + + const tx = buildAndSignTx([instructions], payer, blockhash.blockhash); + const txId = await sendAndConfirmTx(connection, tx); + + return { + txId, + }; +} + +/** + * Get most recent , active state tree data + * we store in lookup table for each public state tree + */ +export async function getLightStateTreeInfo( + connection: Connection, + stateTreeLookupTableAddress: PublicKey, + nullifyTableAddress: PublicKey, +) { + const stateTreeLookupTable = await connection.getAddressLookupTable( + stateTreeLookupTableAddress, + ); + + if (!stateTreeLookupTable) { + throw new Error('State tree lookup table not found'); + } + + const stateTreePubkeys = stateTreeLookupTable.value?.state.addresses || []; + + const nullifyTable = + await connection.getAddressLookupTable(nullifyTableAddress); + + if (!nullifyTable) { + throw new Error('Nullify table not found'); + } + + const nullifyTablePubkeys = nullifyTable.value?.state.addresses || []; + + const activeStateTrees = stateTreePubkeys.filter( + (_, index) => + index % 2 === 0 && + !nullifyTablePubkeys.includes(stateTreePubkeys[index]), + ); + const activeQueues = stateTreePubkeys.filter( + (_, index) => + index % 2 !== 0 && + !nullifyTablePubkeys.includes(stateTreePubkeys[index]), + ); + + if (activeStateTrees.length !== activeQueues.length) { + throw new Error( + 'Must have equal number of active state trees and queues', + ); + } + + return { + activeStateTrees, + activeQueues, + }; +} diff --git a/js/stateless.js/src/utils/index.ts b/js/stateless.js/src/utils/index.ts index 4c09b156ee..27dcab82d1 100644 --- a/js/stateless.js/src/utils/index.ts +++ b/js/stateless.js/src/utils/index.ts @@ -5,6 +5,5 @@ export * from './parse-validity-proof'; export * from './pipe'; export * from './send-and-confirm'; export * from './sleep'; -export * from './test-utils'; export * from './validation'; export * from './calculate-compute-unit-price';