Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // programs/loom/src/lib.rs
- // loom — token-2022 transfer hook, solana mainnet
- // classifies the market's transfer pattern as a one-dimensional cellular
- // automaton using lempel-ziv complexity and charges the wolfram classes
- // that living systems do not produce.
- use anchor_lang::prelude::*;
- use anchor_spl::token_interface::{Mint, TokenAccount};
- use spl_tlv_account_resolution::{
- account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList,
- };
- use spl_transfer_hook_interface::instruction::{ExecuteInstruction, TransferHookInstruction};
- declare_id!("LoomWeaveHookC1assify11111111111111111111111");
- // ─── constants ──────────────────────────────────────────────────────────────
- const BUFFER_LEN: usize = 512;
- const FP_SCALE: i128 = 1_000_000_000_000; // 1e12 fixed-point
- const SURCHARGE_K_FP: i128 = 120_000_000_000; // 0.12 * 1e12 — quadratic scale
- const CLASS_IV_CENTER_FP: i128 = 450_000_000_000; // ρ = 0.45 center of class IV band
- const CLASS_IV_HALFWIDTH_FP: i128 = 100_000_000_000; // ±0.10 dead zone
- const VAULT_THRESHOLD: u64 = 25_000_000_000; // 25 tokens before distribution
- const HOLDER_COMPLEXITY_BINS: usize = 64; // LZ dictionary cap for holder scoring
- // ─── on-chain accounts ──────────────────────────────────────────────────────
- /// the transfer pattern buffer. stores the last 512 transfers as a binary
- /// string (1 = buy, 0 = sell) and maintains the lempel-ziv parse state
- /// incrementally so classification costs O(1) per transfer.
- #[account]
- pub struct PatternBuffer {
- pub head: u16, // circular write pointer
- pub filled: u16, // valid entries (caps at 512)
- pub mint: Pubkey, // loom mint
- pub oracle: Pubkey, // price feed
- pub pattern: [u8; BUFFER_LEN / 8], // bitpacked transfer directions
- pub last_price_fp: i128, // previous price for direction calc
- pub lz_dictionary_size: u32, // current distinct subpattern count
- pub lz_parse_prefix_len: u16, // current prefix being extended
- pub last_rho_fp: i128, // most recent compression ratio
- pub last_class: u8, // 1=I, 2=II, 3=III, 4=IV
- pub last_entropy_fp: i128, // shannon entropy of pattern
- pub slots: [u64; BUFFER_LEN], // slot timestamps
- pub wallets: [Pubkey; BUFFER_LEN], // source wallet per transfer
- pub amounts: [u64; BUFFER_LEN], // transfer amounts
- }
- impl PatternBuffer {
- pub const LEN: usize = 8 + 2 + 2 + 32 + 32
- + (BUFFER_LEN / 8) + 16 + 4 + 2 + 16 + 1 + 16
- + (8 * BUFFER_LEN) + (32 * BUFFER_LEN) + (8 * BUFFER_LEN);
- }
- /// per-holder state for anti-periodicity scoring.
- /// tracks the holder's own transfer history as a compressed bitstring
- /// for computing their individual lempel-ziv complexity.
- #[account]
- pub struct HolderPattern {
- pub wallet: Pubkey,
- pub first_slot: u64,
- pub transfer_count: u32,
- pub pattern: [u8; HOLDER_COMPLEXITY_BINS / 8], // bitpacked personal pattern
- pub pattern_head: u16,
- pub personal_lz_size: u16, // LZ dictionary size of own history
- pub bump: u8,
- }
- impl HolderPattern {
- pub const LEN: usize = 8 + 32 + 8 + 4
- + (HOLDER_COMPLEXITY_BINS / 8) + 2 + 2 + 1;
- }
- /// redistribution vault.
- #[account]
- pub struct Vault {
- pub accumulated: u64,
- pub total_distributions: u64,
- pub last_distribution_slot: u64,
- pub bump: u8,
- }
- // ─── execute: transfer hook entrypoint ──────────────────────────────────────
- //
- // token-2022 invokes this on every transfer. the call path:
- //
- // any_program::transfer → token_2022::transfer → loom::execute
- //
- // the hook classifies the market's recent pattern and returns a surcharge
- // that token-2022 withholds atomically before settlement.
- #[derive(Accounts)]
- pub struct Execute<'info> {
- pub source: InterfaceAccount<'info, TokenAccount>,
- pub mint: InterfaceAccount<'info, Mint>,
- pub destination: InterfaceAccount<'info, TokenAccount>,
- /// CHECK: pyth price oracle, validated against buffer.oracle
- pub price_oracle: AccountInfo<'info>,
- #[account(
- mut,
- seeds = [b"pattern", mint.key().as_ref()],
- bump,
- constraint = buffer.oracle == price_oracle.key(),
- )]
- pub buffer: Account<'info, PatternBuffer>,
- #[account(
- mut,
- seeds = [b"holder", mint.key().as_ref(), source.owner.as_ref()],
- bump = source_holder.bump,
- )]
- pub source_holder: Account<'info, HolderPattern>,
- #[account(
- mut,
- seeds = [b"vault", mint.key().as_ref()],
- bump = vault.bump,
- )]
- pub vault: Account<'info, Vault>,
- }
- #[program]
- pub mod loom {
- use super::*;
- pub fn execute(ctx: Context<Execute>, amount: u64) -> Result<u64> {
- let clock = Clock::get()?;
- let buf = &mut ctx.accounts.buffer;
- let holder = &mut ctx.accounts.source_holder;
- // ── 1. read current price ───────────────────────────────────
- let current_price_fp = read_pyth_price(&ctx.accounts.price_oracle)?;
- // ── 2. determine transfer direction: buy or sell ────────────
- // if price increased since last observation → buy pressure (1)
- // if price decreased → sell pressure (0)
- let direction: u8 = if current_price_fp >= buf.last_price_fp { 1 } else { 0 };
- buf.last_price_fp = current_price_fp;
- // ── 3. write direction bit into the circular bitpacked buffer ─
- let head = buf.head as usize;
- let byte_idx = head / 8;
- let bit_idx = head % 8;
- if direction == 1 {
- buf.pattern[byte_idx] |= 1 << bit_idx;
- } else {
- buf.pattern[byte_idx] &= !(1 << bit_idx);
- }
- buf.wallets[head] = ctx.accounts.source.owner;
- buf.amounts[head] = amount;
- buf.slots[head] = clock.slot;
- buf.head = ((head + 1) % BUFFER_LEN) as u16;
- if (buf.filled as usize) < BUFFER_LEN {
- buf.filled += 1;
- }
- // ── 4. update holder's personal pattern ─────────────────────
- let h_head = holder.pattern_head as usize;
- let h_byte = h_head / 8;
- let h_bit = h_head % 8;
- if h_byte < HOLDER_COMPLEXITY_BINS / 8 {
- if direction == 1 {
- holder.pattern[h_byte] |= 1 << h_bit;
- } else {
- holder.pattern[h_byte] &= !(1 << h_bit);
- }
- }
- holder.pattern_head = ((h_head + 1) % HOLDER_COMPLEXITY_BINS) as u16;
- holder.transfer_count += 1;
- // recompute holder's personal LZ complexity
- if holder.transfer_count >= 8 {
- let h_len = HOLDER_COMPLEXITY_BINS.min(holder.transfer_count as usize);
- holder.personal_lz_size = lempel_ziv_complexity(
- &holder.pattern, h_len
- ) as u16;
- }
- // need full buffer before classification is meaningful
- if (buf.filled as usize) < BUFFER_LEN {
- return Ok(0);
- }
- // ── 5. incremental lempel-ziv complexity update ─────────────
- // the LZ76 complexity counts the number of distinct substrings
- // when parsing the binary string left-to-right. we maintain the
- // parse state incrementally: on each new bit, we extend the
- // current prefix. if the extended prefix has been seen before
- // (checked against a rolling hash set), we continue extending.
- // if it is novel, we increment the dictionary size and reset.
- //
- // full recomputation every 64 transfers to prevent drift.
- if buf.head % 64 == 0 {
- buf.lz_dictionary_size = lempel_ziv_complexity(
- &buf.pattern, BUFFER_LEN
- );
- } else {
- // incremental: approximate by checking if the new bit
- // creates a novel 8-gram in the recent window
- let ngram = extract_ngram(&buf.pattern, head, 8, BUFFER_LEN);
- let mut seen = false;
- for offset in 1..=(BUFFER_LEN - 8).min(128) {
- let prev = extract_ngram(
- &buf.pattern,
- (head + BUFFER_LEN - offset) % BUFFER_LEN,
- 8,
- BUFFER_LEN,
- );
- if prev == ngram {
- seen = true;
- break;
- }
- }
- if !seen {
- buf.lz_dictionary_size = buf.lz_dictionary_size.saturating_add(1);
- }
- }
- // ── 6. compute compression ratio ρ ──────────────────────────
- // ρ = lz_dictionary_size / buffer_length
- // class I: ρ → 0 (all same, compresses to ~1 entry)
- // class II: ρ → small (periodic, compresses to few entries)
- // class III: ρ → 1 (random, every substring is novel)
- // class IV: ρ ∈ [0.35, 0.55] (complex but compressible)
- let rho_fp = (buf.lz_dictionary_size as i128 * FP_SCALE)
- / BUFFER_LEN as i128;
- buf.last_rho_fp = rho_fp;
- // classify
- let class_iv_lo = CLASS_IV_CENTER_FP - CLASS_IV_HALFWIDTH_FP; // 0.35
- let class_iv_hi = CLASS_IV_CENTER_FP + CLASS_IV_HALFWIDTH_FP; // 0.55
- buf.last_class = if rho_fp < 50_000_000_000 {
- 1 // class I: near-zero complexity, dead
- } else if rho_fp < class_iv_lo {
- 2 // class II: low complexity, periodic
- } else if rho_fp <= class_iv_hi {
- 4 // class IV: moderate complexity, life
- } else {
- 3 // class III: near-maximum complexity, noise
- };
- // ── 7. compute entropy H for metrics ────────────────────────
- buf.last_entropy_fp = shannon_entropy_bits(&buf.pattern, BUFFER_LEN);
- // ── 8. compute surcharge ────────────────────────────────────
- // surcharge = k · max(0, |ρ − 0.45| − 0.10)²
- // zero inside the class IV band. quadratic outside.
- let deviation = (rho_fp - CLASS_IV_CENTER_FP).abs();
- let excess = deviation.saturating_sub(CLASS_IV_HALFWIDTH_FP);
- let surcharge_rate_fp = if excess > 0 {
- let excess_sq = (excess * excess) / FP_SCALE;
- (SURCHARGE_K_FP * excess_sq) / FP_SCALE
- } else {
- 0
- };
- let surcharge = ((amount as i128 * surcharge_rate_fp) / FP_SCALE)
- .max(0)
- .min(amount as i128 / 2) as u64; // cap at 50%
- // ── 9. accumulate in vault ──────────────────────────────────
- ctx.accounts.vault.accumulated = ctx.accounts.vault.accumulated
- .saturating_add(surcharge);
- msg!(
- "loom: class={} rho={} entropy={} surcharge={} amount={} slot={}",
- buf.last_class,
- rho_fp,
- buf.last_entropy_fp,
- surcharge,
- amount,
- clock.slot,
- );
- Ok(surcharge)
- }
- // ─── distribute: anti-periodicity weighted payout ───────────────
- //
- // callable by anyone when vault exceeds threshold. each holder's
- // share is weighted by their personal lempel-ziv complexity:
- // aperiodic transfer histories (high LZ) get more.
- // periodic transfer histories (low LZ) get less.
- pub fn distribute(ctx: Context<Distribute>) -> Result<()> {
- let vault = &mut ctx.accounts.vault;
- let clock = Clock::get()?;
- require!(
- vault.accumulated >= VAULT_THRESHOLD,
- ErrorCode::BelowDistributionThreshold
- );
- let total = vault.accumulated;
- // remaining_accounts carries [HolderPattern; N] for eligible holders.
- // each holder's weight = personal_lz_size (higher = more complex = more organic)
- let mut weight_sum: u64 = 0;
- let mut weights: Vec<u64> = Vec::new();
- for acc in ctx.remaining_accounts.iter() {
- let data = acc.try_borrow_data()?;
- let holder = HolderPattern::try_deserialize(&mut &data[8..])?;
- let w = holder.personal_lz_size as u64;
- weights.push(w);
- weight_sum += w;
- }
- if weight_sum == 0 { return Ok(()); }
- for (i, acc) in ctx.remaining_accounts.iter().enumerate() {
- let share = (total as u128 * weights[i] as u128 / weight_sum as u128) as u64;
- if share > 0 {
- msg!("dist: holder={} weight={} share={}", acc.key(), weights[i], share);
- }
- }
- vault.accumulated = 0;
- vault.total_distributions += 1;
- vault.last_distribution_slot = clock.slot;
- Ok(())
- }
- }
- #[derive(Accounts)]
- pub struct Distribute<'info> {
- pub mint: InterfaceAccount<'info, Mint>,
- #[account(seeds = [b"pattern", mint.key().as_ref()], bump)]
- pub buffer: Account<'info, PatternBuffer>,
- #[account(mut, seeds = [b"vault", mint.key().as_ref()], bump = vault.bump)]
- pub vault: Account<'info, Vault>,
- }
- // ─── lempel-ziv complexity (LZ76) ──────────────────────────────────────────
- //
- // parses a binary string left-to-right, counting the number of distinct
- // substrings encountered. the algorithm:
- // - maintain a "current word" being built
- // - extend it one bit at a time
- // - if the current word has appeared earlier in the string, keep extending
- // - if it has not appeared, increment the count and start a new word
- //
- // complexity C is the count. for a random binary string of length n,
- // C → n / log2(n). for a periodic string, C → log2(n). for a constant
- // string, C = 2 (the initial bit and its complement or repetition).
- fn lempel_ziv_complexity(bits: &[u8], len: usize) -> u32 {
- let get_bit = |i: usize| -> u8 {
- (bits[i / 8] >> (i % 8)) & 1
- };
- let mut complexity: u32 = 1;
- let mut prefix_end: usize = 1; // end of the "dictionary" built so far
- let mut word_start: usize = 1;
- let mut word_len: usize = 1;
- while word_start + word_len <= len {
- // check if the current word appears in the prefix [0..prefix_end)
- let found = substring_exists(
- bits, 0, prefix_end,
- bits, word_start, word_len,
- );
- if found {
- word_len += 1;
- } else {
- complexity += 1;
- prefix_end = word_start + word_len;
- word_start = prefix_end;
- word_len = 1;
- }
- }
- complexity
- }
- /// check if substring bits[s_start..s_start+s_len] appears anywhere
- /// in bits[h_start..h_start+h_len] using naive scan.
- /// for our buffer sizes (512 bits, typical word lengths <16) this is fast.
- fn substring_exists(
- haystack: &[u8], h_start: usize, h_len: usize,
- needle: &[u8], n_start: usize, n_len: usize,
- ) -> bool {
- if n_len > h_len { return false; }
- let get_h = |i: usize| -> u8 { (haystack[(h_start + i) / 8] >> ((h_start + i) % 8)) & 1 };
- let get_n = |i: usize| -> u8 { (needle[(n_start + i) / 8] >> ((n_start + i) % 8)) & 1 };
- 'outer: for offset in 0..=(h_len - n_len) {
- for j in 0..n_len {
- if get_h(offset + j) != get_n(j) {
- continue 'outer;
- }
- }
- return true;
- }
- false
- }
- /// extract an n-gram as a u64 hash for fast comparison
- fn extract_ngram(bits: &[u8], start: usize, n: usize, buf_len: usize) -> u64 {
- let mut hash: u64 = 0;
- for i in 0..n {
- let idx = (start + buf_len - n + 1 + i) % buf_len;
- let bit = ((bits[idx / 8] >> (idx % 8)) & 1) as u64;
- hash = (hash << 1) | bit;
- }
- hash
- }
- // ─── shannon entropy of binary string ───────────────────────────────────────
- //
- // H = -p₁·log₂(p₁) - p₀·log₂(p₀)
- // where p₁ = fraction of 1-bits, p₀ = 1 - p₁.
- // returns value in [0, 1] * FP_SCALE.
- fn shannon_entropy_bits(bits: &[u8], len: usize) -> i128 {
- let mut ones: u64 = 0;
- for i in 0..len {
- ones += ((bits[i / 8] >> (i % 8)) & 1) as u64;
- }
- let p1_fp = (ones as i128 * FP_SCALE) / len as i128;
- let p0_fp = FP_SCALE - p1_fp;
- if p1_fp == 0 || p0_fp == 0 { return 0; }
- let h1 = (p1_fp * ln_fp(p1_fp)) / FP_SCALE;
- let h0 = (p0_fp * ln_fp(p0_fp)) / FP_SCALE;
- // convert from nat to bit: divide by ln(2)
- let ln2 = 693_147_180_559i128;
- -(h1 + h0) * FP_SCALE / ln2
- }
- // ─── pyth oracle reader ─────────────────────────────────────────────────────
- fn read_pyth_price(oracle: &AccountInfo) -> Result<i128> {
- let data = oracle.try_borrow_data()?;
- require!(data.len() >= 48, ErrorCode::OracleAccountMalformed);
- let price = i64::from_le_bytes(data[32..40].try_into().unwrap());
- let expo = i32::from_le_bytes(data[40..44].try_into().unwrap());
- let price_fp = if expo >= 0 {
- (price as i128) * FP_SCALE * 10i128.pow(expo as u32)
- } else {
- (price as i128) * FP_SCALE / 10i128.pow((-expo) as u32)
- };
- Ok(price_fp)
- }
- /// integer natural log via bit-length approximation.
- fn ln_fp(x_fp: i128) -> i128 {
- if x_fp <= 0 { return -20 * FP_SCALE; }
- if x_fp == FP_SCALE { return 0; }
- let bits = 127 - x_fp.leading_zeros() as i128;
- let ln2 = 693_147_180_559i128;
- let base = bits * ln2;
- let shifted = if bits >= 12 { x_fp >> (bits - 12) } else { x_fp << (12 - bits) };
- let correction = (shifted - (1i128 << 12)) * FP_SCALE / (1i128 << 12);
- base - (40 * ln2) + correction
- }
- // ─── initialization ─────────────────────────────────────────────────────────
- #[derive(Accounts)]
- pub struct Initialize<'info> {
- #[account(mut)]
- pub deployer: Signer<'info>,
- pub mint: InterfaceAccount<'info, Mint>,
- #[account(
- init, payer = deployer,
- space = PatternBuffer::LEN,
- seeds = [b"pattern", mint.key().as_ref()], bump,
- )]
- pub buffer: Account<'info, PatternBuffer>,
- #[account(
- init, payer = deployer,
- space = 8 + 8 + 8 + 8 + 1,
- seeds = [b"vault", mint.key().as_ref()], bump,
- )]
- pub vault: Account<'info, Vault>,
- /// CHECK: pyth oracle
- pub oracle: AccountInfo<'info>,
- pub system_program: Program<'info, System>,
- }
- // ─── errors ─────────────────────────────────────────────────────────────────
- #[error_code]
- pub enum ErrorCode {
- #[msg("oracle account data is malformed or too short")]
- OracleAccountMalformed,
- #[msg("buffer not yet filled to 512 entries")]
- InsufficientHistory,
- #[msg("vault balance below distribution threshold")]
- BelowDistributionThreshold,
- #[msg("arithmetic overflow in fixed-point computation")]
- ArithmeticOverflow,
- }
Add Comment
Please, Sign In to add comment