openzoo 0.51.4 → 0.51.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +12 -1
  2. package/bin/openzoo.js +21 -46
  3. package/lib/chatgpt-install.js +106 -0
  4. package/lib/chatgpt.js +108 -90
  5. package/lib/config.js +34 -0
  6. package/lib/modelcommand.js +443 -0
  7. package/lib/pay.js +60 -4
  8. package/lib/proxy.js +39 -0
  9. package/package.json +4 -7
  10. package/transmute/README.md +0 -385
  11. package/transmute/bin/openzoo-transmute.js +0 -7
  12. package/transmute/docs/OPENZOO_CLI_PATCH.md +0 -139
  13. package/transmute/docs/VERCEL_TO_SOLANA.md +0 -381
  14. package/transmute/lib/build.js +0 -381
  15. package/transmute/lib/cli.js +0 -293
  16. package/transmute/lib/compile/bytecode.js +0 -591
  17. package/transmute/lib/compile/hostids.js +0 -11
  18. package/transmute/lib/compile/index.js +0 -159
  19. package/transmute/lib/compile/ir.js +0 -1634
  20. package/transmute/lib/compile/parse.js +0 -753
  21. package/transmute/lib/compile/rust.js +0 -781
  22. package/transmute/lib/deploy.js +0 -357
  23. package/transmute/lib/eligibility.js +0 -211
  24. package/transmute/lib/gateway.js +0 -710
  25. package/transmute/lib/hub.js +0 -290
  26. package/transmute/lib/solana.js +0 -387
  27. package/transmute/lib/vercel.js +0 -296
  28. package/transmute/lib/wallet.js +0 -41
  29. package/transmute/lib/wire.js +0 -208
  30. package/transmute/prebuilt/zoo_vm.v0.so +0 -0
  31. package/transmute/runtime/zoo-host/Cargo.lock +0 -588
  32. package/transmute/runtime/zoo-host/Cargo.toml +0 -30
  33. package/transmute/runtime/zoo-host/examples/vmrun.rs +0 -43
  34. package/transmute/runtime/zoo-host/src/assets.rs +0 -214
  35. package/transmute/runtime/zoo-host/src/ctx.rs +0 -350
  36. package/transmute/runtime/zoo-host/src/fmt.rs +0 -188
  37. package/transmute/runtime/zoo-host/src/helpers.rs +0 -245
  38. package/transmute/runtime/zoo-host/src/json.rs +0 -263
  39. package/transmute/runtime/zoo-host/src/kv.rs +0 -206
  40. package/transmute/runtime/zoo-host/src/lib.rs +0 -137
  41. package/transmute/runtime/zoo-host/src/site.rs +0 -94
  42. package/transmute/runtime/zoo-host/src/val.rs +0 -1050
  43. package/transmute/runtime/zoo-host/src/vm.rs +0 -902
  44. package/transmute/runtime/zoo-host/src/wire.rs +0 -239
  45. package/transmute/runtime/zoo-vm/Cargo.lock +0 -596
  46. package/transmute/runtime/zoo-vm/Cargo.toml +0 -23
  47. package/transmute/runtime/zoo-vm/src/lib.rs +0 -92
  48. package/transmute/site/.env.production +0 -2
  49. package/transmute/site/api/echo.js +0 -10
  50. package/transmute/site/api/hello.js +0 -12
  51. package/transmute/site/api/hits.js +0 -13
  52. package/transmute/site/api/time.js +0 -5
  53. package/transmute/site/package.json +0 -6
  54. package/transmute/site/public/app.js +0 -16
  55. package/transmute/site/public/index.html +0 -96
