Skip to main content

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}