From d7e15e0f5a3455f199c746bd5a44d0c7fbbd74e4 Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Mon, 16 Jun 2025 18:56:47 -0400 Subject: [PATCH 1/4] wip synced bump add idempotent example update env example --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index f14f528..3b2246d 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ # Example .env file -RPC_ENDPOINT="https://devnet.helius-rpc.com?api-key=" +RPC_ENDPOINT="https://mainnet.helius-rpc.com?api-key=" PAYER_KEYPAIR="bs58encoded string of secret key" MINT_ADDRESS="D3tw8PvgsJqqFBUKcJxiLAe78wbvu3jzospbtbTt9u18" AUTHORITY_KEYPAIR="bs58encoded string of secret key" From be832e36631a350f936f040b0e33841e117a32b8 Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Wed, 25 Jun 2025 15:53:05 -0400 Subject: [PATCH 2/4] wip --- src/scripts/compress.ts | 21 +++++++++++---------- src/scripts/connection.ts | 6 ++++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/scripts/compress.ts b/src/scripts/compress.ts index 93937e6..bc1f5ee 100644 --- a/src/scripts/compress.ts +++ b/src/scripts/compress.ts @@ -81,20 +81,21 @@ const PAYER_KEYPAIR = Keypair.fromSecretKey( // Creates a cPDA for a given set of recipients. This lets you retry txns without handling spends client-side. // The whole txn will fail if the same set of seeds (with the same order) is used a second time. - instructions.push( - await createIdempotentAirdropInstruction( - connection, - payer.publicKey, - mintAddress, - recipients, - treeInfo - ) - ); + // instructions.push( + // await createIdempotentAirdropInstruction( + // connection, + // payer.publicKey, + // mintAddress, + // recipients, + // treeInfo + // ) + // ); // Use zk-compression LUT for your network // https://www.zkcompression.com/developers/protocol-addresses-and-urls#lookup-tables const lookupTableAddress = new web3.PublicKey( - "9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ" // mainnet + // "9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ" // mainnet + "qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V" // devnet ); // Get the lookup table account state const lookupTableAccount = ( diff --git a/src/scripts/connection.ts b/src/scripts/connection.ts index 410da06..e900225 100644 --- a/src/scripts/connection.ts +++ b/src/scripts/connection.ts @@ -1,5 +1,6 @@ import { Rpc } from "@lightprotocol/stateless.js"; import { RPC_ENDPOINT } from "../constants"; +import { PublicKey } from "@solana/web3.js"; const stateless = require("@lightprotocol/stateless.js"); const connection: Rpc = stateless.createRpc(RPC_ENDPOINT, RPC_ENDPOINT); @@ -10,4 +11,9 @@ const connection: Rpc = stateless.createRpc(RPC_ENDPOINT, RPC_ENDPOINT); const health = await connection.getIndexerHealth(); console.log("Indexer Health:", health); + + const accs = await connection.getCompressedAccountsByOwner( + new PublicKey("6MZszp7ihPjUeoi8RJs9NNC4jBxi7beiqvXHJhxd7fe") + ); + console.log(accs); })(); From 39352728bc0c7af37d83931bad154a485a4fc430 Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Mon, 7 Jul 2025 13:02:33 -0400 Subject: [PATCH 3/4] add idempotent --- src/scripts/compress-idempotent.ts | 111 +++++++++++++++++++++++++++++ src/scripts/compress.ts | 36 ++-------- src/scripts/connection.ts | 14 +++- 3 files changed, 127 insertions(+), 34 deletions(-) create mode 100644 src/scripts/compress-idempotent.ts diff --git a/src/scripts/compress-idempotent.ts b/src/scripts/compress-idempotent.ts new file mode 100644 index 0000000..ca9937f --- /dev/null +++ b/src/scripts/compress-idempotent.ts @@ -0,0 +1,111 @@ +import * as web3 from "@solana/web3.js"; +import { Keypair, PublicKey } from "@solana/web3.js"; +import { + CompressedTokenProgram, + getTokenPoolInfos, + selectTokenPoolInfo, +} from "@lightprotocol/compressed-token"; +import { + bn, + buildAndSignTx, + calculateComputeUnitPrice, + createRpc, + dedupeSigner, + Rpc, + selectStateTreeInfo, + sendAndConfirmTx, +} from "@lightprotocol/stateless.js"; +import * as splToken from "@solana/spl-token"; +import dotenv from "dotenv"; +import bs58 from "bs58"; +import { createIdempotentAirdropInstruction } from "./idempotent"; +dotenv.config(); + +const RPC_ENDPOINT = process.env.RPC_ENDPOINT; +const MINT_ADDRESS = new PublicKey(process.env.MINT_ADDRESS!); +const PAYER_KEYPAIR = Keypair.fromSecretKey( + bs58.decode(process.env.PAYER_KEYPAIR!) +); + +(async () => { + try { + const connection: Rpc = createRpc(RPC_ENDPOINT); + const mintAddress = MINT_ADDRESS; + const payer = PAYER_KEYPAIR; + + const amount = bn(111); // each recipient will receive 111 tokens + const recipients = [PAYER_KEYPAIR.publicKey].map( + (address) => new PublicKey(address) + ); + const activeStateTrees = await connection.getStateTreeInfos(); + + /// Pick a new tree for each transaction! + const treeInfo = selectStateTreeInfo(activeStateTrees); + + // Create an SPL token account for the sender. + // The sender will send tokens from this account to the recipients as compressed tokens. + const sourceTokenAccount = await splToken.getOrCreateAssociatedTokenAccount( + connection, + payer, + mintAddress, + payer.publicKey + ); + + // Airdrop to example recipients addresses + // 1 recipient = 120_000 CU + // 5 recipients = 170_000 CU + // with idempotent cPDA = +250_000 CU + const instructions: web3.TransactionInstruction[] = []; + instructions.push( + web3.ComputeBudgetProgram.setComputeUnitLimit({ units: 370_000 }), + web3.ComputeBudgetProgram.setComputeUnitPrice({ + // Replace this with a dynamic priority_fee to land during high network load. + microLamports: calculateComputeUnitPrice(20_000, 370_000), + }) + ); + + const compressInstruction = await CompressedTokenProgram.compress({ + payer: payer.publicKey, + owner: payer.publicKey, + source: sourceTokenAccount.address, + toAddress: recipients, + amount: recipients.map(() => amount), + mint: mintAddress, + outputStateTreeInfo: treeInfo, + tokenPoolInfo: selectTokenPoolInfo( + await getTokenPoolInfos(connection, mintAddress) + ), + }); + instructions.push(compressInstruction); + + // Creates a cPDA for a given set of recipients. This lets you retry txns without handling spends client-side. + // The whole txn will fail if the same set of seeds (with the same order) is used a second time. + instructions.push( + await createIdempotentAirdropInstruction( + connection, + payer.publicKey, + mintAddress, + recipients, + treeInfo + ) + ); + + // Sign the transaction with the payer and owner keypair + const owner = payer; + const additionalSigners = dedupeSigner(payer, [owner]); + + const { blockhash } = await connection.getLatestBlockhash(); + + const tx = buildAndSignTx( + instructions, + payer, + blockhash, + additionalSigners + ); + + const txId = await sendAndConfirmTx(connection, tx); + console.log(`txId: ${txId}`); + } catch (e) { + console.error(`Compression failed:`, e); + } +})(); diff --git a/src/scripts/compress.ts b/src/scripts/compress.ts index bc1f5ee..d6c925f 100644 --- a/src/scripts/compress.ts +++ b/src/scripts/compress.ts @@ -18,7 +18,6 @@ import { import * as splToken from "@solana/spl-token"; import dotenv from "dotenv"; import bs58 from "bs58"; -import { createIdempotentAirdropInstruction } from "./idempotent"; dotenv.config(); const RPC_ENDPOINT = process.env.RPC_ENDPOINT; @@ -33,11 +32,10 @@ const PAYER_KEYPAIR = Keypair.fromSecretKey( const mintAddress = MINT_ADDRESS; const payer = PAYER_KEYPAIR; - const amount = bn(333); // each recipient will receive 111 tokens - const recipients = [ - "GMPWaPPrCeZPse5kwSR3WUrqYAPrVZBSVwymqh7auNW7", - "GMPWaPPrCeZPse5kwSR3WUrqYAPrVZBSVwymqh7auNW7", - ].map((address) => new PublicKey(address)); + const amount = bn(111); // each recipient will receive 111 tokens + const recipients = [PAYER_KEYPAIR.publicKey].map( + (address) => new PublicKey(address) + ); const activeStateTrees = await connection.getStateTreeInfos(); /// Pick a new tree for each transaction! @@ -79,29 +77,6 @@ const PAYER_KEYPAIR = Keypair.fromSecretKey( }); instructions.push(compressInstruction); - // Creates a cPDA for a given set of recipients. This lets you retry txns without handling spends client-side. - // The whole txn will fail if the same set of seeds (with the same order) is used a second time. - // instructions.push( - // await createIdempotentAirdropInstruction( - // connection, - // payer.publicKey, - // mintAddress, - // recipients, - // treeInfo - // ) - // ); - - // Use zk-compression LUT for your network - // https://www.zkcompression.com/developers/protocol-addresses-and-urls#lookup-tables - const lookupTableAddress = new web3.PublicKey( - // "9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ" // mainnet - "qAJZMgnQJ8G6vA3WRcjD9Jan1wtKkaCFWLWskxJrR5V" // devnet - ); - // Get the lookup table account state - const lookupTableAccount = ( - await connection.getAddressLookupTable(lookupTableAddress) - ).value!; - // Sign the transaction with the payer and owner keypair const owner = payer; const additionalSigners = dedupeSigner(payer, [owner]); @@ -112,8 +87,7 @@ const PAYER_KEYPAIR = Keypair.fromSecretKey( instructions, payer, blockhash, - additionalSigners, - [lookupTableAccount] + additionalSigners ); const txId = await sendAndConfirmTx(connection, tx); diff --git a/src/scripts/connection.ts b/src/scripts/connection.ts index e900225..0878c98 100644 --- a/src/scripts/connection.ts +++ b/src/scripts/connection.ts @@ -1,5 +1,5 @@ import { Rpc } from "@lightprotocol/stateless.js"; -import { RPC_ENDPOINT } from "../constants"; +import { MINT_ADDRESS, RPC_ENDPOINT } from "../constants"; import { PublicKey } from "@solana/web3.js"; const stateless = require("@lightprotocol/stateless.js"); @@ -12,8 +12,16 @@ const connection: Rpc = stateless.createRpc(RPC_ENDPOINT, RPC_ENDPOINT); const health = await connection.getIndexerHealth(); console.log("Indexer Health:", health); - const accs = await connection.getCompressedAccountsByOwner( - new PublicKey("6MZszp7ihPjUeoi8RJs9NNC4jBxi7beiqvXHJhxd7fe") + // const accs = await connection.getCompressedAccountsByOwner( + // new PublicKey("6MZszp7ihPjUeoi8RJs9NNC4jBxi7beiqvXHJhxd7fe") + // ); + console.log("connection", connection.rpcEndpoint); + const accs = await connection.getCompressedTokenAccountsByOwner( + new PublicKey("6MZszp7ihPjUeoi8RJs9NNC4jBxi7beiqvXHJhxd7fe"), + { + mint: MINT_ADDRESS, + // mint: new PublicKey("APMHh8w6ZWLCv4M5daKAvGzcFLemFPvrNfdHiTLEYghK"), + } ); console.log(accs); })(); From 44947367181eb61b9ce881a456c4d9668db12b17 Mon Sep 17 00:00:00 2001 From: Swenschaeferjohann Date: Mon, 7 Jul 2025 13:04:31 -0400 Subject: [PATCH 4/4] connection --- src/scripts/connection.ts | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/scripts/connection.ts b/src/scripts/connection.ts index 0878c98..d62cf98 100644 --- a/src/scripts/connection.ts +++ b/src/scripts/connection.ts @@ -1,6 +1,6 @@ import { Rpc } from "@lightprotocol/stateless.js"; -import { MINT_ADDRESS, RPC_ENDPOINT } from "../constants"; -import { PublicKey } from "@solana/web3.js"; +import { RPC_ENDPOINT, PAYER_KEYPAIR } from "../constants"; + const stateless = require("@lightprotocol/stateless.js"); const connection: Rpc = stateless.createRpc(RPC_ENDPOINT, RPC_ENDPOINT); @@ -12,16 +12,9 @@ const connection: Rpc = stateless.createRpc(RPC_ENDPOINT, RPC_ENDPOINT); const health = await connection.getIndexerHealth(); console.log("Indexer Health:", health); - // const accs = await connection.getCompressedAccountsByOwner( - // new PublicKey("6MZszp7ihPjUeoi8RJs9NNC4jBxi7beiqvXHJhxd7fe") - // ); console.log("connection", connection.rpcEndpoint); const accs = await connection.getCompressedTokenAccountsByOwner( - new PublicKey("6MZszp7ihPjUeoi8RJs9NNC4jBxi7beiqvXHJhxd7fe"), - { - mint: MINT_ADDRESS, - // mint: new PublicKey("APMHh8w6ZWLCv4M5daKAvGzcFLemFPvrNfdHiTLEYghK"), - } + PAYER_KEYPAIR.publicKey ); - console.log(accs); + console.log("compressed token accounts: ", accs); })();