Skip to content

Commit d56f2c1

Browse files
committed
solana: transfer before burn/mint to trigger hooks
1 parent 102f0b4 commit d56f2c1

File tree

10 files changed

+451
-196
lines changed

10 files changed

+451
-196
lines changed

sdk/solana/src/ntt.ts

+55-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Program } from "@coral-xyz/anchor";
2-
import { associatedAddress } from "@coral-xyz/anchor/dist/cjs/utils/token.js";
32
import * as splToken from "@solana/spl-token";
43
import {
54
createAssociatedTokenAccountInstruction,
@@ -9,6 +8,7 @@ import {
98
Connection,
109
Keypair,
1110
PublicKey,
11+
SystemProgram,
1212
Transaction,
1313
TransactionInstruction,
1414
TransactionMessage,
@@ -236,16 +236,11 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
236236
);
237237
}
238238

239-
const custodyAddress = associatedAddress({
240-
mint: args.mint,
241-
owner: this.pdas.tokenAuthority(),
242-
});
243-
244239
const tokenProgram = mintInfo.owner;
245240
const limit = new BN(args.outboundLimit.toString());
246241
const ix = await this.program.methods
247242
.initialize({ chainId, limit: limit, mode })
248-
.accounts({
243+
.accountsStrict({
249244
payer: args.payer.publicKey,
250245
deployer: args.owner.publicKey,
251246
programData: programDataAddress(this.program.programId),
@@ -254,8 +249,10 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
254249
rateLimit: this.pdas.outboxRateLimitAccount(),
255250
tokenProgram,
256251
tokenAuthority: this.pdas.tokenAuthority(),
257-
custody: custodyAddress,
252+
custody: await this.custodyAccountAddress(args.mint, tokenProgram),
258253
bpfLoaderUpgradeableProgram: BPF_LOADER_UPGRADEABLE_PROGRAM_ID,
254+
associatedTokenProgram: splToken.ASSOCIATED_TOKEN_PROGRAM_ID,
255+
systemProgram: SystemProgram.programId,
259256
})
260257
.instruction();
261258

@@ -278,14 +275,15 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
278275

279276
const ix = await this.program.methods
280277
.registerTransceiver()
281-
.accounts({
278+
.accountsStrict({
282279
payer: args.payer.publicKey,
283280
owner: args.owner.publicKey,
284281
config: this.pdas.configAccount(),
285282
transceiver: args.transceiver,
286283
registeredTransceiver: this.pdas.registeredTransceiver(
287284
args.transceiver
288285
),
286+
systemProgram: SystemProgram.programId,
289287
})
290288
.instruction();
291289

@@ -307,6 +305,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
307305
feeCollector: whAccs.wormholeFeeCollector,
308306
sequence: whAccs.wormholeSequence,
309307
program: this.core.address,
308+
systemProgram: SystemProgram.programId,
310309
},
311310
})
312311
.instruction();
@@ -337,11 +336,12 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
337336
chainId: { id: toChainId(peer.chain) },
338337
address: Array.from(peer.address.toUniversalAddress().toUint8Array()),
339338
})
340-
.accounts({
339+
.accountsStrict({
341340
payer: sender,
342341
owner: sender,
343342
config: this.pdas.configAccount(),
344343
peer: this.pdas.transceiverPeerAccount(peer.chain),
344+
systemProgram: SystemProgram.programId,
345345
})
346346
.instruction(),
347347
this.program.methods
@@ -390,12 +390,13 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
390390
limit: new BN(inboundLimit.toString()),
391391
tokenDecimals: tokenDecimals,
392392
})
393-
.accounts({
393+
.accountsStrict({
394394
payer: sender,
395395
owner: sender,
396396
config: this.pdas.configAccount(),
397397
peer: this.pdas.peerAccount(peer.chain),
398398
inboxRateLimit: this.pdas.inboxRateLimitAccount(peer.chain),
399+
systemProgram: SystemProgram.programId,
399400
})
400401
.instruction();
401402

@@ -671,7 +672,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
671672
),
672673
shouldQueue: args.transferArgs.shouldQueue,
673674
})
674-
.accounts({
675+
.accountsStrict({
675676
common: {
676677
payer: args.payer,
677678
config: { config: this.pdas.configAccount() },
@@ -680,10 +681,11 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
680681
tokenProgram: config.tokenProgram,
681682
outboxItem: args.outboxItem,
682683
outboxRateLimit: this.pdas.outboxRateLimitAccount(),
684+
systemProgram: SystemProgram.programId,
685+
custody: config.custody,
683686
},
684687
peer: this.pdas.peerAccount(recipientChain),
685688
inboxRateLimit: this.pdas.inboxRateLimitAccount(recipientChain),
686-
custody: config.custody,
687689
sessionAuthority: sessionAuthority,
688690
})
689691
.instruction();
@@ -712,21 +714,25 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
712714
),
713715
shouldQueue: args.transferArgs.shouldQueue,
714716
})
715-
.accounts({
717+
.accountsStrict({
716718
common: {
717719
payer: args.payer,
718720
config: { config: this.pdas.configAccount() },
719721
mint: config.mint,
720722
from: args.from,
721723
outboxItem: args.outboxItem,
722724
outboxRateLimit: this.pdas.outboxRateLimitAccount(),
725+
custody: config.custody,
726+
tokenProgram: config.tokenProgram,
727+
systemProgram: SystemProgram.programId,
723728
},
724729
peer: this.pdas.peerAccount(recipientChain),
725730
inboxRateLimit: this.pdas.inboxRateLimitAccount(recipientChain),
726731
sessionAuthority: this.pdas.sessionAuthority(
727732
args.fromAuthority,
728733
args.transferArgs
729734
),
735+
tokenAuthority: this.pdas.tokenAuthority(),
730736
})
731737
.instruction();
732738
}
@@ -757,6 +763,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
757763
feeCollector: whAccs.wormholeFeeCollector,
758764
sequence: whAccs.wormholeSequence,
759765
program: this.core.address,
766+
systemProgram: SystemProgram.programId,
760767
},
761768
})
762769
.instruction();
@@ -773,7 +780,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
773780
const emitterChain = wormholeNTT.emitterChain;
774781
return await this.program.methods
775782
.receiveWormholeMessage()
776-
.accounts({
783+
.accountsStrict({
777784
payer: payer,
778785
config: { config: this.pdas.configAccount() },
779786
peer: this.pdas.transceiverPeerAccount(emitterChain),
@@ -785,6 +792,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
785792
emitterChain,
786793
nttMessage.id
787794
),
795+
systemProgram: SystemProgram.programId,
788796
})
789797
.instruction();
790798
}
@@ -805,7 +813,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
805813

