track meta of wrapped assets; update SDK

This commit is contained in:
Hendrik Hofstadt 2020-08-08 12:35:24 +02:00
parent 3aaba4b5bc
commit 3b96e0cc6d
10 changed files with 311 additions and 5599 deletions

2326
solana/bridge/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -19,8 +19,8 @@ default = ["solana-sdk/default", "spl-token/default"]
num-derive = "0.2"
num-traits = "0.2"
remove_dir_all = "=0.5.0"
solana-sdk = { version = "=1.2.17", default-features = false, optional = true }
spl-token = { package = "spl-token", git="https://github.com/hendrikhofstadt/solana-program-library", branch="256b-primitive-types", default-features = false, optional = true }
solana-sdk = { version = "1.3.1", default-features = false, optional = true }
spl-token = { package = "spl-token", git = "https://github.com/solana-labs/solana-program-library", default-features = false, optional = true }
thiserror = "1.0"
byteorder = "1.3.4"
zerocopy = "0.3.0"

View File

@ -34,6 +34,9 @@ pub enum Error {
/// The deserialization of the TransferOutProposal state returned something besides State::TransferOutProposal.
#[error("ExpectedTransferOutProposal")]
ExpectedTransferOutProposal,
/// The deserialization of the GuardianSet state returned something besides State::WrappedAssetMeta.
#[error("ExpectedWrappedAssetMeta")]
ExpectedWrappedAssetMeta,
/// State is uninitialized.
#[error("State is unititialized")]
UninitializedState,

View File

@ -15,6 +15,7 @@ impl PrintProgramError for Error {
Error::ExpectedAccount => info!("Error: ExpectedAccount"),
Error::ExpectedBridge => info!("Error: ExpectedBridge"),
Error::ExpectedGuardianSet => info!("Error: ExpectedGuardianSet"),
Error::ExpectedWrappedAssetMeta => info!("Error: ExpectedWrappedAssetMeta"),
Error::UninitializedState => info!("Error: State is unititialized"),
Error::InvalidProgramAddress => info!("Error: InvalidProgramAddress"),
Error::InvalidVAAFormat => info!("Error: InvalidVAAFormat"),

View File

@ -306,6 +306,7 @@ pub fn post_vaa(
let mut vaa_data: Vec<u8> = vec![];
vaa_data.push(v.len() as u8);
vaa_data.append(&mut v.to_vec());
vaa_data.resize(200, 0);
let mut vaa_chain: [u8; 200] = [0; 200];
vaa_chain.copy_from_slice(vaa_data.as_slice());

View File

@ -690,7 +690,7 @@ impl Bridge {
token_account,
authority,
all_signers.as_slice(),
amount,
amount.as_u64(),
)?;
invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]])
}
@ -710,7 +710,7 @@ impl Bridge {
destination,
bridge,
&[],
amount,
amount.as_u64(),
)?;
invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]])
}
@ -730,7 +730,7 @@ impl Bridge {
destination,
authority,
&[],
amount,
amount.as_u64(),
)?;
invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]])
}
@ -750,7 +750,7 @@ impl Bridge {
destination,
bridge,
&[],
amount,
amount.as_u64(),
)?;
invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]])
}
@ -793,14 +793,8 @@ impl Bridge {
payer,
&Self::derive_wrapped_asset_seeds(bridge, asset.chain, asset.address),
)?;
let ix = spl_token::instruction::initialize_mint(
token_program,
mint,
None,
Some(bridge),
U256::from(0),
8,
)?;
let ix =
spl_token::instruction::initialize_mint(token_program, mint, None, Some(bridge), 0, 8)?;
invoke_signed(&ix, accounts, &[&["bridge".as_bytes()]])
}

View File

