1
+ use std:: ops:: { Deref , DerefMut } ;
2
+
1
3
use anchor_lang:: prelude:: * ;
2
4
use anchor_spl:: token_interface;
3
5
use ntt_messages:: mode:: Mode ;
@@ -66,6 +68,34 @@ pub struct ReleaseInboundMint<'info> {
66
68
common : ReleaseInbound < ' info > ,
67
69
}
68
70
71
+ impl < ' info > Deref for ReleaseInboundMint < ' info > {
72
+ type Target = ReleaseInbound < ' info > ;
73
+
74
+ fn deref ( & self ) -> & Self :: Target {
75
+ & self . common
76
+ }
77
+ }
78
+
79
+ impl Deref for ReleaseInboundMintBumps {
80
+ type Target = ReleaseInboundBumps ;
81
+
82
+ fn deref ( & self ) -> & Self :: Target {
83
+ & self . common
84
+ }
85
+ }
86
+
87
+ impl < ' info > DerefMut for ReleaseInboundMint < ' info > {
88
+ fn deref_mut ( & mut self ) -> & mut Self :: Target {
89
+ & mut self . common
90
+ }
91
+ }
92
+
93
+ #[ derive( Accounts ) ]
94
+ pub struct ReleaseInboundMintDefault < ' info > {
95
+ #[ account( constraint = common. mint. mint_authority. unwrap( ) == common. token_authority. key( ) ) ]
96
+ common : ReleaseInboundMint < ' info > ,
97
+ }
98
+
69
99
/// Release an inbound transfer and mint the tokens to the recipient.
70
100
/// When `revert_on_error` is true, the transaction will revert if the
71
101
/// release timestamp has not been reached. When `revert_on_error` is false, the
@@ -74,23 +104,9 @@ pub struct ReleaseInboundMint<'info> {
74
104
/// together with [`crate::instructions::redeem`] in a transaction, so that the minting
75
105
/// is attempted optimistically.
76
106
pub fn release_inbound_mint < ' info > (
77
- ctx : Context < ' _ , ' _ , ' _ , ' info , ReleaseInboundMint < ' info > > ,
107
+ ctx : Context < ' _ , ' _ , ' _ , ' info , ReleaseInboundMintDefault < ' info > > ,
78
108
args : ReleaseInboundArgs ,
79
109
) -> Result < ( ) > {
80
- let inbox_item = & mut ctx. accounts . common . inbox_item ;
81
-
82
- let released = inbox_item. try_release ( ) ?;
83
-
84
- if !released {
85
- if args. revert_on_delay {
86
- return Err ( NTTError :: CantReleaseYet . into ( ) ) ;
87
- } else {
88
- return Ok ( ( ) ) ;
89
- }
90
- }
91
-
92
- assert ! ( inbox_item. release_status == ReleaseStatus :: Released ) ;
93
-
94
110
// NOTE: minting tokens is a two-step process:
95
111
// 1. Mint tokens to the custody account
96
112
// 2. Transfer the tokens from the custody account to the recipient
@@ -120,7 +136,7 @@ pub fn release_inbound_mint<'info>(
120
136
& [ ctx. bumps . common . token_authority ] ,
121
137
] ] ,
122
138
) ,
123
- inbox_item. amount ,
139
+ ctx . accounts . common . inbox_item . amount ,
124
140
) ?;
125
141
126
142
// Step 2: transfer the tokens from the custody account to the recipient
@@ -131,13 +147,100 @@ pub fn release_inbound_mint<'info>(
131
147
ctx. accounts . common . recipient . to_account_info ( ) ,
132
148
ctx. accounts . common . token_authority . to_account_info ( ) ,
133
149
ctx. remaining_accounts ,
134
- inbox_item. amount ,
150
+ ctx. accounts . common . inbox_item . amount ,
151
+ ctx. accounts . common . mint . decimals ,
152
+ & [ & [
153
+ crate :: TOKEN_AUTHORITY_SEED ,
154
+ & [ ctx. bumps . common . token_authority ] ,
155
+ ] ] ,
156
+ ) ?;
157
+
158
+ release_inbox_item ( & mut ctx. accounts . common . inbox_item , args. revert_on_delay )
159
+ }
160
+
161
+ #[ derive( Accounts ) ]
162
+ pub struct ReleaseInboundMintMultisig < ' info > {
163
+ #[ account( constraint = common. mint. mint_authority. unwrap( ) == multisig. key( ) ) ]
164
+ common : ReleaseInboundMint < ' info > ,
165
+
166
+ /// CHECK: multisig account should be mint authority
167
+ pub multisig : UncheckedAccount < ' info > ,
168
+ }
169
+
170
+ pub fn release_inbound_mint_multisig < ' info > (
171
+ ctx : Context < ' _ , ' _ , ' _ , ' info , ReleaseInboundMintMultisig < ' info > > ,
172
+ args : ReleaseInboundArgs ,
173
+ ) -> Result < ( ) > {
174
+ // NOTE: minting tokens is a two-step process:
175
+ // 1. Mint tokens to the custody account
176
+ // 2. Transfer the tokens from the custody account to the recipient
177
+ //
178
+ // This is done to ensure that if the token has a transfer hook defined, it
179
+ // will be called after the tokens are minted.
180
+ // Unfortunately the Token2022 program doesn't trigger transfer hooks when
181
+ // minting tokens, so we have to do it "manually" via a transfer.
182
+ //
183
+ // If we didn't do this, transfer hooks could be bypassed by transferring
184
+ // the tokens out through NTT first, then back in to the intended recipient.
185
+ //
186
+ // The [`transfer_burn`] function operates in a similar way
187
+ // (transfer to custody from sender, *then* burn).
188
+
189
+ // Step 1: mint tokens to the custody account
190
+ let ix = spl_token_2022:: instruction:: mint_to (
191
+ & ctx. accounts . common . token_program . key ( ) ,
192
+ & ctx. accounts . common . mint . key ( ) ,
193
+ & ctx. accounts . common . custody . key ( ) ,
194
+ & ctx. accounts . multisig . key ( ) ,
195
+ & [ & ctx. accounts . common . token_authority . key ( ) ] ,
196
+ ctx. accounts . common . inbox_item . amount ,
197
+ ) ?;
198
+ solana_program:: program:: invoke_signed (
199
+ & ix,
200
+ & [
201
+ ctx. accounts . common . custody . to_account_info ( ) ,
202
+ ctx. accounts . common . mint . to_account_info ( ) ,
203
+ ctx. accounts . common . token_authority . to_account_info ( ) ,
204
+ ctx. accounts . multisig . to_account_info ( ) ,
205
+ ] ,
206
+ & [ & [
207
+ crate :: TOKEN_AUTHORITY_SEED ,
208
+ & [ ctx. bumps . common . token_authority ] ,
209
+ ] ] ,
210
+ ) ?;
211
+
212
+ // Step 2: transfer the tokens from the custody account to the recipient
213
+ onchain:: invoke_transfer_checked (
214
+ & ctx. accounts . common . token_program . key ( ) ,
215
+ ctx. accounts . common . custody . to_account_info ( ) ,
216
+ ctx. accounts . common . mint . to_account_info ( ) ,
217
+ ctx. accounts . common . recipient . to_account_info ( ) ,
218
+ ctx. accounts . common . token_authority . to_account_info ( ) ,
219
+ ctx. remaining_accounts ,
220
+ ctx. accounts . common . inbox_item . amount ,
135
221
ctx. accounts . common . mint . decimals ,
136
222
& [ & [
137
223
crate :: TOKEN_AUTHORITY_SEED ,
138
224
& [ ctx. bumps . common . token_authority ] ,
139
225
] ] ,
140
226
) ?;
227
+
228
+ release_inbox_item ( & mut ctx. accounts . common . inbox_item , args. revert_on_delay )
229
+ }
230
+
231
+ fn release_inbox_item ( inbox_item : & mut InboxItem , revert_on_delay : bool ) -> Result < ( ) > {
232
+ let released = inbox_item. try_release ( ) ?;
233
+
234
+ if !released {
235
+ if revert_on_delay {
236
+ return Err ( NTTError :: CantReleaseYet . into ( ) ) ;
237
+ } else {
238
+ return Ok ( ( ) ) ;
239
+ }
240
+ }
241
+
242
+ assert ! ( inbox_item. release_status == ReleaseStatus :: Released ) ;
243
+
141
244
Ok ( ( ) )
142
245
}
143
246
0 commit comments