1
- import { Program } from "@coral-xyz/anchor" ;
1
+ import { Program , web3 } from "@coral-xyz/anchor" ;
2
2
import * as splToken from "@solana/spl-token" ;
3
3
import {
4
4
createAssociatedTokenAccountInstruction ,
5
5
getAssociatedTokenAddressSync ,
6
6
} from "@solana/spl-token" ;
7
7
import {
8
+ AddressLookupTableAccount ,
9
+ AddressLookupTableProgram ,
8
10
Connection ,
9
11
Keypair ,
10
12
PublicKey ,
@@ -71,6 +73,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
71
73
program : Program < NttBindings . NativeTokenTransfer > ;
72
74
config ?: NttBindings . Config ;
73
75
quoter ?: NttQuoter < N , C > ;
76
+ addressLookupTable ?: AddressLookupTableAccount ;
74
77
75
78
constructor (
76
79
readonly network : N ,
@@ -263,6 +266,100 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
263
266
{ transaction : tx , signers : [ ] } ,
264
267
"Ntt.Initialize"
265
268
) ;
269
+
270
+ yield * this . initializeOrUpdateLUT ( { payer : args . payer } ) ;
271
+ }
272
+
273
+ // This function should be called after each upgrade. If there's nothing to
274
+ // do, it won't actually submit a transaction, so it's cheap to call.
275
+ async * initializeOrUpdateLUT ( args : { payer : Keypair } ) {
276
+ // TODO: find a more robust way of fetching a recent slot
277
+ const slot = ( await this . program . provider . connection . getSlot ( ) ) - 1 ;
278
+
279
+ const [ _ , lutAddress ] = web3 . AddressLookupTableProgram . createLookupTable ( {
280
+ authority : this . pdas . lutAuthority ( ) ,
281
+ payer : args . payer . publicKey ,
282
+ recentSlot : slot ,
283
+ } ) ;
284
+
285
+ const whAccs = utils . getWormholeDerivedAccounts (
286
+ this . program . programId ,
287
+ this . core . address
288
+ ) ;
289
+ const config = await this . getConfig ( ) ;
290
+
291
+ const entries = {
292
+ config : this . pdas . configAccount ( ) ,
293
+ custody : config . custody ,
294
+ tokenProgram : config . tokenProgram ,
295
+ mint : config . mint ,
296
+ tokenAuthority : this . pdas . tokenAuthority ( ) ,
297
+ outboxRateLimit : this . pdas . outboxRateLimitAccount ( ) ,
298
+ wormhole : {
299
+ bridge : whAccs . wormholeBridge ,
300
+ feeCollector : whAccs . wormholeFeeCollector ,
301
+ sequence : whAccs . wormholeSequence ,
302
+ program : this . core . address ,
303
+ systemProgram : SystemProgram . programId ,
304
+ clock : web3 . SYSVAR_CLOCK_PUBKEY ,
305
+ rent : web3 . SYSVAR_RENT_PUBKEY ,
306
+ } ,
307
+ } ;
308
+
309
+ // collect all pubkeys in entries recursively
310
+ const collectPubkeys = ( obj : any ) : Array < PublicKey > => {
311
+ const pubkeys = new Array < PublicKey > ( ) ;
312
+ for ( const key in obj ) {
313
+ const value = obj [ key ] ;
314
+ if ( value instanceof PublicKey ) {
315
+ pubkeys . push ( value ) ;
316
+ } else if ( typeof value === "object" ) {
317
+ pubkeys . push ( ...collectPubkeys ( value ) ) ;
318
+ }
319
+ }
320
+ return pubkeys ;
321
+ } ;
322
+ const pubkeys = collectPubkeys ( entries ) . map ( ( pk ) => pk . toBase58 ( ) ) ;
323
+
324
+ var existingLut : web3 . AddressLookupTableAccount | null = null ;
325
+ try {
326
+ existingLut = await this . getAddressLookupTable ( false ) ;
327
+ } catch {
328
+ // swallow errors here, it just means that lut doesn't exist
329
+ }
330
+
331
+ if ( existingLut !== null ) {
332
+ const existingPubkeys =
333
+ existingLut . state . addresses ?. map ( ( a ) => a . toBase58 ( ) ) ?? [ ] ;
334
+
335
+ // if pubkeys contains keys that are not in the existing LUT, we need to
336
+ // add them to the LUT
337
+ const missingPubkeys = pubkeys . filter (
338
+ ( pk ) => ! existingPubkeys . includes ( pk )
339
+ ) ;
340
+
341
+ if ( missingPubkeys . length === 0 ) {
342
+ return existingLut ;
343
+ }
344
+ }
345
+
346
+ const ix = await this . program . methods
347
+ . initializeLut ( new BN ( slot ) )
348
+ . accountsStrict ( {
349
+ payer : args . payer . publicKey ,
350
+ authority : this . pdas . lutAuthority ( ) ,
351
+ lutAddress,
352
+ lut : this . pdas . lutAccount ( ) ,
353
+ lutProgram : AddressLookupTableProgram . programId ,
354
+ systemProgram : SystemProgram . programId ,
355
+ entries,
356
+ } )
357
+ . instruction ( ) ;
358
+
359
+ const tx = new Transaction ( ) . add ( ix ) ;
360
+ tx . feePayer = args . payer . publicKey ;
361
+
362
+ yield this . createUnsignedTx ( { transaction : tx } , "Ntt.InitializeLUT" ) ;
266
363
}
267
364
268
365
async * registerTransceiver ( args : {
@@ -294,7 +391,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
294
391
) ;
295
392
const broadcastIx = await this . program . methods
296
393
. broadcastWormholeId ( )
297
- . accounts ( {
394
+ . accountsStrict ( {
298
395
payer : args . payer . publicKey ,
299
396
config : this . pdas . configAccount ( ) ,
300
397
mint : config . mint ,
@@ -306,6 +403,8 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
306
403
sequence : whAccs . wormholeSequence ,
307
404
program : this . core . address ,
308
405
systemProgram : SystemProgram . programId ,
406
+ clock : web3 . SYSVAR_CLOCK_PUBKEY ,
407
+ rent : web3 . SYSVAR_RENT_PUBKEY ,
309
408
} ,
310
409
} )
311
410
. instruction ( ) ;
@@ -346,7 +445,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
346
445
. instruction ( ) ,
347
446
this . program . methods
348
447
. broadcastWormholePeer ( { chainId : toChainId ( peer . chain ) } )
349
- . accounts ( {
448
+ . accountsStrict ( {
350
449
payer : sender ,
351
450
config : this . pdas . configAccount ( ) ,
352
451
peer : this . pdas . transceiverPeerAccount ( peer . chain ) ,
@@ -357,6 +456,9 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
357
456
feeCollector : whAccs . wormholeFeeCollector ,
358
457
sequence : whAccs . wormholeSequence ,
359
458
program : this . core . address ,
459
+ clock : web3 . SYSVAR_CLOCK_PUBKEY ,
460
+ rent : web3 . SYSVAR_RENT_PUBKEY ,
461
+ systemProgram : SystemProgram . programId ,
360
462
} ,
361
463
} )
362
464
. instruction ( ) ,
@@ -751,7 +853,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
751
853
. releaseWormholeOutbound ( {
752
854
revertOnDelay : args . revertOnDelay ,
753
855
} )
754
- . accounts ( {
856
+ . accountsStrict ( {
755
857
payer : args . payer ,
756
858
config : { config : this . pdas . configAccount ( ) } ,
757
859
outboxItem : args . outboxItem ,
@@ -764,6 +866,8 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
764
866
sequence : whAccs . wormholeSequence ,
765
867
program : this . core . address ,
766
868
systemProgram : SystemProgram . programId ,
869
+ clock : web3 . SYSVAR_CLOCK_PUBKEY ,
870
+ rent : web3 . SYSVAR_RENT_PUBKEY ,
767
871
} ,
768
872
} )
769
873
. instruction ( ) ;
@@ -943,6 +1047,32 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
943
1047
}
944
1048
}
945
1049
1050
+ async getAddressLookupTable (
1051
+ useCache = true
1052
+ ) : Promise < AddressLookupTableAccount > {
1053
+ if ( ! useCache || ! this . addressLookupTable ) {
1054
+ const lut = await this . program . account . lut . fetchNullable (
1055
+ this . pdas . lutAccount ( )
1056
+ ) ;
1057
+ if ( ! lut )
1058
+ throw new Error (
1059
+ "Address lookup table not found. Did you forget to call initializeLUT?"
1060
+ ) ;
1061
+
1062
+ const response = await this . connection . getAddressLookupTable ( lut . address ) ;
1063
+ if ( response . value === null ) throw new Error ( "Could not fetch LUT" ) ;
1064
+
1065
+ this . addressLookupTable = response . value ;
1066
+ }
1067
+
1068
+ if ( ! this . addressLookupTable )
1069
+ throw new Error (
1070
+ "Address lookup table not found. Did you forget to call initializeLUT?"
1071
+ ) ;
1072
+
1073
+ return this . addressLookupTable ;
1074
+ }
1075
+
946
1076
createUnsignedTx (
947
1077
txReq : SolanaTransaction ,
948
1078
description : string ,
0 commit comments