@ -188,6 +188,12 @@ impl Bridge {
Ok(*Bridge::unpack(&mut info.data.borrow_mut()).map_err(|_| Error::ExpectedGuardianSet)?)
}
/// Deserializes a `WrappedAssetMeta`.
pub fn wrapped_meta_deserialize(info: &AccountInfo) -> Result<WrappedAssetMeta, Error> {
Ok(*Bridge::unpack(&mut info.data.borrow_mut())
.map_err(|_| Error::ExpectedWrappedAssetMeta)?)
}
/// Deserializes a `TransferOutProposal`.
pub fn transfer_out_proposal_deserialize(
info: &AccountInfo,
@ -196,7 +202,7 @@ impl Bridge {
.map_err(|_| Error::ExpectedTransferOutProposal)?)
}
/// Unpacks a token state from a bytes buffer while assuring that the state is initialized.
/// Unpacks a state from a bytes buffer while assuring that the state is initialized.
pub fn unpack<T: IsInitialized>(input: &mut [u8]) -> Result<&mut T, ProgramError> {
let mut_ref: &mut T = Self::unpack_unchecked(input)?;
if !mut_ref.is_initialized() {
@ -204,7 +210,7 @@ impl Bridge {
}
Ok(mut_ref)
}
/// Unpacks a token state from a bytes buffer without checking that the state is initialized.
/// Unpacks a state from a bytes buffer without checking that the state is initialized.
pub fn unpack_unchecked<T: IsInitialized>(input: &mut [u8]) -> Result<&mut T, ProgramError> {
if input.len() != size_of::<T>() {
return Err(ProgramError::InvalidAccountData);
@ -212,6 +218,15 @@ impl Bridge {
#[allow(clippy::cast_ptr_alignment)]
Ok(unsafe { &mut *(&mut input[0] as *mut u8 as *mut T) })
}
/// Unpacks a state from a bytes buffer without checking that the state is initialized.
pub fn unpack_unchecked_immutable<T: IsInitialized>(input: &[u8]) -> Result<&T, ProgramError> {
if input.len() != size_of::<T>() {
return Err(ProgramError::InvalidAccountData);
}
#[allow(clippy::cast_ptr_alignment)]
Ok(unsafe { &*(&input[0] as *const u8 as *const T) })
}
}
/// Implementation of derivations
@ -377,8 +392,7 @@ impl Bridge {
pub fn derive_key(program_id: &Pubkey, seeds: &Vec<Vec<u8>>) -> Result<Pubkey, Error> {
let s: Vec<_> = seeds.iter().map(|item| item.as_slice()).collect();
Pubkey::create_program_address(s.as_slice(), program_id)
.or(Err(Error::InvalidProgramAddress))
Ok(Pubkey::find_program_address(s.as_slice(), program_id).0)
}
}

3103
solana/cli/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -8,12 +8,13 @@ edition = "2018"
[dependencies]
clap = "2.33.0"
solana-clap-utils = { version = "1.2.17"}
solana-cli-config = { version = "1.2.17" }
solana-logger = { version = "1.2.17" }
solana-sdk = { version = "1.2.17" }
solana-client = { version = "1.2.17" }
solana-faucet = "1.2.17"
spl-token = { package = "spl-token", git="https://github.com/hendrikhofstadt/solana-program-library", branch="256b-primitive-types" }
solana-clap-utils = { version = "1.3.1"}
solana-cli-config = { version = "1.3.1" }
solana-logger = { version = "1.3.1" }
solana-sdk = { version = "1.3.1" }
solana-client = { version = "1.3.1" }
solana-faucet = "1.3.1"
spl-token = { package = "spl-token", git = "https://github.com/solana-labs/solana-program-library" }
wormhole-bridge = { path = "../bridge" }
primitive-types = {version ="0.7.2", default-features = false}
hex = "0.4.2"

View File

@ -1,8 +1,5 @@
use std::fmt::Display;
use std::net::{IpAddr, SocketAddr};
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;
use std::{mem::size_of, process::exit};
use clap::{
@ -14,12 +11,10 @@ use solana_clap_utils::{
input_parsers::{keypair_of, pubkey_of},
input_validators::{is_amount, is_keypair, is_pubkey_or_keypair, is_url},
};
use solana_client::rpc_client::RpcClient;
use solana_client::rpc_request::TokenAccountsFilter;
use solana_faucet::faucet::request_airdrop_transaction;
use solana_sdk::hash::Hash;
use solana_client::{rpc_client::RpcClient, rpc_request::TokenAccountsFilter};
use solana_sdk::system_instruction::create_account;
use solana_sdk::{
commitment_config::CommitmentConfig,
native_token::*,
pubkey::Pubkey,
signature::{read_keypair_file, Keypair, Signer},
@ -29,22 +24,23 @@ use solana_sdk::{
use spl_token::{
self,
instruction::*,
native_mint,
state::{Account, Mint},
};
use spl_bridge::instruction::{
initialize, post_vaa, transfer_out, BridgeInstruction, ForeignAddress, TransferOutPayload,
VAAData, CHAIN_ID_SOLANA,
};
use spl_bridge::state::{
AssetMeta, Bridge, BridgeConfig, ClaimedVAA, GuardianSet, TransferOutProposal,
};
use hex;
use solana_clap_utils::input_parsers::value_of;
use solana_clap_utils::input_validators::is_derivation;
use spl_bridge::instruction::*;
use spl_bridge::state::*;
use spl_bridge::syscalls::RawKey;
struct Config {
rpc_client: RpcClient,
owner: Keypair,
fee_payer: Keypair,
commitment_config: CommitmentConfig,
}
type Error = Box<dyn std::error::Error>;
@ -80,6 +76,19 @@ fn check_owner_balance(config: &Config, required_balance: u64) -> Result<(), Err
}
}
fn get_decimals_for_token(config: &Config, token: &Pubkey) -> Result<u8, Error> {
if *token == native_mint::id() {
Ok(native_mint::DECIMALS)
} else {
let mint = config
.rpc_client
.get_token_mint_with_commitment(token, config.commitment_config)?
.value
.ok_or_else(|| format!("Invalid token: {}", token))?;
Ok(mint.decimals)
}
}
fn command_deploy_bridge(config: &Config) -> CommmandResult {
println!("Deploying bridge program");
@ -147,6 +156,14 @@ fn command_lock_tokens(
.get_minimum_balance_for_rent_exemption(size_of::<Mint>())?;
let p = Pubkey::from_str("7AeSppn3AjaeYScZsnRf1ZRQvtyo4Ke5gx7PAJ3r7BFp").unwrap();
let bridge_key = Bridge::derive_bridge_id(&p)?;
// Check whether we can find wrapped asset meta for the given token
let wrapped_key = Bridge::derive_wrapped_meta_id(&p, &bridge_key, &token)?;
let wrapped_info = config.rpc_client.get_account(&wrapped_key).or_else(Err)?;
let wrapped_meta: &WrappedAssetMeta =
Bridge::unpack_unchecked_immutable(wrapped_info.data.as_slice())?;
let ix = transfer_out(
&p,
&config.owner.pubkey(),
@ -155,9 +172,18 @@ fn command_lock_tokens(
&TransferOutPayload {
amount: U256::from(amount),
chain_id: to_chain,
asset: AssetMeta {
address: token.to_bytes(), // TODO fetch from WASSET (if WASSET)
chain: CHAIN_ID_SOLANA, //TODO fetch from WASSET (if WASSET)
asset: {
if wrapped_meta.is_initialized() {
AssetMeta {
address: wrapped_meta.address,
chain: wrapped_meta.chain,
}
} else {
AssetMeta {
address: token.to_bytes(),
chain: CHAIN_ID_SOLANA,
}
}
},
target,
nonce,
@ -166,14 +192,14 @@ fn command_lock_tokens(
println!("custody: {}, ", ix.accounts[7].pubkey.to_string());
// Approve tokens
let mut ix_a = approve(
let ix_a = approve(
&spl_token::id(),
&account,
&ix.accounts[3].pubkey,
&config.owner.pubkey(),
&[],
U256::from(amount),
);
amount,
)?;
// TODO remove create calls
let mut ix_c = create_account(
@ -226,7 +252,7 @@ fn command_submit_vaa(config: &Config, vaa: &[u8]) -> CommmandResult {
Ok(Some(transaction))
}
fn command_create_token(config: &Config) -> CommmandResult {
fn command_create_token(config: &Config, decimals: u8) -> CommmandResult {
let token = Keypair::new();
println!("Creating token {}", token.pubkey());
@ -248,8 +274,8 @@ fn command_create_token(config: &Config) -> CommmandResult {
&token.pubkey(),
None,
Some(&config.owner.pubkey()),
U256::from(0),
9, // hard code 9 decimal places to match the sol/lamports relationship
0,
decimals,
)?,
],
Some(&config.fee_payer.pubkey()),
@ -341,7 +367,40 @@ fn command_transfer(
"Transfer {} tokens\n Sender: {}\n Recipient: {}",
ui_amount, sender, recipient
);
let amount = sol_to_lamports(ui_amount);
let sender_token_account = config
.rpc_client
.get_token_account_with_commitment(&sender, config.commitment_config)?
.value;
let recipient_token_account = config
.rpc_client
.get_token_account_with_commitment(&recipient, config.commitment_config)?
.value;
let decimals = match (sender_token_account, recipient_token_account) {
(Some(sender_token_account), Some(recipient_token_account)) => {
if sender_token_account.mint != recipient_token_account.mint {
eprintln!("Error: token mismatch between sender and recipient");
exit(1)
}
get_decimals_for_token(config, &sender_token_account.mint.parse::<Pubkey>()?)?
}
(None, _) => {
eprintln!(
"Error: sender account is invalid or does not exist: {}",
sender
);
exit(1)
}
(Some(_), None) => {
eprintln!(
"Error: recipient account is invalid or does not exist: {}",
recipient
);
exit(1)
}
};
let amount = spl_token::ui_amount_to_amount(ui_amount, decimals);
let mut transaction = Transaction::new_with_payer(
&[transfer(
@ -350,37 +409,7 @@ fn command_transfer(
&recipient,
&config.owner.pubkey(),
&[],
U256::from(amount),
)?],
Some(&config.fee_payer.pubkey()),
);
let (recent_blockhash, fee_calculator) = config.rpc_client.get_recent_blockhash()?;
check_fee_payer_balance(config, fee_calculator.calculate_fee(&transaction.message()))?;
transaction.sign(&[&config.fee_payer, &config.owner], recent_blockhash);
Ok(Some(transaction))
}
fn command_approve(
config: &Config,
sender: Pubkey,
ui_amount: f64,
recipient: Pubkey,
) -> CommmandResult {
println!(
"Approve {} tokens\n Sender: {}\n Recipient: {}",
ui_amount, sender, recipient
);
let amount = sol_to_lamports(ui_amount);
let mut transaction = Transaction::new_with_payer(
&[approve(
&spl_token::id(),
&sender,
&recipient,
&config.owner.pubkey(),
&[],
U256::from(amount),
amount,
)?],
Some(&config.fee_payer.pubkey()),
);
@ -393,7 +422,25 @@ fn command_approve(
fn command_burn(config: &Config, source: Pubkey, ui_amount: f64) -> CommmandResult {
println!("Burn {} tokens\n Source: {}", ui_amount, source);
let amount = sol_to_lamports(ui_amount);
let source_token_account = config
.rpc_client
.get_token_account_with_commitment(&source, config.commitment_config)?
.value;
let decimals = match source_token_account {
Some(source_token_account) => {
get_decimals_for_token(config, &source_token_account.mint.parse::<Pubkey>()?)?
}
None => {
eprintln!(
"Error: burn account is invalid or does not exist: {}",
source
);
exit(1)
}
};
let amount = spl_token::ui_amount_to_amount(ui_amount, decimals);
let mut transaction = Transaction::new_with_payer(
&[burn(
@ -401,7 +448,7 @@ fn command_burn(config: &Config, source: Pubkey, ui_amount: f64) -> CommmandResu
&source,
&config.owner.pubkey(),
&[],
U256::from(amount),
amount,
)?],
Some(&config.fee_payer.pubkey()),
);
@ -419,10 +466,28 @@ fn command_mint(
recipient: Pubkey,
) -> CommmandResult {
println!(
"Mint {} tokens\n Token: {}\n Recipient: {}",
"Minting {} tokens\n Token: {}\n Recipient: {}",
ui_amount, token, recipient
);
let amount = sol_to_lamports(ui_amount);
let recipient_token_account = config
.rpc_client
.get_token_account_with_commitment(&recipient, config.commitment_config)?
.value;
let decimals = match recipient_token_account {
Some(recipient_token_account) => {
get_decimals_for_token(config, &recipient_token_account.mint.parse::<Pubkey>()?)?
}
None => {
eprintln!(
"Error: recipient account is invalid or does not exist: {}",
recipient
);
exit(1)
}
};
let amount = spl_token::ui_amount_to_amount(ui_amount, decimals);
let mut transaction = Transaction::new_with_payer(
&[mint_to(
@ -431,7 +496,7 @@ fn command_mint(
&recipient,
&config.owner.pubkey(),
&[],
U256::from(amount),
amount,
)?],
Some(&config.fee_payer.pubkey()),
);
@ -459,7 +524,7 @@ fn command_wrap(config: &Config, sol: f64) -> CommmandResult {
initialize_account(
&spl_token::id(),
&account.pubkey(),
&spl_token::native_mint::id(),
&native_mint::id(),
&config.owner.pubkey(),
)?,
],
@ -480,7 +545,12 @@ fn command_unwrap(config: &Config, address: Pubkey) -> CommmandResult {
println!("Unwrapping {}", address);
println!(
" Amount: {} SOL\n Recipient: {}",
lamports_to_sol(config.rpc_client.get_balance(&address)?),
lamports_to_sol(
config
.rpc_client
.get_balance_with_commitment(&address, config.commitment_config)?
.value
),
config.owner.pubkey()
);
@ -502,24 +572,36 @@ fn command_unwrap(config: &Config, address: Pubkey) -> CommmandResult {
}
fn command_balance(config: &Config, address: Pubkey) -> CommmandResult {
let balance = config.rpc_client.get_token_account_balance(&address)?;
println!("{}", balance);
let balance = config
.rpc_client
.get_token_account_balance_with_commitment(&address, config.commitment_config)?
.value;
println!("{}", balance.ui_amount);
Ok(None)
}
fn command_supply(_config: &Config, address: Pubkey) -> CommmandResult {
println!("supply {}", address);
fn command_supply(config: &Config, address: Pubkey) -> CommmandResult {
let supply = config
.rpc_client
.get_token_supply_with_commitment(&address, config.commitment_config)?
.value;
println!("{}", supply.ui_amount);
Ok(None)
}
fn command_accounts(config: &Config, token: Option<Pubkey>) -> CommmandResult {
let accounts = config.rpc_client.get_token_accounts_by_owner(
let accounts = config
.rpc_client
.get_token_accounts_by_owner_with_commitment(
&config.owner.pubkey(),
match token {
Some(token) => TokenAccountsFilter::Mint(token),
None => TokenAccountsFilter::ProgramId(spl_token::id()),
},
)?;
config.commitment_config,
)?
.value;
if accounts.is_empty() {
println!("None");
}
@ -527,17 +609,21 @@ fn command_accounts(config: &Config, token: Option<Pubkey>) -> CommmandResult {
println!("Account Token Balance");
println!("-------------------------------------------------------------------------------------------------");
for (address, account) in accounts {
let balance = match config.rpc_client.get_token_account_balance(&address) {
Ok(response) => response,
Err(err) => 0,
let balance = match config
.rpc_client
.get_token_account_balance_with_commitment(&address, config.commitment_config)
{
Ok(response) => response.value.ui_amount.to_string(),
Err(err) => format!("{}", err),
};
println!("{:<44} {:<44} {}", address, account.lamports, balance);
println!("{:<44} {:<44} {}", address, account.mint, balance);
}
Ok(None)
}
fn main() {
let default_decimals = &format!("{}", native_mint::DECIMALS);
let matches = App::new(crate_name!())
.about(crate_description!())
.version(crate_version!())
@ -588,8 +674,20 @@ fn main() {
Defaults to the client keypair.",
),
)
.subcommand(SubCommand::with_name("create-token").about("Create a new token"))
.subcommand(SubCommand::with_name("create-bridge").about("Create a new bridge"))
.subcommand(SubCommand::with_name("create-token").about("Create a new token")
.arg(
Arg::with_name("decimals")
.long("decimals")
.validator(|s| {
s.parse::<u8>().map_err(|e| format!("{}", e))?;
Ok(())
})
.value_name("DECIMALS")
.takes_value(true)
.default_value(&default_decimals)
.help("Number of base 10 digits to the right of the decimal place"),
)
)
.subcommand(
SubCommand::with_name("create-account")
.about("Create a new token account")
@ -687,55 +785,6 @@ fn main() {
.help("The token account address of recipient"),
),
)
.subcommand(
SubCommand::with_name("lock")
.about("Transfer tokens to another chain")
.arg(
Arg::with_name("sender")
.validator(is_pubkey_or_keypair)
.value_name("SENDER_TOKEN_ACCOUNT_ADDRESS")
.takes_value(true)
.index(1)
.required(true)
.help("The token account address of the sender"),
)
.arg(
Arg::with_name("token")
.validator(is_pubkey_or_keypair)
.value_name("TOKEN_ADDRESS")
.takes_value(true)
.index(2)
.required(true)
.help("The mint address"),
)
.arg(
Arg::with_name("amount")
.validator(is_amount)
.value_name("AMOUNT")
.takes_value(true)
.index(3)
.required(true)
.help("Amount to transfer out"),
)
.arg(
Arg::with_name("chain")
.validator(is_u8)
.value_name("CHAIN")
.takes_value(true)
.index(4)
.required(true)
.help("Chain to transfer to"),
)
.arg(
Arg::with_name("nonce")
.validator(is_u8)
.value_name("NONCE")
.takes_value(true)
.index(5)
.required(true)
.help("Nonce of the transfer"),
),
)
.subcommand(
SubCommand::with_name("burn")
.about("Burn tokens from an account")
@ -853,6 +902,69 @@ fn main() {
.help("The address of the token account to unwrap"),
),
)
.subcommand(SubCommand::with_name("create-bridge").about("Create a new bridge"))
.subcommand(
SubCommand::with_name("lock")
.about("Transfer tokens to another chain")
.arg(
Arg::with_name("sender")
.validator(is_pubkey_or_keypair)
.value_name("SENDER_TOKEN_ACCOUNT_ADDRESS")
.takes_value(true)
.index(1)
.required(true)
.help("The token account address of the sender"),
)
.arg(
Arg::with_name("token")
.validator(is_pubkey_or_keypair)
.value_name("TOKEN_ADDRESS")
.takes_value(true)
.index(2)
.required(true)
.help("The mint address"),
)
.arg(
Arg::with_name("amount")
.validator(is_amount)
.value_name("AMOUNT")
.takes_value(true)
.index(3)
.required(true)
.help("Amount to transfer out"),
)
.arg(
Arg::with_name("chain")
.validator(is_u8)
.value_name("CHAIN")
.takes_value(true)
.index(4)
.required(true)
.help("Chain to transfer to"),
)
.arg(
Arg::with_name("nonce")
.validator(is_u8)
.value_name("NONCE")
.takes_value(true)
.index(5)
.required(true)
.help("Nonce of the transfer"),
),
)
.subcommand(
SubCommand::with_name("postvaa")
.about("Submit a VAA to the chain")
.arg(
Arg::with_name("vaa")
.validator(is_hex)
.value_name("HEX_VAA")
.takes_value(true)
.index(1)
.required(true)
.help("The vaa to be posted"),
)
)
.get_matches();
let config = {
@ -878,21 +990,16 @@ fn main() {
rpc_client: RpcClient::new(json_rpc_url),
owner,
fee_payer,
commitment_config: CommitmentConfig::single(),
}
};
solana_logger::setup_with_default("solana=info");
let _ = match matches.subcommand() {
("create-token", Some(_arg_matches)) => command_create_token(&config),
("create-bridge", Some(_arg_matches)) => command_deploy_bridge(&config),
("lock", Some(arg_matches)) => {
let account = pubkey_of(arg_matches, "sender").unwrap();
let amount = value_t_or_exit!(arg_matches, "amount", u64);
let nonce = value_t_or_exit!(arg_matches, "nonce", u32);
let chain = value_t_or_exit!(arg_matches, "chain", u8);
let token = pubkey_of(arg_matches, "token").unwrap();
command_lock_tokens(&config, account, token, amount, chain, [0; 32], nonce)
("create-token", Some(arg_matches)) => {
let decimals = value_t_or_exit!(arg_matches, "decimals", u8);
command_create_token(&config, decimals)
}
("create-account", Some(arg_matches)) => {
let token = pubkey_of(arg_matches, "token").unwrap();
@ -909,12 +1016,6 @@ fn main() {
let recipient = pubkey_of(arg_matches, "recipient").unwrap();
command_transfer(&config, sender, amount, recipient)
}
("approve", Some(arg_matches)) => {
let sender = pubkey_of(arg_matches, "sender").unwrap();
let amount = value_t_or_exit!(arg_matches, "amount", f64);
let recipient = pubkey_of(arg_matches, "recipient").unwrap();
command_approve(&config, sender, amount, recipient)
}
("burn", Some(arg_matches)) => {
let source = pubkey_of(arg_matches, "source").unwrap();
let amount = value_t_or_exit!(arg_matches, "amount", f64);
@ -946,6 +1047,20 @@ fn main() {
let token = pubkey_of(arg_matches, "token");
command_accounts(&config, token)
}
("create-bridge", Some(_arg_matches)) => command_deploy_bridge(&config),
("lock", Some(arg_matches)) => {
let account = pubkey_of(arg_matches, "sender").unwrap();
let amount = value_t_or_exit!(arg_matches, "amount", u64);
let nonce = value_t_or_exit!(arg_matches, "nonce", u32);
let chain = value_t_or_exit!(arg_matches, "chain", u8);
let token = pubkey_of(arg_matches, "token").unwrap();
command_lock_tokens(&config, account, token, amount, chain, [0; 32], nonce)
}
("postvaa", Some(arg_matches)) => {
let vaa_string: String = value_of(arg_matches, "vaa").unwrap();
let vaa = hex::decode(vaa_string).unwrap();
command_submit_vaa(&config, vaa.as_slice())
}
_ => unreachable!(),
}
.and_then(|transaction| {
@ -955,7 +1070,10 @@ fn main() {
// confirmation by default for better UX
let signature = config
.rpc_client
.send_and_confirm_transaction_with_spinner(&transaction)?;
.send_and_confirm_transaction_with_spinner_and_commitment(
&transaction,
config.commitment_config,
)?;
println!("Signature: {}", signature);
}
Ok(())
@ -979,3 +1097,12 @@ where
))
}
}
pub fn is_hex<T>(value: T) -> Result<(), String>
where
T: AsRef<str> + Display,
{
hex::decode(value.to_string())
.map(|_| ())
.map_err(|e| format!("{}", e))
}