use axum::http::StatusCode;
use axum::response::{IntoResponse, Response};
use axum::Json;
use handlebars::Handlebars;
use serde::{Deserialize, Serialize};
use solana_sdk::instruction::{AccountMeta, Instruction};
use solana_sdk::message::Message;
use solana_sdk::pubkey;
use solana_sdk::signature::Keypair;
use solana_sdk::signer::Signer;
use solana_sdk::transaction::Transaction;
pub extern crate base64;
pub extern crate bincode;
pub extern crate colored;
pub extern crate solana_client;
pub extern crate tower_http;
pub extern crate znap_macros;
pub mod env;
pub mod prelude {
pub use super::env::Env;
pub use super::{
Action, ActionLinks, ActionMetadata, ActionResponse, ActionTransaction, Error, ErrorCode,
LinkedAction, LinkedActionParameter, Result, ToMetadata,
};
pub use base64;
pub use bincode;
pub use colored;
pub use solana_client;
pub use tower_http;
pub use znap_macros::{collection, Action, ErrorCode};
}
pub trait Action {}
pub trait ErrorCode {}
pub type Result<T> = core::result::Result<T, Error>;
pub trait ToMetadata {
fn to_metadata() -> ActionMetadata;
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateActionPayload {
pub account: String,
}
#[derive(Debug, Serialize)]
pub struct ActionResponse {
pub transaction: String,
pub message: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct ActionTransaction {
pub transaction: Transaction,
pub message: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct ActionMetadata {
pub icon: String,
pub title: String,
pub description: String,
pub label: String,
pub links: Option<ActionLinks>,
pub disabled: bool,
pub error: Option<ActionError>,
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct ActionError {
pub message: String,
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct ActionLinks {
pub actions: Vec<LinkedAction>,
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct LinkedAction {
pub label: String,
pub href: String,
pub parameters: Vec<LinkedActionParameter>,
}
#[derive(Debug, Deserialize, Serialize, PartialEq)]
pub struct LinkedActionParameter {
pub label: String,
pub name: String,
pub required: bool,
}
#[derive(Debug)]
pub struct Error {
pub code: StatusCode,
pub name: String,
pub message: String,
}
impl Error {
pub fn new(code: StatusCode, name: String, message: impl Into<String>) -> Self {
Self {
code,
name,
message: message.into(),
}
}
}
impl IntoResponse for Error {
fn into_response(self) -> Response {
(
self.code,
Json(ErrorResponse {
name: self.name.clone(),
message: self.message.clone(),
}),
)
.into_response()
}
}
#[derive(Serialize, Deserialize)]
struct ErrorResponse {
name: String,
message: String,
}
pub fn add_action_identity_proof(transaction: Transaction, keypair: &Keypair) -> Transaction {
let identity_pubkey = keypair.pubkey();
let reference_keypair = Keypair::new();
let reference_pubkey = reference_keypair.pubkey();
let identity_signature = keypair.sign_message(&reference_pubkey.to_bytes());
let identity_message = format!(
"solana-action:{}:{}:{}",
identity_pubkey, reference_pubkey, identity_signature
);
let mut identity_added = false;
let mut instructions_with_identity: Vec<Instruction> = transaction
.message
.instructions
.iter()
.map(|instruction| {
let program_id =
transaction.message.account_keys[instruction.program_id_index as usize];
let mut accounts: Vec<AccountMeta> = instruction
.accounts
.iter()
.map(|account_index| {
let pubkey = transaction.message.account_keys[*account_index as usize];
match transaction
.message
.is_maybe_writable(*account_index as usize, None)
{
true => AccountMeta::new(
pubkey,
transaction.message.is_signer(*account_index as usize),
),
false => AccountMeta::new_readonly(
pubkey,
transaction.message.is_signer(*account_index as usize),
),
}
})
.collect();
if !identity_added
&& program_id.to_string() != "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
{
accounts.push(AccountMeta::new_readonly(reference_pubkey, false));
accounts.push(AccountMeta::new_readonly(identity_pubkey, false));
identity_added = true;
}
Instruction {
program_id,
data: instruction.data.clone(),
accounts,
}
})
.collect();
instructions_with_identity.push(Instruction {
accounts: vec![],
data: identity_message.as_bytes().to_vec(),
program_id: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"),
});
let transaction_message_with_identity = Message::new(&instructions_with_identity, None);
Transaction::new_unsigned(transaction_message_with_identity)
}
pub fn render_source<T>(source: &str, data: &T) -> String
where
T: Serialize,
{
let mut handlebars = Handlebars::new();
assert!(handlebars
.register_template_string("template", source)
.is_ok());
let output = handlebars.render("template", &data).unwrap();
handlebars.clear_templates();
output
}
pub fn render_parameters<T>(
parameters: &[LinkedActionParameter],
data: &T,
) -> Vec<LinkedActionParameter>
where
T: Serialize,
{
parameters
.iter()
.map(|parameter| {
let name = render_source(¶meter.name, &data);
let label = render_source(¶meter.label, &data);
LinkedActionParameter {
label,
name,
required: parameter.required,
}
})
.collect()
}
pub fn render_action_links<T>(links: Option<&ActionLinks>, data: &T) -> Option<ActionLinks>
where
T: Serialize,
{
match links {
Some(ActionLinks { actions }) => Some(ActionLinks {
actions: actions
.iter()
.map(|link| {
let label = render_source(&link.label, &data);
let href = render_source(&link.href, &data);
LinkedAction {
label,
href,
parameters: render_parameters(&link.parameters, &data),
}
})
.collect(),
}),
_ => None,
}
}
pub fn render_metadata<T>(
metadata: &ActionMetadata,
data: &T,
disabled: bool,
error: Option<ActionError>,
) -> ActionMetadata
where
T: Serialize,
{
let title = render_source(&metadata.title, &data);
let description = render_source(&metadata.description, &data);
let label = render_source(&metadata.label, &data);
let icon = render_source(&metadata.icon, &data);
let links = render_action_links(metadata.links.as_ref(), &data);
ActionMetadata {
title,
icon,
description,
label,
links,
disabled,
error,
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Status {
pub active: bool,
}