1use axum::http::StatusCode;
79use axum::response::{IntoResponse, Response};
80use axum::Json;
81use handlebars::Handlebars;
82use serde::{Deserialize, Serialize};
83use solana_sdk::instruction::{AccountMeta, Instruction};
84use solana_sdk::message::Message;
85use solana_sdk::pubkey;
86use solana_sdk::signature::Keypair;
87use solana_sdk::signer::Signer;
88use solana_sdk::transaction::Transaction;
89pub extern crate base64;
90pub extern crate bincode;
91pub extern crate colored;
92pub extern crate solana_client;
93pub extern crate tower_http;
94pub extern crate znap_macros;
95
96pub mod env;
97pub mod prelude {
98 pub use super::env::Env;
99 pub use super::{
100 Action, ActionLinks, ActionMetadata, ActionResponse, ActionTransaction, Error, ErrorCode,
101 LinkedAction, LinkedActionParameter, Result, ToMetadata,
102 };
103 pub use base64;
104 pub use bincode;
105 pub use colored;
106 pub use solana_client;
107 pub use tower_http;
108 pub use znap_macros::{collection, Action, ErrorCode};
109}
110
111pub trait Action {}
113
114pub trait ErrorCode {}
116
117pub type Result<T> = core::result::Result<T, Error>;
119
120pub trait ToMetadata {
122 fn to_metadata() -> ActionMetadata;
123}
124
125#[derive(Debug, Serialize, Deserialize)]
127pub struct CreateActionPayload {
128 pub account: String,
129}
130
131#[derive(Debug, Serialize)]
133pub struct ActionResponse {
134 pub transaction: String,
135 pub message: Option<String>,
136}
137
138#[derive(Debug, Deserialize, Serialize)]
140pub struct ActionTransaction {
141 pub transaction: Transaction,
142 pub message: Option<String>,
143}
144
145#[derive(Debug, Deserialize, Serialize, PartialEq)]
147pub struct ActionMetadata {
148 pub icon: String,
149 pub title: String,
150 pub description: String,
151 pub label: String,
152 pub links: Option<ActionLinks>,
153 pub disabled: bool,
154 pub error: Option<ActionError>,
155}
156
157#[derive(Debug, Deserialize, Serialize, PartialEq)]
158pub struct ActionError {
159 pub message: String,
160}
161
162#[derive(Debug, Deserialize, Serialize, PartialEq)]
163pub struct ActionLinks {
164 pub actions: Vec<LinkedAction>,
165}
166
167#[derive(Debug, Deserialize, Serialize, PartialEq)]
168pub struct LinkedAction {
169 pub label: String,
170 pub href: String,
171 pub parameters: Vec<LinkedActionParameter>,
172}
173
174#[derive(Debug, Deserialize, Serialize, PartialEq)]
175pub struct LinkedActionParameter {
176 pub label: String,
177 pub name: String,
178 pub required: bool,
179}
180
181#[derive(Debug)]
183pub struct Error {
184 pub code: StatusCode,
185 pub name: String,
186 pub message: String,
187}
188
189impl Error {
190 pub fn new(code: StatusCode, name: String, message: impl Into<String>) -> Self {
191 Self {
192 code,
193 name,
194 message: message.into(),
195 }
196 }
197}
198
199impl IntoResponse for Error {
200 fn into_response(self) -> Response {
201 (
202 self.code,
203 Json(ErrorResponse {
204 name: self.name.clone(),
205 message: self.message.clone(),
206 }),
207 )
208 .into_response()
209 }
210}
211
212#[derive(Serialize, Deserialize)]
213struct ErrorResponse {
214 name: String,
215 message: String,
216}
217
218pub fn add_action_identity_proof(transaction: Transaction, keypair: &Keypair) -> Transaction {
219 let identity_pubkey = keypair.pubkey();
220
221 let reference_keypair = Keypair::new();
222 let reference_pubkey = reference_keypair.pubkey();
223
224 let identity_signature = keypair.sign_message(&reference_pubkey.to_bytes());
225 let identity_message = format!(
226 "solana-action:{}:{}:{}",
227 identity_pubkey, reference_pubkey, identity_signature
228 );
229
230 let mut identity_added = false;
231
232 let mut instructions_with_identity: Vec<Instruction> = transaction
233 .message
234 .instructions
235 .iter()
236 .map(|instruction| {
237 let program_id =
238 transaction.message.account_keys[instruction.program_id_index as usize];
239
240 let mut accounts: Vec<AccountMeta> = instruction
241 .accounts
242 .iter()
243 .map(|account_index| {
244 let pubkey = transaction.message.account_keys[*account_index as usize];
245
246 match transaction
247 .message
248 .is_maybe_writable(*account_index as usize, None)
249 {
250 true => AccountMeta::new(
251 pubkey,
252 transaction.message.is_signer(*account_index as usize),
253 ),
254 false => AccountMeta::new_readonly(
255 pubkey,
256 transaction.message.is_signer(*account_index as usize),
257 ),
258 }
259 })
260 .collect();
261
262 if !identity_added
263 && program_id.to_string() != "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
264 {
265 accounts.push(AccountMeta::new_readonly(reference_pubkey, false));
266 accounts.push(AccountMeta::new_readonly(identity_pubkey, false));
267
268 identity_added = true;
269 }
270
271 Instruction {
272 program_id,
273 data: instruction.data.clone(),
274 accounts,
275 }
276 })
277 .collect();
278
279 instructions_with_identity.push(Instruction {
280 accounts: vec![],
281 data: identity_message.as_bytes().to_vec(),
282 program_id: pubkey!("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"),
283 });
284
285 let transaction_message_with_identity = Message::new(&instructions_with_identity, None);
286
287 Transaction::new_unsigned(transaction_message_with_identity)
288}
289
290pub fn render_source<T>(source: &str, data: &T) -> String
291where
292 T: Serialize,
293{
294 let mut handlebars = Handlebars::new();
295
296 assert!(handlebars
297 .register_template_string("template", source)
298 .is_ok());
299 let output = handlebars.render("template", &data).unwrap();
300
301 handlebars.clear_templates();
302
303 output
304}
305
306pub fn render_parameters<T>(
307 parameters: &[LinkedActionParameter],
308 data: &T,
309) -> Vec<LinkedActionParameter>
310where
311 T: Serialize,
312{
313 parameters
314 .iter()
315 .map(|parameter| {
316 let name = render_source(¶meter.name, &data);
317 let label = render_source(¶meter.label, &data);
318
319 LinkedActionParameter {
320 label,
321 name,
322 required: parameter.required,
323 }
324 })
325 .collect()
326}
327
328pub fn render_action_links<T>(links: Option<&ActionLinks>, data: &T) -> Option<ActionLinks>
329where
330 T: Serialize,
331{
332 match links {
333 Some(ActionLinks { actions }) => Some(ActionLinks {
334 actions: actions
335 .iter()
336 .map(|link| {
337 let label = render_source(&link.label, &data);
338 let href = render_source(&link.href, &data);
339
340 LinkedAction {
341 label,
342 href,
343 parameters: render_parameters(&link.parameters, &data),
344 }
345 })
346 .collect(),
347 }),
348 _ => None,
349 }
350}
351
352pub fn render_metadata<T>(
353 metadata: &ActionMetadata,
354 data: &T,
355 disabled: bool,
356 error: Option<ActionError>,
357) -> ActionMetadata
358where
359 T: Serialize,
360{
361 let title = render_source(&metadata.title, &data);
362 let description = render_source(&metadata.description, &data);
363 let label = render_source(&metadata.label, &data);
364 let icon = render_source(&metadata.icon, &data);
365 let links = render_action_links(metadata.links.as_ref(), &data);
366
367 ActionMetadata {
368 title,
369 icon,
370 description,
371 label,
372 links,
373 disabled,
374 error,
375 }
376}
377
378#[derive(Serialize, Deserialize, Debug)]
379pub struct Status {
380 pub active: bool,
381}