icepick-solana: add support for utilizing durable nonces
This commit is contained in:
parent
be5f9a9fa0
commit
ca0fc3eef9
|
@ -12,6 +12,7 @@ icepick-module = { version = "0.1.0", path = "../../icepick-module" }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
solana-rpc-client = { version = "2.1.1", default-features = false }
|
solana-rpc-client = { version = "2.1.1", default-features = false }
|
||||||
|
solana-rpc-client-nonce-utils = "2.1.7"
|
||||||
solana-sdk = { version = "2.1.1" }
|
solana-sdk = { version = "2.1.1" }
|
||||||
solana-transaction-status = "2.1.1"
|
solana-transaction-status = "2.1.1"
|
||||||
solana-transaction-status-client-types = "2.1.1"
|
solana-transaction-status-client-types = "2.1.1"
|
||||||
|
|
|
@ -129,6 +129,21 @@ impl std::fmt::Display for Cluster {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: While, technically, they both fit in the same width, it is _important_ to have different
|
||||||
|
// functionality based on which is provided, as Nonce requires an incremention instruction.
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "snake_case", untagged)]
|
||||||
|
pub enum Hashable {
|
||||||
|
Nonce {
|
||||||
|
nonce_data: String,
|
||||||
|
nonce_address: String,
|
||||||
|
nonce_authority: String,
|
||||||
|
},
|
||||||
|
Blockhash {
|
||||||
|
blockhash: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct GetBlockhash {
|
pub struct GetBlockhash {
|
||||||
cluster: Option<Cluster>,
|
cluster: Option<Cluster>,
|
||||||
|
@ -161,6 +176,12 @@ pub struct CreateNonceAccountAndSigningKey {
|
||||||
from_address: String,
|
from_address: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct GetNonceAccountData {
|
||||||
|
nonce_address: String,
|
||||||
|
cluster: Option<Cluster>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct FindNonceAccounts {
|
pub struct FindNonceAccounts {
|
||||||
authorization_address: String,
|
authorization_address: String,
|
||||||
|
@ -198,6 +219,19 @@ pub struct TransferToken {
|
||||||
fee_payer_address: Option<String>,
|
fee_payer_address: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Compile {
|
||||||
|
#[serde(flatten)]
|
||||||
|
hashable: Hashable,
|
||||||
|
derivation_accounts: Vec<u32>,
|
||||||
|
instructions: Vec<solana_sdk::instruction::Instruction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Inspect {
|
||||||
|
transaction: solana_sdk::transaction::Transaction,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct Sign {
|
pub struct Sign {
|
||||||
blockhash: String,
|
blockhash: String,
|
||||||
|
@ -231,10 +265,13 @@ pub enum Operation {
|
||||||
AwaitFunds(AwaitFunds),
|
AwaitFunds(AwaitFunds),
|
||||||
GetTokenInfo(GetTokenInfo),
|
GetTokenInfo(GetTokenInfo),
|
||||||
CreateNonceAccountAndSigningKey(CreateNonceAccountAndSigningKey),
|
CreateNonceAccountAndSigningKey(CreateNonceAccountAndSigningKey),
|
||||||
|
GetNonceAccountData(GetNonceAccountData),
|
||||||
FindNonceAccounts(FindNonceAccounts),
|
FindNonceAccounts(FindNonceAccounts),
|
||||||
Transfer(Transfer),
|
Transfer(Transfer),
|
||||||
CreateTokenAccount(CreateTokenAccount),
|
CreateTokenAccount(CreateTokenAccount),
|
||||||
TransferToken(TransferToken),
|
TransferToken(TransferToken),
|
||||||
|
Compile(Compile),
|
||||||
|
Inspect(Inspect),
|
||||||
Sign(Sign),
|
Sign(Sign),
|
||||||
Broadcast(Broadcast),
|
Broadcast(Broadcast),
|
||||||
}
|
}
|
||||||
|
@ -292,135 +329,148 @@ impl Module for Solana {
|
||||||
.to_string(),
|
.to_string(),
|
||||||
r#type: ArgumentType::Required,
|
r#type: ArgumentType::Required,
|
||||||
};
|
};
|
||||||
vec![
|
let get_blockhash = icepick_module::help::Operation {
|
||||||
icepick_module::help::Operation {
|
name: "get-blockhash".to_string(),
|
||||||
name: "get-blockhash".to_string(),
|
description: "Get the latest blockhash".to_string(),
|
||||||
description: "Get the latest blockhash".to_string(),
|
arguments: vec![cluster.clone()],
|
||||||
arguments: vec![cluster.clone()],
|
};
|
||||||
},
|
let generate_wallet = icepick_module::help::Operation {
|
||||||
icepick_module::help::Operation {
|
name: "generate-wallet".to_string(),
|
||||||
name: "generate-wallet".to_string(),
|
description: "Generate the derivation index for a wallet.".to_string(),
|
||||||
description: "Generate the derivation index for a wallet.".to_string(),
|
arguments: vec![Argument {
|
||||||
arguments: vec![Argument {
|
name: "account".to_string(),
|
||||||
name: "account".to_string(),
|
description: "The derivation account used for generating the wallet.".to_string(),
|
||||||
description: "The derivation account used for generating the wallet."
|
r#type: ArgumentType::Optional,
|
||||||
.to_string(),
|
}],
|
||||||
r#type: ArgumentType::Optional,
|
};
|
||||||
}],
|
let get_wallet_address = icepick_module::help::Operation {
|
||||||
},
|
name: "get-wallet-address".to_string(),
|
||||||
icepick_module::help::Operation {
|
description: "Get the address for a given wallet.".to_string(),
|
||||||
name: "get-wallet-address".to_string(),
|
arguments: vec![],
|
||||||
description: "Get the address for a given wallet.".to_string(),
|
};
|
||||||
arguments: vec![],
|
let await_funds = icepick_module::help::Operation {
|
||||||
},
|
name: "await-funds".to_string(),
|
||||||
icepick_module::help::Operation {
|
description: "Await a minimum amount of funds in an account".to_string(),
|
||||||
name: "await-funds".to_string(),
|
arguments: vec![
|
||||||
description: "Await a minimum amount of funds in an account".to_string(),
|
Argument {
|
||||||
arguments: vec![Argument {
|
|
||||||
name: "address".to_string(),
|
name: "address".to_string(),
|
||||||
description: "The address to monitor".to_string(),
|
description: "The address to monitor".to_string(),
|
||||||
r#type: ArgumentType::Required,
|
r#type: ArgumentType::Required,
|
||||||
}, Argument {
|
},
|
||||||
|
Argument {
|
||||||
name: "amount".to_string(),
|
name: "amount".to_string(),
|
||||||
description: "The amount of lamports to await".to_string(),
|
description: "The amount of lamports to await".to_string(),
|
||||||
r#type: ArgumentType::Required,
|
r#type: ArgumentType::Required,
|
||||||
}],
|
},
|
||||||
},
|
],
|
||||||
icepick_module::help::Operation {
|
};
|
||||||
name: "get-token-info".to_string(),
|
let get_token_info = icepick_module::help::Operation {
|
||||||
description: "Get the address for a given token.".to_string(),
|
name: "get-token-info".to_string(),
|
||||||
arguments: vec![Argument {
|
description: "Get the address for a given token.".to_string(),
|
||||||
name: "token".to_string(),
|
arguments: vec![Argument {
|
||||||
description: "The token to look up".to_string(),
|
name: "token".to_string(),
|
||||||
|
description: "The token to look up".to_string(),
|
||||||
|
r#type: ArgumentType::Required,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
let create_nonce_account_and_signing_key = icepick_module::help::Operation {
|
||||||
|
name: "create-nonce-account-and-signing-key".to_string(),
|
||||||
|
description: "Create a nonce account for signing durable transactions".to_string(),
|
||||||
|
arguments: vec![
|
||||||
|
account.clone(),
|
||||||
|
from_address.clone(),
|
||||||
|
Argument {
|
||||||
|
name: "authorization_address".to_string(),
|
||||||
|
description: "The account authorized to use and advance the nonce.".to_string(),
|
||||||
r#type: ArgumentType::Required,
|
r#type: ArgumentType::Required,
|
||||||
}],
|
},
|
||||||
},
|
],
|
||||||
icepick_module::help::Operation {
|
};
|
||||||
name: "create-nonce-account-and-signing-key".to_string(),
|
let get_nonce_account_data = icepick_module::help::Operation {
|
||||||
description: "Create a nonce account for signing durable transactions".to_string(),
|
name: "get-nonce-account-data".to_string(),
|
||||||
arguments: vec![
|
description: "Get the data for a nonce account".to_string(),
|
||||||
account.clone(),
|
arguments: vec![
|
||||||
from_address.clone(),
|
cluster.clone(),
|
||||||
Argument {
|
Argument {
|
||||||
name: "authorization_address".to_string(),
|
name: "nonce_address".to_string(),
|
||||||
description: "The account authorized to use and advance the nonce.".to_string(),
|
description: "The address of the nonce account.".to_string(),
|
||||||
r#type: ArgumentType::Required,
|
r#type: ArgumentType::Required,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
};
|
||||||
icepick_module::help::Operation {
|
let find_nonce_accounts = icepick_module::help::Operation {
|
||||||
name: "find-nonce-accounts".to_string(),
|
name: "find-nonce-accounts".to_string(),
|
||||||
description: "Find all nonce accounts for an authorized address".to_string(),
|
description: "Find all nonce accounts for an authorized address".to_string(),
|
||||||
arguments: vec![
|
arguments: vec![
|
||||||
cluster.clone(),
|
cluster.clone(),
|
||||||
Argument {
|
Argument {
|
||||||
name: "authorization_address".to_string(),
|
name: "authorization_address".to_string(),
|
||||||
description: "The account authorized to use and advance nonces.".to_string(),
|
description: "The account authorized to use and advance nonces.".to_string(),
|
||||||
r#type: ArgumentType::Required,
|
r#type: ArgumentType::Required,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
};
|
||||||
icepick_module::help::Operation {
|
|
||||||
name: "transfer".to_string(),
|
let transfer = icepick_module::help::Operation {
|
||||||
description: "Transfer SOL from a Keyfork wallet to an external wallet."
|
name: "transfer".to_string(),
|
||||||
.to_string(),
|
description: "Transfer SOL from a Keyfork wallet to an external wallet.".to_string(),
|
||||||
arguments: vec![
|
arguments: vec![
|
||||||
Argument {
|
Argument {
|
||||||
name: "amount".to_string(),
|
name: "amount".to_string(),
|
||||||
description: "The amount of SOL to transfer.".to_string(),
|
description: "The amount of SOL to transfer.".to_string(),
|
||||||
r#type: ArgumentType::Required,
|
r#type: ArgumentType::Required,
|
||||||
},
|
},
|
||||||
account.clone(),
|
account.clone(),
|
||||||
Argument {
|
Argument {
|
||||||
name: "to_address".to_string(),
|
name: "to_address".to_string(),
|
||||||
description: "The address to send SOL to.".to_string(),
|
description: "The address to send SOL to.".to_string(),
|
||||||
r#type: ArgumentType::Required,
|
r#type: ArgumentType::Required,
|
||||||
},
|
},
|
||||||
from_address.clone(),
|
from_address.clone(),
|
||||||
fee.clone(),
|
fee.clone(),
|
||||||
fee_payer.clone(),
|
fee_payer.clone(),
|
||||||
fee_payer_address.clone(),
|
fee_payer_address.clone(),
|
||||||
],
|
],
|
||||||
},
|
};
|
||||||
icepick_module::help::Operation {
|
let stake = icepick_module::help::Operation {
|
||||||
name: "stake".to_string(),
|
name: "stake".to_string(),
|
||||||
description: "Stake SOL to earn rewards.".to_string(),
|
description: "Stake SOL to earn rewards.".to_string(),
|
||||||
arguments: vec![
|
arguments: vec![
|
||||||
Argument {
|
Argument {
|
||||||
name: "amount".to_string(),
|
name: "amount".to_string(),
|
||||||
description: "The amount of SOL to stake.".to_string(),
|
description: "The amount of SOL to stake.".to_string(),
|
||||||
r#type: ArgumentType::Required,
|
r#type: ArgumentType::Required,
|
||||||
},
|
},
|
||||||
account.clone(),
|
account.clone(),
|
||||||
from_address.clone(),
|
from_address.clone(),
|
||||||
fee.clone(),
|
fee.clone(),
|
||||||
fee_payer.clone(),
|
fee_payer.clone(),
|
||||||
fee_payer_address.clone(),
|
fee_payer_address.clone(),
|
||||||
],
|
],
|
||||||
},
|
};
|
||||||
// kinda BS that you have to make an account for a token, but ok.
|
// kinda BS that you have to make an account for a token, but ok.
|
||||||
icepick_module::help::Operation {
|
let create_token_account = icepick_module::help::Operation {
|
||||||
name: "create-token-account".to_string(),
|
name: "create-token-account".to_string(),
|
||||||
description: "Create an account for a given token".to_string(),
|
description: "Create an account for a given token".to_string(),
|
||||||
arguments: vec![
|
arguments: vec![
|
||||||
Argument {
|
Argument {
|
||||||
name: "wallet_address".to_string(),
|
name: "wallet_address".to_string(),
|
||||||
description: "The address of the token.".to_string(),
|
description: "The address of the token.".to_string(),
|
||||||
r#type: ArgumentType::Required,
|
r#type: ArgumentType::Required,
|
||||||
},
|
},
|
||||||
Argument {
|
Argument {
|
||||||
name: "token_address".to_string(),
|
name: "token_address".to_string(),
|
||||||
description: "The address of the token.".to_string(),
|
description: "The address of the token.".to_string(),
|
||||||
r#type: ArgumentType::Required,
|
r#type: ArgumentType::Required,
|
||||||
},
|
},
|
||||||
Argument {
|
Argument {
|
||||||
name: "funder_address".to_string(),
|
name: "funder_address".to_string(),
|
||||||
description: "The address of the funder (signer).".to_string(),
|
description: "The address of the funder (signer).".to_string(),
|
||||||
r#type: ArgumentType::Optional,
|
r#type: ArgumentType::Optional,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
};
|
||||||
icepick_module::help::Operation {
|
let transfer_token = icepick_module::help::Operation {
|
||||||
name: "transfer-token".to_string(),
|
name: "transfer-token".to_string(),
|
||||||
description: "Transfer tokens from a Keyfork wallet to an external wallet."
|
description: "Transfer tokens from a Keyfork wallet to an external wallet."
|
||||||
.to_string(),
|
.to_string(),
|
||||||
|
@ -455,21 +505,61 @@ impl Module for Solana {
|
||||||
fee_payer.clone(),
|
fee_payer.clone(),
|
||||||
fee_payer_address.clone(),
|
fee_payer_address.clone(),
|
||||||
],
|
],
|
||||||
},
|
};
|
||||||
icepick_module::help::Operation {
|
let compile = icepick_module::help::Operation {
|
||||||
name: "sign".to_string(),
|
name: "compile".to_string(),
|
||||||
description: "Sign a previously-generated transaction.".to_string(),
|
description: "Compile instructions into a transaction".to_string(),
|
||||||
arguments: vec![Argument {
|
arguments: vec![
|
||||||
name: "blockhash".to_string(),
|
Argument {
|
||||||
description: "A recent blockhash".to_string(),
|
name: "blockhash".to_string(),
|
||||||
r#type: ArgumentType::Required,
|
description: "A recent blockhash, must be provided in place of nonce"
|
||||||
}],
|
.to_string(),
|
||||||
},
|
r#type: ArgumentType::Optional,
|
||||||
icepick_module::help::Operation {
|
},
|
||||||
name: "broadcast".to_string(),
|
Argument {
|
||||||
description: "Broadcast a signed transaction".to_string(),
|
name: "nonce".to_string(),
|
||||||
arguments: vec![cluster.clone()],
|
description: "A durable nonce, must be provided in place of blockhash"
|
||||||
},
|
.to_string(),
|
||||||
|
r#type: ArgumentType::Optional,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
let inspect = icepick_module::help::Operation {
|
||||||
|
name: "inspect".to_string(),
|
||||||
|
description: "Print a transaction using base64.".to_string(),
|
||||||
|
arguments: vec![],
|
||||||
|
};
|
||||||
|
let sign = icepick_module::help::Operation {
|
||||||
|
name: "sign".to_string(),
|
||||||
|
description: "Sign a previously-generated transaction.".to_string(),
|
||||||
|
arguments: vec![Argument {
|
||||||
|
name: "blockhash".to_string(),
|
||||||
|
description: "A recent blockhash".to_string(),
|
||||||
|
r#type: ArgumentType::Required,
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
let broadcast = icepick_module::help::Operation {
|
||||||
|
name: "broadcast".to_string(),
|
||||||
|
description: "Broadcast a signed transaction".to_string(),
|
||||||
|
arguments: vec![cluster.clone()],
|
||||||
|
};
|
||||||
|
vec![
|
||||||
|
get_blockhash,
|
||||||
|
generate_wallet,
|
||||||
|
get_wallet_address,
|
||||||
|
await_funds,
|
||||||
|
get_token_info,
|
||||||
|
create_nonce_account_and_signing_key,
|
||||||
|
get_nonce_account_data,
|
||||||
|
find_nonce_accounts,
|
||||||
|
transfer,
|
||||||
|
stake,
|
||||||
|
create_token_account,
|
||||||
|
transfer_token,
|
||||||
|
compile,
|
||||||
|
inspect,
|
||||||
|
sign,
|
||||||
|
broadcast,
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -594,6 +684,10 @@ impl Module for Solana {
|
||||||
let from_pk = Pubkey::from_str(&from_address).unwrap();
|
let from_pk = Pubkey::from_str(&from_address).unwrap();
|
||||||
let authorization_pk = Pubkey::from_str(&authorization_address).unwrap();
|
let authorization_pk = Pubkey::from_str(&authorization_address).unwrap();
|
||||||
|
|
||||||
|
if from_account.is_some() {
|
||||||
|
unimplemented!("alternative derivation accounts are not yet implemented");
|
||||||
|
}
|
||||||
|
|
||||||
let instructions = system_instruction::create_nonce_account(
|
let instructions = system_instruction::create_nonce_account(
|
||||||
&from_pk,
|
&from_pk,
|
||||||
&keypair.pubkey(),
|
&keypair.pubkey(),
|
||||||
|
@ -602,20 +696,35 @@ impl Module for Solana {
|
||||||
1500000,
|
1500000,
|
||||||
);
|
);
|
||||||
|
|
||||||
let message = solana_sdk::message::Message::new(&instructions, None);
|
#[allow(clippy::identity_op)]
|
||||||
let transaction = solana_sdk::transaction::Transaction::new_unsigned(message);
|
|
||||||
let from_account = from_account
|
|
||||||
.and_then(|a| u32::from_str(&a).ok())
|
|
||||||
.unwrap_or(0);
|
|
||||||
let requested_accounts = vec![from_account | 1 << 31];
|
|
||||||
|
|
||||||
Ok(serde_json::json!({
|
Ok(serde_json::json!({
|
||||||
"blob": {
|
"blob": {
|
||||||
"nonce_pubkey": keypair.pubkey().to_string(),
|
"nonce_pubkey": keypair.pubkey().to_string(),
|
||||||
"nonce_privkey": [keypair.secret().to_bytes()],
|
"nonce_privkey": [keypair.secret().to_bytes()],
|
||||||
"transaction": transaction,
|
"transaction": instructions,
|
||||||
|
},
|
||||||
|
"derivation_accounts": [0u32 | 1 << 31],
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Operation::GetNonceAccountData(GetNonceAccountData {
|
||||||
|
nonce_address,
|
||||||
|
cluster,
|
||||||
|
}) => {
|
||||||
|
let nonce_pk = Pubkey::from_str(&nonce_address).unwrap();
|
||||||
|
let cluster = cluster.unwrap_or(Cluster::MainnetBeta);
|
||||||
|
let cluster_url = format!("https://api.{cluster}.solana.com");
|
||||||
|
let client = solana_rpc_client::rpc_client::RpcClient::new(cluster_url);
|
||||||
|
|
||||||
|
let nonce_account = client.get_account(&nonce_pk).unwrap();
|
||||||
|
let nonce =
|
||||||
|
solana_rpc_client_nonce_utils::data_from_account(&nonce_account).unwrap();
|
||||||
|
|
||||||
|
Ok(serde_json::json!({
|
||||||
|
"blob": {
|
||||||
|
"authority": nonce.authority.to_string(),
|
||||||
|
"durable_nonce": nonce.durable_nonce.as_hash().to_string(),
|
||||||
|
"lamports_per_signature": nonce.fee_calculator.lamports_per_signature,
|
||||||
},
|
},
|
||||||
"derivation_accounts": requested_accounts,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
Operation::FindNonceAccounts(FindNonceAccounts {
|
Operation::FindNonceAccounts(FindNonceAccounts {
|
||||||
|
@ -749,56 +858,30 @@ impl Module for Solana {
|
||||||
from_account,
|
from_account,
|
||||||
to_address,
|
to_address,
|
||||||
from_address,
|
from_address,
|
||||||
fee: _,
|
fee,
|
||||||
fee_payer,
|
fee_payer,
|
||||||
fee_payer_address,
|
fee_payer_address,
|
||||||
}) => {
|
}) => {
|
||||||
// TODO:
|
if from_account.is_some() {
|
||||||
// parse address for to_address
|
unimplemented!("from_account");
|
||||||
|
}
|
||||||
|
if fee.is_some() | fee_payer.is_some() | fee_payer_address.is_some() {
|
||||||
|
unimplemented!("fee")
|
||||||
|
}
|
||||||
|
|
||||||
let amount = f64::from_str(&amount).expect("float amount");
|
let amount = f64::from_str(&amount).expect("float amount");
|
||||||
let amount: u64 = (amount * LAMPORTS_PER_SOL as f64) as u64;
|
let amount: u64 = (amount * LAMPORTS_PER_SOL as f64) as u64;
|
||||||
|
|
||||||
let to_pk = Pubkey::from_str(&to_address).unwrap();
|
let to_pk = Pubkey::from_str(&to_address).unwrap();
|
||||||
let from_pk = Pubkey::from_str(&from_address).unwrap();
|
let from_pk = Pubkey::from_str(&from_address).unwrap();
|
||||||
let payer_account_and_pk = {
|
|
||||||
// If a fee payer is given, a fee payer address must also be given, since the
|
|
||||||
// address must be known before signing the transaction.
|
|
||||||
match (&fee_payer, &fee_payer_address) {
|
|
||||||
(Some(payer), Some(address)) => {
|
|
||||||
// Use the provided account
|
|
||||||
Some((
|
|
||||||
u32::from_str(payer).unwrap(),
|
|
||||||
Pubkey::from_str(address).unwrap(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
(None, None) => {
|
|
||||||
// Use the transaction account
|
|
||||||
None
|
|
||||||
}
|
|
||||||
_ => panic!("Invalid combination of fee_payer and fee_payer_address"),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let instruction = system_instruction::transfer(&from_pk, &to_pk, amount);
|
let instruction = system_instruction::transfer(&from_pk, &to_pk, amount);
|
||||||
let message = solana_sdk::message::Message::new(
|
#[allow(clippy::identity_op)]
|
||||||
&[instruction],
|
|
||||||
payer_account_and_pk.map(|v| v.1).as_ref(),
|
|
||||||
);
|
|
||||||
let transaction = solana_sdk::transaction::Transaction::new_unsigned(message);
|
|
||||||
// TODO: error handling from_str
|
|
||||||
let from_account = from_account
|
|
||||||
.and_then(|a| u32::from_str(&a).ok())
|
|
||||||
.unwrap_or(0);
|
|
||||||
let mut requested_accounts = vec![];
|
|
||||||
requested_accounts.push(from_account | 1 << 31);
|
|
||||||
if let Some((account, _)) = &payer_account_and_pk {
|
|
||||||
requested_accounts.push(*account | 1 << 31);
|
|
||||||
}
|
|
||||||
Ok(serde_json::json!({
|
Ok(serde_json::json!({
|
||||||
"blob": {
|
"blob": {
|
||||||
"transaction": transaction,
|
"instructions": [instruction],
|
||||||
|
// This is done in blob since it's compiled in the next step
|
||||||
|
"derivation_accounts": [0u32 | 1 << 31],
|
||||||
},
|
},
|
||||||
"derivation_accounts": requested_accounts,
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
Operation::CreateTokenAccount(CreateTokenAccount {
|
Operation::CreateTokenAccount(CreateTokenAccount {
|
||||||
|
@ -843,39 +926,23 @@ impl Module for Solana {
|
||||||
fee_payer,
|
fee_payer,
|
||||||
fee_payer_address,
|
fee_payer_address,
|
||||||
}) => {
|
}) => {
|
||||||
// TODO: deduplicate code used in Transfer
|
if from_account.is_some() {
|
||||||
|
unimplemented!("from_account");
|
||||||
|
}
|
||||||
|
if fee.is_some() | fee_payer.is_some() | fee_payer_address.is_some() {
|
||||||
|
unimplemented!("fee")
|
||||||
|
}
|
||||||
|
|
||||||
let amount = f64::from_str(&amount).expect("float amount");
|
let amount = f64::from_str(&amount).expect("float amount");
|
||||||
let decimals = u8::from_str(&decimals).expect("decimals");
|
let decimals = u8::from_str(&decimals).expect("decimals");
|
||||||
let amount: u64 = (amount * 10u64.pow(decimals as u32) as f64) as u64;
|
let amount: u64 = (amount * 10u64.pow(decimals as u32) as f64) as u64;
|
||||||
|
|
||||||
|
use solana_sdk::pubkey::Pubkey;
|
||||||
use spl_associated_token_account::get_associated_token_address;
|
use spl_associated_token_account::get_associated_token_address;
|
||||||
let to_pk = Pubkey::from_str(&to_address).unwrap();
|
let to_pk = Pubkey::from_str(&to_address).unwrap();
|
||||||
let from_pk = Pubkey::from_str(&from_address).unwrap();
|
let from_pk = Pubkey::from_str(&from_address).unwrap();
|
||||||
let token_pk = Pubkey::from_str(&token_address).unwrap();
|
let token_pk = Pubkey::from_str(&token_address).unwrap();
|
||||||
let payer_account_and_pk = {
|
|
||||||
// If a fee payer is given, a fee payer address must also be given, since the
|
|
||||||
// address must be known before signing the transaction.
|
|
||||||
match (&fee_payer, &fee_payer_address) {
|
|
||||||
(Some(payer), Some(address)) => {
|
|
||||||
// Use the provided account
|
|
||||||
Some((
|
|
||||||
u32::from_str(payer).unwrap(),
|
|
||||||
Pubkey::from_str(address).unwrap(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
(None, None) => {
|
|
||||||
// Use the transaction account
|
|
||||||
None
|
|
||||||
}
|
|
||||||
_ => panic!("Invalid combination of fee_payer and fee_payer_address"),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let token_program_id = spl_token::ID;
|
let token_program_id = spl_token::ID;
|
||||||
let mut signers = vec![&from_pk];
|
|
||||||
if let Some((_, pk)) = payer_account_and_pk.as_ref() {
|
|
||||||
signers.push(pk);
|
|
||||||
}
|
|
||||||
|
|
||||||
let from_token_address = get_associated_token_address(&from_pk, &token_pk);
|
let from_token_address = get_associated_token_address(&from_pk, &token_pk);
|
||||||
let to_token_address = get_associated_token_address(&to_pk, &token_pk);
|
let to_token_address = get_associated_token_address(&to_pk, &token_pk);
|
||||||
|
@ -892,36 +959,75 @@ impl Module for Solana {
|
||||||
decimals, // decimals
|
decimals, // decimals
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
// TODO: check if this works with payer
|
|
||||||
|
// TODO: check if this works with multisig
|
||||||
// this is required because the Solana SDK does not set the primary transactional
|
// this is required because the Solana SDK does not set the primary transactional
|
||||||
// key as writable (the one that would be paying computation fees) in the event a
|
// key as writable (the one that would be paying computation fees) in the event a
|
||||||
// payer is not provided. The transactional account must be writable for the
|
// payer is not provided. The transactional account must be writable for the
|
||||||
// computation fee to be paid.
|
// computation fee to be paid.
|
||||||
if payer_account_and_pk.is_none() {
|
for account in instruction.accounts.iter_mut() {
|
||||||
for account in instruction.accounts.iter_mut() {
|
if account.pubkey == from_pk {
|
||||||
if account.pubkey == from_pk {
|
account.is_writable = true;
|
||||||
account.is_writable = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let message = solana_sdk::message::Message::new(
|
|
||||||
&[instruction],
|
|
||||||
payer_account_and_pk.map(|v| v.1).as_ref(),
|
|
||||||
);
|
|
||||||
// message.header.num_readonly_signed_accounts = 0;
|
|
||||||
let transaction =
|
|
||||||
solana_sdk::transaction::Transaction::new_unsigned(message.clone());
|
|
||||||
/*
|
|
||||||
use base64::prelude::*;
|
|
||||||
eprintln!("{}", BASE64_STANDARD.encode(transaction.message_data()));
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[allow(clippy::identity_op)]
|
#[allow(clippy::identity_op)]
|
||||||
Ok(serde_json::json!({
|
Ok(serde_json::json!({
|
||||||
"blob": {
|
"blob": {
|
||||||
|
"instructions": [instruction],
|
||||||
|
"derivation_accounts": [0u32 | 1 << 31],
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Operation::Compile(Compile {
|
||||||
|
hashable,
|
||||||
|
derivation_accounts,
|
||||||
|
mut instructions,
|
||||||
|
}) => {
|
||||||
|
use solana_sdk::{hash::Hash, message::Message, transaction::Transaction};
|
||||||
|
|
||||||
|
let (hash, transaction) = match hashable {
|
||||||
|
// We already have the account from GetNonceAccountData,
|
||||||
|
// which also gives us the authority and the nonce itself.
|
||||||
|
Hashable::Nonce {
|
||||||
|
nonce_data,
|
||||||
|
nonce_address,
|
||||||
|
nonce_authority,
|
||||||
|
} => {
|
||||||
|
let account_pk = Pubkey::from_str(&nonce_address).unwrap();
|
||||||
|
let authority_pk = Pubkey::from_str(&nonce_authority).unwrap();
|
||||||
|
|
||||||
|
let hash = Hash::from_str(&nonce_data).unwrap();
|
||||||
|
let increment_nonce =
|
||||||
|
system_instruction::advance_nonce_account(&account_pk, &authority_pk);
|
||||||
|
|
||||||
|
instructions.insert(0, increment_nonce);
|
||||||
|
let message = Message::new(&instructions, None);
|
||||||
|
let transaction = Transaction::new_unsigned(message);
|
||||||
|
(hash, transaction)
|
||||||
|
}
|
||||||
|
Hashable::Blockhash { blockhash } => {
|
||||||
|
let blockhash = Hash::from_str(&blockhash).unwrap();
|
||||||
|
let message = Message::new(&instructions, None);
|
||||||
|
let transaction = Transaction::new_unsigned(message);
|
||||||
|
(blockhash, transaction)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(serde_json::json!({
|
||||||
|
"blob": {
|
||||||
|
"hash": hash,
|
||||||
"transaction": transaction,
|
"transaction": transaction,
|
||||||
},
|
},
|
||||||
"derivation_accounts": [0u32 | 1 << 31],
|
"derivation_accounts": derivation_accounts,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
Operation::Inspect(Inspect { transaction }) => {
|
||||||
|
use base64::prelude::*;
|
||||||
|
|
||||||
|
Ok(serde_json::json!({
|
||||||
|
"blob": {
|
||||||
|
"formatted_transaction": BASE64_STANDARD.encode(transaction.message_data())
|
||||||
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
Operation::Sign(Sign {
|
Operation::Sign(Sign {
|
||||||
|
|
Loading…
Reference in New Issue