solana: token bridge transfer with payload
This commit is contained in:
parent
d8e7a5f93f
commit
ee4583099f
|
@ -8,6 +8,9 @@ edition = "2018"
|
||||||
# Helper methods will target the Wormhole mainnet contract addresses.
|
# Helper methods will target the Wormhole mainnet contract addresses.
|
||||||
mainnet = []
|
mainnet = []
|
||||||
|
|
||||||
|
# Helper methosd will target the Wormhole testnet contract addresses.
|
||||||
|
testnet = []
|
||||||
|
|
||||||
# Helper methosd will target the Wormhole devnet contract addresses.
|
# Helper methosd will target the Wormhole devnet contract addresses.
|
||||||
devnet = []
|
devnet = []
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
pub mod attest;
|
pub mod attest;
|
||||||
pub mod complete_transfer;
|
pub mod complete_transfer;
|
||||||
|
pub mod complete_transfer_payload;
|
||||||
pub mod create_wrapped;
|
pub mod create_wrapped;
|
||||||
pub mod governance;
|
pub mod governance;
|
||||||
pub mod initialize;
|
pub mod initialize;
|
||||||
|
@ -7,6 +8,7 @@ pub mod transfer;
|
||||||
|
|
||||||
pub use attest::*;
|
pub use attest::*;
|
||||||
pub use complete_transfer::*;
|
pub use complete_transfer::*;
|
||||||
|
pub use complete_transfer_payload::*;
|
||||||
pub use create_wrapped::*;
|
pub use create_wrapped::*;
|
||||||
pub use governance::*;
|
pub use governance::*;
|
||||||
pub use initialize::*;
|
pub use initialize::*;
|
||||||
|
|
|
@ -0,0 +1,312 @@
|
||||||
|
use crate::{
|
||||||
|
accounts::{
|
||||||
|
ConfigAccount,
|
||||||
|
CustodyAccount,
|
||||||
|
CustodyAccountDerivationData,
|
||||||
|
CustodySigner,
|
||||||
|
Endpoint,
|
||||||
|
EndpointDerivationData,
|
||||||
|
MintSigner,
|
||||||
|
WrappedDerivationData,
|
||||||
|
WrappedMetaDerivationData,
|
||||||
|
WrappedMint,
|
||||||
|
WrappedTokenMeta,
|
||||||
|
},
|
||||||
|
messages::{
|
||||||
|
PayloadTransfer,
|
||||||
|
PayloadTransferWithPayload,
|
||||||
|
},
|
||||||
|
types::*,
|
||||||
|
TokenBridgeError::*,
|
||||||
|
};
|
||||||
|
use bridge::{
|
||||||
|
vaa::ClaimableVAA,
|
||||||
|
CHAIN_ID_SOLANA,
|
||||||
|
};
|
||||||
|
use solana_program::{
|
||||||
|
account_info::AccountInfo,
|
||||||
|
program::invoke_signed,
|
||||||
|
program_error::ProgramError,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
use solitaire::{
|
||||||
|
processors::seeded::{
|
||||||
|
invoke_seeded,
|
||||||
|
Seeded,
|
||||||
|
},
|
||||||
|
CreationLamports::Exempt,
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
use spl_token::state::{
|
||||||
|
Account,
|
||||||
|
Mint,
|
||||||
|
};
|
||||||
|
use std::ops::{
|
||||||
|
Deref,
|
||||||
|
DerefMut,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(FromAccounts)]
|
||||||
|
pub struct CompleteNativeWithPayload<'b> {
|
||||||
|
pub payer: Mut<Signer<AccountInfo<'b>>>,
|
||||||
|
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
|
||||||
|
|
||||||
|
pub vaa: ClaimableVAA<'b, PayloadTransferWithPayload>,
|
||||||
|
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
|
||||||
|
|
||||||
|
pub to: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
|
||||||
|
/// Transfer with payload can only be redeemed by the recipient. The idea is
|
||||||
|
/// to target contracts which can then decide how to process the payload.
|
||||||
|
///
|
||||||
|
/// The actual recipient (the `to` field above) is an associated token
|
||||||
|
/// account of the target contract and not the contract itself, so we also need
|
||||||
|
/// to take the target contract's address directly. This will be the owner
|
||||||
|
/// of the associated token account. This ownership check cannot be
|
||||||
|
/// expressed in Solitaire, so we have to check it explicitly in
|
||||||
|
/// [`complete_native_with_payload`]
|
||||||
|
/// We require that the contract is a signer of this transaction.
|
||||||
|
pub to_owner: MaybeMut<Signer<Info<'b>>>,
|
||||||
|
pub to_fees: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
|
||||||
|
pub custody: Mut<CustodyAccount<'b, { AccountState::Initialized }>>,
|
||||||
|
pub mint: Data<'b, SplMint, { AccountState::Initialized }>,
|
||||||
|
|
||||||
|
pub custody_signer: CustodySigner<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&CompleteNativeWithPayload<'a>> for EndpointDerivationData {
|
||||||
|
fn from(accs: &CompleteNativeWithPayload<'a>) -> Self {
|
||||||
|
EndpointDerivationData {
|
||||||
|
emitter_chain: accs.vaa.meta().emitter_chain,
|
||||||
|
emitter_address: accs.vaa.meta().emitter_address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&CompleteNativeWithPayload<'a>> for CustodyAccountDerivationData {
|
||||||
|
fn from(accs: &CompleteNativeWithPayload<'a>) -> Self {
|
||||||
|
CustodyAccountDerivationData {
|
||||||
|
mint: *accs.mint.info().key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> InstructionContext<'b> for CompleteNativeWithPayload<'b> {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BorshDeserialize, BorshSerialize, Default)]
|
||||||
|
pub struct CompleteNativeWithPayloadData {}
|
||||||
|
|
||||||
|
pub fn complete_native_with_payload(
|
||||||
|
ctx: &ExecutionContext,
|
||||||
|
accs: &mut CompleteNativeWithPayload,
|
||||||
|
data: CompleteNativeWithPayloadData,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Verify the chain registration
|
||||||
|
let derivation_data: EndpointDerivationData = (&*accs).into();
|
||||||
|
accs.chain_registration
|
||||||
|
.verify_derivation(ctx.program_id, &derivation_data)?;
|
||||||
|
|
||||||
|
// Verify that the custody account is derived correctly
|
||||||
|
let derivation_data: CustodyAccountDerivationData = (&*accs).into();
|
||||||
|
accs.custody
|
||||||
|
.verify_derivation(ctx.program_id, &derivation_data)?;
|
||||||
|
|
||||||
|
// Verify mints
|
||||||
|
if *accs.mint.info().key != accs.to.mint {
|
||||||
|
return Err(InvalidMint.into());
|
||||||
|
}
|
||||||
|
if *accs.mint.info().key != accs.to_fees.mint {
|
||||||
|
return Err(InvalidMint.into());
|
||||||
|
}
|
||||||
|
if *accs.mint.info().key != accs.custody.mint {
|
||||||
|
return Err(InvalidMint.into());
|
||||||
|
}
|
||||||
|
if *accs.custody_signer.key != accs.custody.owner {
|
||||||
|
return Err(WrongAccountOwner.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify VAA
|
||||||
|
if accs.vaa.token_address != accs.mint.info().key.to_bytes() {
|
||||||
|
return Err(InvalidMint.into());
|
||||||
|
}
|
||||||
|
if accs.vaa.token_chain != 1 {
|
||||||
|
return Err(InvalidChain.into());
|
||||||
|
}
|
||||||
|
if accs.vaa.to_chain != CHAIN_ID_SOLANA {
|
||||||
|
return Err(InvalidChain.into());
|
||||||
|
}
|
||||||
|
if accs.vaa.to != accs.to_owner.info().key.to_bytes() {
|
||||||
|
return Err(InvalidRecipient.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// VAA-specified recipient must be token account owner
|
||||||
|
if *accs.to_owner.info().key != accs.to.owner {
|
||||||
|
return Err(InvalidRecipient.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent vaa double signing
|
||||||
|
accs.vaa.verify(ctx.program_id)?;
|
||||||
|
accs.vaa.claim(ctx, accs.payer.key)?;
|
||||||
|
|
||||||
|
let mut amount = accs.vaa.amount.as_u64();
|
||||||
|
let mut fee = accs.vaa.fee.as_u64();
|
||||||
|
|
||||||
|
// Wormhole always caps transfers at 8 decimals; un-truncate if the local token has more
|
||||||
|
if accs.mint.decimals > 8 {
|
||||||
|
amount *= 10u64.pow((accs.mint.decimals - 8) as u32);
|
||||||
|
fee *= 10u64.pow((accs.mint.decimals - 8) as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer tokens
|
||||||
|
let transfer_ix = spl_token::instruction::transfer(
|
||||||
|
&spl_token::id(),
|
||||||
|
accs.custody.info().key,
|
||||||
|
accs.to.info().key,
|
||||||
|
accs.custody_signer.key,
|
||||||
|
&[],
|
||||||
|
amount.checked_sub(fee).unwrap(),
|
||||||
|
)?;
|
||||||
|
invoke_seeded(&transfer_ix, ctx, &accs.custody_signer, None)?;
|
||||||
|
|
||||||
|
// Transfer fees
|
||||||
|
let transfer_ix = spl_token::instruction::transfer(
|
||||||
|
&spl_token::id(),
|
||||||
|
accs.custody.info().key,
|
||||||
|
accs.to_fees.info().key,
|
||||||
|
accs.custody_signer.key,
|
||||||
|
&[],
|
||||||
|
fee,
|
||||||
|
)?;
|
||||||
|
invoke_seeded(&transfer_ix, ctx, &accs.custody_signer, None)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(FromAccounts)]
|
||||||
|
pub struct CompleteWrappedWithPayload<'b> {
|
||||||
|
pub payer: Mut<Signer<AccountInfo<'b>>>,
|
||||||
|
pub config: ConfigAccount<'b, { AccountState::Initialized }>,
|
||||||
|
|
||||||
|
// Signed message for the transfer
|
||||||
|
pub vaa: ClaimableVAA<'b, PayloadTransferWithPayload>,
|
||||||
|
|
||||||
|
pub chain_registration: Endpoint<'b, { AccountState::Initialized }>,
|
||||||
|
|
||||||
|
pub to: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
|
||||||
|
/// Transfer with payload can only be redeemed by the recipient. The idea is
|
||||||
|
/// to target contracts which can then decide how to process the payload.
|
||||||
|
///
|
||||||
|
/// The actual recipient (the `to` field above) is an associated token
|
||||||
|
/// account of the target contract and not the contract itself, so we also need
|
||||||
|
/// to take the target contract's address directly. This will be the owner
|
||||||
|
/// of the associated token account. This ownership check cannot be
|
||||||
|
/// expressed in Solitaire, so we have to check it explicitly in
|
||||||
|
/// [`complete_native_with_payload`]
|
||||||
|
/// We require that the contract is a signer of this transaction.
|
||||||
|
pub to_owner: MaybeMut<Signer<Info<'b>>>,
|
||||||
|
pub to_fees: Mut<Data<'b, SplAccount, { AccountState::Initialized }>>,
|
||||||
|
pub mint: Mut<WrappedMint<'b, { AccountState::Initialized }>>,
|
||||||
|
pub wrapped_meta: WrappedTokenMeta<'b, { AccountState::Initialized }>,
|
||||||
|
|
||||||
|
pub mint_authority: MintSigner<'b>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&CompleteWrappedWithPayload<'a>> for EndpointDerivationData {
|
||||||
|
fn from(accs: &CompleteWrappedWithPayload<'a>) -> Self {
|
||||||
|
EndpointDerivationData {
|
||||||
|
emitter_chain: accs.vaa.meta().emitter_chain,
|
||||||
|
emitter_address: accs.vaa.meta().emitter_address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&CompleteWrappedWithPayload<'a>> for WrappedDerivationData {
|
||||||
|
fn from(accs: &CompleteWrappedWithPayload<'a>) -> Self {
|
||||||
|
WrappedDerivationData {
|
||||||
|
token_chain: accs.vaa.token_chain,
|
||||||
|
token_address: accs.vaa.token_address,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'b> InstructionContext<'b> for CompleteWrappedWithPayload<'b> {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BorshDeserialize, BorshSerialize, Default)]
|
||||||
|
pub struct CompleteWrappedWithPayloadData {}
|
||||||
|
|
||||||
|
pub fn complete_wrapped_with_payload(
|
||||||
|
ctx: &ExecutionContext,
|
||||||
|
accs: &mut CompleteWrappedWithPayload,
|
||||||
|
data: CompleteWrappedWithPayloadData,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Verify the chain registration
|
||||||
|
let derivation_data: EndpointDerivationData = (&*accs).into();
|
||||||
|
accs.chain_registration
|
||||||
|
.verify_derivation(ctx.program_id, &derivation_data)?;
|
||||||
|
|
||||||
|
// Verify mint
|
||||||
|
accs.wrapped_meta.verify_derivation(
|
||||||
|
ctx.program_id,
|
||||||
|
&WrappedMetaDerivationData {
|
||||||
|
mint_key: *accs.mint.info().key,
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
if accs.wrapped_meta.token_address != accs.vaa.token_address
|
||||||
|
|| accs.wrapped_meta.chain != accs.vaa.token_chain
|
||||||
|
{
|
||||||
|
return Err(InvalidMint.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify mints
|
||||||
|
if *accs.mint.info().key != accs.to.mint {
|
||||||
|
return Err(InvalidMint.into());
|
||||||
|
}
|
||||||
|
if *accs.mint.info().key != accs.to_fees.mint {
|
||||||
|
return Err(InvalidMint.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify VAA
|
||||||
|
if accs.vaa.to_chain != CHAIN_ID_SOLANA {
|
||||||
|
return Err(InvalidChain.into());
|
||||||
|
}
|
||||||
|
if accs.vaa.to != accs.to_owner.info().key.to_bytes() {
|
||||||
|
return Err(InvalidRecipient.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// VAA-specified recipient must be token account owner
|
||||||
|
if *accs.to_owner.info().key != accs.to.owner {
|
||||||
|
return Err(InvalidRecipient.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
accs.vaa.verify(ctx.program_id)?;
|
||||||
|
accs.vaa.claim(ctx, accs.payer.key)?;
|
||||||
|
|
||||||
|
// Mint tokens
|
||||||
|
let mint_ix = spl_token::instruction::mint_to(
|
||||||
|
&spl_token::id(),
|
||||||
|
accs.mint.info().key,
|
||||||
|
accs.to.info().key,
|
||||||
|
accs.mint_authority.key,
|
||||||
|
&[],
|
||||||
|
accs.vaa
|
||||||
|
.amount
|
||||||
|
.as_u64()
|
||||||
|
.checked_sub(accs.vaa.fee.as_u64())
|
||||||
|
.unwrap(),
|
||||||
|
)?;
|
||||||
|
invoke_seeded(&mint_ix, ctx, &accs.mint_authority, None)?;
|
||||||
|
|
||||||
|
// Mint fees
|
||||||
|
let mint_ix = spl_token::instruction::mint_to(
|
||||||
|
&spl_token::id(),
|
||||||
|
accs.mint.info().key,
|
||||||
|
accs.to_fees.info().key,
|
||||||
|
accs.mint_authority.key,
|
||||||
|
&[],
|
||||||
|
accs.vaa.fee.as_u64(),
|
||||||
|
)?;
|
||||||
|
invoke_seeded(&mint_ix, ctx, &accs.mint_authority, None)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -13,7 +13,10 @@ use crate::{
|
||||||
WrappedMint,
|
WrappedMint,
|
||||||
WrappedTokenMeta,
|
WrappedTokenMeta,
|
||||||
},
|
},
|
||||||
messages::PayloadTransfer,
|
messages::{
|
||||||
|
PayloadTransfer,
|
||||||
|
PayloadTransferWithPayload,
|
||||||
|
},
|
||||||
types::*,
|
types::*,
|
||||||
TokenBridgeError,
|
TokenBridgeError,
|
||||||
TokenBridgeError::{
|
TokenBridgeError::{
|
||||||
|
@ -68,6 +71,8 @@ use std::ops::{
|
||||||
DerefMut,
|
DerefMut,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub type TransferNativeWithPayload<'b> = TransferNative<'b>;
|
||||||
|
|
||||||
#[derive(FromAccounts)]
|
#[derive(FromAccounts)]
|
||||||
pub struct TransferNative<'b> {
|
pub struct TransferNative<'b> {
|
||||||
pub payer: Mut<Signer<AccountInfo<'b>>>,
|
pub payer: Mut<Signer<AccountInfo<'b>>>,
|
||||||
|
@ -134,66 +139,7 @@ pub fn transfer_native(
|
||||||
return Err(InvalidChain.into());
|
return Err(InvalidChain.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the custody account is derived correctly
|
let (amount, fee) = verify_and_execute_native_transfers(ctx, accs, data.amount, data.fee)?;
|
||||||
let derivation_data: CustodyAccountDerivationData = (&*accs).into();
|
|
||||||
accs.custody
|
|
||||||
.verify_derivation(ctx.program_id, &derivation_data)?;
|
|
||||||
|
|
||||||
// Verify mints
|
|
||||||
if accs.from.mint != *accs.mint.info().key {
|
|
||||||
return Err(TokenBridgeError::InvalidMint.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fee must be less than amount
|
|
||||||
if data.fee > data.amount {
|
|
||||||
return Err(InvalidFee.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the token is not a wrapped token
|
|
||||||
if let COption::Some(mint_authority) = accs.mint.mint_authority {
|
|
||||||
if mint_authority == MintSigner::key(None, ctx.program_id) {
|
|
||||||
return Err(TokenBridgeError::TokenNotNative.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !accs.custody.is_initialized() {
|
|
||||||
accs.custody
|
|
||||||
.create(&(&*accs).into(), ctx, accs.payer.key, Exempt)?;
|
|
||||||
|
|
||||||
let init_ix = spl_token::instruction::initialize_account(
|
|
||||||
&spl_token::id(),
|
|
||||||
accs.custody.info().key,
|
|
||||||
accs.mint.info().key,
|
|
||||||
accs.custody_signer.key,
|
|
||||||
)?;
|
|
||||||
invoke_signed(&init_ix, ctx.accounts, &[])?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let trunc_divisor = 10u64.pow(8.max(accs.mint.decimals as u32) - 8);
|
|
||||||
// Truncate to 8 decimals
|
|
||||||
let amount: u64 = data.amount / trunc_divisor;
|
|
||||||
let fee: u64 = data.fee / trunc_divisor;
|
|
||||||
// Untruncate the amount to drop the remainder so we don't "burn" user's funds.
|
|
||||||
let amount_trunc: u64 = amount * trunc_divisor;
|
|
||||||
|
|
||||||
// Transfer tokens
|
|
||||||
let transfer_ix = spl_token::instruction::transfer(
|
|
||||||
&spl_token::id(),
|
|
||||||
accs.from.info().key,
|
|
||||||
accs.custody.info().key,
|
|
||||||
accs.authority_signer.key,
|
|
||||||
&[],
|
|
||||||
amount_trunc,
|
|
||||||
)?;
|
|
||||||
invoke_seeded(&transfer_ix, ctx, &accs.authority_signer, None)?;
|
|
||||||
|
|
||||||
// Pay fee
|
|
||||||
let transfer_ix = solana_program::system_instruction::transfer(
|
|
||||||
accs.payer.key,
|
|
||||||
accs.fee_collector.key,
|
|
||||||
accs.bridge.config.fee,
|
|
||||||
);
|
|
||||||
invoke(&transfer_ix, ctx.accounts)?;
|
|
||||||
|
|
||||||
// Post message
|
// Post message
|
||||||
let payload = PayloadTransfer {
|
let payload = PayloadTransfer {
|
||||||
|
@ -233,6 +179,137 @@ pub fn transfer_native(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(BorshDeserialize, BorshSerialize, Default)]
|
||||||
|
pub struct TransferNativeWithPayloadData {
|
||||||
|
pub nonce: u32,
|
||||||
|
pub amount: u64,
|
||||||
|
pub fee: u64,
|
||||||
|
pub target_address: Address,
|
||||||
|
pub target_chain: ChainID,
|
||||||
|
pub payload: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer_native_with_payload(
|
||||||
|
ctx: &ExecutionContext,
|
||||||
|
accs: &mut TransferNative,
|
||||||
|
data: TransferNativeWithPayloadData,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Prevent transferring to the same chain.
|
||||||
|
if data.target_chain == CHAIN_ID_SOLANA {
|
||||||
|
return Err(InvalidChain.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (amount, fee) = verify_and_execute_native_transfers(ctx, accs, data.amount, data.fee)?;
|
||||||
|
|
||||||
|
// Post message
|
||||||
|
let payload = PayloadTransferWithPayload {
|
||||||
|
amount: U256::from(amount),
|
||||||
|
token_address: accs.mint.info().key.to_bytes(),
|
||||||
|
token_chain: CHAIN_ID_SOLANA,
|
||||||
|
to: data.target_address,
|
||||||
|
to_chain: data.target_chain,
|
||||||
|
fee: U256::from(fee),
|
||||||
|
payload: data.payload,
|
||||||
|
};
|
||||||
|
let params = (
|
||||||
|
bridge::instruction::Instruction::PostMessage,
|
||||||
|
PostMessageData {
|
||||||
|
nonce: data.nonce,
|
||||||
|
payload: payload.try_to_vec()?,
|
||||||
|
consistency_level: ConsistencyLevel::Finalized,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let ix = Instruction::new_with_bytes(
|
||||||
|
accs.config.wormhole_bridge,
|
||||||
|
params.try_to_vec()?.as_slice(),
|
||||||
|
vec![
|
||||||
|
AccountMeta::new(*accs.bridge.info().key, false),
|
||||||
|
AccountMeta::new(*accs.message.key, true),
|
||||||
|
AccountMeta::new_readonly(*accs.emitter.key, true),
|
||||||
|
AccountMeta::new(*accs.sequence.key, false),
|
||||||
|
AccountMeta::new(*accs.payer.key, true),
|
||||||
|
AccountMeta::new(*accs.fee_collector.key, false),
|
||||||
|
AccountMeta::new_readonly(*accs.clock.info().key, false),
|
||||||
|
AccountMeta::new_readonly(solana_program::system_program::id(), false),
|
||||||
|
AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
invoke_seeded(&ix, ctx, &accs.emitter, None)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_and_execute_native_transfers(
|
||||||
|
ctx: &ExecutionContext,
|
||||||
|
accs: &mut TransferNative,
|
||||||
|
raw_amount: u64,
|
||||||
|
raw_fee: u64,
|
||||||
|
) -> Result<(u64, u64)> {
|
||||||
|
// Verify that the custody account is derived correctly
|
||||||
|
let derivation_data: CustodyAccountDerivationData = (&*accs).into();
|
||||||
|
accs.custody
|
||||||
|
.verify_derivation(ctx.program_id, &derivation_data)?;
|
||||||
|
|
||||||
|
// Verify mints
|
||||||
|
if accs.from.mint != *accs.mint.info().key {
|
||||||
|
return Err(TokenBridgeError::InvalidMint.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fee must be less than amount
|
||||||
|
if raw_fee > raw_amount {
|
||||||
|
return Err(InvalidFee.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the token is not a wrapped token
|
||||||
|
if let COption::Some(mint_authority) = accs.mint.mint_authority {
|
||||||
|
if mint_authority == MintSigner::key(None, ctx.program_id) {
|
||||||
|
return Err(TokenBridgeError::TokenNotNative.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !accs.custody.is_initialized() {
|
||||||
|
accs.custody
|
||||||
|
.create(&(&*accs).into(), ctx, accs.payer.key, Exempt)?;
|
||||||
|
|
||||||
|
let init_ix = spl_token::instruction::initialize_account(
|
||||||
|
&spl_token::id(),
|
||||||
|
accs.custody.info().key,
|
||||||
|
accs.mint.info().key,
|
||||||
|
accs.custody_signer.key,
|
||||||
|
)?;
|
||||||
|
invoke_signed(&init_ix, ctx.accounts, &[])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let trunc_divisor = 10u64.pow(8.max(accs.mint.decimals as u32) - 8);
|
||||||
|
// Truncate to 8 decimals
|
||||||
|
let amount: u64 = raw_amount / trunc_divisor;
|
||||||
|
let fee: u64 = raw_fee / trunc_divisor;
|
||||||
|
// Untruncate the amount to drop the remainder so we don't "burn" user's funds.
|
||||||
|
let amount_trunc: u64 = amount * trunc_divisor;
|
||||||
|
|
||||||
|
// Transfer tokens
|
||||||
|
let transfer_ix = spl_token::instruction::transfer(
|
||||||
|
&spl_token::id(),
|
||||||
|
accs.from.info().key,
|
||||||
|
accs.custody.info().key,
|
||||||
|
accs.authority_signer.key,
|
||||||
|
&[],
|
||||||
|
amount_trunc,
|
||||||
|
)?;
|
||||||
|
invoke_seeded(&transfer_ix, ctx, &accs.authority_signer, None)?;
|
||||||
|
|
||||||
|
// Pay fee
|
||||||
|
let transfer_ix = solana_program::system_instruction::transfer(
|
||||||
|
accs.payer.key,
|
||||||
|
accs.fee_collector.key,
|
||||||
|
accs.bridge.config.fee,
|
||||||
|
);
|
||||||
|
invoke(&transfer_ix, ctx.accounts)?;
|
||||||
|
|
||||||
|
Ok((amount, fee))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(FromAccounts)]
|
#[derive(FromAccounts)]
|
||||||
pub struct TransferWrapped<'b> {
|
pub struct TransferWrapped<'b> {
|
||||||
pub payer: Mut<Signer<AccountInfo<'b>>>,
|
pub payer: Mut<Signer<AccountInfo<'b>>>,
|
||||||
|
@ -263,6 +340,8 @@ pub struct TransferWrapped<'b> {
|
||||||
pub clock: Sysvar<'b, Clock>,
|
pub clock: Sysvar<'b, Clock>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type TransferWrappedWithPayload<'b> = TransferWrapped<'b>;
|
||||||
|
|
||||||
impl<'a> From<&TransferWrapped<'a>> for WrappedDerivationData {
|
impl<'a> From<&TransferWrapped<'a>> for WrappedDerivationData {
|
||||||
fn from(accs: &TransferWrapped<'a>) -> Self {
|
fn from(accs: &TransferWrapped<'a>) -> Self {
|
||||||
WrappedDerivationData {
|
WrappedDerivationData {
|
||||||
|
@ -302,45 +381,7 @@ pub fn transfer_wrapped(
|
||||||
return Err(InvalidChain.into());
|
return Err(InvalidChain.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that the from account is owned by the from_owner
|
verify_and_execute_wrapped_transfers(ctx, accs, data.amount, data.fee)?;
|
||||||
if &accs.from.owner != accs.from_owner.key {
|
|
||||||
return Err(WrongAccountOwner.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify mints
|
|
||||||
if accs.mint.info().key != &accs.from.mint {
|
|
||||||
return Err(TokenBridgeError::InvalidMint.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fee must be less than amount
|
|
||||||
if data.fee > data.amount {
|
|
||||||
return Err(InvalidFee.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that meta is correct
|
|
||||||
let derivation_data: WrappedMetaDerivationData = (&*accs).into();
|
|
||||||
accs.wrapped_meta
|
|
||||||
.verify_derivation(ctx.program_id, &derivation_data)?;
|
|
||||||
|
|
||||||
// Burn tokens
|
|
||||||
let burn_ix = spl_token::instruction::burn(
|
|
||||||
&spl_token::id(),
|
|
||||||
accs.from.info().key,
|
|
||||||
accs.mint.info().key,
|
|
||||||
accs.authority_signer.key,
|
|
||||||
&[],
|
|
||||||
data.amount,
|
|
||||||
)?;
|
|
||||||
invoke_seeded(&burn_ix, ctx, &accs.authority_signer, None)?;
|
|
||||||
|
|
||||||
// Pay fee
|
|
||||||
let transfer_ix = solana_program::system_instruction::transfer(
|
|
||||||
accs.payer.key,
|
|
||||||
accs.fee_collector.key,
|
|
||||||
accs.bridge.config.fee,
|
|
||||||
);
|
|
||||||
|
|
||||||
invoke(&transfer_ix, ctx.accounts)?;
|
|
||||||
|
|
||||||
// Post message
|
// Post message
|
||||||
let payload = PayloadTransfer {
|
let payload = PayloadTransfer {
|
||||||
|
@ -379,3 +420,113 @@ pub fn transfer_wrapped(
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(BorshDeserialize, BorshSerialize, Default)]
|
||||||
|
pub struct TransferWrappedWithPayloadData {
|
||||||
|
pub nonce: u32,
|
||||||
|
pub amount: u64,
|
||||||
|
pub fee: u64,
|
||||||
|
pub target_address: Address,
|
||||||
|
pub target_chain: ChainID,
|
||||||
|
pub payload: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer_wrapped_with_payload(
|
||||||
|
ctx: &ExecutionContext,
|
||||||
|
accs: &mut TransferWrapped,
|
||||||
|
data: TransferWrappedWithPayloadData,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Prevent transferring to the same chain.
|
||||||
|
if data.target_chain == CHAIN_ID_SOLANA {
|
||||||
|
return Err(InvalidChain.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_and_execute_wrapped_transfers(ctx, accs, data.amount, data.fee)?;
|
||||||
|
|
||||||
|
// Post message
|
||||||
|
let payload = PayloadTransferWithPayload {
|
||||||
|
amount: U256::from(data.amount),
|
||||||
|
token_address: accs.wrapped_meta.token_address,
|
||||||
|
token_chain: accs.wrapped_meta.chain,
|
||||||
|
to: data.target_address,
|
||||||
|
to_chain: data.target_chain,
|
||||||
|
fee: U256::from(data.fee),
|
||||||
|
payload: data.payload,
|
||||||
|
};
|
||||||
|
let params = (
|
||||||
|
bridge::instruction::Instruction::PostMessage,
|
||||||
|
PostMessageData {
|
||||||
|
nonce: data.nonce,
|
||||||
|
payload: payload.try_to_vec()?,
|
||||||
|
consistency_level: ConsistencyLevel::Finalized,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let ix = Instruction::new_with_bytes(
|
||||||
|
accs.config.wormhole_bridge,
|
||||||
|
params.try_to_vec()?.as_slice(),
|
||||||
|
vec![
|
||||||
|
AccountMeta::new(*accs.bridge.info().key, false),
|
||||||
|
AccountMeta::new(*accs.message.key, true),
|
||||||
|
AccountMeta::new_readonly(*accs.emitter.key, true),
|
||||||
|
AccountMeta::new(*accs.sequence.key, false),
|
||||||
|
AccountMeta::new(*accs.payer.key, true),
|
||||||
|
AccountMeta::new(*accs.fee_collector.key, false),
|
||||||
|
AccountMeta::new_readonly(*accs.clock.info().key, false),
|
||||||
|
AccountMeta::new_readonly(solana_program::system_program::id(), false),
|
||||||
|
AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
invoke_seeded(&ix, ctx, &accs.emitter, None)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_and_execute_wrapped_transfers(
|
||||||
|
ctx: &ExecutionContext,
|
||||||
|
accs: &mut TransferWrapped,
|
||||||
|
amount: u64,
|
||||||
|
fee: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
// Verify that the from account is owned by the from_owner
|
||||||
|
if &accs.from.owner != accs.from_owner.key {
|
||||||
|
return Err(WrongAccountOwner.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify mints
|
||||||
|
if accs.mint.info().key != &accs.from.mint {
|
||||||
|
return Err(TokenBridgeError::InvalidMint.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fee must be less than amount
|
||||||
|
if fee > amount {
|
||||||
|
return Err(InvalidFee.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that meta is correct
|
||||||
|
let derivation_data: WrappedMetaDerivationData = (&*accs).into();
|
||||||
|
accs.wrapped_meta
|
||||||
|
.verify_derivation(ctx.program_id, &derivation_data)?;
|
||||||
|
|
||||||
|
// Burn tokens
|
||||||
|
let burn_ix = spl_token::instruction::burn(
|
||||||
|
&spl_token::id(),
|
||||||
|
accs.from.info().key,
|
||||||
|
accs.mint.info().key,
|
||||||
|
accs.authority_signer.key,
|
||||||
|
&[],
|
||||||
|
amount,
|
||||||
|
)?;
|
||||||
|
invoke_seeded(&burn_ix, ctx, &accs.authority_signer, None)?;
|
||||||
|
|
||||||
|
// Pay fee
|
||||||
|
let transfer_ix = solana_program::system_instruction::transfer(
|
||||||
|
accs.payer.key,
|
||||||
|
accs.fee_collector.key,
|
||||||
|
accs.bridge.config.fee,
|
||||||
|
);
|
||||||
|
|
||||||
|
invoke(&transfer_ix, ctx.accounts)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -32,7 +32,12 @@ use crate::{
|
||||||
PayloadAssetMeta,
|
PayloadAssetMeta,
|
||||||
PayloadGovernanceRegisterChain,
|
PayloadGovernanceRegisterChain,
|
||||||
PayloadTransfer,
|
PayloadTransfer,
|
||||||
|
PayloadTransferWithPayload,
|
||||||
},
|
},
|
||||||
|
CompleteNativeWithPayloadData,
|
||||||
|
CompleteWrappedWithPayloadData,
|
||||||
|
TransferNativeWithPayloadData,
|
||||||
|
TransferWrappedWithPayloadData,
|
||||||
};
|
};
|
||||||
use borsh::BorshSerialize;
|
use borsh::BorshSerialize;
|
||||||
use bridge::{
|
use bridge::{
|
||||||
|
@ -72,7 +77,10 @@ use solitaire::{
|
||||||
AccountState,
|
AccountState,
|
||||||
};
|
};
|
||||||
use spl_token::state::Mint;
|
use spl_token::state::Mint;
|
||||||
use std::str::FromStr;
|
use std::{
|
||||||
|
cmp::min,
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn initialize(
|
pub fn initialize(
|
||||||
program_id: Pubkey,
|
program_id: Pubkey,
|
||||||
|
@ -147,6 +155,66 @@ pub fn complete_native(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn complete_native_with_payload(
|
||||||
|
program_id: Pubkey,
|
||||||
|
bridge_id: Pubkey,
|
||||||
|
payer: Pubkey,
|
||||||
|
message_key: Pubkey,
|
||||||
|
vaa: PostVAAData,
|
||||||
|
to: Pubkey,
|
||||||
|
to_owner: Pubkey,
|
||||||
|
fee_recipient: Option<Pubkey>,
|
||||||
|
mint: Pubkey,
|
||||||
|
data: CompleteNativeWithPayloadData,
|
||||||
|
) -> solitaire::Result<Instruction> {
|
||||||
|
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
|
||||||
|
let (message_acc, claim_acc) = claimable_vaa(program_id, message_key, vaa.clone());
|
||||||
|
let endpoint = Endpoint::<'_, { AccountState::Initialized }>::key(
|
||||||
|
&EndpointDerivationData {
|
||||||
|
emitter_chain: vaa.emitter_chain,
|
||||||
|
emitter_address: vaa.emitter_address,
|
||||||
|
},
|
||||||
|
&program_id,
|
||||||
|
);
|
||||||
|
let custody_key = CustodyAccount::<'_, { AccountState::Initialized }>::key(
|
||||||
|
&CustodyAccountDerivationData { mint },
|
||||||
|
&program_id,
|
||||||
|
);
|
||||||
|
let custody_signer_key = CustodySigner::key(None, &program_id);
|
||||||
|
|
||||||
|
Ok(Instruction {
|
||||||
|
program_id,
|
||||||
|
accounts: vec![
|
||||||
|
AccountMeta::new(payer, true),
|
||||||
|
AccountMeta::new_readonly(config_key, false),
|
||||||
|
message_acc,
|
||||||
|
claim_acc,
|
||||||
|
AccountMeta::new_readonly(endpoint, false),
|
||||||
|
AccountMeta::new(to, false),
|
||||||
|
AccountMeta::new_readonly(to_owner, true),
|
||||||
|
if let Some(fee_r) = fee_recipient {
|
||||||
|
AccountMeta::new(fee_r, false)
|
||||||
|
} else {
|
||||||
|
AccountMeta::new(to, false)
|
||||||
|
},
|
||||||
|
AccountMeta::new(custody_key, false),
|
||||||
|
AccountMeta::new_readonly(mint, false),
|
||||||
|
AccountMeta::new_readonly(custody_signer_key, false),
|
||||||
|
// Dependencies
|
||||||
|
AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
|
||||||
|
AccountMeta::new_readonly(solana_program::system_program::id(), false),
|
||||||
|
// Program
|
||||||
|
AccountMeta::new_readonly(bridge_id, false),
|
||||||
|
AccountMeta::new_readonly(spl_token::id(), false),
|
||||||
|
],
|
||||||
|
data: (
|
||||||
|
crate::instruction::Instruction::CompleteNativeWithPayload,
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
.try_to_vec()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn complete_wrapped(
|
pub fn complete_wrapped(
|
||||||
program_id: Pubkey,
|
program_id: Pubkey,
|
||||||
bridge_id: Pubkey,
|
bridge_id: Pubkey,
|
||||||
|
@ -208,6 +276,73 @@ pub fn complete_wrapped(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn complete_wrapped_with_payload(
|
||||||
|
program_id: Pubkey,
|
||||||
|
bridge_id: Pubkey,
|
||||||
|
payer: Pubkey,
|
||||||
|
message_key: Pubkey,
|
||||||
|
vaa: PostVAAData,
|
||||||
|
payload: PayloadTransferWithPayload,
|
||||||
|
to: Pubkey,
|
||||||
|
to_owner: Pubkey,
|
||||||
|
fee_recipient: Option<Pubkey>,
|
||||||
|
data: CompleteWrappedWithPayloadData,
|
||||||
|
) -> solitaire::Result<Instruction> {
|
||||||
|
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
|
||||||
|
let (message_acc, claim_acc) = claimable_vaa(program_id, message_key, vaa.clone());
|
||||||
|
let endpoint = Endpoint::<'_, { AccountState::Initialized }>::key(
|
||||||
|
&EndpointDerivationData {
|
||||||
|
emitter_chain: vaa.emitter_chain,
|
||||||
|
emitter_address: vaa.emitter_address,
|
||||||
|
},
|
||||||
|
&program_id,
|
||||||
|
);
|
||||||
|
let mint_key = WrappedMint::<'_, { AccountState::Uninitialized }>::key(
|
||||||
|
&WrappedDerivationData {
|
||||||
|
token_chain: payload.token_chain,
|
||||||
|
token_address: payload.token_address,
|
||||||
|
},
|
||||||
|
&program_id,
|
||||||
|
);
|
||||||
|
let meta_key = WrappedTokenMeta::<'_, { AccountState::Uninitialized }>::key(
|
||||||
|
&WrappedMetaDerivationData { mint_key },
|
||||||
|
&program_id,
|
||||||
|
);
|
||||||
|
let mint_authority_key = MintSigner::key(None, &program_id);
|
||||||
|
|
||||||
|
Ok(Instruction {
|
||||||
|
program_id,
|
||||||
|
accounts: vec![
|
||||||
|
AccountMeta::new(payer, true),
|
||||||
|
AccountMeta::new_readonly(config_key, false),
|
||||||
|
message_acc,
|
||||||
|
claim_acc,
|
||||||
|
AccountMeta::new_readonly(endpoint, false),
|
||||||
|
AccountMeta::new(to, false),
|
||||||
|
AccountMeta::new_readonly(to_owner, true),
|
||||||
|
if let Some(fee_r) = fee_recipient {
|
||||||
|
AccountMeta::new(fee_r, false)
|
||||||
|
} else {
|
||||||
|
AccountMeta::new(to, false)
|
||||||
|
},
|
||||||
|
AccountMeta::new(mint_key, false),
|
||||||
|
AccountMeta::new_readonly(meta_key, false),
|
||||||
|
AccountMeta::new_readonly(mint_authority_key, false),
|
||||||
|
// Dependencies
|
||||||
|
AccountMeta::new_readonly(solana_program::sysvar::rent::id(), false),
|
||||||
|
AccountMeta::new_readonly(solana_program::system_program::id(), false),
|
||||||
|
// Program
|
||||||
|
AccountMeta::new_readonly(bridge_id, false),
|
||||||
|
AccountMeta::new_readonly(spl_token::id(), false),
|
||||||
|
],
|
||||||
|
data: (
|
||||||
|
crate::instruction::Instruction::CompleteWrappedWithPayload,
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
.try_to_vec()?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_wrapped(
|
pub fn create_wrapped(
|
||||||
program_id: Pubkey,
|
program_id: Pubkey,
|
||||||
bridge_id: Pubkey,
|
bridge_id: Pubkey,
|
||||||
|
@ -333,6 +468,50 @@ pub fn transfer_native(
|
||||||
from: Pubkey,
|
from: Pubkey,
|
||||||
mint: Pubkey,
|
mint: Pubkey,
|
||||||
data: TransferNativeData,
|
data: TransferNativeData,
|
||||||
|
) -> solitaire::Result<Instruction> {
|
||||||
|
transfer_native_raw(
|
||||||
|
program_id,
|
||||||
|
bridge_id,
|
||||||
|
payer,
|
||||||
|
message_key,
|
||||||
|
from,
|
||||||
|
mint,
|
||||||
|
(crate::instruction::Instruction::TransferNative, data).try_to_vec()?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer_native_with_payload(
|
||||||
|
program_id: Pubkey,
|
||||||
|
bridge_id: Pubkey,
|
||||||
|
payer: Pubkey,
|
||||||
|
message_key: Pubkey,
|
||||||
|
from: Pubkey,
|
||||||
|
mint: Pubkey,
|
||||||
|
data: TransferNativeWithPayloadData,
|
||||||
|
) -> solitaire::Result<Instruction> {
|
||||||
|
transfer_native_raw(
|
||||||
|
program_id,
|
||||||
|
bridge_id,
|
||||||
|
payer,
|
||||||
|
message_key,
|
||||||
|
from,
|
||||||
|
mint,
|
||||||
|
(
|
||||||
|
crate::instruction::Instruction::TransferNativeWithPayload,
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
.try_to_vec()?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_native_raw(
|
||||||
|
program_id: Pubkey,
|
||||||
|
bridge_id: Pubkey,
|
||||||
|
payer: Pubkey,
|
||||||
|
message_key: Pubkey,
|
||||||
|
from: Pubkey,
|
||||||
|
mint: Pubkey,
|
||||||
|
data: Vec<u8>,
|
||||||
) -> solitaire::Result<Instruction> {
|
) -> solitaire::Result<Instruction> {
|
||||||
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
|
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
|
||||||
let custody_key = CustodyAccount::<'_, { AccountState::Initialized }>::key(
|
let custody_key = CustodyAccount::<'_, { AccountState::Initialized }>::key(
|
||||||
|
@ -346,14 +525,6 @@ pub fn transfer_native(
|
||||||
|
|
||||||
// Bridge keys
|
// Bridge keys
|
||||||
let bridge_config = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &bridge_id);
|
let bridge_config = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &bridge_id);
|
||||||
let payload = PayloadTransfer {
|
|
||||||
amount: U256::from(data.amount),
|
|
||||||
token_address: mint.to_bytes(),
|
|
||||||
token_chain: 1,
|
|
||||||
to: data.target_address,
|
|
||||||
to_chain: data.target_chain,
|
|
||||||
fee: U256::from(data.fee),
|
|
||||||
};
|
|
||||||
let sequence_key = Sequence::key(
|
let sequence_key = Sequence::key(
|
||||||
&SequenceDerivationData {
|
&SequenceDerivationData {
|
||||||
emitter_key: &emitter_key,
|
emitter_key: &emitter_key,
|
||||||
|
@ -385,7 +556,7 @@ pub fn transfer_native(
|
||||||
AccountMeta::new_readonly(bridge_id, false),
|
AccountMeta::new_readonly(bridge_id, false),
|
||||||
AccountMeta::new_readonly(spl_token::id(), false),
|
AccountMeta::new_readonly(spl_token::id(), false),
|
||||||
],
|
],
|
||||||
data: (crate::instruction::Instruction::TransferNative, data).try_to_vec()?,
|
data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,6 +570,58 @@ pub fn transfer_wrapped(
|
||||||
token_chain: u16,
|
token_chain: u16,
|
||||||
token_address: ForeignAddress,
|
token_address: ForeignAddress,
|
||||||
data: TransferWrappedData,
|
data: TransferWrappedData,
|
||||||
|
) -> solitaire::Result<Instruction> {
|
||||||
|
transfer_wrapped_raw(
|
||||||
|
program_id,
|
||||||
|
bridge_id,
|
||||||
|
payer,
|
||||||
|
message_key,
|
||||||
|
from,
|
||||||
|
from_owner,
|
||||||
|
token_chain,
|
||||||
|
token_address,
|
||||||
|
(crate::instruction::Instruction::TransferWrapped, data).try_to_vec()?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer_wrapped_with_payload(
|
||||||
|
program_id: Pubkey,
|
||||||
|
bridge_id: Pubkey,
|
||||||
|
payer: Pubkey,
|
||||||
|
message_key: Pubkey,
|
||||||
|
from: Pubkey,
|
||||||
|
from_owner: Pubkey,
|
||||||
|
token_chain: u16,
|
||||||
|
token_address: ForeignAddress,
|
||||||
|
data: TransferWrappedWithPayloadData,
|
||||||
|
) -> solitaire::Result<Instruction> {
|
||||||
|
transfer_wrapped_raw(
|
||||||
|
program_id,
|
||||||
|
bridge_id,
|
||||||
|
payer,
|
||||||
|
message_key,
|
||||||
|
from,
|
||||||
|
from_owner,
|
||||||
|
token_chain,
|
||||||
|
token_address,
|
||||||
|
(
|
||||||
|
crate::instruction::Instruction::TransferWrappedWithPayload,
|
||||||
|
data,
|
||||||
|
)
|
||||||
|
.try_to_vec()?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transfer_wrapped_raw(
|
||||||
|
program_id: Pubkey,
|
||||||
|
bridge_id: Pubkey,
|
||||||
|
payer: Pubkey,
|
||||||
|
message_key: Pubkey,
|
||||||
|
from: Pubkey,
|
||||||
|
from_owner: Pubkey,
|
||||||
|
token_chain: u16,
|
||||||
|
token_address: ForeignAddress,
|
||||||
|
data: Vec<u8>,
|
||||||
) -> solitaire::Result<Instruction> {
|
) -> solitaire::Result<Instruction> {
|
||||||
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
|
let config_key = ConfigAccount::<'_, { AccountState::Uninitialized }>::key(None, &program_id);
|
||||||
|
|
||||||
|
@ -421,14 +644,6 @@ pub fn transfer_wrapped(
|
||||||
|
|
||||||
// Bridge keys
|
// Bridge keys
|
||||||
let bridge_config = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &bridge_id);
|
let bridge_config = Bridge::<'_, { AccountState::Uninitialized }>::key(None, &bridge_id);
|
||||||
let payload = PayloadTransfer {
|
|
||||||
amount: U256::from(data.amount),
|
|
||||||
token_address,
|
|
||||||
token_chain,
|
|
||||||
to: data.target_address,
|
|
||||||
to_chain: data.target_chain,
|
|
||||||
fee: U256::from(data.fee),
|
|
||||||
};
|
|
||||||
let sequence_key = Sequence::key(
|
let sequence_key = Sequence::key(
|
||||||
&SequenceDerivationData {
|
&SequenceDerivationData {
|
||||||
emitter_key: &emitter_key,
|
emitter_key: &emitter_key,
|
||||||
|
@ -460,7 +675,7 @@ pub fn transfer_wrapped(
|
||||||
AccountMeta::new_readonly(bridge_id, false),
|
AccountMeta::new_readonly(bridge_id, false),
|
||||||
AccountMeta::new_readonly(spl_token::id(), false),
|
AccountMeta::new_readonly(spl_token::id(), false),
|
||||||
],
|
],
|
||||||
data: (crate::instruction::Instruction::TransferWrapped, data).try_to_vec()?,
|
data,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
#![feature(adt_const_params)]
|
#![feature(adt_const_params)]
|
||||||
#![deny(unused_must_use)]
|
#![deny(unused_must_use)]
|
||||||
|
|
||||||
|
@ -23,19 +22,27 @@ pub mod types;
|
||||||
pub use api::{
|
pub use api::{
|
||||||
attest_token,
|
attest_token,
|
||||||
complete_native,
|
complete_native,
|
||||||
|
complete_native_with_payload,
|
||||||
complete_wrapped,
|
complete_wrapped,
|
||||||
|
complete_wrapped_with_payload,
|
||||||
create_wrapped,
|
create_wrapped,
|
||||||
initialize,
|
initialize,
|
||||||
register_chain,
|
register_chain,
|
||||||
transfer_native,
|
transfer_native,
|
||||||
|
transfer_native_with_payload,
|
||||||
transfer_wrapped,
|
transfer_wrapped,
|
||||||
|
transfer_wrapped_with_payload,
|
||||||
upgrade_contract,
|
upgrade_contract,
|
||||||
AttestToken,
|
AttestToken,
|
||||||
AttestTokenData,
|
AttestTokenData,
|
||||||
CompleteNative,
|
CompleteNative,
|
||||||
CompleteNativeData,
|
CompleteNativeData,
|
||||||
|
CompleteNativeWithPayload,
|
||||||
|
CompleteNativeWithPayloadData,
|
||||||
CompleteWrapped,
|
CompleteWrapped,
|
||||||
CompleteWrappedData,
|
CompleteWrappedData,
|
||||||
|
CompleteWrappedWithPayload,
|
||||||
|
CompleteWrappedWithPayloadData,
|
||||||
CreateWrapped,
|
CreateWrapped,
|
||||||
CreateWrappedData,
|
CreateWrappedData,
|
||||||
Initialize,
|
Initialize,
|
||||||
|
@ -44,8 +51,12 @@ pub use api::{
|
||||||
RegisterChainData,
|
RegisterChainData,
|
||||||
TransferNative,
|
TransferNative,
|
||||||
TransferNativeData,
|
TransferNativeData,
|
||||||
|
TransferNativeWithPayload,
|
||||||
|
TransferNativeWithPayloadData,
|
||||||
TransferWrapped,
|
TransferWrapped,
|
||||||
TransferWrappedData,
|
TransferWrappedData,
|
||||||
|
TransferWrappedWithPayload,
|
||||||
|
TransferWrappedWithPayloadData,
|
||||||
UpgradeContract,
|
UpgradeContract,
|
||||||
UpgradeContractData,
|
UpgradeContractData,
|
||||||
};
|
};
|
||||||
|
@ -87,13 +98,17 @@ impl From<TokenBridgeError> for SolitaireError {
|
||||||
}
|
}
|
||||||
|
|
||||||
solitaire! {
|
solitaire! {
|
||||||
Initialize => initialize,
|
Initialize => initialize,
|
||||||
AttestToken => attest_token,
|
AttestToken => attest_token,
|
||||||
CompleteNative => complete_native,
|
CompleteNative => complete_native,
|
||||||
CompleteWrapped => complete_wrapped,
|
CompleteWrapped => complete_wrapped,
|
||||||
TransferWrapped => transfer_wrapped,
|
TransferWrapped => transfer_wrapped,
|
||||||
TransferNative => transfer_native,
|
TransferNative => transfer_native,
|
||||||
RegisterChain => register_chain,
|
RegisterChain => register_chain,
|
||||||
CreateWrapped => create_wrapped,
|
CreateWrapped => create_wrapped,
|
||||||
UpgradeContract => upgrade_contract,
|
UpgradeContract => upgrade_contract,
|
||||||
|
CompleteNativeWithPayload => complete_native_with_payload,
|
||||||
|
CompleteWrappedWithPayload => complete_wrapped_with_payload,
|
||||||
|
TransferWrappedWithPayload => transfer_wrapped_with_payload,
|
||||||
|
TransferNativeWithPayload => transfer_native_with_payload,
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,6 +122,89 @@ impl SerializePayload for PayloadTransfer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DeserializePayload for PayloadTransferWithPayload {
|
||||||
|
fn deserialize(buf: &mut &[u8]) -> Result<Self, SolitaireError> {
|
||||||
|
let mut v = Cursor::new(buf);
|
||||||
|
|
||||||
|
if v.read_u8()? != 3 {
|
||||||
|
return Err(SolitaireError::Custom(0));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut am_data: [u8; 32] = [0; 32];
|
||||||
|
v.read_exact(&mut am_data)?;
|
||||||
|
let amount = U256::from_big_endian(&am_data);
|
||||||
|
|
||||||
|
let mut token_address = Address::default();
|
||||||
|
v.read_exact(&mut token_address)?;
|
||||||
|
|
||||||
|
let token_chain = v.read_u16::<BigEndian>()?;
|
||||||
|
|
||||||
|
let mut to = Address::default();
|
||||||
|
v.read_exact(&mut to)?;
|
||||||
|
|
||||||
|
let to_chain = v.read_u16::<BigEndian>()?;
|
||||||
|
|
||||||
|
let mut fee_data: [u8; 32] = [0; 32];
|
||||||
|
v.read_exact(&mut fee_data)?;
|
||||||
|
let fee = U256::from_big_endian(&fee_data);
|
||||||
|
|
||||||
|
let mut payload = vec![];
|
||||||
|
v.read_to_end(&mut payload)?;
|
||||||
|
|
||||||
|
Ok(PayloadTransferWithPayload {
|
||||||
|
amount,
|
||||||
|
token_address,
|
||||||
|
token_chain,
|
||||||
|
to,
|
||||||
|
to_chain,
|
||||||
|
fee,
|
||||||
|
payload,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SerializePayload for PayloadTransferWithPayload {
|
||||||
|
fn serialize<W: Write>(&self, writer: &mut W) -> Result<(), SolitaireError> {
|
||||||
|
// Payload ID
|
||||||
|
writer.write_u8(3)?;
|
||||||
|
|
||||||
|
let mut am_data: [u8; 32] = [0; 32];
|
||||||
|
self.amount.to_big_endian(&mut am_data);
|
||||||
|
writer.write(&am_data)?;
|
||||||
|
|
||||||
|
writer.write(&self.token_address)?;
|
||||||
|
writer.write_u16::<BigEndian>(self.token_chain)?;
|
||||||
|
writer.write(&self.to)?;
|
||||||
|
writer.write_u16::<BigEndian>(self.to_chain)?;
|
||||||
|
|
||||||
|
let mut fee_data: [u8; 32] = [0; 32];
|
||||||
|
self.fee.to_big_endian(&mut fee_data);
|
||||||
|
writer.write(&fee_data)?;
|
||||||
|
|
||||||
|
writer.write(self.payload.as_slice())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
|
pub struct PayloadTransferWithPayload {
|
||||||
|
// Amount being transferred (big-endian uint256)
|
||||||
|
pub amount: U256,
|
||||||
|
// Address of the token. Left-zero-padded if shorter than 32 bytes
|
||||||
|
pub token_address: Address,
|
||||||
|
// Chain ID of the token
|
||||||
|
pub token_chain: ChainID,
|
||||||
|
// Address of the recipient. Left-zero-padded if shorter than 32 bytes
|
||||||
|
pub to: Address,
|
||||||
|
// Chain ID of the recipient
|
||||||
|
pub to_chain: ChainID,
|
||||||
|
// Amount of tokens (big-endian uint256) that the user is willing to pay as relayer fee. Must be <= Amount.
|
||||||
|
pub fee: U256,
|
||||||
|
// Arbitrary payload
|
||||||
|
pub payload: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub struct PayloadAssetMeta {
|
pub struct PayloadAssetMeta {
|
||||||
// Address of the token. Left-zero-padded if shorter than 32 bytes
|
// Address of the token. Left-zero-padded if shorter than 32 bytes
|
||||||
|
|
|
@ -28,24 +28,44 @@ pub struct Config {
|
||||||
pub wormhole_bridge: Pubkey,
|
pub wormhole_bridge: Pubkey,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "cpi"))]
|
||||||
impl Owned for Config {
|
impl Owned for Config {
|
||||||
fn owner(&self) -> AccountOwner {
|
fn owner(&self) -> AccountOwner {
|
||||||
AccountOwner::This
|
AccountOwner::This
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cpi")]
|
||||||
|
impl Owned for Config {
|
||||||
|
fn owner(&self) -> AccountOwner {
|
||||||
|
use solana_program::pubkey::Pubkey;
|
||||||
|
use std::str::FromStr;
|
||||||
|
AccountOwner::Other(Pubkey::from_str(env!("TOKEN_BRIDGE_ADDRESS")).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
|
#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
|
||||||
pub struct EndpointRegistration {
|
pub struct EndpointRegistration {
|
||||||
pub chain: ChainID,
|
pub chain: ChainID,
|
||||||
pub contract: Address,
|
pub contract: Address,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "cpi"))]
|
||||||
impl Owned for EndpointRegistration {
|
impl Owned for EndpointRegistration {
|
||||||
fn owner(&self) -> AccountOwner {
|
fn owner(&self) -> AccountOwner {
|
||||||
AccountOwner::This
|
AccountOwner::This
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cpi")]
|
||||||
|
impl Owned for EndpointRegistration {
|
||||||
|
fn owner(&self) -> AccountOwner {
|
||||||
|
use solana_program::pubkey::Pubkey;
|
||||||
|
use std::str::FromStr;
|
||||||
|
AccountOwner::Other(Pubkey::from_str(env!("TOKEN_BRIDGE_ADDRESS")).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
|
#[derive(Default, Clone, Copy, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
|
||||||
pub struct WrappedMeta {
|
pub struct WrappedMeta {
|
||||||
pub chain: ChainID,
|
pub chain: ChainID,
|
||||||
|
@ -53,11 +73,21 @@ pub struct WrappedMeta {
|
||||||
pub original_decimals: u8,
|
pub original_decimals: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "cpi"))]
|
||||||
impl Owned for WrappedMeta {
|
impl Owned for WrappedMeta {
|
||||||
fn owner(&self) -> AccountOwner {
|
fn owner(&self) -> AccountOwner {
|
||||||
AccountOwner::This
|
AccountOwner::This
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "cpi")]
|
||||||
|
impl Owned for WrappedMeta {
|
||||||
|
fn owner(&self) -> AccountOwner {
|
||||||
|
use solana_program::pubkey::Pubkey;
|
||||||
|
use std::str::FromStr;
|
||||||
|
AccountOwner::Other(Pubkey::from_str(env!("TOKEN_BRIDGE_ADDRESS")).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pack_type!(SplMint, Mint, AccountOwner::Other(spl_token::id()));
|
pack_type!(SplMint, Mint, AccountOwner::Other(spl_token::id()));
|
||||||
pack_type!(SplAccount, Account, AccountOwner::Other(spl_token::id()));
|
pack_type!(SplAccount, Account, AccountOwner::Other(spl_token::id()));
|
||||||
|
|
|
@ -15,7 +15,9 @@ use crate::{
|
||||||
create_wrapped,
|
create_wrapped,
|
||||||
register_chain,
|
register_chain,
|
||||||
transfer_native,
|
transfer_native,
|
||||||
|
transfer_native_with_payload,
|
||||||
transfer_wrapped,
|
transfer_wrapped,
|
||||||
|
transfer_wrapped_with_payload,
|
||||||
upgrade_contract,
|
upgrade_contract,
|
||||||
},
|
},
|
||||||
messages::{
|
messages::{
|
||||||
|
@ -33,7 +35,9 @@ use crate::{
|
||||||
CreateWrappedData,
|
CreateWrappedData,
|
||||||
RegisterChainData,
|
RegisterChainData,
|
||||||
TransferNativeData,
|
TransferNativeData,
|
||||||
|
TransferNativeWithPayloadData,
|
||||||
TransferWrappedData,
|
TransferWrappedData,
|
||||||
|
TransferWrappedWithPayloadData,
|
||||||
};
|
};
|
||||||
use borsh::BorshDeserialize;
|
use borsh::BorshDeserialize;
|
||||||
use bridge::{
|
use bridge::{
|
||||||
|
@ -115,6 +119,52 @@ pub fn transfer_native_ix(
|
||||||
JsValue::from_serde(&ix).unwrap()
|
JsValue::from_serde(&ix).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn transfer_native_with_payload_ix(
|
||||||
|
program_id: String,
|
||||||
|
bridge_id: String,
|
||||||
|
payer: String,
|
||||||
|
message: String,
|
||||||
|
from: String,
|
||||||
|
mint: String,
|
||||||
|
nonce: u32,
|
||||||
|
amount: u64,
|
||||||
|
fee: u64,
|
||||||
|
target_address: Vec<u8>,
|
||||||
|
target_chain: u16,
|
||||||
|
payload: Vec<u8>,
|
||||||
|
) -> JsValue {
|
||||||
|
let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
|
||||||
|
let bridge_id = Pubkey::from_str(bridge_id.as_str()).unwrap();
|
||||||
|
let payer = Pubkey::from_str(payer.as_str()).unwrap();
|
||||||
|
let message = Pubkey::from_str(message.as_str()).unwrap();
|
||||||
|
let from = Pubkey::from_str(from.as_str()).unwrap();
|
||||||
|
let mint = Pubkey::from_str(mint.as_str()).unwrap();
|
||||||
|
|
||||||
|
let mut target_addr = [0u8; 32];
|
||||||
|
target_addr.copy_from_slice(target_address.as_slice());
|
||||||
|
|
||||||
|
let ix = transfer_native_with_payload(
|
||||||
|
program_id,
|
||||||
|
bridge_id,
|
||||||
|
payer,
|
||||||
|
message,
|
||||||
|
from,
|
||||||
|
mint,
|
||||||
|
TransferNativeWithPayloadData {
|
||||||
|
nonce,
|
||||||
|
amount,
|
||||||
|
fee,
|
||||||
|
target_address: target_addr,
|
||||||
|
target_chain,
|
||||||
|
payload,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
JsValue::from_serde(&ix).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn transfer_wrapped_ix(
|
pub fn transfer_wrapped_ix(
|
||||||
program_id: String,
|
program_id: String,
|
||||||
|
@ -165,6 +215,58 @@ pub fn transfer_wrapped_ix(
|
||||||
JsValue::from_serde(&ix).unwrap()
|
JsValue::from_serde(&ix).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn transfer_wrapped_with_payload_ix(
|
||||||
|
program_id: String,
|
||||||
|
bridge_id: String,
|
||||||
|
payer: String,
|
||||||
|
message: String,
|
||||||
|
from: String,
|
||||||
|
from_owner: String,
|
||||||
|
token_chain: u16,
|
||||||
|
token_address: Vec<u8>,
|
||||||
|
nonce: u32,
|
||||||
|
amount: u64,
|
||||||
|
fee: u64,
|
||||||
|
target_address: Vec<u8>,
|
||||||
|
target_chain: u16,
|
||||||
|
payload: Vec<u8>,
|
||||||
|
) -> JsValue {
|
||||||
|
let program_id = Pubkey::from_str(program_id.as_str()).unwrap();
|
||||||
|
let bridge_id = Pubkey::from_str(bridge_id.as_str()).unwrap();
|
||||||
|
let payer = Pubkey::from_str(payer.as_str()).unwrap();
|
||||||
|
let message = Pubkey::from_str(message.as_str()).unwrap();
|
||||||
|
let from = Pubkey::from_str(from.as_str()).unwrap();
|
||||||
|
let from_owner = Pubkey::from_str(from_owner.as_str()).unwrap();
|
||||||
|
|
||||||
|
let mut target_addr = [0u8; 32];
|
||||||
|
target_addr.copy_from_slice(target_address.as_slice());
|
||||||
|
let mut token_addr = [0u8; 32];
|
||||||
|
token_addr.copy_from_slice(token_address.as_slice());
|
||||||
|
|
||||||
|
let ix = transfer_wrapped_with_payload(
|
||||||
|
program_id,
|
||||||
|
bridge_id,
|
||||||
|
payer,
|
||||||
|
message,
|
||||||
|
from,
|
||||||
|
from_owner,
|
||||||
|
token_chain,
|
||||||
|
token_addr,
|
||||||
|
TransferWrappedWithPayloadData {
|
||||||
|
nonce,
|
||||||
|
amount,
|
||||||
|
fee,
|
||||||
|
target_address: target_addr,
|
||||||
|
target_chain,
|
||||||
|
payload,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
JsValue::from_serde(&ix).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn complete_transfer_native_ix(
|
pub fn complete_transfer_native_ix(
|
||||||
program_id: String,
|
program_id: String,
|
||||||
|
|
Loading…
Reference in New Issue