Guest User

Untitled

a guest
May 17th, 2026
137
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 20.15 KB | Cryptocurrency | 0 0
  1. // programs/loom/src/lib.rs
  2. // loom — token-2022 transfer hook, solana mainnet
  3. // classifies the market's transfer pattern as a one-dimensional cellular
  4. // automaton using lempel-ziv complexity and charges the wolfram classes
  5. // that living systems do not produce.
  6.  
  7. use anchor_lang::prelude::*;
  8. use anchor_spl::token_interface::{Mint, TokenAccount};
  9. use spl_tlv_account_resolution::{
  10. account::ExtraAccountMeta, seeds::Seed, state::ExtraAccountMetaList,
  11. };
  12. use spl_transfer_hook_interface::instruction::{ExecuteInstruction, TransferHookInstruction};
  13.  
  14. declare_id!("LoomWeaveHookC1assify11111111111111111111111");
  15.  
  16. // ─── constants ──────────────────────────────────────────────────────────────
  17.  
  18. const BUFFER_LEN: usize = 512;
  19. const FP_SCALE: i128 = 1_000_000_000_000; // 1e12 fixed-point
  20. const SURCHARGE_K_FP: i128 = 120_000_000_000; // 0.12 * 1e12 — quadratic scale
  21. const CLASS_IV_CENTER_FP: i128 = 450_000_000_000; // ρ = 0.45 center of class IV band
  22. const CLASS_IV_HALFWIDTH_FP: i128 = 100_000_000_000; // ±0.10 dead zone
  23. const VAULT_THRESHOLD: u64 = 25_000_000_000; // 25 tokens before distribution
  24. const HOLDER_COMPLEXITY_BINS: usize = 64; // LZ dictionary cap for holder scoring
  25.  
  26. // ─── on-chain accounts ──────────────────────────────────────────────────────
  27.  
  28. /// the transfer pattern buffer. stores the last 512 transfers as a binary
  29. /// string (1 = buy, 0 = sell) and maintains the lempel-ziv parse state
  30. /// incrementally so classification costs O(1) per transfer.
  31. #[account]
  32. pub struct PatternBuffer {
  33. pub head: u16, // circular write pointer
  34. pub filled: u16, // valid entries (caps at 512)
  35. pub mint: Pubkey, // loom mint
  36. pub oracle: Pubkey, // price feed
  37. pub pattern: [u8; BUFFER_LEN / 8], // bitpacked transfer directions
  38. pub last_price_fp: i128, // previous price for direction calc
  39. pub lz_dictionary_size: u32, // current distinct subpattern count
  40. pub lz_parse_prefix_len: u16, // current prefix being extended
  41. pub last_rho_fp: i128, // most recent compression ratio
  42. pub last_class: u8, // 1=I, 2=II, 3=III, 4=IV
  43. pub last_entropy_fp: i128, // shannon entropy of pattern
  44. pub slots: [u64; BUFFER_LEN], // slot timestamps
  45. pub wallets: [Pubkey; BUFFER_LEN], // source wallet per transfer
  46. pub amounts: [u64; BUFFER_LEN], // transfer amounts
  47. }
  48.  
  49. impl PatternBuffer {
  50. pub const LEN: usize = 8 + 2 + 2 + 32 + 32
  51. + (BUFFER_LEN / 8) + 16 + 4 + 2 + 16 + 1 + 16
  52. + (8 * BUFFER_LEN) + (32 * BUFFER_LEN) + (8 * BUFFER_LEN);
  53. }
  54.  
  55. /// per-holder state for anti-periodicity scoring.
  56. /// tracks the holder's own transfer history as a compressed bitstring
  57. /// for computing their individual lempel-ziv complexity.
  58. #[account]
  59. pub struct HolderPattern {
  60. pub wallet: Pubkey,
  61. pub first_slot: u64,
  62. pub transfer_count: u32,
  63. pub pattern: [u8; HOLDER_COMPLEXITY_BINS / 8], // bitpacked personal pattern
  64. pub pattern_head: u16,
  65. pub personal_lz_size: u16, // LZ dictionary size of own history
  66. pub bump: u8,
  67. }
  68.  
  69. impl HolderPattern {
  70. pub const LEN: usize = 8 + 32 + 8 + 4
  71. + (HOLDER_COMPLEXITY_BINS / 8) + 2 + 2 + 1;
  72. }
  73.  
  74. /// redistribution vault.
  75. #[account]
  76. pub struct Vault {
  77. pub accumulated: u64,
  78. pub total_distributions: u64,
  79. pub last_distribution_slot: u64,
  80. pub bump: u8,
  81. }
  82.  
  83. // ─── execute: transfer hook entrypoint ──────────────────────────────────────
  84. //
  85. // token-2022 invokes this on every transfer. the call path:
  86. //
  87. // any_program::transfer → token_2022::transfer → loom::execute
  88. //
  89. // the hook classifies the market's recent pattern and returns a surcharge
  90. // that token-2022 withholds atomically before settlement.
  91.  
  92. #[derive(Accounts)]
  93. pub struct Execute<'info> {
  94. pub source: InterfaceAccount<'info, TokenAccount>,
  95. pub mint: InterfaceAccount<'info, Mint>,
  96. pub destination: InterfaceAccount<'info, TokenAccount>,
  97. /// CHECK: pyth price oracle, validated against buffer.oracle
  98. pub price_oracle: AccountInfo<'info>,
  99. #[account(
  100. mut,
  101. seeds = [b"pattern", mint.key().as_ref()],
  102. bump,
  103. constraint = buffer.oracle == price_oracle.key(),
  104. )]
  105. pub buffer: Account<'info, PatternBuffer>,
  106. #[account(
  107. mut,
  108. seeds = [b"holder", mint.key().as_ref(), source.owner.as_ref()],
  109. bump = source_holder.bump,
  110. )]
  111. pub source_holder: Account<'info, HolderPattern>,
  112. #[account(
  113. mut,
  114. seeds = [b"vault", mint.key().as_ref()],
  115. bump = vault.bump,
  116. )]
  117. pub vault: Account<'info, Vault>,
  118. }
  119.  
  120. #[program]
  121. pub mod loom {
  122. use super::*;
  123.  
  124. pub fn execute(ctx: Context<Execute>, amount: u64) -> Result<u64> {
  125. let clock = Clock::get()?;
  126. let buf = &mut ctx.accounts.buffer;
  127. let holder = &mut ctx.accounts.source_holder;
  128.  
  129. // ── 1. read current price ───────────────────────────────────
  130. let current_price_fp = read_pyth_price(&ctx.accounts.price_oracle)?;
  131.  
  132. // ── 2. determine transfer direction: buy or sell ────────────
  133. // if price increased since last observation → buy pressure (1)
  134. // if price decreased → sell pressure (0)
  135. let direction: u8 = if current_price_fp >= buf.last_price_fp { 1 } else { 0 };
  136. buf.last_price_fp = current_price_fp;
  137.  
  138. // ── 3. write direction bit into the circular bitpacked buffer ─
  139. let head = buf.head as usize;
  140. let byte_idx = head / 8;
  141. let bit_idx = head % 8;
  142. if direction == 1 {
  143. buf.pattern[byte_idx] |= 1 << bit_idx;
  144. } else {
  145. buf.pattern[byte_idx] &= !(1 << bit_idx);
  146. }
  147. buf.wallets[head] = ctx.accounts.source.owner;
  148. buf.amounts[head] = amount;
  149. buf.slots[head] = clock.slot;
  150. buf.head = ((head + 1) % BUFFER_LEN) as u16;
  151. if (buf.filled as usize) < BUFFER_LEN {
  152. buf.filled += 1;
  153. }
  154.  
  155. // ── 4. update holder's personal pattern ─────────────────────
  156. let h_head = holder.pattern_head as usize;
  157. let h_byte = h_head / 8;
  158. let h_bit = h_head % 8;
  159. if h_byte < HOLDER_COMPLEXITY_BINS / 8 {
  160. if direction == 1 {
  161. holder.pattern[h_byte] |= 1 << h_bit;
  162. } else {
  163. holder.pattern[h_byte] &= !(1 << h_bit);
  164. }
  165. }
  166. holder.pattern_head = ((h_head + 1) % HOLDER_COMPLEXITY_BINS) as u16;
  167. holder.transfer_count += 1;
  168.  
  169. // recompute holder's personal LZ complexity
  170. if holder.transfer_count >= 8 {
  171. let h_len = HOLDER_COMPLEXITY_BINS.min(holder.transfer_count as usize);
  172. holder.personal_lz_size = lempel_ziv_complexity(
  173. &holder.pattern, h_len
  174. ) as u16;
  175. }
  176.  
  177. // need full buffer before classification is meaningful
  178. if (buf.filled as usize) < BUFFER_LEN {
  179. return Ok(0);
  180. }
  181.  
  182. // ── 5. incremental lempel-ziv complexity update ─────────────
  183. // the LZ76 complexity counts the number of distinct substrings
  184. // when parsing the binary string left-to-right. we maintain the
  185. // parse state incrementally: on each new bit, we extend the
  186. // current prefix. if the extended prefix has been seen before
  187. // (checked against a rolling hash set), we continue extending.
  188. // if it is novel, we increment the dictionary size and reset.
  189. //
  190. // full recomputation every 64 transfers to prevent drift.
  191.  
  192. if buf.head % 64 == 0 {
  193. buf.lz_dictionary_size = lempel_ziv_complexity(
  194. &buf.pattern, BUFFER_LEN
  195. );
  196. } else {
  197. // incremental: approximate by checking if the new bit
  198. // creates a novel 8-gram in the recent window
  199. let ngram = extract_ngram(&buf.pattern, head, 8, BUFFER_LEN);
  200. let mut seen = false;
  201. for offset in 1..=(BUFFER_LEN - 8).min(128) {
  202. let prev = extract_ngram(
  203. &buf.pattern,
  204. (head + BUFFER_LEN - offset) % BUFFER_LEN,
  205. 8,
  206. BUFFER_LEN,
  207. );
  208. if prev == ngram {
  209. seen = true;
  210. break;
  211. }
  212. }
  213. if !seen {
  214. buf.lz_dictionary_size = buf.lz_dictionary_size.saturating_add(1);
  215. }
  216. }
  217.  
  218. // ── 6. compute compression ratio ρ ──────────────────────────
  219. // ρ = lz_dictionary_size / buffer_length
  220. // class I: ρ → 0 (all same, compresses to ~1 entry)
  221. // class II: ρ → small (periodic, compresses to few entries)
  222. // class III: ρ → 1 (random, every substring is novel)
  223. // class IV: ρ ∈ [0.35, 0.55] (complex but compressible)
  224.  
  225. let rho_fp = (buf.lz_dictionary_size as i128 * FP_SCALE)
  226. / BUFFER_LEN as i128;
  227. buf.last_rho_fp = rho_fp;
  228.  
  229. // classify
  230. let class_iv_lo = CLASS_IV_CENTER_FP - CLASS_IV_HALFWIDTH_FP; // 0.35
  231. let class_iv_hi = CLASS_IV_CENTER_FP + CLASS_IV_HALFWIDTH_FP; // 0.55
  232.  
  233. buf.last_class = if rho_fp < 50_000_000_000 {
  234. 1 // class I: near-zero complexity, dead
  235. } else if rho_fp < class_iv_lo {
  236. 2 // class II: low complexity, periodic
  237. } else if rho_fp <= class_iv_hi {
  238. 4 // class IV: moderate complexity, life
  239. } else {
  240. 3 // class III: near-maximum complexity, noise
  241. };
  242.  
  243. // ── 7. compute entropy H for metrics ────────────────────────
  244. buf.last_entropy_fp = shannon_entropy_bits(&buf.pattern, BUFFER_LEN);
  245.  
  246. // ── 8. compute surcharge ────────────────────────────────────
  247. // surcharge = k · max(0, |ρ − 0.45| − 0.10)²
  248. // zero inside the class IV band. quadratic outside.
  249.  
  250. let deviation = (rho_fp - CLASS_IV_CENTER_FP).abs();
  251. let excess = deviation.saturating_sub(CLASS_IV_HALFWIDTH_FP);
  252.  
  253. let surcharge_rate_fp = if excess > 0 {
  254. let excess_sq = (excess * excess) / FP_SCALE;
  255. (SURCHARGE_K_FP * excess_sq) / FP_SCALE
  256. } else {
  257. 0
  258. };
  259.  
  260. let surcharge = ((amount as i128 * surcharge_rate_fp) / FP_SCALE)
  261. .max(0)
  262. .min(amount as i128 / 2) as u64; // cap at 50%
  263.  
  264. // ── 9. accumulate in vault ──────────────────────────────────
  265. ctx.accounts.vault.accumulated = ctx.accounts.vault.accumulated
  266. .saturating_add(surcharge);
  267.  
  268. msg!(
  269. "loom: class={} rho={} entropy={} surcharge={} amount={} slot={}",
  270. buf.last_class,
  271. rho_fp,
  272. buf.last_entropy_fp,
  273. surcharge,
  274. amount,
  275. clock.slot,
  276. );
  277.  
  278. Ok(surcharge)
  279. }
  280.  
  281. // ─── distribute: anti-periodicity weighted payout ───────────────
  282. //
  283. // callable by anyone when vault exceeds threshold. each holder's
  284. // share is weighted by their personal lempel-ziv complexity:
  285. // aperiodic transfer histories (high LZ) get more.
  286. // periodic transfer histories (low LZ) get less.
  287.  
  288. pub fn distribute(ctx: Context<Distribute>) -> Result<()> {
  289. let vault = &mut ctx.accounts.vault;
  290. let clock = Clock::get()?;
  291.  
  292. require!(
  293. vault.accumulated >= VAULT_THRESHOLD,
  294. ErrorCode::BelowDistributionThreshold
  295. );
  296.  
  297. let total = vault.accumulated;
  298.  
  299. // remaining_accounts carries [HolderPattern; N] for eligible holders.
  300. // each holder's weight = personal_lz_size (higher = more complex = more organic)
  301. let mut weight_sum: u64 = 0;
  302. let mut weights: Vec<u64> = Vec::new();
  303.  
  304. for acc in ctx.remaining_accounts.iter() {
  305. let data = acc.try_borrow_data()?;
  306. let holder = HolderPattern::try_deserialize(&mut &data[8..])?;
  307. let w = holder.personal_lz_size as u64;
  308. weights.push(w);
  309. weight_sum += w;
  310. }
  311.  
  312. if weight_sum == 0 { return Ok(()); }
  313.  
  314. for (i, acc) in ctx.remaining_accounts.iter().enumerate() {
  315. let share = (total as u128 * weights[i] as u128 / weight_sum as u128) as u64;
  316. if share > 0 {
  317. msg!("dist: holder={} weight={} share={}", acc.key(), weights[i], share);
  318. }
  319. }
  320.  
  321. vault.accumulated = 0;
  322. vault.total_distributions += 1;
  323. vault.last_distribution_slot = clock.slot;
  324.  
  325. Ok(())
  326. }
  327. }
  328.  
  329. #[derive(Accounts)]
  330. pub struct Distribute<'info> {
  331. pub mint: InterfaceAccount<'info, Mint>,
  332. #[account(seeds = [b"pattern", mint.key().as_ref()], bump)]
  333. pub buffer: Account<'info, PatternBuffer>,
  334. #[account(mut, seeds = [b"vault", mint.key().as_ref()], bump = vault.bump)]
  335. pub vault: Account<'info, Vault>,
  336. }
  337.  
  338. // ─── lempel-ziv complexity (LZ76) ──────────────────────────────────────────
  339. //
  340. // parses a binary string left-to-right, counting the number of distinct
  341. // substrings encountered. the algorithm:
  342. // - maintain a "current word" being built
  343. // - extend it one bit at a time
  344. // - if the current word has appeared earlier in the string, keep extending
  345. // - if it has not appeared, increment the count and start a new word
  346. //
  347. // complexity C is the count. for a random binary string of length n,
  348. // C → n / log2(n). for a periodic string, C → log2(n). for a constant
  349. // string, C = 2 (the initial bit and its complement or repetition).
  350.  
  351. fn lempel_ziv_complexity(bits: &[u8], len: usize) -> u32 {
  352. let get_bit = |i: usize| -> u8 {
  353. (bits[i / 8] >> (i % 8)) & 1
  354. };
  355.  
  356. let mut complexity: u32 = 1;
  357. let mut prefix_end: usize = 1; // end of the "dictionary" built so far
  358. let mut word_start: usize = 1;
  359. let mut word_len: usize = 1;
  360.  
  361. while word_start + word_len <= len {
  362. // check if the current word appears in the prefix [0..prefix_end)
  363. let found = substring_exists(
  364. bits, 0, prefix_end,
  365. bits, word_start, word_len,
  366. );
  367.  
  368. if found {
  369. word_len += 1;
  370. } else {
  371. complexity += 1;
  372. prefix_end = word_start + word_len;
  373. word_start = prefix_end;
  374. word_len = 1;
  375. }
  376. }
  377.  
  378. complexity
  379. }
  380.  
  381. /// check if substring bits[s_start..s_start+s_len] appears anywhere
  382. /// in bits[h_start..h_start+h_len] using naive scan.
  383. /// for our buffer sizes (512 bits, typical word lengths <16) this is fast.
  384. fn substring_exists(
  385. haystack: &[u8], h_start: usize, h_len: usize,
  386. needle: &[u8], n_start: usize, n_len: usize,
  387. ) -> bool {
  388. if n_len > h_len { return false; }
  389.  
  390. let get_h = |i: usize| -> u8 { (haystack[(h_start + i) / 8] >> ((h_start + i) % 8)) & 1 };
  391. let get_n = |i: usize| -> u8 { (needle[(n_start + i) / 8] >> ((n_start + i) % 8)) & 1 };
  392.  
  393. 'outer: for offset in 0..=(h_len - n_len) {
  394. for j in 0..n_len {
  395. if get_h(offset + j) != get_n(j) {
  396. continue 'outer;
  397. }
  398. }
  399. return true;
  400. }
  401. false
  402. }
  403.  
  404. /// extract an n-gram as a u64 hash for fast comparison
  405. fn extract_ngram(bits: &[u8], start: usize, n: usize, buf_len: usize) -> u64 {
  406. let mut hash: u64 = 0;
  407. for i in 0..n {
  408. let idx = (start + buf_len - n + 1 + i) % buf_len;
  409. let bit = ((bits[idx / 8] >> (idx % 8)) & 1) as u64;
  410. hash = (hash << 1) | bit;
  411. }
  412. hash
  413. }
  414.  
  415. // ─── shannon entropy of binary string ───────────────────────────────────────
  416. //
  417. // H = -p₁·log₂(p₁) - p₀·log₂(p₀)
  418. // where p₁ = fraction of 1-bits, p₀ = 1 - p₁.
  419. // returns value in [0, 1] * FP_SCALE.
  420.  
  421. fn shannon_entropy_bits(bits: &[u8], len: usize) -> i128 {
  422. let mut ones: u64 = 0;
  423. for i in 0..len {
  424. ones += ((bits[i / 8] >> (i % 8)) & 1) as u64;
  425. }
  426. let p1_fp = (ones as i128 * FP_SCALE) / len as i128;
  427. let p0_fp = FP_SCALE - p1_fp;
  428.  
  429. if p1_fp == 0 || p0_fp == 0 { return 0; }
  430.  
  431. let h1 = (p1_fp * ln_fp(p1_fp)) / FP_SCALE;
  432. let h0 = (p0_fp * ln_fp(p0_fp)) / FP_SCALE;
  433.  
  434. // convert from nat to bit: divide by ln(2)
  435. let ln2 = 693_147_180_559i128;
  436. -(h1 + h0) * FP_SCALE / ln2
  437. }
  438.  
  439. // ─── pyth oracle reader ─────────────────────────────────────────────────────
  440.  
  441. fn read_pyth_price(oracle: &AccountInfo) -> Result<i128> {
  442. let data = oracle.try_borrow_data()?;
  443. require!(data.len() >= 48, ErrorCode::OracleAccountMalformed);
  444.  
  445. let price = i64::from_le_bytes(data[32..40].try_into().unwrap());
  446. let expo = i32::from_le_bytes(data[40..44].try_into().unwrap());
  447.  
  448. let price_fp = if expo >= 0 {
  449. (price as i128) * FP_SCALE * 10i128.pow(expo as u32)
  450. } else {
  451. (price as i128) * FP_SCALE / 10i128.pow((-expo) as u32)
  452. };
  453.  
  454. Ok(price_fp)
  455. }
  456.  
  457. /// integer natural log via bit-length approximation.
  458. fn ln_fp(x_fp: i128) -> i128 {
  459. if x_fp <= 0 { return -20 * FP_SCALE; }
  460. if x_fp == FP_SCALE { return 0; }
  461. let bits = 127 - x_fp.leading_zeros() as i128;
  462. let ln2 = 693_147_180_559i128;
  463. let base = bits * ln2;
  464. let shifted = if bits >= 12 { x_fp >> (bits - 12) } else { x_fp << (12 - bits) };
  465. let correction = (shifted - (1i128 << 12)) * FP_SCALE / (1i128 << 12);
  466. base - (40 * ln2) + correction
  467. }
  468.  
  469. // ─── initialization ─────────────────────────────────────────────────────────
  470.  
  471. #[derive(Accounts)]
  472. pub struct Initialize<'info> {
  473. #[account(mut)]
  474. pub deployer: Signer<'info>,
  475. pub mint: InterfaceAccount<'info, Mint>,
  476. #[account(
  477. init, payer = deployer,
  478. space = PatternBuffer::LEN,
  479. seeds = [b"pattern", mint.key().as_ref()], bump,
  480. )]
  481. pub buffer: Account<'info, PatternBuffer>,
  482. #[account(
  483. init, payer = deployer,
  484. space = 8 + 8 + 8 + 8 + 1,
  485. seeds = [b"vault", mint.key().as_ref()], bump,
  486. )]
  487. pub vault: Account<'info, Vault>,
  488. /// CHECK: pyth oracle
  489. pub oracle: AccountInfo<'info>,
  490. pub system_program: Program<'info, System>,
  491. }
  492.  
  493. // ─── errors ─────────────────────────────────────────────────────────────────
  494.  
  495. #[error_code]
  496. pub enum ErrorCode {
  497. #[msg("oracle account data is malformed or too short")]
  498. OracleAccountMalformed,
  499. #[msg("buffer not yet filled to 512 entries")]
  500. InsufficientHistory,
  501. #[msg("vault balance below distribution threshold")]
  502. BelowDistributionThreshold,
  503. #[msg("arithmetic overflow in fixed-point computation")]
  504. ArithmeticOverflow,
  505. }
  506.  
Add Comment
Please, Sign In to add comment