@@ -20,6 +20,7 @@ import {
20
20
sendAndConfirmTransaction ,
21
21
type TransactionSignature ,
22
22
type Connection ,
23
+ SystemProgram ,
23
24
TransactionMessage ,
24
25
VersionedTransaction
25
26
} from '@solana/web3.js'
@@ -232,7 +233,7 @@ export class NTT {
232
233
const tokenProgram = mintInfo . owner
233
234
const ix = await this . program . methods
234
235
. initialize ( { chainId, limit : args . outboundLimit , mode } )
235
- . accounts ( {
236
+ . accountsStrict ( {
236
237
payer : args . payer . publicKey ,
237
238
deployer : args . owner . publicKey ,
238
239
programData : programDataAddress ( this . program . programId ) ,
@@ -241,8 +242,10 @@ export class NTT {
241
242
rateLimit : this . outboxRateLimitAccountAddress ( ) ,
242
243
tokenProgram,
243
244
tokenAuthority : this . tokenAuthorityAddress ( ) ,
244
- custody : await this . custodyAccountAddress ( args . mint ) ,
245
+ custody : await this . custodyAccountAddress ( args . mint , tokenProgram ) ,
245
246
bpfLoaderUpgradeableProgram : BPF_LOADER_UPGRADEABLE_PROGRAM_ID ,
247
+ associatedTokenProgram : splToken . ASSOCIATED_TOKEN_PROGRAM_ID ,
248
+ systemProgram : SystemProgram . programId ,
246
249
} ) . instruction ( ) ;
247
250
return sendAndConfirmTransaction ( this . program . provider . connection , new Transaction ( ) . add ( ix ) , [ args . payer , args . owner ] ) ;
248
251
}
@@ -298,7 +301,9 @@ export class NTT {
298
301
args . from ,
299
302
this . sessionAuthorityAddress ( args . fromAuthority . publicKey , transferArgs ) ,
300
303
args . fromAuthority . publicKey ,
301
- BigInt ( args . amount . toString ( ) )
304
+ BigInt ( args . amount . toString ( ) ) ,
305
+ [ ] ,
306
+ config . tokenProgram
302
307
) ;
303
308
const tx = new Transaction ( )
304
309
tx . add ( approveIx , transferIx , releaseIx )
@@ -398,7 +403,7 @@ export class NTT {
398
403
shouldQueue : args . shouldQueue
399
404
}
400
405
401
- return await this . program . methods
406
+ const transferIx = await this . program . methods
402
407
. transferLock ( transferArgs )
403
408
. accounts ( {
404
409
common : {
@@ -416,6 +421,39 @@ export class NTT {
416
421
sessionAuthority : this . sessionAuthorityAddress ( args . fromAuthority , transferArgs )
417
422
} )
418
423
. instruction ( )
424
+
425
+ const mintInfo = await splToken . getMint (
426
+ this . program . provider . connection ,
427
+ config . mint ,
428
+ undefined ,
429
+ config . tokenProgram
430
+ )
431
+ const transferHook = splToken . getTransferHook ( mintInfo )
432
+
433
+ if ( transferHook ) {
434
+ const source = args . from
435
+ const mint = config . mint
436
+ const destination = await this . custodyAccountAddress ( config )
437
+ const owner = this . sessionAuthorityAddress ( args . fromAuthority , transferArgs )
438
+ await addExtraAccountMetasForExecute (
439
+ this . program . provider . connection ,
440
+ transferIx ,
441
+ transferHook . programId ,
442
+ source ,
443
+ mint ,
444
+ destination ,
445
+ owner ,
446
+ // TODO(csongor): compute the amount that's passed into transfer.
447
+ // Leaving this 0 is fine unless the transfer hook accounts addresses
448
+ // depend on the amount (which is unlikely).
449
+ // If this turns out to be the case, the amount to put here is the
450
+ // untrimmed amount after removing dust.
451
+ 0 ,
452
+ ) ;
453
+ }
454
+
455
+ return transferIx
456
+
419
457
}
420
458
421
459
/**
@@ -496,14 +534,15 @@ export class NTT {
496
534
. releaseInboundMint ( {
497
535
revertOnDelay : args . revertOnDelay
498
536
} )
499
- . accounts ( {
537
+ . accountsStrict ( {
500
538
common : {
501
539
payer : args . payer ,
502
540
config : { config : this . configAccountAddress ( ) } ,
503
541
inboxItem : this . inboxItemAccountAddress ( args . chain , args . nttMessage ) ,
504
- recipient : getAssociatedTokenAddressSync ( mint , recipientAddress ) ,
542
+ recipient : getAssociatedTokenAddressSync ( mint , recipientAddress , true , config . tokenProgram ) ,
505
543
mint,
506
- tokenAuthority : this . tokenAuthorityAddress ( )
544
+ tokenAuthority : this . tokenAuthorityAddress ( ) ,
545
+ tokenProgram : config . tokenProgram
507
546
}
508
547
} )
509
548
. instruction ( )
@@ -551,22 +590,50 @@ export class NTT {
551
590
552
591
const mint = await this . mintAccountAddress ( config )
553
592
554
- return await this . program . methods
593
+ const transferIx = await this . program . methods
555
594
. releaseInboundUnlock ( {
556
595
revertOnDelay : args . revertOnDelay
557
596
} )
558
- . accounts ( {
597
+ . accountsStrict ( {
559
598
common : {
560
599
payer : args . payer ,
561
600
config : { config : this . configAccountAddress ( ) } ,
562
601
inboxItem : this . inboxItemAccountAddress ( args . chain , args . nttMessage ) ,
563
- recipient : getAssociatedTokenAddressSync ( mint , recipientAddress ) ,
602
+ recipient : getAssociatedTokenAddressSync ( mint , recipientAddress , true , config . tokenProgram ) ,
564
603
mint,
565
- tokenAuthority : this . tokenAuthorityAddress ( )
604
+ tokenAuthority : this . tokenAuthorityAddress ( ) ,
605
+ tokenProgram : config . tokenProgram
566
606
} ,
567
607
custody : await this . custodyAccountAddress ( config )
568
608
} )
569
609
. instruction ( )
610
+
611
+ const mintInfo = await splToken . getMint ( this . program . provider . connection , config . mint , undefined , config . tokenProgram )
612
+ const transferHook = splToken . getTransferHook ( mintInfo )
613
+
614
+ if ( transferHook ) {
615
+ const source = await this . custodyAccountAddress ( config )
616
+ const mint = config . mint
617
+ const destination = getAssociatedTokenAddressSync ( mint , recipientAddress , true , config . tokenProgram )
618
+ const owner = this . tokenAuthorityAddress ( )
619
+ await addExtraAccountMetasForExecute (
620
+ this . program . provider . connection ,
621
+ transferIx ,
622
+ transferHook . programId ,
623
+ source ,
624
+ mint ,
625
+ destination ,
626
+ owner ,
627
+ // TODO(csongor): compute the amount that's passed into transfer.
628
+ // Leaving this 0 is fine unless the transfer hook accounts addresses
629
+ // depend on the amount (which is unlikely).
630
+ // If this turns out to be the case, the amount to put here is the
631
+ // untrimmed amount after removing dust.
632
+ 0 ,
633
+ ) ;
634
+ }
635
+
636
+ return transferIx
570
637
}
571
638
572
639
async releaseInboundUnlock ( args : {
@@ -891,15 +958,98 @@ export class NTT {
891
958
* (i.e. the program is initialised), the mint is derived from the config.
892
959
* Otherwise, the mint must be provided.
893
960
*/
894
- async custodyAccountAddress ( configOrMint : Config | PublicKey ) : Promise < PublicKey > {
961
+ async custodyAccountAddress ( configOrMint : Config | PublicKey , tokenProgram = splToken . TOKEN_PROGRAM_ID ) : Promise < PublicKey > {
895
962
if ( configOrMint instanceof PublicKey ) {
896
- return associatedAddress ( { mint : configOrMint , owner : this . tokenAuthorityAddress ( ) } )
963
+ return splToken . getAssociatedTokenAddress ( configOrMint , this . tokenAuthorityAddress ( ) , true , tokenProgram )
897
964
} else {
898
- return associatedAddress ( { mint : await this . mintAccountAddress ( configOrMint ) , owner : this . tokenAuthorityAddress ( ) } )
965
+ return splToken . getAssociatedTokenAddress ( configOrMint . mint , this . tokenAuthorityAddress ( ) , true , configOrMint . tokenProgram )
899
966
}
900
967
}
901
968
}
902
969
903
970
function exhaustive < A > ( _ : never ) : A {
904
971
throw new Error ( 'Impossible' )
905
972
}
973
+
974
+ /**
975
+ * TODO: this is copied from @solana/spl-token, because the most recent released
976
+ * version (0.4.3) is broken (does object equality instead of structural on the pubkey)
977
+ *
978
+ * this version fixes that error, looks like it's also fixed on main:
979
+ * https://github.com/solana-labs/solana-program-library/blob/ad4eb6914c5e4288ad845f29f0003cd3b16243e7/token/js/src/extensions/transferHook/instructions.ts#L208
980
+ */
981
+ async function addExtraAccountMetasForExecute (
982
+ connection : Connection ,
983
+ instruction : TransactionInstruction ,
984
+ programId : PublicKey ,
985
+ source : PublicKey ,
986
+ mint : PublicKey ,
987
+ destination : PublicKey ,
988
+ owner : PublicKey ,
989
+ amount : number | bigint ,
990
+ commitment ?: Commitment
991
+ ) {
992
+ const validateStatePubkey = splToken . getExtraAccountMetaAddress ( mint , programId ) ;
993
+ const validateStateAccount = await connection . getAccountInfo ( validateStatePubkey , commitment ) ;
994
+ if ( validateStateAccount == null ) {
995
+ return instruction ;
996
+ }
997
+ const validateStateData = splToken . getExtraAccountMetas ( validateStateAccount ) ;
998
+
999
+ // Check to make sure the provided keys are in the instruction
1000
+ if ( ! [ source , mint , destination , owner ] . every ( ( key ) => instruction . keys . some ( ( meta ) => meta . pubkey . equals ( key ) ) ) ) {
1001
+ throw new Error ( 'Missing required account in instruction' ) ;
1002
+ }
1003
+
1004
+ const executeInstruction = splToken . createExecuteInstruction (
1005
+ programId ,
1006
+ source ,
1007
+ mint ,
1008
+ destination ,
1009
+ owner ,
1010
+ validateStatePubkey ,
1011
+ BigInt ( amount )
1012
+ ) ;
1013
+
1014
+ for ( const extraAccountMeta of validateStateData ) {
1015
+ executeInstruction . keys . push (
1016
+ deEscalateAccountMeta (
1017
+ await splToken . resolveExtraAccountMeta (
1018
+ connection ,
1019
+ extraAccountMeta ,
1020
+ executeInstruction . keys ,
1021
+ executeInstruction . data ,
1022
+ executeInstruction . programId
1023
+ ) ,
1024
+ executeInstruction . keys
1025
+ )
1026
+ ) ;
1027
+ }
1028
+
1029
+ // Add only the extra accounts resolved from the validation state
1030
+ instruction . keys . push ( ...executeInstruction . keys . slice ( 5 ) ) ;
1031
+
1032
+ // Add the transfer hook program ID and the validation state account
1033
+ instruction . keys . push ( { pubkey : programId , isSigner : false , isWritable : false } ) ;
1034
+ instruction . keys . push ( { pubkey : validateStatePubkey , isSigner : false , isWritable : false } ) ;
1035
+ }
1036
+
1037
+ // TODO: delete (see above)
1038
+ function deEscalateAccountMeta ( accountMeta : AccountMeta , accountMetas : AccountMeta [ ] ) : AccountMeta {
1039
+ const maybeHighestPrivileges = accountMetas
1040
+ . filter ( ( x ) => x . pubkey . equals ( accountMeta . pubkey ) )
1041
+ . reduce < { isSigner : boolean ; isWritable : boolean } | undefined > ( ( acc , x ) => {
1042
+ if ( ! acc ) return { isSigner : x . isSigner , isWritable : x . isWritable } ;
1043
+ return { isSigner : acc . isSigner || x . isSigner , isWritable : acc . isWritable || x . isWritable } ;
1044
+ } , undefined ) ;
1045
+ if ( maybeHighestPrivileges ) {
1046
+ const { isSigner, isWritable } = maybeHighestPrivileges ;
1047
+ if ( ! isSigner && isSigner !== accountMeta . isSigner ) {
1048
+ accountMeta . isSigner = false ;
1049
+ }
1050
+ if ( ! isWritable && isWritable !== accountMeta . isWritable ) {
1051
+ accountMeta . isWritable = false ;
1052
+ }
1053
+ }
1054
+ return accountMeta ;
1055
+ }
0 commit comments