@@ -1,214 +0,0 @@
1
- //! `static/` on PDAs, plus the site manifest.
2
- //!
3
- //! Path → PDA `["asset", sha256("zoo-asset" ‖ path)]`. Account data:
4
- //! `[u8 ver=1][u8 bump][u32 total_len][u8 ct_len][ct bytes][file bytes...]`.
5
- //!
6
- //! Writes are gated by the program's upgrade authority: the caller passes the
7
- //! BPF upgradeable loader's ProgramData account and must sign as the
8
- //! authority recorded in it. A program whose authority was burned has a
9
- //! frozen frontend, which is the right default for "deployed to mainnet".
10
- use crate::{kv::err_str, ERR_NOT_AUTHORITY};
11
- use pinocchio::{
12
- cpi::{Seed, Signer},
13
- error::ProgramError,
14
- sysvars::{rent::Rent, Sysvar},
15
- AccountView, Address, ProgramResult, Resize,
16
- };
17
- use pinocchio_system::instructions::{CreateAccount, Transfer};
18
-
19
- pub const FIXED_HEADER: usize = 7;
20
- /// Largest account a single CPI `create_account` may allocate.
21
- pub const MAX_INITIAL: usize = 10_240;
22
- pub const MAX_GROW: usize = 10_240;
23
-
24
- pub const BPF_LOADER_UPGRADEABLE: Address = Address::from_str_const("BPFLoaderUpgradeab1e11111111111111111111111");
25
-
26
- pub fn path_hash(path: &str) -> [u8; 32] {
27
- solana_sha256_hasher::hashv(&[b"zoo-asset", path.as_bytes()]).to_bytes()
28
- }
29
-
30
- pub fn pda_for_hash(program_id: &Address, hash: &[u8; 32]) -> (Address, u8) {
31
- Address::find_program_address(&[b"asset", hash], program_id)
32
- }
33
-
34
- /// Asset PDA under the shared runtime: `["asset", site_id, hash]`.
35
- pub fn pda_for_hash_ns(program_id: &Address, ns: Option<&[u8; 32]>, hash: &[u8; 32]) -> (Address, u8) {
36
- match ns {
37
- Some(n) => Address::find_program_address(&[b"asset", n, hash], program_id),
38
- None => pda_for_hash(program_id, hash),
39
- }
40
- }
41
-
42
- /// Who may write assets: a compiled site's upgrade authority, or under the
43
- /// shared runtime the authority recorded in the site account (`accounts[1]`).
44
- #[derive(Clone, Copy)]
45
- pub enum Auth {
46
- Upgrade,
47
- Site([u8; 32]),
48
- }
49
-
50
- pub fn check_auth(program_id: &Address, accounts: &[AccountView], auth: Auth) -> ProgramResult {
51
- match auth {
52
- Auth::Upgrade => check_authority(program_id, accounts),
53
- Auth::Site(site) => crate::site::check_site_authority(program_id, accounts, &site),
54
- }
55
- }
56
-
57
- /// Verify `accounts[0]` signs as the upgrade authority of `program_id`
58
- /// (read off `accounts[1]`, the ProgramData account).
59
- pub fn check_authority(program_id: &Address, accounts: &[AccountView]) -> ProgramResult {
60
- let authority = accounts.first().ok_or(ProgramError::NotEnoughAccountKeys)?;
61
- let program_data = accounts.get(1).ok_or(ProgramError::NotEnoughAccountKeys)?;
62
- if !authority.is_signer() {
63
- return Err(ProgramError::MissingRequiredSignature);
64
- }
65
- let (expected, _) = Address::find_program_address(&[program_id.as_ref()], &BPF_LOADER_UPGRADEABLE);
66
- if *program_data.address() != expected || !program_data.owned_by(&BPF_LOADER_UPGRADEABLE) {
67
- return Err(ProgramError::Custom(ERR_NOT_AUTHORITY));
68
- }
69
- let d = program_data.try_borrow()?;
70
- // UpgradeableLoaderState::ProgramData { slot: u64, upgrade_authority_address: Option<Pubkey> }
71
- // bincode: u32 enum tag (3) | u64 slot | u8 option | 32 bytes
72
- if d.len() < 45 || u32::from_le_bytes([d[0], d[1], d[2], d[3]]) != 3 || d[12] != 1 {
73
- return Err(ProgramError::Custom(ERR_NOT_AUTHORITY));
74
- }
75
- if d[13..45] != authority.address().as_ref()[..] {
76
- return Err(ProgramError::Custom(ERR_NOT_AUTHORITY));
77
- }
78
- Ok(())
79
- }
80
-
81
- fn split_hash(data: &[u8]) -> Result<(&[u8; 32], &[u8]), ProgramError> {
82
- if data.len() < 32 {
83
- return Err(ProgramError::InvalidInstructionData);
84
- }
85
- let (h, rest) = data.split_at(32);
86
- let h: &[u8; 32] = match h.try_into() { Ok(h) => h, Err(_) => return Err(ProgramError::InvalidInstructionData) };
87
- Ok((h, rest))
88
- }
89
-
90
- /// `[32 hash][u32 total_len][u8 ct_len][ct]`; accounts: authority, program
91
- /// data, asset PDA, system program.
92
- pub fn init(program_id: &Address, accounts: &mut [AccountView], data: &[u8]) -> ProgramResult {
93
- init_ns(program_id, accounts, data, None, Auth::Upgrade)
94
- }
95
-
96
- pub fn init_ns(program_id: &Address, accounts: &mut [AccountView], data: &[u8], ns: Option<[u8; 32]>, auth: Auth) -> ProgramResult {
97
- check_auth(program_id, accounts, auth)?;
98
- let (hash, rest) = split_hash(data)?;
99
- if rest.len() < 5 {
100
- return Err(ProgramError::InvalidInstructionData);
101
- }
102
- let total = u32::from_le_bytes([rest[0], rest[1], rest[2], rest[3]]) as usize;
103
- let ct_len = rest[4] as usize;
104
- let ct = rest.get(5..5 + ct_len).ok_or(ProgramError::InvalidInstructionData)?;
105
- let header = FIXED_HEADER + ct_len;
106
- let (addr, bump) = pda_for_hash_ns(program_id, ns.as_ref(), hash);
107
- let (head, tail) = accounts.split_at_mut(2);
108
- let authority = &head[0];
109
- let asset = tail.first_mut().ok_or(ProgramError::NotEnoughAccountKeys)?;
110
- if *asset.address() != addr {
111
- return Err(ProgramError::InvalidSeeds);
112
- }
113
- let space = (header + total).min(MAX_INITIAL).max(header);
114
- if asset.owned_by(program_id) {
115
- // Re-init of an existing asset (redeploy): resize to the new header.
116
- let rent = Rent::get()?;
117
- let min = rent.try_minimum_balance(space)?;
118
- if min > asset.lamports() {
119
- Transfer { from: authority, to: asset, lamports: min - asset.lamports() }.invoke()?;
120
- }
121
- asset.resize(space)?;
122
- } else {
123
- let bump_bytes = [bump];
124
- match ns.as_ref() {
125
- Some(n) => {
126
- let seeds = [Seed::from(b"asset".as_ref()), Seed::from(n.as_ref()), Seed::from(hash.as_ref()), Seed::from(bump_bytes.as_ref())];
127
- let signer = Signer::from(&seeds);
128
- CreateAccount::with_minimum_balance(authority, asset, space as u64, program_id, None)?.invoke_signed(&[signer])?;
129
- }
130
- None => {
131
- let seeds = [Seed::from(b"asset".as_ref()), Seed::from(hash.as_ref()), Seed::from(bump_bytes.as_ref())];
132
- let signer = Signer::from(&seeds);
133
- CreateAccount::with_minimum_balance(authority, asset, space as u64, program_id, None)?.invoke_signed(&[signer])?;
134
- }
135
- }
136
- }
137
- let mut d = asset.try_borrow_mut()?;
138
- d[0] = 1;
139
- d[1] = bump;
140
- d[2..6].copy_from_slice(&(total as u32).to_le_bytes());
141
- d[6] = ct_len as u8;
142
- d[7..7 + ct_len].copy_from_slice(ct);
143
- Ok(())
144
- }
145
-
146
- /// `[32 hash][u32 offset][bytes]`; same accounts as `init`.
147
- pub fn write(program_id: &Address, accounts: &mut [AccountView], data: &[u8]) -> ProgramResult {
148
- write_ns(program_id, accounts, data, None, Auth::Upgrade)
149
- }
150
-
151
- pub fn write_ns(program_id: &Address, accounts: &mut [AccountView], data: &[u8], ns: Option<[u8; 32]>, auth: Auth) -> ProgramResult {
152
- check_auth(program_id, accounts, auth)?;
153
- let (hash, rest) = split_hash(data)?;
154
- if rest.len() < 4 {
155
- return Err(ProgramError::InvalidInstructionData);
156
- }
157
- let offset = u32::from_le_bytes([rest[0], rest[1], rest[2], rest[3]]) as usize;
158
- let bytes = &rest[4..];
159
- let (addr, _) = pda_for_hash_ns(program_id, ns.as_ref(), hash);
160
- let (head, tail) = accounts.split_at_mut(2);
161
- let authority = &head[0];
162
- let asset = tail.first_mut().ok_or(ProgramError::NotEnoughAccountKeys)?;
163
- if *asset.address() != addr || !asset.owned_by(program_id) {
164
- return Err(ProgramError::InvalidSeeds);
165
- }
166
- let (header, total) = {
167
- let d = asset.try_borrow()?;
168
- if d.len() < FIXED_HEADER || d[0] != 1 {
169
- return Err(ProgramError::InvalidAccountData);
170
- }
171
- (FIXED_HEADER + d[6] as usize, u32::from_le_bytes([d[2], d[3], d[4], d[5]]) as usize)
172
- };
173
- let end = header + offset + bytes.len();
174
- if offset + bytes.len() > total {
175
- return Err(ProgramError::InvalidInstructionData);
176
- }
177
- if end > asset.data_len() {
178
- if end - asset.data_len() > MAX_GROW {
179
- return Err(ProgramError::InvalidRealloc);
180
- }
181
- let rent = Rent::get()?;
182
- let min = rent.try_minimum_balance(end)?;
183
- if min > asset.lamports() {
184
- Transfer { from: authority, to: asset, lamports: min - asset.lamports() }.invoke()?;
185
- }
186
- asset.resize(end)?;
187
- }
188
- let mut d = asset.try_borrow_mut()?;
189
- d[header + offset..end].copy_from_slice(bytes);
190
- Ok(())
191
- }
192
-
193
- /// `[32 hash]`: reclaim the rent to the authority.
194
- pub fn close(program_id: &Address, accounts: &mut [AccountView], data: &[u8]) -> ProgramResult {
195
- close_ns(program_id, accounts, data, None, Auth::Upgrade)
196
- }
197
-
198
- pub fn close_ns(program_id: &Address, accounts: &mut [AccountView], data: &[u8], ns: Option<[u8; 32]>, auth: Auth) -> ProgramResult {
199
- check_auth(program_id, accounts, auth)?;
200
- let (hash, _) = split_hash(data)?;
201
- let (addr, _) = pda_for_hash_ns(program_id, ns.as_ref(), hash);
202
- let (head, tail) = accounts.split_at_mut(2);
203
- let authority = &mut head[0];
204
- let asset = tail.first_mut().ok_or(ProgramError::NotEnoughAccountKeys)?;
205
- if *asset.address() != addr || !asset.owned_by(program_id) {
206
- return Err(ProgramError::InvalidSeeds);
207
- }
208
- let lamports = asset.lamports();
209
- asset.set_lamports(0);
210
- authority.set_lamports(authority.lamports() + lamports);
211
- asset.close()?;
212
- let _ = err_str; // keep the helper linked for kv
213
- Ok(())
214
- }
@@ -1,350 +0,0 @@
1
- //! Per-invocation context: the Vercel `req`/`res` pair plus the account set.
2
- use crate::json;
3
- use crate::kv::KvState;
4
- use crate::val::{parse_query, Val};
5
- use crate::wire::{Req, Resp};
6
- use alloc::{string::String, vec::Vec};
7
- use crate::fmt::{push_i64, push_padded};
8
- use pinocchio::{
9
- sysvars::{clock::Clock, Sysvar},
10
- AccountView, Address,
11
- };
12
-
13
- pub struct Ctx<'a> {
14
- pub program_id: &'a Address,
15
- pub accounts: &'a mut [AccountView],
16
- pub req: Req,
17
- pub res: Resp,
18
- pub sent: bool,
19
- pub env: &'a [(&'static str, &'static str)],
20
- /// Environment from a bytecode module (shared runtime); consulted first.
21
- pub env_dyn: Vec<(String, String)>,
22
- /// Site namespace for KV/asset seeds: `None` for a compiled site program
23
- /// (the program id is the namespace), `Some(site_id)` under the shared runtime.
24
- pub ns: Option<[u8; 32]>,
25
- pub kv: KvState,
26
- query_cache: Option<Val>,
27
- body_cache: Option<Val>,
28
- }
29
-
30
- impl<'a> Ctx<'a> {
31
- pub fn new(
32
- program_id: &'a Address,
33
- accounts: &'a mut [AccountView],
34
- req: Req,
35
- env: &'a [(&'static str, &'static str)],
36
- ) -> Self {
37
- Ctx {
38
- program_id,
39
- accounts,
40
- req,
41
- res: Resp::default(),
42
- sent: false,
43
- env,
44
- env_dyn: Vec::new(),
45
- ns: None,
46
- kv: KvState::default(),
47
- query_cache: None,
48
- body_cache: None,
49
- }
50
- }
51
-
52
- // ---- req ----
53
-
54
- pub fn req_method(&self) -> Val {
55
- Val::str(self.req.method_str())
56
- }
57
- pub fn req_path(&self) -> Val {
58
- Val::str(&self.req.path)
59
- }
60
- /// `req.url` as Node sees it: path + query.
61
- pub fn req_url(&self) -> Val {
62
- if self.req.query.is_empty() {
63
- Val::str(&self.req.path)
64
- } else {
65
- let mut u = String::from(self.req.path.as_str());
66
- u.push('?');
67
- u.push_str(&self.req.query);
68
- Val::Str(u)
69
- }
70
- }
71
- /// Full URL for `new URL(request.url)` in app-router handlers. The origin
72
- /// is synthetic; only path/search matter on chain.
73
- pub fn req_full_url(&self) -> Val {
74
- let mut u = String::from("https://zoo.sol");
75
- u.push_str(&self.req_url().to_js_string());
76
- Val::Str(u)
77
- }
78
- pub fn req_query(&mut self) -> Val {
79
- if self.query_cache.is_none() {
80
- self.query_cache = Some(parse_query(&self.req.query));
81
- }
82
- self.query_cache.clone().unwrap()
83
- }
84
- /// `searchParams.get(name)`: string or null.
85
- pub fn req_query_get(&mut self, name: &Val) -> Val {
86
- match self.req_query().get(name) {
87
- Val::Undef => Val::Null,
88
- v => v,
89
- }
90
- }
91
- pub fn req_headers(&self) -> Val {
92
- self.req.headers_val()
93
- }
94
- /// `req.headers.get(name)` / `req.headers[name]` (null when absent).
95
- pub fn req_header(&self, name: &Val) -> Val {
96
- match self.req.header(&name.to_js_string()) {
97
- Some(v) => Val::str(v),
98
- None => Val::Null,
99
- }
100
- }
101
- pub fn req_text(&self) -> Val {
102
- Val::Str(String::from_utf8_lossy(&self.req.body).into_owned())
103
- }
104
- /// `req.body` with Vercel's helper semantics: JSON when the content type
105
- /// says so (or the body parses), urlencoded → object, else the raw string.
106
- pub fn req_body(&mut self) -> Val {
107
- if let Some(b) = &self.body_cache {
108
- return b.clone();
109
- }
110
- let ct = self.req.header("content-type").unwrap_or("").to_ascii_lowercase();
111
- let text = String::from_utf8_lossy(&self.req.body).into_owned();
112
- let v = if self.req.body.is_empty() {
113
- Val::Undef
114
- } else if ct.contains("application/json") {
115
- json::parse(&text).unwrap_or(Val::Undef)
116
- } else if ct.contains("application/x-www-form-urlencoded") {
117
- parse_query(&text)
118
- } else if ct.is_empty() {
119
- json::parse(&text).unwrap_or(Val::Str(text))
120
- } else {
121
- Val::Str(text)
122
- };
123
- self.body_cache = Some(v.clone());
124
- v
125
- }
126
- /// `await request.json()`: throws on a malformed body like fetch does.
127
- pub fn req_json(&mut self) -> Result<Val, Val> {
128
- let text = String::from_utf8_lossy(&self.req.body).into_owned();
129
- json::parse(&text).map_err(|_| Val::str("SyntaxError: Unexpected end of JSON input"))
130
- }
131
-
132
- // ---- res (pages-router helpers) ----
133
-
134
- pub fn res_status(&mut self, code: &Val) {
135
- let n = code.to_num();
136
- if n.is_finite() && (100.0..1000.0).contains(&n) {
137
- self.res.status = n as u16;
138
- }
139
- }
140
- pub fn res_header(&mut self, name: &Val, value: &Val) {
141
- self.res.set_header(&name.to_js_string(), &value.to_js_string());
142
- }
143
- pub fn res_json(&mut self, v: &Val) {
144
- if !self.res.has_header("content-type") {
145
- self.res.set_header("content-type", "application/json; charset=utf-8");
146
- }
147
- self.res.body = json::stringify(v).into_bytes();
148
- self.sent = true;
149
- }
150
- pub fn res_send(&mut self, v: &Val) {
151
- match v {
152
- Val::Undef | Val::Null => {
153
- self.res.body.clear();
154
- self.sent = true;
155
- }
156
- Val::Str(s) => {
157
- if !self.res.has_header("content-type") {
158
- self.res.set_header("content-type", "text/html; charset=utf-8");
159
- }
160
- self.res.body = s.clone().into_bytes();
161
- self.sent = true;
162
- }
163
- Val::Num(_) | Val::Bool(_) | Val::Arr(_) | Val::Obj(_) => self.res_json(v),
164
- }
165
- }
166
- pub fn res_end(&mut self, v: &Val) {
167
- match v {
168
- Val::Undef | Val::Null => {}
169
- other => self.res.body = other.to_js_string().into_bytes(),
170
- }
171
- self.sent = true;
172
- }
173
- pub fn res_redirect(&mut self, status: &Val, url: &Val) {
174
- let (code, target) = if url.is_undef() { (Val::Num(307.0), status.clone()) } else { (status.clone(), url.clone()) };
175
- self.res_status(&code);
176
- self.res.set_header("location", &target.to_js_string());
177
- self.sent = true;
178
- }
179
-
180
- // ---- app-router / fetch-style responses ----
181
-
182
- fn apply_init(&mut self, init: &Val) {
183
- if let Val::Obj(_) = init {
184
- let st = init.get_str("status");
185
- if !st.is_undef() {
186
- self.res_status(&st);
187
- }
188
- if let Val::Obj(h) = init.get_str("headers") {
189
- for (k, v) in h.iter() {
190
- self.res.set_header(k, &v.to_js_string());
191
- }
192
- }
193
- }
194
- }
195
- /// `Response.json(body, init)` / `NextResponse.json(body, init)`.
196
- pub fn respond_json(&mut self, body: &Val, init: &Val) {
197
- self.apply_init(init);
198
- self.res_json(body);
199
- }
200
- /// `new Response(body, init)` / `new NextResponse(body, init)`.
201
- pub fn respond(&mut self, body: &Val, init: &Val) {
202
- self.apply_init(init);
203
- match body {
204
- Val::Undef | Val::Null => {
205
- self.res.body.clear();
206
- }
207
- other => {
208
- if !self.res.has_header("content-type") {
209
- self.res.set_header("content-type", "text/plain; charset=utf-8");
210
- }
211
- self.res.body = other.to_js_string().into_bytes();
212
- }
213
- }
214
- self.sent = true;
215
- }
216
- /// `NextResponse.redirect(url, status)` / `Response.redirect`.
217
- pub fn respond_redirect(&mut self, url: &Val, status: &Val) {
218
- let code = if status.is_undef() { Val::Num(307.0) } else { status.clone() };
219
- self.res_redirect(&code, url);
220
- }
221
-
222
- // ---- environment / time ----
223
-
224
- pub fn env(&self, name: &Val) -> Val {
225
- let n = name.to_js_string();
226
- for (k, v) in self.env_dyn.iter() {
227
- if *k == n {
228
- return Val::str(v);
229
- }
230
- }
231
- for (k, v) in self.env.iter() {
232
- if *k == n {
233
- return Val::str(v);
234
- }
235
- }
236
- Val::Undef
237
- }
238
- pub fn env_obj(&self) -> Val {
239
- let mut o = Val::obj();
240
- for (k, v) in self.env_dyn.iter() {
241
- o.set_str(k, Val::str(v));
242
- }
243
- for (k, v) in self.env.iter() {
244
- o.set_str(k, Val::str(v));
245
- }
246
- o
247
- }
248
- fn clock(&self) -> Option<Clock> {
249
- Clock::get().ok()
250
- }
251
- /// `Date.now()`: the cluster clock, in ms. 0 off-chain (host tests).
252
- pub fn now_ms(&self) -> Val {
253
- Val::Num(self.clock().map(|c| c.unix_timestamp as f64 * 1000.0).unwrap_or(0.0))
254
- }
255
- pub fn slot(&self) -> Val {
256
- Val::Num(self.clock().map(|c| c.slot as f64).unwrap_or(0.0))
257
- }
258
- /// `new Date().toISOString()`.
259
- pub fn now_iso(&self) -> Val {
260
- Val::Str(iso8601(self.now_ms().to_num()))
261
- }
262
-
263
- // ---- misc helpers used by generated code ----
264
-
265
- pub fn throw(msg: &str) -> Val {
266
- Val::str(msg)
267
- }
268
- pub fn new_error(args: &[Val]) -> Val {
269
- let mut e = Val::obj();
270
- e.set_str("name", Val::str("Error"));
271
- e.set_str("message", Val::Str(args.first().map(|v| v.to_js_string()).unwrap_or_default()));
272
- e
273
- }
274
- pub fn payer_address(&self) -> Val {
275
- self.accounts.first().map(|a| Val::Str(base58(a.address().as_ref()))).unwrap_or(Val::Null)
276
- }
277
- pub fn accounts_len(&self) -> usize {
278
- self.accounts.len()
279
- }
280
- pub fn program_address(&self) -> Val {
281
- Val::Str(base58(self.program_id.as_ref()))
282
- }
283
- }
284
-
285
- /// ms since epoch → `YYYY-MM-DDTHH:MM:SS.mmmZ`
286
- pub fn iso8601(ms: f64) -> String {
287
- let total_ms = if ms.is_finite() { ms as i64 } else { 0 };
288
- let secs = total_ms.div_euclid(1000);
289
- let milli = total_ms.rem_euclid(1000);
290
- let days = secs.div_euclid(86400);
291
- let sod = secs.rem_euclid(86400);
292
- // civil_from_days (Howard Hinnant)
293
- let z = days + 719_468;
294
- let era = z.div_euclid(146_097);
295
- let doe = z - era * 146_097;
296
- let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365;
297
- let y = yoe + era * 400;
298
- let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
299
- let mp = (5 * doy + 2) / 153;
300
- let d = doy - (153 * mp + 2) / 5 + 1;
301
- let m = if mp < 10 { mp + 3 } else { mp - 9 };
302
- let y = if m <= 2 { y + 1 } else { y };
303
- let mut out = String::new();
304
- push_i64(&mut out, y);
305
- out.push('-');
306
- push_padded(&mut out, m as u64, 2);
307
- out.push('-');
308
- push_padded(&mut out, d as u64, 2);
309
- out.push('T');
310
- push_padded(&mut out, (sod / 3600) as u64, 2);
311
- out.push(':');
312
- push_padded(&mut out, ((sod % 3600) / 60) as u64, 2);
313
- out.push(':');
314
- push_padded(&mut out, (sod % 60) as u64, 2);
315
- out.push('.');
316
- push_padded(&mut out, milli as u64, 3);
317
- out.push('Z');
318
- out
319
- }
320
-
321
-
322
- const B58: &[u8; 58] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
323
-
324
- pub fn base58(bytes: &[u8]) -> String {
325
- let mut digits: Vec<u8> = Vec::with_capacity(bytes.len() * 2);
326
- for &b in bytes {
327
- let mut carry = b as u32;
328
- for d in digits.iter_mut() {
329
- carry += (*d as u32) << 8;
330
- *d = (carry % 58) as u8;
331
- carry /= 58;
332
- }
333
- while carry > 0 {
334
- digits.push((carry % 58) as u8);
335
- carry /= 58;
336
- }
337
- }
338
- let mut s = String::new();
339
- for &b in bytes {
340
- if b == 0 {
341
- s.push('1');
342
- } else {
343
- break;
344
- }
345
- }
346
- for &d in digits.iter().rev() {
347
- s.push(B58[d as usize] as char);
348
- }
349
- s
350
- }