icepick sol transfer-token
This commit is contained in:
parent
da5f29614c
commit
85e9d34fa8
|
@ -1476,6 +1476,7 @@ dependencies = [
|
|||
name = "icepick-solana"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"ed25519-dalek 1.0.1",
|
||||
"icepick-module",
|
||||
"serde",
|
||||
|
@ -1484,6 +1485,7 @@ dependencies = [
|
|||
"solana-sdk",
|
||||
"spl-associated-token-account",
|
||||
"spl-token",
|
||||
"spl-token-2022",
|
||||
"thiserror 2.0.3",
|
||||
]
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.22.1"
|
||||
ed25519-dalek = "=1.0.1"
|
||||
icepick-module = { version = "0.1.0", path = "../../icepick-module" }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
@ -12,4 +13,5 @@ solana-rpc-client = { version = "2.1.1", default-features = false }
|
|||
solana-sdk = { version = "2.1.1" }
|
||||
spl-associated-token-account = "6.0.0"
|
||||
spl-token = "7.0.0"
|
||||
spl-token-2022 = "6.0.0"
|
||||
thiserror = "2.0.3"
|
||||
|
|
|
@ -147,6 +147,20 @@ pub struct CreateTokenAccount {
|
|||
blockhash: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct TransferToken {
|
||||
amount: String,
|
||||
token_address: String,
|
||||
blockhash: String,
|
||||
to_address: String,
|
||||
from_account: Option<String>,
|
||||
from_address: String,
|
||||
fee: Option<String>,
|
||||
fee_payer: Option<String>,
|
||||
fee_payer_address: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Sign {}
|
||||
|
@ -180,6 +194,7 @@ pub enum Operation {
|
|||
GetTokenAddress(GetTokenAddress),
|
||||
Transfer(Transfer),
|
||||
CreateTokenAccount(CreateTokenAccount),
|
||||
TransferToken(TransferToken),
|
||||
Sign(Sign),
|
||||
Broadcast(Broadcast),
|
||||
}
|
||||
|
@ -336,6 +351,38 @@ impl Module for Solana {
|
|||
},
|
||||
],
|
||||
},
|
||||
icepick_module::help::Operation {
|
||||
name: "transfer-token".to_string(),
|
||||
description: "Transfer tokens from a Keyfork wallet to an external wallet."
|
||||
.to_string(),
|
||||
arguments: vec![
|
||||
Argument {
|
||||
name: "amount".to_string(),
|
||||
description: "The amount of tokens to transfer.".to_string(),
|
||||
r#type: ArgumentType::Required,
|
||||
},
|
||||
Argument {
|
||||
name: "token-address".to_string(),
|
||||
description: "The address of the token.".to_string(),
|
||||
r#type: ArgumentType::Required,
|
||||
},
|
||||
account.clone(),
|
||||
Argument {
|
||||
name: "to-address".to_string(),
|
||||
description: "The address to send the tokens to.".to_string(),
|
||||
r#type: ArgumentType::Required,
|
||||
},
|
||||
Argument {
|
||||
name: "from-address".to_string(),
|
||||
description: "The address to send the tokens from; will be used to verify the derivation account.".to_string(),
|
||||
r#type: ArgumentType::Required,
|
||||
},
|
||||
blockhash.clone(),
|
||||
fee.clone(),
|
||||
fee_payer.clone(),
|
||||
fee_payer_address.clone(),
|
||||
],
|
||||
},
|
||||
icepick_module::help::Operation {
|
||||
name: "sign".to_string(),
|
||||
description: "Sign a previously-generated transaction.".to_string(),
|
||||
|
@ -484,6 +531,101 @@ impl Module for Solana {
|
|||
"derivation-accounts": [0u32 | 1 << 31],
|
||||
}))
|
||||
}
|
||||
Operation::TransferToken(TransferToken {
|
||||
amount,
|
||||
token_address,
|
||||
blockhash,
|
||||
to_address,
|
||||
from_account,
|
||||
from_address,
|
||||
fee,
|
||||
fee_payer,
|
||||
fee_payer_address,
|
||||
}) => {
|
||||
// TODO: deduplicate code used in Transfer
|
||||
|
||||
// no transfer between types of currency, the only amount is the amount
|
||||
// of the lowest denomination. no floats, like with SOL / lamports.
|
||||
let amount = u64::from_str(&amount).expect("integer amount");
|
||||
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use spl_associated_token_account::get_associated_token_address;
|
||||
let to_pk = Pubkey::from_str(&to_address).unwrap();
|
||||
let from_pk = Pubkey::from_str(&from_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 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 to_token_address = get_associated_token_address(&to_pk, &token_pk);
|
||||
let decimals = 9u8;
|
||||
let mut instruction = spl_token_2022::instruction::transfer_checked(
|
||||
&token_program_id, // token program id
|
||||
&from_token_address, // source, as token address
|
||||
&token_pk, // mint
|
||||
&to_token_address, // destination, as token address
|
||||
&from_pk, // authority, as source sol address
|
||||
// TODO: signers should be [] when not using multisig
|
||||
// but should contain all signers when multisig
|
||||
&[], // signers
|
||||
// TODO: make amount floatable
|
||||
amount * 10u64.pow(decimals as u32), // amount
|
||||
decimals, // decimals
|
||||
)
|
||||
.unwrap();
|
||||
// TODO: check if this works with payer
|
||||
// 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
|
||||
// payer is not provided. The transactional account must be writable for the
|
||||
// computation fee to be paid.
|
||||
if payer_account_and_pk.is_none() {
|
||||
for account in instruction.accounts.iter_mut() {
|
||||
if account.pubkey == from_pk {
|
||||
account.is_writable = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
let hash = solana_sdk::hash::Hash::from_str(&blockhash).unwrap();
|
||||
let message = solana_sdk::message::Message::new_with_blockhash(
|
||||
&[instruction],
|
||||
payer_account_and_pk.map(|v| v.1).as_ref(),
|
||||
&hash,
|
||||
);
|
||||
// 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)]
|
||||
Ok(serde_json::json!({
|
||||
"blob": transaction,
|
||||
"derivation-accounts": [0u32 | 1 << 31],
|
||||
}))
|
||||
}
|
||||
Operation::Sign(_) => {
|
||||
let blob = request.blob.expect("passed in instruction blob");
|
||||
let mut transaction: solana_sdk::transaction::Transaction =
|
||||
|
|
Loading…
Reference in New Issue