Skip to main content

arete_hash/protocol/
model.rs

1//! Typed models for `arete.protocol-spec/v1`.
2//!
3//! Deserialize these only from a document that `validate_protocol_spec_value`
4//! accepted. Every collection is required and every optional scalar is
5//! omitted rather than `null`, so a validated document re-serializes to the
6//! same canonical bytes it was hashed from.
7
8use std::collections::BTreeMap;
9
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13use crate::{HashId, ProgramRelease, ProgramSpec, StackManifest};
14
15pub const PROTOCOL_SPEC_SCHEMA_V1: &str = "arete.protocol-spec/v1";
16pub const PROTOCOL_HANDLER_ABI_V1: &str = "arete.protocol-handler/v1";
17pub const PROTOCOL_CTX_V1: &str = "arete.protocol-ctx/v1";
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20#[serde(rename_all = "camelCase", deny_unknown_fields)]
21pub struct ProtocolSpecV1 {
22    pub schema: String,
23    pub name: String,
24    pub version: String,
25    #[serde(default, skip_serializing_if = "Option::is_none")]
26    pub description: Option<String>,
27    #[serde(default, skip_serializing_if = "Option::is_none")]
28    pub handler_abi: Option<String>,
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub ctx_version: Option<String>,
31    pub programs: BTreeMap<String, ProtocolProgramV1>,
32    pub stacks: BTreeMap<String, ProtocolStackDependencyV1>,
33    pub params: ObjectSchemaV1,
34    pub roles: BTreeMap<String, RoleV1>,
35    pub resources: BTreeMap<String, ResourceDeclV1>,
36    pub instance_visibility: InstanceVisibility,
37    pub reads: BTreeMap<String, ReadV1>,
38    pub machines: MachinesV1,
39    pub steps: BTreeMap<String, StepV1>,
40    pub reactions: Vec<ReactionV1>,
41    pub events: BTreeMap<String, EventV1>,
42    pub metadata: MetadataV1,
43    pub stack: StackSurfaceV1,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)]
48pub enum ProtocolProgramV1 {
49    /// A program with a pinned ProgramSpec. Templates resolve against it.
50    #[serde(rename_all = "camelCase")]
51    ProgramSpec {
52        program_id: String,
53        program_spec_hash: HashId<ProgramSpec>,
54        #[serde(default, skip_serializing_if = "Option::is_none")]
55        program_release_hash: Option<HashId<ProgramRelease>>,
56    },
57    /// A runtime program whose instructions trusted Rust builds itself.
58    Builtin { builtin: BuiltinProgram },
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub enum BuiltinProgram {
64    System,
65    SplToken,
66    AssociatedToken,
67    ComputeBudget,
68    Memo,
69}
70
71impl BuiltinProgram {
72    pub const ALL: [&'static str; 5] = [
73        "system",
74        "splToken",
75        "associatedToken",
76        "computeBudget",
77        "memo",
78    ];
79
80    pub const fn program_id(self) -> &'static str {
81        match self {
82            Self::System => "11111111111111111111111111111111",
83            Self::SplToken => "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
84            Self::AssociatedToken => "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
85            Self::ComputeBudget => "ComputeBudget111111111111111111111111111111",
86            Self::Memo => "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr",
87        }
88    }
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92#[serde(rename_all = "camelCase", deny_unknown_fields)]
93pub struct ProtocolStackDependencyV1 {
94    pub stack_manifest_hash: HashId<StackManifest>,
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub package: Option<String>,
97}
98
99/// A restricted JSON Schema subset for args, params, resources and metadata.
100#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase", deny_unknown_fields)]
102pub struct ObjectSchemaV1 {
103    #[serde(rename = "type")]
104    pub schema_type: ObjectType,
105    pub properties: BTreeMap<String, FieldSchemaV1>,
106    pub required: Vec<String>,
107    pub additional_properties: bool,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
111#[serde(rename_all = "camelCase")]
112pub enum ObjectType {
113    Object,
114}
115
116#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
117#[serde(rename_all = "camelCase", deny_unknown_fields)]
118pub struct FieldSchemaV1 {
119    #[serde(rename = "type")]
120    pub field_type: FieldType,
121    #[serde(default, skip_serializing_if = "Option::is_none")]
122    pub format: Option<StringFormat>,
123    #[serde(default, rename = "enum", skip_serializing_if = "Option::is_none")]
124    pub enum_values: Option<Vec<String>>,
125    #[serde(default, skip_serializing_if = "Option::is_none")]
126    pub max_length: Option<u32>,
127    #[serde(default, skip_serializing_if = "Option::is_none")]
128    pub minimum: Option<i64>,
129    #[serde(default, skip_serializing_if = "Option::is_none")]
130    pub maximum: Option<i64>,
131    #[serde(default, skip_serializing_if = "Option::is_none")]
132    pub items: Option<Box<FieldSchemaV1>>,
133    #[serde(default, skip_serializing_if = "Option::is_none")]
134    pub max_items: Option<u32>,
135    #[serde(default, skip_serializing_if = "Option::is_none")]
136    pub properties: Option<BTreeMap<String, FieldSchemaV1>>,
137    #[serde(default, skip_serializing_if = "Option::is_none")]
138    pub required: Option<Vec<String>>,
139    #[serde(default, skip_serializing_if = "Option::is_none")]
140    pub additional_properties: Option<bool>,
141    #[serde(default, skip_serializing_if = "Option::is_none")]
142    pub description: Option<String>,
143}
144
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
146#[serde(rename_all = "camelCase")]
147pub enum FieldType {
148    String,
149    Boolean,
150    Integer,
151    Array,
152    Object,
153}
154
155/// String formats. Integer base units are decimal strings.
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
157#[serde(rename_all = "camelCase")]
158pub enum StringFormat {
159    Pubkey,
160    U8,
161    U16,
162    U32,
163    U64,
164    U128,
165    I64,
166    /// Unix seconds as a canonical decimal string.
167    UnixTimestamp,
168    Uri,
169}
170
171impl StringFormat {
172    pub const ALL: [&'static str; 9] = [
173        "pubkey",
174        "u8",
175        "u16",
176        "u32",
177        "u64",
178        "u128",
179        "i64",
180        "unixTimestamp",
181        "uri",
182    ];
183}
184
185#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
186#[serde(rename_all = "camelCase", deny_unknown_fields)]
187pub struct RoleV1 {
188    #[serde(default, skip_serializing_if = "Option::is_none")]
189    pub description: Option<String>,
190}
191
192#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
193#[serde(rename_all = "camelCase", deny_unknown_fields)]
194pub struct ResourceDeclV1 {
195    pub schema: FieldSchemaV1,
196    #[serde(default, skip_serializing_if = "Option::is_none")]
197    pub description: Option<String>,
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
201#[serde(rename_all = "camelCase")]
202pub enum InstanceVisibility {
203    /// Every integrator of the deployment sees the instance.
204    Shared,
205    /// Only the integrator account that created the instance sees it.
206    Account,
207}
208
209/// A declared chain read. Guards, derivations and templates see its result
210/// as `reads.<name>`.
211#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
212#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)]
213pub enum ReadV1 {
214    /// A program account decoded with the program's ProgramSpec.
215    Account {
216        program: String,
217        account: String,
218        address: String,
219        optional: bool,
220    },
221    /// An SPL token account (amount, mint, owner).
222    TokenAccount { address: String, optional: bool },
223    /// The lamport balance of any account.
224    Lamports { address: String },
225}
226
227#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
228#[serde(rename_all = "camelCase", deny_unknown_fields)]
229pub struct MachinesV1 {
230    pub instance: MachineV1,
231    #[serde(default, skip_serializing_if = "Option::is_none")]
232    pub participant: Option<MachineV1>,
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
236#[serde(rename_all = "camelCase")]
237pub enum MachineKind {
238    Instance,
239    Participant,
240}
241
242impl MachineKind {
243    pub const fn as_str(self) -> &'static str {
244        match self {
245            Self::Instance => "instance",
246            Self::Participant => "participant",
247        }
248    }
249}
250
251#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
252#[serde(rename_all = "camelCase", deny_unknown_fields)]
253pub struct MachineV1 {
254    pub initial: String,
255    pub states: BTreeMap<String, StateV1>,
256    /// Derivation rules, evaluated in order after every observation. The
257    /// first rule whose `from` contains the current state and whose `when`
258    /// holds moves the machine.
259    pub derive: Vec<DeriveRuleV1>,
260    /// Reads the derivations need.
261    pub reads: Vec<String>,
262}
263
264#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
265#[serde(rename_all = "camelCase", deny_unknown_fields)]
266pub struct StateV1 {
267    pub kind: StateKind,
268    pub terminal: bool,
269    #[serde(default, skip_serializing_if = "Option::is_none")]
270    pub description: Option<String>,
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
274#[serde(rename_all = "camelCase")]
275pub enum StateKind {
276    /// Set by a confirmed step's transition.
277    Recorded,
278    /// Set by a derivation rule over reads and time.
279    Derived,
280}
281
282#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
283#[serde(rename_all = "camelCase", deny_unknown_fields)]
284pub struct DeriveRuleV1 {
285    pub from: Vec<String>,
286    pub to: String,
287    pub when: String,
288    /// When `when` depends on time, the instant at which the engine must
289    /// re-evaluate it. The engine schedules a timer for it.
290    #[serde(default, skip_serializing_if = "Option::is_none")]
291    pub at: Option<String>,
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
295#[serde(rename_all = "camelCase")]
296pub enum StepScope {
297    Create,
298    Instance,
299    Participant,
300}
301
302#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
303#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)]
304pub enum AuthorityV1 {
305    /// The wallet that creates the instance. Create steps only.
306    Creator,
307    /// The participant's own wallet. Participant steps only.
308    Participant,
309    /// A named role wallet bound at create. Guarded by
310    /// `wallet == instance.roles.<role>`.
311    Role { role: String },
312    /// An Arete keeper fee payer. Compiler-proven permissionless steps only.
313    Keeper,
314    /// A Squads proposer that may propose but never vote or execute.
315    #[serde(rename_all = "camelCase")]
316    SquadsProposer {
317        multisig: String,
318        /// The Squads v4 vault the step acts from. Absent means vault 0; a
319        /// canonical spec omits it rather than writing 0.
320        #[serde(default, skip_serializing_if = "Option::is_none")]
321        vault_index: Option<u8>,
322    },
323}
324
325impl AuthorityV1 {
326    pub fn signer_class(&self) -> SignerClass {
327        match self {
328            Self::Creator | Self::Participant | Self::Role { .. } => SignerClass::Wallet,
329            Self::Keeper => SignerClass::Keeper,
330            Self::SquadsProposer { .. } => SignerClass::SquadsProposer,
331        }
332    }
333
334    /// The Squads v4 vault index a `squadsProposer` step acts from, with the
335    /// default applied. `None` for every other authority.
336    pub fn squads_vault_index(&self) -> Option<u8> {
337        match self {
338            Self::SquadsProposer { vault_index, .. } => Some(vault_index.unwrap_or(0)),
339            _ => None,
340        }
341    }
342
343    /// Wallet steps are the only steps an app can prepare.
344    pub fn is_client_callable(&self) -> bool {
345        matches!(self.signer_class(), SignerClass::Wallet)
346    }
347}
348
349#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
350#[serde(rename_all = "camelCase")]
351pub enum SignerClass {
352    Wallet,
353    Ephemeral,
354    Attestation,
355    Keeper,
356    SquadsProposer,
357}
358
359impl SignerClass {
360    pub const ALL: [&'static str; 5] = [
361        "wallet",
362        "ephemeral",
363        "attestation",
364        "keeper",
365        "squadsProposer",
366    ];
367}
368
369#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
370#[serde(rename_all = "camelCase")]
371pub enum StepAccess {
372    Public,
373    Secret,
374}
375
376#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
377#[serde(rename_all = "camelCase", deny_unknown_fields)]
378pub struct StepV1 {
379    pub scope: StepScope,
380    #[serde(default, skip_serializing_if = "Option::is_none")]
381    pub title: Option<String>,
382    #[serde(default, skip_serializing_if = "Option::is_none")]
383    pub description: Option<String>,
384    pub authority: AuthorityV1,
385    /// The maximum access. Present exactly on client-callable steps.
386    #[serde(default, skip_serializing_if = "Option::is_none")]
387    pub access: Option<StepAccess>,
388    pub args: ObjectSchemaV1,
389    pub guards: Vec<GuardV1>,
390    pub reads: Vec<String>,
391    /// Names of fresh keypairs allocated per intent, as `ephemeral.<name>`.
392    pub ephemeral: Vec<String>,
393    pub fee: FeeBasisV1,
394    pub transitions: Vec<TransitionV1>,
395    #[serde(default, skip_serializing_if = "Option::is_none")]
396    pub bind: Option<BindV1>,
397    pub glue: GlueV1,
398    pub build: BuildV1,
399}
400
401#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
402#[serde(rename_all = "camelCase", deny_unknown_fields)]
403pub struct GuardV1 {
404    /// The typed rejection reason.
405    pub code: String,
406    pub expr: String,
407    #[serde(default, skip_serializing_if = "Option::is_none")]
408    pub message: Option<String>,
409}
410
411#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
412#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)]
413pub enum FeeBasisV1 {
414    None,
415    /// A percentage of an input amount argument. The revision's fee policy
416    /// supplies the rate and destinations.
417    #[serde(rename_all = "camelCase")]
418    Input {
419        amount_arg: String,
420        asset: FeeAssetV1,
421    },
422    /// A fixed amount chosen by the revision's fee policy.
423    Fixed,
424}
425
426#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
427#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)]
428pub enum FeeAssetV1 {
429    Native,
430    #[serde(rename_all = "camelCase")]
431    Spl {
432        mint: String,
433        source_arg: String,
434    },
435}
436
437#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
438#[serde(rename_all = "camelCase", deny_unknown_fields)]
439pub struct TransitionV1 {
440    pub machine: MachineKind,
441    pub from: Vec<String>,
442    pub to: String,
443}
444
445#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
446#[serde(rename_all = "camelCase", deny_unknown_fields)]
447pub struct BindV1 {
448    /// Instance account name → expression over the create transaction's
449    /// outputs (ephemeral keys, derived addresses, args, params).
450    pub accounts: BTreeMap<String, String>,
451    /// Role → expression over args or params. Immutable for the instance.
452    pub roles: BTreeMap<String, String>,
453}
454
455#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
456#[serde(rename_all = "camelCase", deny_unknown_fields)]
457pub struct GlueV1 {
458    /// Idempotent associated token accounts to create before the step.
459    pub atas: Vec<AtaGlueV1>,
460    /// Wrap native SOL into the fee payer's wSOL account before the step.
461    #[serde(default, skip_serializing_if = "Option::is_none")]
462    pub wrap_sol: Option<WrapSolV1>,
463    /// Close the fee payer's wSOL account after the step.
464    pub unwrap_sol: bool,
465}
466
467#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
468#[serde(rename_all = "camelCase", deny_unknown_fields)]
469pub struct AtaGlueV1 {
470    pub owner: String,
471    pub mint: String,
472}
473
474#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
475#[serde(rename_all = "camelCase", deny_unknown_fields)]
476pub struct WrapSolV1 {
477    pub amount: String,
478}
479
480#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
481#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)]
482pub enum BuildV1 {
483    Template {
484        instructions: Vec<TemplateInstructionV1>,
485    },
486    /// The escape hatch: a reviewed handler export in the release's bundle.
487    /// `programs` is the allowlist the handler's output is checked against.
488    Handler {
489        export: String,
490        programs: Vec<String>,
491    },
492}
493
494#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
495#[serde(rename_all = "camelCase", deny_unknown_fields)]
496pub struct TemplateInstructionV1 {
497    pub program: String,
498    pub instruction: String,
499    pub args: BTreeMap<String, String>,
500    pub accounts: BTreeMap<String, String>,
501    /// Trailing accounts no IDL names, appended after the instruction's IDL
502    /// accounts in this order. Omitted when there are none.
503    #[serde(default, skip_serializing_if = "Vec::is_empty")]
504    pub remaining_accounts: Vec<RemainingAccountV1>,
505}
506
507/// One trailing account of a template instruction. `pubkey` is an account
508/// expression; remaining accounts never sign, so `is_signer` is always false.
509#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
510#[serde(rename_all = "camelCase", deny_unknown_fields)]
511pub struct RemainingAccountV1 {
512    pub pubkey: String,
513    pub is_writable: bool,
514    pub is_signer: bool,
515}
516
517#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
518#[serde(rename_all = "camelCase", deny_unknown_fields)]
519pub struct ReactionV1 {
520    pub id: String,
521    pub trigger: TriggerV1,
522    pub action: ActionV1,
523}
524
525#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
526#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)]
527pub enum TriggerV1 {
528    /// Fires once at `time` while the machine is in one of `in`.
529    At {
530        machine: MachineKind,
531        #[serde(rename = "in")]
532        in_states: Vec<String>,
533        time: String,
534    },
535    /// Fires when `condition` becomes true while the machine is in `in`.
536    When {
537        machine: MachineKind,
538        #[serde(rename = "in")]
539        in_states: Vec<String>,
540        condition: String,
541    },
542    /// Fires on a transition into one of `to` (from one of `from`, if given).
543    OnTransition {
544        machine: MachineKind,
545        from: Vec<String>,
546        to: Vec<String>,
547    },
548    /// Fires when an intent for `step` confirms.
549    OnConfirmed { step: String },
550}
551
552#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
553#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)]
554pub enum ActionV1 {
555    RunKeeperStep { step: String },
556    ProposeSquads { step: String },
557    Emit { event: String },
558}
559
560#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
561#[serde(rename_all = "camelCase", deny_unknown_fields)]
562pub struct EventV1 {
563    pub machine: MachineKind,
564    #[serde(default, skip_serializing_if = "Option::is_none")]
565    pub description: Option<String>,
566}
567
568#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
569#[serde(rename_all = "camelCase", deny_unknown_fields)]
570pub struct MetadataV1 {
571    #[serde(default, skip_serializing_if = "Option::is_none")]
572    pub instance: Option<MetadataSchemaV1>,
573    #[serde(default, skip_serializing_if = "Option::is_none")]
574    pub participant: Option<MetadataSchemaV1>,
575}
576
577#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
578#[serde(rename_all = "camelCase", deny_unknown_fields)]
579pub struct MetadataSchemaV1 {
580    pub fields: BTreeMap<String, MetadataFieldV1>,
581    pub max_bytes: u32,
582}
583
584#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
585#[serde(rename_all = "camelCase", deny_unknown_fields)]
586pub struct MetadataFieldV1 {
587    pub schema: FieldSchemaV1,
588    pub writer: MetadataWriter,
589    pub filterable: bool,
590}
591
592#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
593#[serde(rename_all = "camelCase")]
594pub enum MetadataWriter {
595    /// The account that created the instance (or the publisher).
596    Creator,
597    /// The participant's wallet, with a wallet proof.
598    Participant,
599}
600
601/// What the generated protocol stack tracks.
602#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
603#[serde(rename_all = "camelCase", deny_unknown_fields)]
604pub struct StackSurfaceV1 {
605    /// Bound instance accounts the stack decodes, by bound account name.
606    pub instance_accounts: BTreeMap<String, StackInstanceAccountV1>,
607    /// Machine-visible values mapped from the decoded instance accounts.
608    pub instance_fields: BTreeMap<String, StackInstanceFieldV1>,
609    /// Program instructions that count as protocol activity.
610    pub activity: Vec<StackActivityV1>,
611}
612
613#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
614#[serde(rename_all = "camelCase", deny_unknown_fields)]
615pub struct StackInstanceAccountV1 {
616    pub program: String,
617    pub account: String,
618}
619
620#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
621#[serde(rename_all = "camelCase", deny_unknown_fields)]
622pub struct StackInstanceFieldV1 {
623    /// A key of `instanceAccounts`.
624    pub account: String,
625    /// A field path in that account's decoded data.
626    pub field: String,
627}
628
629#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
630#[serde(rename_all = "camelCase", deny_unknown_fields)]
631pub struct StackActivityV1 {
632    /// The protocol step this instruction belongs to.
633    pub step: String,
634    pub program: String,
635    pub instruction: String,
636    /// The instruction account that is a registered instance account.
637    pub instance_account: String,
638    /// The instruction account holding the acting wallet.
639    pub wallet_account: String,
640    /// An instruction argument summed into participant and instance totals.
641    #[serde(default, skip_serializing_if = "Option::is_none")]
642    pub amount_arg: Option<String>,
643}
644
645/// Parsed form of the canonical JSON value of any contract.
646pub type ContractValue = Value;