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,99 @@ 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
+
361
+ yield this . createUnsignedTx ( { transaction : tx } , "Ntt.InitializeLUT" ) ;
266
362
}
267
363
268
364
async * registerTransceiver ( args : {
@@ -294,7 +390,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
294
390
) ;
295
391
const broadcastIx = await this . program . methods
296
392
. broadcastWormholeId ( )
297
- . accounts ( {
393
+ . accountsStrict ( {
298
394
payer : args . payer . publicKey ,
299
395
config : this . pdas . configAccount ( ) ,
300
396
mint : config . mint ,
@@ -306,6 +402,8 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
306
402
sequence : whAccs . wormholeSequence ,
307
403
program : this . core . address ,
308
404
systemProgram : SystemProgram . programId ,
405
+ clock : web3 . SYSVAR_CLOCK_PUBKEY ,
406
+ rent : web3 . SYSVAR_RENT_PUBKEY ,
309
407
} ,
310
408
} )
311
409
. instruction ( ) ;
@@ -346,7 +444,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
346
444
. instruction ( ) ,
347
445
this . program . methods
348
446
. broadcastWormholePeer ( { chainId : toChainId ( peer . chain ) } )
349
- . accounts ( {
447
+ . accountsStrict ( {
350
448
payer : sender ,
351
449
config : this . pdas . configAccount ( ) ,
352
450
peer : this . pdas . transceiverPeerAccount ( peer . chain ) ,
@@ -357,6 +455,9 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
357
455
feeCollector : whAccs . wormholeFeeCollector ,
358
456
sequence : whAccs . wormholeSequence ,
359
457
program : this . core . address ,
458
+ clock : web3 . SYSVAR_CLOCK_PUBKEY ,
459
+ rent : web3 . SYSVAR_RENT_PUBKEY ,
460
+ systemProgram : SystemProgram . programId ,
360
461
} ,
361
462
} )
362
463
. instruction ( ) ,
@@ -751,7 +852,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
751
852
. releaseWormholeOutbound ( {
752
853
revertOnDelay : args . revertOnDelay ,
753
854
} )
754
- . accounts ( {
855
+ . accountsStrict ( {
755
856
payer : args . payer ,
756
857
config : { config : this . pdas . configAccount ( ) } ,
757
858
outboxItem : args . outboxItem ,
@@ -764,6 +865,8 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
764
865
sequence : whAccs . wormholeSequence ,
765
866
program : this . core . address ,
766
867
systemProgram : SystemProgram . programId ,
868
+ clock : web3 . SYSVAR_CLOCK_PUBKEY ,
869
+ rent : web3 . SYSVAR_RENT_PUBKEY ,
767
870
} ,
768
871
} )
769
872
. instruction ( ) ;
@@ -943,6 +1046,32 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
943
1046
}
944
1047
}
945
1048
1049
+ async getAddressLookupTable (
1050
+ useCache = true
1051
+ ) : Promise < AddressLookupTableAccount > {
1052
+ if ( ! useCache || ! this . addressLookupTable ) {
1053
+ const lut = await this . program . account . lut . fetchNullable (
1054
+ this . pdas . lutAccount ( )
1055
+ ) ;
1056
+ if ( ! lut )
1057
+ throw new Error (
1058
+ "Address lookup table not found. Did you forget to call initializeLUT?"
1059
+ ) ;
1060
+
1061
+ const response = await this . connection . getAddressLookupTable ( lut . address ) ;
1062
+ if ( response . value === null ) throw new Error ( "Could not fetch LUT" ) ;
1063
+
1064
+ this . addressLookupTable = response . value ;
1065
+ }
1066
+
1067
+ if ( ! this . addressLookupTable )
1068
+ throw new Error (
1069
+ "Address lookup table not found. Did you forget to call initializeLUT?"
1070
+ ) ;
1071
+
1072
+ return this . addressLookupTable ;
1073
+ }
1074
+
946
1075
createUnsignedTx (
947
1076
txReq : SolanaTransaction ,
948
1077
description : string ,
0 commit comments