33use access_control:: AccessControl ;
44use account_utils:: { create_pda_account, SizedData } ;
55use borsh:: { BorshDeserialize , BorshSerialize } ;
6- use hyperlane_core:: { Decode , Encode } ;
6+ use hyperlane_core:: { Decode , Encode , H256 , U256 } ;
77use hyperlane_sealevel_connection_client:: {
8- gas_router:: { GasRouterConfig , HyperlaneGasRouterAccessControl , HyperlaneGasRouterDispatch } ,
8+ gas_router:: { GasRouterConfig , HyperlaneGasRouter , HyperlaneGasRouterAccessControl } ,
99 router:: {
10- HyperlaneRouterAccessControl , HyperlaneRouterDispatch , HyperlaneRouterMessageRecipient ,
10+ HyperlaneRouter , HyperlaneRouterAccessControl , HyperlaneRouterMessageRecipient ,
1111 RemoteRouterConfig ,
1212 } ,
1313 HyperlaneConnectionClient , HyperlaneConnectionClientSetterAccessControl ,
1414} ;
15- use hyperlane_sealevel_igp:: accounts:: InterchainGasPaymasterType ;
15+ use hyperlane_sealevel_igp:: {
16+ accounts:: InterchainGasPaymasterType ,
17+ instruction:: { Instruction as IgpInstruction , PayForGas as IgpPayForGas } ,
18+ } ;
1619use hyperlane_sealevel_mailbox:: {
20+ instruction:: { Instruction as MailboxInstruction , OutboxDispatch as MailboxOutboxDispatch } ,
1721 mailbox_message_dispatch_authority_pda_seeds, mailbox_process_authority_pda_seeds,
1822} ;
1923use hyperlane_sealevel_message_recipient_interface:: HandleInstruction ;
@@ -22,9 +26,9 @@ use serializable_account_meta::{SerializableAccountMeta, SimulationReturnData};
2226use solana_program:: {
2327 account_info:: { next_account_info, AccountInfo } ,
2428 entrypoint:: ProgramResult ,
25- instruction:: AccountMeta ,
29+ instruction:: { AccountMeta , Instruction } ,
2630 msg,
27- program:: set_return_data,
31+ program:: { get_return_data , invoke , invoke_signed , set_return_data} ,
2832 program_error:: ProgramError ,
2933 pubkey:: Pubkey ,
3034 rent:: Rent ,
@@ -305,17 +309,71 @@ where
305309 return Err ( ProgramError :: IncorrectProgramId ) ;
306310 }
307311
308- // Account 3: Mailbox program
312+ // Resolve the enrolled router for the destination domain.
313+ let router = * token
314+ . router ( xfer. destination_domain )
315+ . ok_or ( ProgramError :: InvalidArgument ) ?;
316+
317+ // Delegate to the shared remote-dispatch helper.
318+ Self :: transfer_remote_to (
319+ program_id,
320+ & token,
321+ system_program_account,
322+ spl_noop,
323+ accounts_iter,
324+ xfer. destination_domain ,
325+ xfer. recipient ,
326+ router,
327+ xfer. amount_or_id ,
328+ ) ?;
329+
330+ msg ! (
331+ "Warp route transfer completed to destination: {}, recipient: {}, router: {}" ,
332+ xfer. destination_domain,
333+ xfer. recipient,
334+ router
335+ ) ;
336+
337+ Ok ( ( ) )
338+ }
339+
340+ /// Shared remote-dispatch helper: validates mailbox accounts, converts
341+ /// amounts, calls plugin `transfer_in`, dispatches via mailbox CPI, and
342+ /// optionally pays IGP. The caller is responsible for validating
343+ /// system_program, spl_noop, loading the token, and resolving the router.
344+ ///
345+ /// Accounts consumed from the iterator (starting after spl_noop / token PDA):
346+ /// - mailbox program
347+ /// - mailbox outbox
348+ /// - dispatch authority PDA
349+ /// - sender wallet (signer)
350+ /// - unique message account
351+ /// - dispatched message PDA
352+ /// - optional IGP accounts
353+ /// - plugin transfer_in accounts
354+ #[ allow( clippy:: too_many_arguments) ]
355+ pub fn transfer_remote_to < ' a , ' b > (
356+ program_id : & Pubkey ,
357+ token : & HyperlaneToken < T > ,
358+ system_program_account : & ' a AccountInfo < ' b > ,
359+ spl_noop : & ' a AccountInfo < ' b > ,
360+ accounts_iter : & mut std:: slice:: Iter < ' a , AccountInfo < ' b > > ,
361+ destination_domain : u32 ,
362+ recipient : H256 ,
363+ router : H256 ,
364+ amount_or_id : U256 ,
365+ ) -> ProgramResult {
366+ // Mailbox program
309367 let mailbox_info = next_account_info ( accounts_iter) ?;
310368 if mailbox_info. key != & token. mailbox {
311369 return Err ( ProgramError :: IncorrectProgramId ) ;
312370 }
313371
314- // Account 4: Mailbox Outbox data account.
372+ // Mailbox Outbox data account.
315373 // No verification is performed here, the Mailbox will do that.
316374 let mailbox_outbox_account = next_account_info ( accounts_iter) ?;
317375
318- // Account 5: Message dispatch authority
376+ // Message dispatch authority
319377 let dispatch_authority_account = next_account_info ( accounts_iter) ?;
320378 let dispatch_authority_seeds: & [ & [ u8 ] ] =
321379 mailbox_message_dispatch_authority_pda_seeds ! ( token. dispatch_authority_bump) ;
@@ -325,37 +383,37 @@ where
325383 return Err ( ProgramError :: InvalidArgument ) ;
326384 }
327385
328- // Account 6: Sender account / mailbox payer
386+ // Sender account / mailbox payer
329387 let sender_wallet = next_account_info ( accounts_iter) ?;
330388 if !sender_wallet. is_signer {
331389 return Err ( ProgramError :: MissingRequiredSignature ) ;
332390 }
333391
334- // Account 7: Unique message / gas payment account
392+ // Unique message / gas payment account
335393 // Defer to the checks in the Mailbox / IGP, no need to verify anything here.
336394 let unique_message_account = next_account_info ( accounts_iter) ?;
337395
338- // Account 8: Message storage PDA.
396+ // Message storage PDA.
339397 // Similarly defer to the checks in the Mailbox to ensure account validity.
340398 let dispatched_message_pda = next_account_info ( accounts_iter) ?;
341399
342400 let igp_payment_accounts =
343401 if let Some ( ( igp_program_id, igp_account_type) ) = token. interchain_gas_paymaster ( ) {
344- // Account 9: The IGP program
402+ // The IGP program
345403 let igp_program_account = next_account_info ( accounts_iter) ?;
346404 if igp_program_account. key != igp_program_id {
347405 return Err ( ProgramError :: InvalidArgument ) ;
348406 }
349407
350- // Account 10: The IGP program data.
408+ // The IGP program data.
351409 // No verification is performed here, the IGP will do that.
352410 let igp_program_data_account = next_account_info ( accounts_iter) ?;
353411
354- // Account 11: The gas payment PDA.
412+ // The gas payment PDA.
355413 // No verification is performed here, the IGP will do that.
356414 let igp_payment_pda_account = next_account_info ( accounts_iter) ?;
357415
358- // Account 12: The configured IGP account.
416+ // The configured IGP account.
359417 let configured_igp_account = next_account_info ( accounts_iter) ?;
360418 if configured_igp_account. key != igp_account_type. key ( ) {
361419 return Err ( ProgramError :: InvalidArgument ) ;
@@ -393,7 +451,6 @@ where
393451 igp_payment_account_infos. push ( configured_igp_account. clone ( ) ) ;
394452 }
395453 InterchainGasPaymasterType :: OverheadIgp ( _) => {
396- // Account 13: The inner IGP account.
397454 let inner_igp_account = next_account_info ( accounts_iter) ?;
398455
399456 // The inner IGP is expected first, then the overhead IGP.
@@ -412,8 +469,7 @@ where
412469 } ;
413470
414471 // The amount denominated in the local decimals.
415- let local_amount: u64 = xfer
416- . amount_or_id
472+ let local_amount: u64 = amount_or_id
417473 . try_into ( )
418474 . map_err ( |_| Error :: IntegerOverflow ) ?;
419475 // Convert to the remote number of decimals, which is universally understood
@@ -423,7 +479,7 @@ where
423479 // Transfer `local_amount` of tokens in...
424480 T :: transfer_in (
425481 program_id,
426- & * token,
482+ token,
427483 sender_wallet,
428484 accounts_iter,
429485 local_amount,
@@ -433,6 +489,16 @@ where
433489 return Err ( ProgramError :: from ( Error :: ExtraneousAccount ) ) ;
434490 }
435491
492+ // The token message body, which specifies the remote_amount.
493+ let token_transfer_message = TokenMessage :: new ( recipient, remote_amount, vec ! [ ] ) . to_vec ( ) ;
494+
495+ // Build mailbox dispatch CPI with explicit router as recipient.
496+ let dispatch_instruction = MailboxInstruction :: OutboxDispatch ( MailboxOutboxDispatch {
497+ sender : * program_id,
498+ destination_domain,
499+ recipient : router,
500+ message_body : token_transfer_message,
501+ } ) ;
436502 let dispatch_account_metas = vec ! [
437503 AccountMeta :: new( * mailbox_outbox_account. key, false ) ,
438504 AccountMeta :: new_readonly( * dispatch_authority_account. key, true ) ,
@@ -452,42 +518,49 @@ where
452518 dispatched_message_pda. clone ( ) ,
453519 ] ;
454520
455- // The token message body, which specifies the remote_amount.
456- let token_transfer_message =
457- TokenMessage :: new ( xfer. recipient , remote_amount, vec ! [ ] ) . to_vec ( ) ;
521+ let mailbox_ixn = Instruction {
522+ program_id : token. mailbox ,
523+ data : dispatch_instruction
524+ . into_instruction_data ( )
525+ . map_err ( |_| ProgramError :: BorshIoError ) ?,
526+ accounts : dispatch_account_metas,
527+ } ;
528+ invoke_signed (
529+ & mailbox_ixn,
530+ dispatch_account_infos,
531+ & [ dispatch_authority_seeds] ,
532+ ) ?;
458533
534+ // Pay for gas if IGP is configured.
459535 if let Some ( ( igp_payment_account_metas, igp_payment_account_infos) ) = igp_payment_accounts {
460- // Dispatch the message and pay for gas.
461- HyperlaneGasRouterDispatch :: dispatch_with_gas (
462- & * token,
463- program_id,
464- dispatch_authority_seeds,
465- xfer. destination_domain ,
466- token_transfer_message,
467- dispatch_account_metas,
468- dispatch_account_infos,
536+ let ( igp_program_id, _) = token
537+ . interchain_gas_paymaster ( )
538+ . ok_or ( ProgramError :: InvalidArgument ) ?;
539+
540+ let ( returning_program_id, returned_data) =
541+ get_return_data ( ) . ok_or ( ProgramError :: InvalidArgument ) ?;
542+ if returning_program_id != token. mailbox {
543+ return Err ( ProgramError :: InvalidArgument ) ;
544+ }
545+ let message_id =
546+ H256 :: try_from_slice ( & returned_data) . map_err ( |_| ProgramError :: InvalidArgument ) ?;
547+
548+ let destination_gas = token
549+ . destination_gas ( destination_domain)
550+ . ok_or ( ProgramError :: InvalidArgument ) ?;
551+
552+ let igp_ixn = Instruction :: new_with_borsh (
553+ * igp_program_id,
554+ & IgpInstruction :: PayForGas ( IgpPayForGas {
555+ message_id,
556+ destination_domain,
557+ gas_amount : destination_gas,
558+ } ) ,
469559 igp_payment_account_metas,
470- & igp_payment_account_infos,
471- ) ?;
472- } else {
473- // Dispatch the message.
474- token. dispatch (
475- program_id,
476- dispatch_authority_seeds,
477- xfer. destination_domain ,
478- token_transfer_message,
479- dispatch_account_metas,
480- dispatch_account_infos,
481- ) ?;
560+ ) ;
561+ invoke ( & igp_ixn, & igp_payment_account_infos) ?;
482562 }
483563
484- msg ! (
485- "Warp route transfer completed to destination: {}, recipient: {}, remote_amount: {}" ,
486- xfer. destination_domain,
487- xfer. recipient,
488- remote_amount
489- ) ;
490-
491564 Ok ( ( ) )
492565 }
493566
0 commit comments