palinurus_core/borsh.rs
1//! Borsh instruction-data helpers for Solana instructions the Palinurus plugins emit.
2//!
3//! Pure-Rust, `wasm32-wasip2`-friendly. The `borsh` crate handles primitive
4//! (u8 / u32 / u64 / i64 / Vec / arrays) serialization; this module defines the
5//! Solana-specific structs + helpers, verified byte-for-byte against canonical
6//! reference encoders:
7//!
8//! - [`CreateAttestationIxData`] — the Solana Attestation Service (SAS)
9//! `create_attestation` instruction DATA. Layout verified 2026-07-19 against
10//! the sas-lib Codama-generated codec (`tools/verify-borsh.mjs` oracle,
11//! `sas-lib@1.0.10`):
12//! `[u8 discriminator=6] [32-byte nonce] [u32 LE len][N bytes data] [i64 LE expiry]`
13//! - [`memo_ix_data`] — the SPL Memo program v3 instruction DATA = raw UTF-8
14//! bytes (no discriminator, no length prefix; ground-truth from
15//! `@solana/spl-memo` `createMemoInstruction`).
16//!
17//! The nonce is a `Pubkey` (used as an attestation-PDA seed for uniqueness — the
18//! replay guard), NOT a u64 counter. `expiry` is a signed `i64` unix timestamp.
19//! `data` is the schema-encoded attestation payload (`serializeAttestationData`
20//! in sas-lib) — produced by the plugin, opaque to this struct.
21
22use crate::base58::Pubkey;
23use borsh::{BorshDeserialize, BorshSerialize};
24
25/// Solana Attestation Service `create_attestation` instruction DATA.
26///
27/// Borsh layout (matches sas-lib `getCreateAttestationInstructionDataEncoder`):
28/// `u8 discriminator` (=6) · `Pubkey nonce` (32 B) · `Vec<u8> data` (u32 LE len + bytes) · `i64 expiry` (LE).
29#[derive(Clone, BorshSerialize, BorshDeserialize, PartialEq, Eq, Debug)]
30pub struct CreateAttestationIxData {
31 /// Instruction discriminator — `CREATE_ATTESTATION_DISCRIMINATOR = 6`.
32 pub discriminator: u8,
33 /// Nonce — a `Pubkey` used as an attestation-PDA seed (uniqueness / replay guard).
34 pub nonce: Pubkey,
35 /// Schema-encoded attestation payload (opaque bytes; the plugin produces them
36 /// via the Schema's Borsh layout).
37 pub data: Vec<u8>,
38 /// Expiry — signed `i64` unix timestamp (seconds).
39 pub expiry: i64,
40}
41
42impl CreateAttestationIxData {
43 /// The SAS `create_attestation` instruction discriminator (u8 = 6).
44 pub const DISCRIMINATOR: u8 = 6;
45
46 /// Convenience constructor (discriminator is filled in automatically).
47 pub fn new(nonce: Pubkey, data: Vec<u8>, expiry: i64) -> Self {
48 Self { discriminator: Self::DISCRIMINATOR, nonce, data, expiry }
49 }
50
51 /// Serialize to the on-wire instruction DATA bytes.
52 pub fn to_ix_bytes(&self) -> Vec<u8> {
53 borsh::to_vec(self).expect("CreateAttestationIxData: borsh serialize is infallible for these types")
54 }
55}
56
57/// SPL Memo program v3 instruction DATA — the raw memo UTF-8 bytes.
58///
59/// The memo program reads `ix.data` directly: no discriminator, no length
60/// prefix. Returns the memo string as a byte vector.
61pub fn memo_ix_data(memo: &str) -> Vec<u8> {
62 memo.as_bytes().to_vec()
63}