4
4
TransactionInstruction ,
5
5
SystemProgram ,
6
6
Connection ,
7
+ AddressLookupTableProgram ,
7
8
} from '@solana/web3.js' ;
8
9
import { BN , Program , AnchorProvider , setProvider } from '@coral-xyz/anchor' ;
9
10
import { IDL , LightCompressedToken } from './idl/light_compressed_token' ;
@@ -47,16 +48,17 @@ type CompressParams = {
47
48
source : PublicKey ;
48
49
/**
49
50
* owner of the compressed token account.
51
+ * To compress to a batch of recipients, pass an array of PublicKeys.
50
52
*/
51
- toAddress : PublicKey ;
53
+ toAddress : PublicKey | PublicKey [ ] ;
52
54
/**
53
55
* Mint address of the token to compress.
54
56
*/
55
57
mint : PublicKey ;
56
58
/**
57
59
* amount of tokens to compress.
58
60
*/
59
- amount : number | BN ;
61
+ amount : number | BN | number [ ] | BN [ ] ;
60
62
/**
61
63
* The state tree that the tx output should be inserted into. Defaults to a
62
64
* public state tree if unspecified.
@@ -241,6 +243,29 @@ export type ApproveAndMintToParams = {
241
243
merkleTree ?: PublicKey ;
242
244
} ;
243
245
246
+ export type CreateTokenProgramLookupTableParams = {
247
+ /**
248
+ * The payer of the transaction.
249
+ */
250
+ payer : PublicKey ;
251
+ /**
252
+ * The authority of the transaction.
253
+ */
254
+ authority : PublicKey ;
255
+ /**
256
+ * Recently finalized Solana slot.
257
+ */
258
+ recentSlot : number ;
259
+ /**
260
+ * Optional Mint addresses to store in the lookup table.
261
+ */
262
+ mints ?: PublicKey [ ] ;
263
+ /**
264
+ * Optional additional addresses to store in the lookup table.
265
+ */
266
+ remainingAccounts ?: PublicKey [ ] ;
267
+ } ;
268
+
244
269
/**
245
270
* Sum up the token amounts of the compressed token accounts
246
271
*/
@@ -513,6 +538,13 @@ export class CompressedTokenProgram {
513
538
const amounts = toArray < BN | number > ( amount ) . map ( amount => bn ( amount ) ) ;
514
539
515
540
const toPubkeys = toArray ( toPubkey ) ;
541
+
542
+ if ( amounts . length !== toPubkeys . length ) {
543
+ throw new Error (
544
+ 'Amount and toPubkey arrays must have the same length' ,
545
+ ) ;
546
+ }
547
+
516
548
const instruction = await this . program . methods
517
549
. mintTo ( toPubkeys , amounts , null )
518
550
. accounts ( {
@@ -658,24 +690,102 @@ export class CompressedTokenProgram {
658
690
}
659
691
660
692
/**
661
- * Construct compress instruction
693
+ * Create lookup table instructions for the token program's default accounts.
694
+ */
695
+ static async createTokenProgramLookupTable (
696
+ params : CreateTokenProgramLookupTableParams ,
697
+ ) {
698
+ const { authority, mints, recentSlot, payer, remainingAccounts } =
699
+ params ;
700
+
701
+ const [ createInstruction , lookupTableAddress ] =
702
+ AddressLookupTableProgram . createLookupTable ( {
703
+ authority,
704
+ payer : authority ,
705
+ recentSlot,
706
+ } ) ;
707
+
708
+ let optionalMintKeys : PublicKey [ ] = [ ] ;
709
+ if ( mints ) {
710
+ optionalMintKeys = [
711
+ ...mints ,
712
+ ...mints . map ( mint => this . deriveTokenPoolPda ( mint ) ) ,
713
+ ] ;
714
+ }
715
+
716
+ const extendInstruction = AddressLookupTableProgram . extendLookupTable ( {
717
+ payer,
718
+ authority,
719
+ lookupTable : lookupTableAddress ,
720
+ addresses : [
721
+ this . deriveCpiAuthorityPda ,
722
+ LightSystemProgram . programId ,
723
+ defaultStaticAccountsStruct ( ) . registeredProgramPda ,
724
+ defaultStaticAccountsStruct ( ) . noopProgram ,
725
+ defaultStaticAccountsStruct ( ) . accountCompressionAuthority ,
726
+ defaultStaticAccountsStruct ( ) . accountCompressionProgram ,
727
+ defaultTestStateTreeAccounts ( ) . merkleTree ,
728
+ defaultTestStateTreeAccounts ( ) . nullifierQueue ,
729
+ defaultTestStateTreeAccounts ( ) . addressTree ,
730
+ defaultTestStateTreeAccounts ( ) . addressQueue ,
731
+ this . programId ,
732
+ TOKEN_PROGRAM_ID ,
733
+ authority ,
734
+ ...optionalMintKeys ,
735
+ ...( remainingAccounts ?? [ ] ) ,
736
+ ] ,
737
+ } ) ;
738
+
739
+ return {
740
+ instructions : [ createInstruction , extendInstruction ] ,
741
+ address : lookupTableAddress ,
742
+ } ;
743
+ }
744
+
745
+ /**
746
+ * Create compress instruction
662
747
* @returns compressInstruction
663
748
*/
664
749
static async compress (
665
750
params : CompressParams ,
666
751
) : Promise < TransactionInstruction > {
667
752
const { payer, owner, source, toAddress, mint, outputStateTree } =
668
753
params ;
669
- const amount = bn ( params . amount ) ;
670
754
671
- const tokenTransferOutputs : TokenTransferOutputData [ ] = [
672
- {
673
- owner : toAddress ,
674
- amount,
675
- lamports : bn ( 0 ) ,
676
- tlv : null ,
677
- } ,
678
- ] ;
755
+ if ( Array . isArray ( params . amount ) !== Array . isArray ( params . toAddress ) ) {
756
+ throw new Error (
757
+ 'Both amount and toAddress must be arrays or both must be single values' ,
758
+ ) ;
759
+ }
760
+
761
+ let tokenTransferOutputs : TokenTransferOutputData [ ] ;
762
+
763
+ if ( Array . isArray ( params . amount ) && Array . isArray ( params . toAddress ) ) {
764
+ if ( params . amount . length !== params . toAddress . length ) {
765
+ throw new Error (
766
+ 'Amount and toAddress arrays must have the same length' ,
767
+ ) ;
768
+ }
769
+ tokenTransferOutputs = params . amount . map ( ( amt , index ) => {
770
+ const amount = bn ( amt ) ;
771
+ return {
772
+ owner : ( params . toAddress as PublicKey [ ] ) [ index ] ,
773
+ amount,
774
+ lamports : bn ( 0 ) ,
775
+ tlv : null ,
776
+ } ;
777
+ } ) ;
778
+ } else {
779
+ tokenTransferOutputs = [
780
+ {
781
+ owner : toAddress as PublicKey ,
782
+ amount : bn ( params . amount as number | BN ) ,
783
+ lamports : bn ( 0 ) ,
784
+ tlv : null ,
785
+ } ,
786
+ ] ;
787
+ }
788
+
679
789
const {
680
790
inputTokenDataWithContext,
681
791
packedOutputTokenData,
@@ -693,7 +803,11 @@ export class CompressedTokenProgram {
693
803
delegatedTransfer : null , // TODO: implement
694
804
inputTokenDataWithContext,
695
805
outputCompressedAccounts : packedOutputTokenData ,
696
- compressOrDecompressAmount : amount ,
806
+ compressOrDecompressAmount : Array . isArray ( params . amount )
807
+ ? params . amount
808
+ . map ( amt => new BN ( amt ) )
809
+ . reduce ( ( sum , amt ) => sum . add ( amt ) , new BN ( 0 ) )
810
+ : new BN ( params . amount ) ,
697
811
isCompress : true ,
698
812
cpiContext : null ,
699
813
lamportsChangeAccountMerkleTreeIndex : null ,
@@ -704,24 +818,20 @@ export class CompressedTokenProgram {
704
818
data ,
705
819
) ;
706
820
707
- const {
708
- accountCompressionAuthority,
709
- noopProgram,
710
- registeredProgramPda,
711
- accountCompressionProgram,
712
- } = defaultStaticAccountsStruct ( ) ;
713
-
714
821
const instruction = await this . program . methods
715
822
. transfer ( encodedData )
716
823
. accounts ( {
717
824
feePayer : payer ,
718
825
authority : owner ,
719
826
cpiAuthorityPda : this . deriveCpiAuthorityPda ,
720
827
lightSystemProgram : LightSystemProgram . programId ,
721
- registeredProgramPda : registeredProgramPda ,
722
- noopProgram : noopProgram ,
723
- accountCompressionAuthority : accountCompressionAuthority ,
724
- accountCompressionProgram : accountCompressionProgram ,
828
+ registeredProgramPda :
829
+ defaultStaticAccountsStruct ( ) . registeredProgramPda ,
830
+ noopProgram : defaultStaticAccountsStruct ( ) . noopProgram ,
831
+ accountCompressionAuthority :
832
+ defaultStaticAccountsStruct ( ) . accountCompressionAuthority ,
833
+ accountCompressionProgram :
834
+ defaultStaticAccountsStruct ( ) . accountCompressionProgram ,
725
835
selfProgram : this . programId ,
726
836
tokenPoolPda : this . deriveTokenPoolPda ( mint ) ,
727
837
compressOrDecompressTokenAccount : source , // token
0 commit comments