use anchor_idl::{Idl, IdlType}; use borsh::BorshDeserialize; use serde_json::{json, Value}; use sha2::{Digest, Sha256}; pub struct SimpleSolanaInput { pub instruction_name: String, pub arguments: Value, } fn discriminator(name: &str) -> [u8; 8] { let preimage = format!("global:{}", name); let hash = Sha256::digest(preimage.as_bytes()); let mut disc = [0u8; 8]; disc.copy_from_slice(&hash[..8]); disc } fn decode_arg(ty: &IdlType, cursor: &mut &[u8]) -> Value { match ty { IdlType::Bool => json!(::deserialize(cursor).unwrap()), IdlType::I256 | IdlType::I128 | IdlType::I64 | IdlType::I32 | IdlType::I16 | IdlType::I8 | IdlType::U256 | IdlType::U128 | IdlType::U64 | IdlType::U32 | IdlType::U16 | IdlType::U8 => json!(::deserialize(cursor) .unwrap() .to_string()), IdlType::String => json!(::deserialize(cursor).unwrap()), // Handle Option IdlType::Option(inner) => { let flag: u8 = ::deserialize(cursor).unwrap(); if flag == 0 { Value::Null } else { decode_arg(inner, cursor) } } // Handle Vec IdlType::Vec(inner) => { // First read the length let len: u32 = ::deserialize(cursor).unwrap(); let mut items = Vec::with_capacity(len as usize); for _ in 0..len { items.push(decode_arg(inner, cursor)); } Value::Array(items) } other => panic!("Unsupported type: {:?}", other), } } pub fn decode_instruction(idl: &Idl, data: &[u8]) -> Option { assert!(data.len() >= 8, "Instruction data too short"); let disc: [u8; 8] = data[..8].try_into().unwrap(); // Find matching instruction let ix = idl .instructions .iter() .find(|ix| discriminator(&ix.name) == disc); let result = match ix { None => None, Some(ix) => { let mut cursor = &data[8..]; let mut args_json = serde_json::Map::new(); for arg in &ix.args { let val = decode_arg(&arg.ty, &mut cursor); args_json.insert(arg.name.clone(), val); } SimpleSolanaInput { instruction_name: ix.name.clone(), arguments: Value::Object(args_json), } .into() } }; result } /// Instructions supported by the token program. pub enum TokenInstruction { /// Transfers tokens from one account to another either directly or via a /// delegate. If this account is associated with the native mint then equal /// amounts of SOL and Tokens will be transferred to the destination /// account. /// /// Accounts expected by this instruction: /// /// * Single owner/delegate /// 0. `[writable]` The source account. /// 1. `[writable]` The destination account. /// 2. `[signer]` The source account's owner/delegate. /// /// * Multisignature owner/delegate /// 0. `[writable]` The source account. /// 1. `[writable]` The destination account. /// 2. `[]` The source account's multisignature owner/delegate. /// 3. ..`3+M` `[signer]` M signer accounts. Transfer { /// The amount of tokens to transfer. amount: u64, }, /// Approves a delegate. A delegate is given the authority over tokens on /// behalf of the source account's owner. /// /// Accounts expected by this instruction: /// /// * Single owner /// 0. `[writable]` The source account. /// 1. `[]` The delegate. /// 2. `[signer]` The source account owner. /// /// * Multisignature owner /// 0. `[writable]` The source account. /// 1. `[]` The delegate. /// 2. `[]` The source account's multisignature owner. /// 3. ..`3+M` `[signer]` M signer accounts Approve { /// The amount of tokens the delegate is approved for. amount: u64, }, /// Revokes the delegate's authority. /// /// Accounts expected by this instruction: /// /// * Single owner /// 0. `[writable]` The source account. /// 1. `[signer]` The source account owner. /// /// * Multisignature owner /// 0. `[writable]` The source account. /// 1. `[]` The source account's multisignature owner. /// 2. ..`2+M` `[signer]` M signer accounts Revoke, /// Transfers tokens from one account to another either directly or via a /// delegate. If this account is associated with the native mint then equal /// amounts of SOL and Tokens will be transferred to the destination /// account. /// /// This instruction differs from Transfer in that the token mint and /// decimals value is checked by the caller. This may be useful when /// creating transactions offline or within a hardware wallet. /// /// Accounts expected by this instruction: /// /// * Single owner/delegate /// 0. `[writable]` The source account. /// 1. `[]` The token mint. /// 2. `[writable]` The destination account. /// 3. `[signer]` The source account's owner/delegate. /// /// * Multisignature owner/delegate /// 0. `[writable]` The source account. /// 1. `[]` The token mint. /// 2. `[writable]` The destination account. /// 3. `[]` The source account's multisignature owner/delegate. /// 4. ..`4+M` `[signer]` M signer accounts. TransferChecked { /// The amount of tokens to transfer. amount: u64, /// Expected number of base 10 digits to the right of the decimal place. decimals: u8, }, /// Approves a delegate. A delegate is given the authority over tokens on /// behalf of the source account's owner. /// /// This instruction differs from Approve in that the token mint and /// decimals value is checked by the caller. This may be useful when /// creating transactions offline or within a hardware wallet. /// /// Accounts expected by this instruction: /// /// * Single owner /// 0. `[writable]` The source account. /// 1. `[]` The token mint. /// 2. `[]` The delegate. /// 3. `[signer]` The source account owner. /// /// * Multisignature owner /// 0. `[writable]` The source account. /// 1. `[]` The token mint. /// 2. `[]` The delegate. /// 3. `[]` The source account's multisignature owner. /// 4. ..`4+M` `[signer]` M signer accounts ApproveChecked { /// The amount of tokens the delegate is approved for. amount: u64, /// Expected number of base 10 digits to the right of the decimal place. decimals: u8, }, } const U64_BYTES: usize = 8; impl TokenInstruction { /// Unpacks a byte buffer into a /// [`TokenInstruction`](enum.TokenInstruction.html). pub fn unpack(input: &[u8]) -> Self { let (&tag, rest) = input.split_first().expect("Invalid transaction byte size"); match tag { 3 | 4 => { let amount = rest .get(..8) .and_then(|slice| slice.try_into().ok()) .map(u64::from_le_bytes) .expect("Invalid transaction, amount is required."); match tag { 3 => Self::Transfer { amount }, 4 => Self::Approve { amount }, _ => unreachable!(), } } 5 => Self::Revoke, 12 => { let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest); Self::TransferChecked { amount, decimals } } 13 => { let (amount, decimals, _rest) = Self::unpack_amount_decimals(rest); Self::ApproveChecked { amount, decimals } } _ => panic!("Transaction type not supported"), } } fn unpack_u64(input: &[u8]) -> (u64, &[u8]) { let value = input .get(..U64_BYTES) .and_then(|slice| slice.try_into().ok()) .map(u64::from_le_bytes) .expect("Invalid transaction, unable to unpack decimal amount"); (value, &input[U64_BYTES..]) } fn unpack_amount_decimals(input: &[u8]) -> (u64, u8, &[u8]) { let (amount, rest) = Self::unpack_u64(input); let (&decimals, rest) = rest .split_first() .expect("Invalid transaction, unable to unpack decimal amount"); (amount, decimals, rest) } }