806814
return await this.program.methods
807815
.redeem({})
808-
.accounts({
816+
.accountsStrict({
809817
payer: payer,
810818
config: this.pdas.configAccount(),
811819
peer: nttManagerPeer,
@@ -818,6 +826,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
818826
inboxItem,
819827
inboxRateLimit,
820828
outboxRateLimit: this.pdas.outboxRateLimitAccount(),
829+
systemProgram: SystemProgram.programId,
821830
})
822831
.instruction();
823832
}
@@ -847,7 +856,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
847856
.releaseInboundMint({
848857
revertOnDelay: args.revertOnDelay,
849858
})
850-
.accounts({
859+
.accountsStrict({
851860
common: {
852861
payer: args.payer,
853862
config: { config: this.pdas.configAccount() },
@@ -858,6 +867,8 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
858867
),
859868
mint: config.mint,
860869
tokenAuthority: this.pdas.tokenAuthority(),
870+
custody: config.custody,
871+
tokenProgram: config.tokenProgram,
861872
},
862873
})
863874
.instruction();
@@ -888,7 +899,7 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
888899
.releaseInboundUnlock({
889900
revertOnDelay: args.revertOnDelay,
890901
})
891-
.accounts({
902+
.accountsStrict({
892903
common: {
893904
payer: args.payer,
894905
config: { config: this.pdas.configAccount() },
@@ -899,17 +910,37 @@ export class SolanaNtt<N extends Network, C extends SolanaChains>
899910
),
900911
mint: config.mint,
901912
tokenAuthority: this.pdas.tokenAuthority(),
913+
custody: config.custody,
914+
tokenProgram: config.tokenProgram,
902915
},
903-
custody: config.custody,
904916
})
905917
.instruction();
906918
}
907919

908-
async custodyAccountAddress(mint: PublicKey): Promise<PublicKey> {
909-
return associatedAddress({
910-
mint: mint,
911-
owner: this.pdas.tokenAuthority(),
912-
});
920+
/**
921+
* Returns the address of the custody account. If the config is available
922+
* (i.e. the program is initialised), the mint is derived from the config.
923+
* Otherwise, the mint must be provided.
924+
*/
925+
async custodyAccountAddress(
926+
configOrMint: NttBindings.Config | PublicKey,
927+
tokenProgram = splToken.TOKEN_PROGRAM_ID
928+
): Promise<PublicKey> {
929+
if (configOrMint instanceof PublicKey) {
930+
return splToken.getAssociatedTokenAddress(
931+
configOrMint,
932+
this.pdas.tokenAuthority(),
933+
true,
934+
tokenProgram
935+
);
936+
} else {
937+
return splToken.getAssociatedTokenAddress(
938+
configOrMint.mint,
939+
this.pdas.tokenAuthority(),
940+
true,
941+
configOrMint.tokenProgram
942+
);
943+
}
913944
}
914945

915946
createUnsignedTx(

solana/idl/json/example_native_token_transfers.json

+44-14
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@
4545
"isMut": true,
4646
"isSigner": false,
4747
"docs": [
48-
"The custody account that holds tokens in locking mode.",
49-
"NOTE: the account is unconditionally initialized, but not used in",
50-
"burning mode.",
48+
"The custody account that holds tokens in locking mode and temporarily",
49+
"holds tokens in burning mode.",
5150
"function if the token account has already been created."
5251
]
5352
},
@@ -139,6 +138,16 @@
139138
"isMut": true,
140139
"isSigner": false
141140
},
141+
{
142+
"name": "custody",
143+
"isMut": true,
144+
"isSigner": false,
145+
"docs": [
146+
"Tokens are always transferred to the custody account first regardless of",
147+
"the mode.",
148+
"For an explanation, see the note in [`transfer_burn`]."
149+
]
150+
},
142151
{
143152
"name": "systemProgram",
144153
"isMut": false,
@@ -159,6 +168,14 @@
159168
{
160169
"name": "sessionAuthority",
161170
"isMut": false,
171+
"isSigner": false,
172+
"docs": [
173+
"See [`crate::SESSION_AUTHORITY_SEED`] for an explanation of the flow."
174+
]
175+
},
176+
{
177+
"name": "tokenAuthority",
178+
"isMut": false,
162179
"isSigner": false
163180
}
164181
],
@@ -220,6 +237,16 @@
220237
"isMut": true,
221238
"isSigner": false
222239
},
240+
{
241+
"name": "custody",
242+
"isMut": true,
243+
"isSigner": false,
244+
"docs": [
245+
"Tokens are always transferred to the custody account first regardless of",
246+
"the mode.",
247+
"For an explanation, see the note in [`transfer_burn`]."
248+
]
249+
},
223250
{
224251
"name": "systemProgram",
225252
"isMut": false,
@@ -240,12 +267,10 @@
240267
{
241268
"name": "sessionAuthority",
242269
"isMut": false,
243-
"isSigner": false
244-
},
245-
{
246-
"name": "custody",
247-
"isMut": true,
248-
"isSigner": false
270+
"isSigner": false,
271+
"docs": [
272+
"See [`crate::SESSION_AUTHORITY_SEED`] for an explanation of the flow."
273+
]
249274
}
250275
],
251276
"args": [
@@ -378,6 +403,11 @@
378403
"name": "tokenProgram",
379404
"isMut": false,
380405
"isSigner": false
406+
},
407+
{
408+
"name": "custody",
409+
"isMut": true,
410+
"isSigner": false
381411
}
382412
]
383413
}
@@ -436,13 +466,13 @@
436466
"name": "tokenProgram",
437467
"isMut": false,
438468
"isSigner": false
469+
},
470+
{
471+
"name": "custody",
472+
"isMut": true,
473+
"isSigner": false
439474
}
440475
]
441-
},
442-
{
443-
"name": "custody",
444-
"isMut": true,
445-
"isSigner": false
446476
}
447477
],
448478
"args": [

0 commit comments

Comments
 (0)