use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::pubkey::Pubkey;
use crate::error::Result;
use super::super::constants::{
ACTION_RELEASE_SOL, ACTION_RELEASE_SPL, ATA_PROGRAM_ID, CORE_PROGRAM_ID, DEFAULT_EVENT_INDEX,
DEFAULT_VAA_EXPIRY, DEFAULT_VAA_VERSION, SYSTEM_PROGRAM_ID, SYSVAR_INSTRUCTIONS_PUBKEY,
TOKEN_BRIDGE_PROGRAM_ID, TOKEN_PROGRAM_ID,
};
use super::helpers::{
build_transaction, compute_unit_limit_instruction, create_ed25519_verify_instruction,
parse_hex_32, parse_pubkey, parse_signatures, payload_length_suffix, resolve_recent_blockhash,
};
use super::super::rpc::SolanaRpc;
use super::super::types::{
ReleaseSolAccounts, ReleaseSolOptions, ReleaseSolResult, ReleaseSplAccounts, ReleaseSplOptions,
ReleaseSplResult,
};
use super::super::utils::{
concat_bytes, derive_rate_limit_state_pda, derive_released_transfer_pda,
derive_router_config_pda, derive_router_signer_pda, derive_token_price_registry_pda,
derive_token_registration_pda, derive_vault_pda, derive_verified_transfer_pda, encode_u32_le,
encode_u64_be, encode_u64_le, generate_discriminator, get_ata,
};
pub async fn build_release_spl_transaction(
options: ReleaseSplOptions,
payer: &Pubkey,
rpc: Option<&dyn SolanaRpc>,
) -> Result<ReleaseSplResult> {
let recipient = parse_pubkey(&options.recipient, "recipient")?;
let mint = parse_pubkey(&options.mint, "mint")?;
let txn_hash = parse_hex_32(&options.txn_id, "txnId")?;
let expected_hash = parse_hex_32(&options.expected_hash, "expectedHash")?;
let (router_signer, _) = derive_router_signer_pda();
let (router_config, _) = derive_router_config_pda();
let (used_marker, _) = derive_verified_transfer_pda(&expected_hash);
let (redeemed_marker, _) = derive_released_transfer_pda(&expected_hash);
let (rate_limit_state, _) = derive_rate_limit_state_pda();
let (token_registration, _) = derive_token_registration_pda(&mint);
let recipient_ata = get_ata(&recipient, &mint);
let vault_ata = get_ata(&router_signer, &mint);
let (signatures, public_keys) = parse_signatures(&options.signatures)?;
let mut signature_instructions = Vec::with_capacity(signatures.len());
for (signature, public_key) in signatures.iter().zip(public_keys.iter()) {
signature_instructions.push(create_ed25519_verify_instruction(
&expected_hash,
signature,
public_key,
)?);
}
let version = [DEFAULT_VAA_VERSION];
let action = [ACTION_RELEASE_SPL];
let expiry = encode_u64_le(DEFAULT_VAA_EXPIRY);
let event_index = encode_u32_le(DEFAULT_EVENT_INDEX);
let timestamp = encode_u64_le(options.timestamp);
let amount_be = encode_u64_be(options.amount);
let amount_le = encode_u64_le(options.amount);
let usd_price_nano_be = encode_u64_be(options.usd_price_nano);
let usd_price_nano_le = encode_u64_le(options.usd_price_nano);
let liquidity_usd_nano_be = encode_u64_be(options.liquidity_usd_nano);
let liquidity_usd_nano_le = encode_u64_le(options.liquidity_usd_nano);
let tier = [options.tier];
let payload = concat_bytes(&[
amount_be.as_ref(),
recipient.as_ref(),
mint.as_ref(),
usd_price_nano_be.as_ref(),
liquidity_usd_nano_be.as_ref(),
tier.as_ref(),
]);
let payload_len = encode_u32_le(payload.len() as u32);
let payload_tail = payload_length_suffix(&payload);
let core_discriminator = generate_discriminator("global:post_verified_transfer");
let core_data = concat_bytes(&[
core_discriminator.as_ref(),
version.as_ref(),
action.as_ref(),
timestamp.as_ref(),
expiry.as_ref(),
txn_hash.as_slice(),
event_index.as_ref(),
payload_len.as_ref(),
payload.as_slice(),
payload_tail.as_ref(),
]);
let core_instruction = Instruction {
program_id: *CORE_PROGRAM_ID,
accounts: vec![
AccountMeta::new(router_config, false),
AccountMeta::new_readonly(*SYSVAR_INSTRUCTIONS_PUBKEY, false),
AccountMeta::new(used_marker, false),
AccountMeta::new(*payer, true),
AccountMeta::new_readonly(*SYSTEM_PROGRAM_ID, false),
AccountMeta::new_readonly(*TOKEN_BRIDGE_PROGRAM_ID, false),
],
data: core_data,
};
let token_discriminator = generate_discriminator("global:release_spl");
let token_data = concat_bytes(&[
token_discriminator.as_ref(),
version.as_ref(),
timestamp.as_ref(),
expiry.as_ref(),
txn_hash.as_slice(),
event_index.as_ref(),
amount_le.as_ref(),
usd_price_nano_le.as_ref(),
liquidity_usd_nano_le.as_ref(),
tier.as_ref(),
]);
let token_instruction = Instruction {
program_id: *TOKEN_BRIDGE_PROGRAM_ID,
accounts: vec![
AccountMeta::new_readonly(*CORE_PROGRAM_ID, false),
AccountMeta::new_readonly(router_config, false),
AccountMeta::new(*payer, true),
AccountMeta::new_readonly(mint, false),
AccountMeta::new_readonly(router_signer, false),
AccountMeta::new(vault_ata, false),
AccountMeta::new_readonly(recipient, false),
AccountMeta::new(recipient_ata, false),
AccountMeta::new_readonly(*ATA_PROGRAM_ID, false),
AccountMeta::new_readonly(used_marker, false),
AccountMeta::new(redeemed_marker, false),
AccountMeta::new(rate_limit_state, false),
AccountMeta::new(token_registration, false),
AccountMeta::new_readonly(*TOKEN_PROGRAM_ID, false),
AccountMeta::new_readonly(*SYSTEM_PROGRAM_ID, false),
AccountMeta::new_readonly(*TOKEN_BRIDGE_PROGRAM_ID, false),
],
data: token_data,
};
let recent_blockhash = resolve_recent_blockhash(rpc).await?;
let mut verify_instructions = Vec::new();
if !signature_instructions.is_empty() {
verify_instructions.push(compute_unit_limit_instruction(400_000));
}
verify_instructions.extend(signature_instructions.iter().cloned());
verify_instructions.push(core_instruction.clone());
let verify_transaction = build_transaction(payer, verify_instructions, recent_blockhash);
let release_transaction =
build_transaction(payer, vec![token_instruction.clone()], recent_blockhash);
let mut combined_instructions = Vec::new();
if !signature_instructions.is_empty() {
combined_instructions.push(compute_unit_limit_instruction(400_000));
}
combined_instructions.extend(signature_instructions.iter().cloned());
combined_instructions.push(core_instruction.clone());
combined_instructions.push(token_instruction.clone());
let transaction = build_transaction(payer, combined_instructions, recent_blockhash);
Ok(ReleaseSplResult {
core_instruction,
token_instruction,
signature_instructions,
transaction,
verify_transaction,
release_transaction,
accounts: ReleaseSplAccounts {
recipient_ata,
vault_ata,
used_marker,
redeemed_marker,
},
})
}
pub async fn build_release_sol_transaction(
options: ReleaseSolOptions,
payer: &Pubkey,
rpc: Option<&dyn SolanaRpc>,
) -> Result<ReleaseSolResult> {
let recipient = parse_pubkey(&options.recipient, "recipient")?;
let txn_hash = parse_hex_32(&options.txn_id, "txnId")?;
let expected_hash = parse_hex_32(&options.expected_hash, "expectedHash")?;
let (vault, _) = derive_vault_pda();
let (router_config, _) = derive_router_config_pda();
let (used_marker, _) = derive_verified_transfer_pda(&expected_hash);
let (redeemed_marker, _) = derive_released_transfer_pda(&expected_hash);
let (rate_limit_state, _) = derive_rate_limit_state_pda();
let (token_price_registry, _) = derive_token_price_registry_pda();
let (signatures, public_keys) = parse_signatures(&options.signatures)?;
let mut signature_instructions = Vec::with_capacity(signatures.len());
for (signature, public_key) in signatures.iter().zip(public_keys.iter()) {
signature_instructions.push(create_ed25519_verify_instruction(
&expected_hash,
signature,
public_key,
)?);
}
let version = [DEFAULT_VAA_VERSION];
let action = [ACTION_RELEASE_SOL];
let expiry = encode_u64_le(DEFAULT_VAA_EXPIRY);
let event_index = encode_u32_le(DEFAULT_EVENT_INDEX);
let timestamp = encode_u64_le(options.timestamp);
let amount_be = encode_u64_be(options.amount);
let amount_le = encode_u64_le(options.amount);
let usd_amount_be = encode_u64_be(options.usd_amount);
let usd_amount_le = encode_u64_le(options.usd_amount);
let payload = concat_bytes(&[
amount_be.as_ref(),
recipient.as_ref(),
usd_amount_be.as_ref(),
]);
let payload_len = encode_u32_le(payload.len() as u32);
let payload_tail = payload_length_suffix(&payload);
let core_discriminator = generate_discriminator("global:post_verified_transfer");
let core_data = concat_bytes(&[
core_discriminator.as_ref(),
version.as_ref(),
action.as_ref(),
timestamp.as_ref(),
expiry.as_ref(),
txn_hash.as_slice(),
event_index.as_ref(),
payload_len.as_ref(),
payload.as_slice(),
payload_tail.as_ref(),
]);
let core_instruction = Instruction {
program_id: *CORE_PROGRAM_ID,
accounts: vec![
AccountMeta::new(router_config, false),
AccountMeta::new_readonly(*SYSVAR_INSTRUCTIONS_PUBKEY, false),
AccountMeta::new(used_marker, false),
AccountMeta::new(*payer, true),
AccountMeta::new_readonly(*SYSTEM_PROGRAM_ID, false),
AccountMeta::new_readonly(*TOKEN_BRIDGE_PROGRAM_ID, false),
],
data: core_data,
};
let token_discriminator = generate_discriminator("global:release_sol");
let token_data = concat_bytes(&[
token_discriminator.as_ref(),
version.as_ref(),
timestamp.as_ref(),
expiry.as_ref(),
txn_hash.as_slice(),
event_index.as_ref(),
amount_le.as_ref(),
recipient.as_ref(),
usd_amount_le.as_ref(),
]);
let token_instruction = Instruction {
program_id: *TOKEN_BRIDGE_PROGRAM_ID,
accounts: vec![
AccountMeta::new_readonly(*CORE_PROGRAM_ID, false),
AccountMeta::new_readonly(router_config, false),
AccountMeta::new(*payer, true),
AccountMeta::new(vault, false),
AccountMeta::new_readonly(used_marker, false),
AccountMeta::new(redeemed_marker, false),
AccountMeta::new(recipient, false),
AccountMeta::new(rate_limit_state, false),
AccountMeta::new(token_price_registry, false),
AccountMeta::new_readonly(*SYSTEM_PROGRAM_ID, false),
AccountMeta::new_readonly(*TOKEN_BRIDGE_PROGRAM_ID, false),
],
data: token_data,
};
let recent_blockhash = resolve_recent_blockhash(rpc).await?;
let mut verify_instructions = Vec::new();
if !signature_instructions.is_empty() {
verify_instructions.push(compute_unit_limit_instruction(400_000));
}
verify_instructions.extend(signature_instructions.iter().cloned());
verify_instructions.push(core_instruction.clone());
let verify_transaction = build_transaction(payer, verify_instructions, recent_blockhash);
let release_transaction =
build_transaction(payer, vec![token_instruction.clone()], recent_blockhash);
let mut combined_instructions = Vec::new();
if !signature_instructions.is_empty() {
combined_instructions.push(compute_unit_limit_instruction(400_000));
}
combined_instructions.extend(signature_instructions.iter().cloned());
combined_instructions.push(core_instruction.clone());
combined_instructions.push(token_instruction.clone());
let transaction = build_transaction(payer, combined_instructions, recent_blockhash);
Ok(ReleaseSolResult {
core_instruction,
token_instruction,
signature_instructions,
transaction,
verify_transaction,
release_transaction,
accounts: ReleaseSolAccounts {
vault,
used_marker,
redeemed_marker,
},
})
}