@dashevo/dapi-grpc 3.1.0-dev.1 → 3.1.0-dev.3

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.
@@ -7,6 +7,7 @@ package org.dash.platform.dapi.v0;
7
7
  import "google/protobuf/timestamp.proto";
8
8
 
9
9
  service Platform {
10
+ // @sdk-ignore: Write-only endpoint, not a query
10
11
  rpc broadcastStateTransition(BroadcastStateTransitionRequest)
11
12
  returns (BroadcastStateTransitionResponse);
12
13
  rpc getIdentity(GetIdentityRequest) returns (GetIdentityResponse);
@@ -42,6 +43,7 @@ service Platform {
42
43
  returns (GetIdentityByNonUniquePublicKeyHashResponse);
43
44
  rpc waitForStateTransitionResult(WaitForStateTransitionResultRequest)
44
45
  returns (WaitForStateTransitionResultResponse);
46
+ // @sdk-ignore: Consensus params fetched via Tenderdash RPC
45
47
  rpc getConsensusParams(GetConsensusParamsRequest)
46
48
  returns (GetConsensusParamsResponse);
47
49
  rpc getProtocolVersionUpgradeState(GetProtocolVersionUpgradeStateRequest)
@@ -116,6 +118,24 @@ service Platform {
116
118
  returns (GetRecentAddressBalanceChangesResponse);
117
119
  rpc getRecentCompactedAddressBalanceChanges(GetRecentCompactedAddressBalanceChangesRequest)
118
120
  returns (GetRecentCompactedAddressBalanceChangesResponse);
121
+ rpc getShieldedEncryptedNotes(GetShieldedEncryptedNotesRequest)
122
+ returns (GetShieldedEncryptedNotesResponse);
123
+ rpc getShieldedAnchors(GetShieldedAnchorsRequest)
124
+ returns (GetShieldedAnchorsResponse);
125
+ rpc getMostRecentShieldedAnchor(GetMostRecentShieldedAnchorRequest)
126
+ returns (GetMostRecentShieldedAnchorResponse);
127
+ rpc getShieldedPoolState(GetShieldedPoolStateRequest)
128
+ returns (GetShieldedPoolStateResponse);
129
+ rpc getShieldedNullifiers(GetShieldedNullifiersRequest)
130
+ returns (GetShieldedNullifiersResponse);
131
+ rpc getNullifiersTrunkState(GetNullifiersTrunkStateRequest)
132
+ returns (GetNullifiersTrunkStateResponse);
133
+ rpc getNullifiersBranchState(GetNullifiersBranchStateRequest)
134
+ returns (GetNullifiersBranchStateResponse);
135
+ rpc getRecentNullifierChanges(GetRecentNullifierChangesRequest)
136
+ returns (GetRecentNullifierChangesResponse);
137
+ rpc getRecentCompactedNullifierChanges(GetRecentCompactedNullifierChangesRequest)
138
+ returns (GetRecentCompactedNullifierChangesResponse);
119
139
  }
120
140
 
121
141
  // Proof message includes cryptographic proofs for validating responses
@@ -557,6 +577,221 @@ message GetDataContractHistoryResponse {
557
577
  }
558
578
 
559
579
  message GetDocumentsRequest {
580
+ // Comparison operator for a single `WhereClause`. Wire values
581
+ // mirror `drive::query::WhereOperator` 1:1; the server maps the
582
+ // enum discriminant directly without re-parsing operator strings.
583
+ //
584
+ // `BETWEEN*` operators expect the right-hand operand to be a
585
+ // 2-element `DocumentFieldValue.list` carrying `[lower, upper]`;
586
+ // `IN` expects a `list` of candidate values; all other operators
587
+ // expect a scalar `DocumentFieldValue` matching the indexed
588
+ // field's type.
589
+ enum WhereOperator {
590
+ EQUAL = 0;
591
+ GREATER_THAN = 1;
592
+ GREATER_THAN_OR_EQUALS = 2;
593
+ LESS_THAN = 3;
594
+ LESS_THAN_OR_EQUALS = 4;
595
+ BETWEEN = 5;
596
+ BETWEEN_EXCLUDE_BOUNDS = 6;
597
+ BETWEEN_EXCLUDE_LEFT = 7;
598
+ BETWEEN_EXCLUDE_RIGHT = 8;
599
+ IN = 9;
600
+ STARTS_WITH = 10;
601
+ }
602
+
603
+ // Tagged scalar (or list) operand for a `WhereClause`. The
604
+ // variant the caller picks names the wire-level primitive type
605
+ // only — the **document type's schema** is the source of truth
606
+ // for the indexed field's actual type, and the server coerces
607
+ // each variant through the schema-driven
608
+ // `document_type.serialize_value_for_key(field, value, …)` path
609
+ // (the same path the CBOR-shaped v0 request flows through). That
610
+ // means:
611
+ //
612
+ // - Identifier-typed fields accept either a `bytes_value` (raw
613
+ // 32 bytes) **or** a `text` (base58-encoded). The schema
614
+ // decides; callers don't need a dedicated identifier variant.
615
+ // - Fixed-width byte fields (e.g. `Bytes20`/`Bytes36`) accept
616
+ // `bytes_value` of the appropriate length; the schema
617
+ // validates the size.
618
+ // - Numeric fields accept the closest-fit signed (`int64_value`)
619
+ // or unsigned (`uint64_value`) variant; the schema coerces to
620
+ // the indexed type (`u8` … `u64`/`i8` … `i64`/`u128`/`i128`).
621
+ // - String / bool fields accept `text` / `bool_value`.
622
+ //
623
+ // The `null_value` variant is the typed-wire equivalent of a CBOR
624
+ // `null` operand on the v0 path. Callers should NOT use it for
625
+ // "no clause" — empty where-clauses are still expressed by
626
+ // leaving `GetDocumentsRequestV1.where_clauses` empty. It exists
627
+ // for clauses that legitimately compare against `null` (e.g.
628
+ // queries on schema-nullable index entries from the v0 wire that
629
+ // round-trip through the v1 surface).
630
+ message DocumentFieldValue {
631
+ // Recursive list — operand for `IN` (candidate values) and
632
+ // `BETWEEN*` (exactly 2 values `[lower, upper]`). Nested
633
+ // `list` is structurally allowed but every supported document
634
+ // index value-type is currently scalar, so callers should not
635
+ // need to nest.
636
+ message ValueList { repeated DocumentFieldValue values = 1; }
637
+
638
+ oneof variant {
639
+ bool bool_value = 1;
640
+ sint64 int64_value = 2 [jstype = JS_STRING];
641
+ uint64 uint64_value = 3 [jstype = JS_STRING];
642
+ double double_value = 4;
643
+ string text = 5;
644
+ bytes bytes_value = 6;
645
+ ValueList list = 7;
646
+ // `bool` payload is a placeholder — only the discriminant
647
+ // matters. Picking the variant means "this operand is null";
648
+ // the bool value itself is ignored on the server.
649
+ bool null_value = 8;
650
+ }
651
+ }
652
+
653
+ // Single `field <op> value` clause. The server reassembles a
654
+ // `Vec<WhereClause>` from the request's `where_clauses` field,
655
+ // runs the same `WhereClause::group_clauses` validator (rejects
656
+ // duplicate / conflicting same-field clauses) and the same
657
+ // `> AND <` → `between*` canonicalizer the CBOR-shaped path uses,
658
+ // then hands the structured clauses to the executor. Wire
659
+ // semantics are identical to v0's CBOR `[field, op, value]`
660
+ // triples — only the envelope differs.
661
+ message WhereClause {
662
+ string field = 1;
663
+ WhereOperator operator = 2;
664
+ DocumentFieldValue value = 3;
665
+ }
666
+
667
+ // Per-group aggregate operand for the left side of a
668
+ // `HavingClause`. Only the per-group aggregates live here:
669
+ // `MIN` / `MAX` / `TOP` / `BOTTOM` are **cross-group** ranking
670
+ // primitives and appear on the right side via `HavingRanking`.
671
+ //
672
+ // **Field semantics by function**:
673
+ // - `COUNT`: empty `field` means `COUNT(*)` (group cardinality);
674
+ // non-empty `field` means `COUNT(field)` (count of non-null
675
+ // values of `field` in the group).
676
+ // - `SUM` / `AVG`: `field` is required.
677
+ message HavingAggregate {
678
+ enum Function {
679
+ COUNT = 0;
680
+ SUM = 1;
681
+ AVG = 2;
682
+ }
683
+ Function function = 1;
684
+ // Required for every function except `COUNT`; for `COUNT` an
685
+ // empty `field` means `COUNT(*)`.
686
+ string field = 2;
687
+ }
688
+
689
+ // Cross-group ranking primitive on the right side of a
690
+ // `HavingClause`. The ranking is computed over the set of
691
+ // group-aggregate results (one per `GROUP BY` row), so
692
+ // `HAVING COUNT(*) EQ MAX` selects groups whose count equals
693
+ // the maximum count across all groups, and
694
+ // `HAVING COUNT(*) IN TOP(5)` selects groups whose count is
695
+ // among the five largest. Concise way to express top-N /
696
+ // bottom-N selection without window functions or
697
+ // `ORDER BY` + `LIMIT`.
698
+ //
699
+ // **Operator compatibility**:
700
+ // - Scalar operators (`=`, `!=`, `<`, `<=`, `>`, `>=`) work
701
+ // with `MIN` / `MAX`. `TOP` / `BOTTOM` with scalar operators
702
+ // only make sense when `n=1` (the single largest / smallest);
703
+ // evaluation rejects other combinations as ambiguous.
704
+ // - `IN` works with `TOP(n)` / `BOTTOM(n)` for set membership.
705
+ // - `BETWEEN*` doesn't compose meaningfully with rankings and
706
+ // is rejected at evaluation time.
707
+ message HavingRanking {
708
+ enum Kind {
709
+ MIN = 0;
710
+ MAX = 1;
711
+ TOP = 2;
712
+ BOTTOM = 3;
713
+ }
714
+ Kind kind = 1;
715
+ // N-th rank for `TOP` / `BOTTOM` (1-indexed: `n=1` is the
716
+ // single largest / smallest). Required for those two kinds;
717
+ // must be unset for `MIN` / `MAX`. The wire allows setting
718
+ // it on `MIN` / `MAX` for forward compatibility, but
719
+ // evaluation rejects it as a malformed ranking.
720
+ optional uint64 n = 2 [jstype = JS_STRING];
721
+ }
722
+
723
+ // Single `HAVING <aggregate> <op> <right>` clause. Multiple
724
+ // entries in `GetDocumentsRequestV1.having` combine with
725
+ // implicit AND — same semantics as multiple `where_clauses`
726
+ // entries. `HAVING COUNT(*) > 5 AND SUM(amount) > 100` is two
727
+ // `HavingClause` rows, not a tree.
728
+ //
729
+ // The operator set mirrors `WhereOperator` minus `STARTS_WITH`
730
+ // (prefix matching has no natural meaning against a scalar
731
+ // aggregate result, even a string-typed one). `BETWEEN*` and
732
+ // `IN` operand semantics match `WhereOperator`: `BETWEEN*`
733
+ // expects a 2-element `DocumentFieldValue.list` carrying
734
+ // `[lower, upper]`, and `IN` expects a `list` of candidate
735
+ // values (or a ranking set via `right.ranking`).
736
+ //
737
+ // The `right` oneof carries either a concrete
738
+ // `DocumentFieldValue` (literal comparison target) or a
739
+ // `HavingRanking` (cross-group reference). Exactly one is set;
740
+ // the wire rejects an unset `right`.
741
+ message HavingClause {
742
+ enum Operator {
743
+ EQUAL = 0;
744
+ NOT_EQUAL = 1;
745
+ GREATER_THAN = 2;
746
+ GREATER_THAN_OR_EQUALS = 3;
747
+ LESS_THAN = 4;
748
+ LESS_THAN_OR_EQUALS = 5;
749
+ BETWEEN = 6;
750
+ BETWEEN_EXCLUDE_BOUNDS = 7;
751
+ BETWEEN_EXCLUDE_LEFT = 8;
752
+ BETWEEN_EXCLUDE_RIGHT = 9;
753
+ IN = 10;
754
+ }
755
+ HavingAggregate aggregate = 1;
756
+ Operator operator = 2;
757
+ oneof right {
758
+ DocumentFieldValue value = 3;
759
+ HavingRanking ranking = 4;
760
+ }
761
+ }
762
+
763
+ // Single `ORDER BY field <direction>` clause. Multi-field
764
+ // ordering is expressed by repeating this message at the
765
+ // request level (`repeated OrderClause order_by = 4`), matching
766
+ // SQL's `ORDER BY a ASC, b DESC` shape.
767
+ // Single ORDER BY entry. Multi-entry ordering is expressed by
768
+ // repeating this message at the request level.
769
+ //
770
+ // The `target` oneof carries either a plain field name
771
+ // (`ORDER BY field`) or an aggregate function applied to a
772
+ // field (`ORDER BY COUNT(*)`, `ORDER BY SUM(amount)`) — the
773
+ // latter sorts per-group result rows produced by `GROUP BY`,
774
+ // useful with `LIMIT` for top-N / bottom-N selection at the
775
+ // routing layer (overlapping `HavingRanking::Top` / `Bottom`
776
+ // but more general because the ranking field can be any
777
+ // aggregate, not just count).
778
+ //
779
+ // **Aggregate target currently rejected** with
780
+ // `Unsupported("ORDER BY on aggregate is not yet implemented")`.
781
+ // The wire surface is shipped now so callers can encode the
782
+ // shape ahead of server support landing.
783
+ message OrderClause {
784
+ oneof target {
785
+ // Plain field name. Today's evaluated form.
786
+ string field = 1;
787
+ // Aggregate function applied to a field, sorted by the
788
+ // per-group result. `function = DOCUMENTS` is invalid
789
+ // here — DOCUMENTS isn't an aggregate.
790
+ HavingAggregate aggregate = 3;
791
+ }
792
+ bool ascending = 2;
793
+ }
794
+
560
795
  message GetDocumentsRequestV0 {
561
796
  bytes data_contract_id =
562
797
  1; // The ID of the data contract containing the documents
@@ -572,7 +807,281 @@ message GetDocumentsRequest {
572
807
  }
573
808
  bool prove = 8; // Flag to request a proof as the response
574
809
  }
575
- oneof version { GetDocumentsRequestV0 v0 = 1; }
810
+
811
+ // SQL-shaped successor to v0 that unifies `getDocuments` and
812
+ // `getDocumentsCount` under a single request type with a typed
813
+ // `select` projection and optional `group_by` / `having` clauses.
814
+ //
815
+ // Mode is determined by `select` × `group_by` × `having`:
816
+ //
817
+ // * `select = DOCUMENTS, group_by = []`: return matched documents
818
+ // (identical semantics to v0).
819
+ // * `select = COUNT, group_by = []`: return a single aggregate
820
+ // count. With an `In` clause the server fans out per-In via
821
+ // `query_aggregate_count` and sums (O(|In| × log n), see
822
+ // `RangeNoProof`'s compound-summed path); with a range clause
823
+ // it uses `AggregateCountOnRange`.
824
+ // * `select = COUNT, group_by = [<field>]`: return per-group
825
+ // `CountEntry` rows. Only supported when the grouping field
826
+ // matches an `In`-constrained or range-constrained where clause;
827
+ // other shapes return `Unsupported` (see supported-shape table
828
+ // below).
829
+ //
830
+ // `having` is wire-reserved for a future server capability. Any
831
+ // non-empty `having` list currently returns
832
+ // `Unsupported("HAVING clause is not yet implemented")`
833
+ // regardless of `select` / `group_by`. The wire shape is
834
+ // `repeated WhereClause` so when execution lands the surface is
835
+ // already typed end-to-end and callers don't need to re-encode.
836
+ //
837
+ // **Supported shapes** (everything else rejects with a typed
838
+ // `QuerySyntaxError::Unsupported` so callers can detect un-wired
839
+ // capabilities without parsing prose). Bullets are kept
840
+ // single-line so the generated Rust doc comments don't trip
841
+ // rustdoc's `list_item_without_indent` lint on continuation
842
+ // lines.
843
+ //
844
+ // `select=DOCUMENTS, group_by=[]`: any where shape v0 supports.
845
+ //
846
+ // `select=COUNT, group_by=[]`:
847
+ // - empty where → `documentsCountable: true` doctype.
848
+ // - `==` only → `countable: true` index covering the fields.
849
+ // - one `In` → `countable: true` index covering the fields (per-In aggregate fan-out).
850
+ // - one range → `rangeCountable: true` index.
851
+ // - one `In` + one range → `rangeCountable: true` compound index (per-In aggregate fan-out on no-proof; rejected on prove because the aggregate proof primitive can't fork).
852
+ //
853
+ // `select=COUNT, group_by=[g]`:
854
+ // - g is the In clause's field → `countable: true` index, grouped by g (PerInValue on no-proof, CountTree element proof per In branch on prove).
855
+ // - g is the range clause's field → `rangeCountable: true` index, grouped by g (RangeDistinct on no-proof, distinct range proof on prove).
856
+ //
857
+ // `select=COUNT, group_by=[a, b]`:
858
+ // - a is the In field AND b is the range field, in that order → existing compound distinct shape; entries carry both `in_key` (= a's value) and `key` (= b's value).
859
+ //
860
+ // **Rejected shapes** (return `Unsupported`):
861
+ // - any non-empty `having` (always — pending future server capability).
862
+ // - `select=DOCUMENTS` with non-empty `group_by`.
863
+ // - `select=COUNT` with `group_by` on a field that is not constrained by an `In` or range where clause.
864
+ // - `select=COUNT` with `group_by.len() > 2`.
865
+ // - `select=COUNT` with 2-field `group_by` that does not match the `(in_field, range_field)` shape above.
866
+ //
867
+ // **Absent-from-tree branches on `In`-grouped queries**: when
868
+ // `select=COUNT, group_by=[in_field]` and an `In` value has no
869
+ // matching documents, the underlying merk index has nothing to
870
+ // emit (zero-count branches aren't materialized as CountTree
871
+ // elements), so the wire `CountEntries.entries` list contains
872
+ // only the In values that exist.
873
+ //
874
+ // The SDK's proof decoder surfaces this by **omission**, not by
875
+ // a sentinel `count` value: the current point-lookup path query
876
+ // doesn't set `absence_proofs_for_non_existing_searched_keys:
877
+ // true`, so grovedb's `verify_query` silently drops absent-Key
878
+ // branches from the verified elements stream. The drive-side
879
+ // verifier (`verify_point_lookup_count_proof`) therefore emits
880
+ // one `SplitCountEntry` per **present** In branch and the SDK
881
+ // wraps those into `CountEntry`. Callers that need to detect
882
+ // "queried but absent" diff their request's In array against
883
+ // the returned entries by `key` (each entry's `key` is the
884
+ // serialized In value, recoverable via
885
+ // `document_type.serialize_value_for_key(in_field, v, …)`).
886
+ // `SplitCountEntry::count`'s `Option<u64>` and the `None`
887
+ // variant exist for a future absence-proof variant; today the
888
+ // wire `CountEntry.count` is plain `uint64`.
889
+ //
890
+ // For range-grouped queries the walker only emits keys that
891
+ // exist in the index, which IS SQL-conformant; no equivalent
892
+ // reconstruction step because the range itself is unbounded and
893
+ // the caller has no explicit "expected keys" list to compare
894
+ // against.
895
+ message GetDocumentsRequestV1 {
896
+ // Projection over the matched row set. `(function, field)`
897
+ // pair — analogous to `HavingAggregate`'s shape but with an
898
+ // additional `DOCUMENTS` variant for the row-fetch path.
899
+ //
900
+ // Determines what the response carries:
901
+ // - `DOCUMENTS`: `ResultData.documents` (matched rows;
902
+ // `field` must be empty).
903
+ // - `COUNT`: `ResultData.counts` carrying row counts. Empty
904
+ // `field` is `COUNT(*)` (group cardinality); non-empty is
905
+ // `COUNT(field)` (non-null `field` values).
906
+ // - `SUM` / `AVG`: `ResultData` carrying numeric
907
+ // aggregate(s); `field` is required and must be a
908
+ // numeric-typed schema field.
909
+ //
910
+ // Single-aggregate vs per-group response shape comes from
911
+ // `group_by` (empty → single, non-empty → per-group entries) —
912
+ // same rule as today's `COUNT` routing.
913
+ //
914
+ // **Server capability today**: `DOCUMENTS`, `COUNT(*)` (empty
915
+ // `field`), `SUM(<field>)`, and `AVG(<field>)` are evaluated
916
+ // end-to-end (no-proof and proof paths route through the drive
917
+ // count / sum / average dispatchers). `COUNT(<field>)` (non-null
918
+ // value counting on a specific field), `MIN(<field>)`, and
919
+ // `MAX(<field>)` are wire-stable but rejected at routing time
920
+ // with `Unsupported("SELECT … is not yet implemented")` so the
921
+ // surface is shipped first and execution lands later without
922
+ // another version bump. MIN/MAX in particular wait on a grovedb
923
+ // primitive — there's no min/max aggregate on count or sum
924
+ // trees today, and the order-by-then-LIMIT-1 emulation has the
925
+ // wrong proof shape for the cryptographic verifier.
926
+ message Select {
927
+ enum Function {
928
+ DOCUMENTS = 0;
929
+ COUNT = 1;
930
+ SUM = 2;
931
+ AVG = 3;
932
+ // Per-group MIN / MAX — `SELECT MIN(field) GROUP BY
933
+ // category` returns the smallest `field` value in each
934
+ // category. Semantically distinct from
935
+ // `HavingRanking::Min` / `Max` (which are cross-group
936
+ // meta-aggregates over group results). MIN/MAX here
937
+ // operate over the row values within each group, the
938
+ // same way `SUM` and `AVG` do.
939
+ MIN = 4;
940
+ MAX = 5;
941
+ }
942
+ Function function = 1;
943
+ // Field the projection function is applied to. See the
944
+ // message-level docstring for the per-function requirement
945
+ // (empty for `DOCUMENTS`, optional for `COUNT`, required
946
+ // for `SUM` / `AVG` / `MIN` / `MAX`).
947
+ string field = 2;
948
+ }
949
+
950
+ bytes data_contract_id = 1; // The data contract owning the documents
951
+ string document_type = 2; // Document type within the contract
952
+ // Structured where clauses. Empty list = no `WHERE` filter
953
+ // (return all matching rows under the document type / contract).
954
+ // See top-level `WhereClause` for shape semantics.
955
+ repeated WhereClause where_clauses = 3;
956
+ // Structured order_by clauses. Empty list = no explicit
957
+ // ordering (server applies the index's natural order). Multiple
958
+ // entries express SQL's `ORDER BY a ASC, b DESC, …` shape; the
959
+ // first entry's direction also governs split-mode entry
960
+ // ordering on `select=COUNT` paths.
961
+ repeated OrderClause order_by = 4;
962
+ // Maximum number of rows to return.
963
+ //
964
+ // **Wire semantics on the `optional uint32` field**: `None`
965
+ // (unset) requests the server's default; `Some(N)` with `N > 0`
966
+ // requests an explicit cap of `N`. `Some(0)` is **rejected with
967
+ // `InvalidLimit` across every SELECT mode** — zero-cap is
968
+ // structurally meaningless and the legacy v0 `uint32`-with-0-as-
969
+ // sentinel mapping doesn't extend to `optional uint32` (the
970
+ // whole point of switching is that `None` carries "unset"
971
+ // explicitly). Callers must send `None` for "use server default."
972
+ //
973
+ // Per-mode behavior of `Some(N > 0)`:
974
+ // - `select=DOCUMENTS`: matched-document cap (same as v0).
975
+ // - `select=COUNT, group_by=[]`: **rejected with `InvalidLimit`
976
+ // when set**. Aggregate count is a single row by construction
977
+ // — a limit would either be redundant (≥ 1) or silently
978
+ // mislead callers if the dispatcher's per-In fan-out honored
979
+ // it and returned a partial sum disguised as a total. Omit
980
+ // `limit` for aggregate count.
981
+ // - `select=COUNT, group_by=[in_field]`: **rejected with
982
+ // `InvalidLimit` when set**. The In array is already capped
983
+ // at 100 entries by `WhereClause::in_values()`, so the
984
+ // result is bounded by construction; a separate `limit`
985
+ // would either be redundant or silently truncate the proof
986
+ // to fewer In branches than the caller asked for (the
987
+ // PointLookupProof shape can't represent a partial-In
988
+ // selection in its `SizedQuery`). Narrow the In array
989
+ // directly to reduce the result set.
990
+ // - `select=COUNT, group_by=[range_field]`: entries cap on
991
+ // the distinct-range walk.
992
+ // - `select=COUNT, group_by=[in_field, range_field]`: global
993
+ // cap over the emitted `(in_key, key)` lex stream — NOT
994
+ // per-In-branch. The compound walk pushes one
995
+ // `SizedQuery::limit` over the combined tuple stream, so a
996
+ // request with `|In| = 3` and `limit = 5` returns at most
997
+ // 5 entries total across all In branches (ordered by
998
+ // `(in_key, key)`, direction from the first `order_by`
999
+ // clause).
1000
+ // Both range-grouped variants share the same validate-don't-
1001
+ // clamp policy on prove paths — `limit > max_query_limit`
1002
+ // returns `InvalidLimit` rather than silent clamping (see
1003
+ // `RangeDistinctProof`'s contract; unset falls back to the
1004
+ // SDK-shared `DEFAULT_QUERY_LIMIT` compile-time constant so
1005
+ // proof bytes are deterministic across operators).
1006
+ optional uint32 limit = 5;
1007
+
1008
+ // Pagination cursor. Valid only for `select=DOCUMENTS`. The
1009
+ // count surface (`select=COUNT`) rejects cursors entirely:
1010
+ // aggregate counts have no concept of "start," and per-group
1011
+ // entry paginators would need a new merk walk that doesn't
1012
+ // exist yet — callers paginate counts by narrowing the
1013
+ // where-clause range itself instead.
1014
+ oneof start {
1015
+ bytes start_after = 6;
1016
+ bytes start_at = 7;
1017
+ }
1018
+
1019
+ bool prove = 8; // Request a grovedb proof instead of raw rows
1020
+
1021
+ // SQL `SELECT` projection list. Multiple entries express
1022
+ // `SELECT f1(a), f2(b), …` — one row per group carrying a
1023
+ // parallel list of aggregate values in the response.
1024
+ //
1025
+ // Empty list defaults to a single `documents()` projection
1026
+ // for v0-style document fetch — callers that don't opt into
1027
+ // the SQL-shaped surface get plain row semantics.
1028
+ //
1029
+ // **Currently rejected when `selects.len() > 1`** with
1030
+ // `Unsupported("multi-projection SELECT is not yet
1031
+ // implemented")`. The single-projection cases `DOCUMENTS`,
1032
+ // `COUNT(*)`, `SUM(<field>)`, and `AVG(<field>)` are evaluated
1033
+ // end-to-end today and route through the drive count / sum /
1034
+ // average dispatchers (see the `GetDocumentsResponseV1`
1035
+ // docstring for the wire-shape table). `COUNT(<field>)`,
1036
+ // `MIN(<field>)`, and `MAX(<field>)` remain rejected at the
1037
+ // per-function gate — see the `Select` message-level docstring
1038
+ // for the rationale (no grovedb min/max primitive; COUNT(field)
1039
+ // needs a non-null counter walk that doesn't exist yet). When
1040
+ // multi-projection lands the response shape gains a parallel
1041
+ // `repeated AggregateValue values` field, so caller code
1042
+ // structured around `repeated Select` doesn't need to be
1043
+ // rewritten when it does.
1044
+ repeated Select selects = 9;
1045
+
1046
+ // SQL `GROUP BY` field names, in left-to-right order. Empty =
1047
+ // no explicit grouping (aggregate for `select=COUNT`). See the
1048
+ // message-level docstring for the supported-shape table.
1049
+ repeated string group_by = 10;
1050
+
1051
+ // SQL `HAVING` clauses — aggregate filters that apply to the
1052
+ // grouped rows produced by `select=COUNT, group_by=[…]`. The
1053
+ // wire shape is `HavingClause`, not `WhereClause`, because
1054
+ // HAVING evaluates against per-group aggregates
1055
+ // (`COUNT`/`SUM`/`AVG`/`MIN`/`MAX`/`TOP`/`BOTTOM`) rather than
1056
+ // row field values. Multiple entries combine with implicit
1057
+ // AND. See `HavingClause` / `HavingAggregate` for the
1058
+ // operator and aggregate-function catalogs.
1059
+ //
1060
+ // **Always rejected when non-empty** today with
1061
+ // `Unsupported("HAVING clause is not yet implemented")`. The
1062
+ // wire shape is shipped now so the future server capability
1063
+ // can land without another version bump — and so callers can
1064
+ // construct full `HAVING COUNT(*) > 5 AND SUM(amount) > 100`
1065
+ // requests in their builders even before the server evaluates
1066
+ // them.
1067
+ repeated HavingClause having = 11;
1068
+
1069
+ // Row-based pagination offset, on top of the cursor-based
1070
+ // `start_after` / `start_at` pagination. `OFFSET N` skips the
1071
+ // first `N` matching rows before applying `limit`. Currently
1072
+ // **always rejected when non-`None`** with
1073
+ // `Unsupported("OFFSET pagination is not yet implemented")`
1074
+ // — the wire surface is shipped now so callers can encode it
1075
+ // ahead of server support landing without another version
1076
+ // bump. Cursor pagination via `start_after` / `start_at`
1077
+ // remains the supported way to page through results.
1078
+ optional uint32 offset = 12;
1079
+ }
1080
+
1081
+ oneof version {
1082
+ GetDocumentsRequestV0 v0 = 1;
1083
+ GetDocumentsRequestV1 v1 = 2;
1084
+ }
576
1085
  }
577
1086
 
578
1087
  message GetDocumentsResponse {
@@ -588,7 +1097,207 @@ message GetDocumentsResponse {
588
1097
  }
589
1098
  ResponseMetadata metadata = 3; // Metadata about the blockchain state
590
1099
  }
591
- oneof version { GetDocumentsResponseV0 v0 = 1; }
1100
+
1101
+ // v1 response. Two-variant outer `oneof` mirrors every other
1102
+ // `Get*Response`: a non-proof result at position 1, the proof at
1103
+ // position 2. The non-proof result is itself a `oneof` because
1104
+ // a single v1 request can produce either matched documents (for
1105
+ // `select=DOCUMENTS`) or count results (for `select=COUNT`) —
1106
+ // wrapping them in an inner `ResultData` keeps the outer shape
1107
+ // canonical without flattening to a three-variant oneof.
1108
+ //
1109
+ // Wire shape by `request.select` × `group_by` × `prove`:
1110
+ // - `select=DOCUMENTS` (no prove) → `result.data.documents`.
1111
+ // - `select=COUNT, group_by=[]` (no prove) → `result.data.counts.aggregate_count`.
1112
+ // - `select=COUNT, group_by=[…]` (no prove) → `result.data.counts.entries`.
1113
+ // - `select=SUM, group_by=[]` (no prove) → `result.data.sums.aggregate_sum`.
1114
+ // - `select=SUM, group_by=[…]` (no prove) → `result.data.sums.entries`.
1115
+ // - `select=AVG, group_by=[]` (no prove) → `result.data.averages.aggregate_average`.
1116
+ // - `select=AVG, group_by=[…]` (no prove) → `result.data.averages.entries`.
1117
+ // - any select (prove) → `result.proof`.
1118
+ //
1119
+ // **SUM / AVG status**: all four shapes above are wired
1120
+ // end-to-end on the drive dispatcher (no-proof and proof paths
1121
+ // both terminate at grovedb's aggregate-sum / sum-tree-walk
1122
+ // primitives, not at a `NotYetImplemented` stub). The four
1123
+ // resolved-mode tables (`compute_aggregate_mode_and_check_limit_v0`,
1124
+ // `detect_count_mode`, `detect_sum_mode`, AVG mirror) decide which
1125
+ // executor to run from the (mode × range × group_by × prove)
1126
+ // tuple; the executors compose count and sum walks for AVG so
1127
+ // there's no separate average primitive on the grovedb side.
1128
+ // Routing details live in
1129
+ // `packages/rs-drive/src/query/drive_document_{sum,average}_query/`.
1130
+ //
1131
+ // `CountResults` / `CountEntry` / `CountEntries` are nested in
1132
+ // `GetDocumentsResponseV1` rather than re-exported from a
1133
+ // top-level message — the v0 `getDocumentsCount` endpoint that
1134
+ // previously owned them has been removed in this version (it
1135
+ // shipped briefly in #3623 and never had stable callers); v1 is
1136
+ // the single home for the count wire types.
1137
+ message GetDocumentsResponseV1 {
1138
+ // Documents result variant — matches the v0 `Documents`
1139
+ // message field-for-field (kept distinct so v1 doesn't reach
1140
+ // into v0's namespace once v0 is eventually retired).
1141
+ message Documents {
1142
+ repeated bytes documents = 1;
1143
+ }
1144
+
1145
+ // A single per-key entry. Carries `in_key` for compound
1146
+ // queries (`In` on a prefix property of a `range_countable`
1147
+ // index plus a range clause on the terminator) where each
1148
+ // entry is keyed by both the In-fork's prefix value (`in_key`)
1149
+ // and the terminator value (`key`). Cross-fork aggregation is
1150
+ // intentionally NOT done server-side — callers get the
1151
+ // unmerged per-`(in_key, key)` view and can reduce client-side
1152
+ // if they want a flat histogram.
1153
+ message CountEntry {
1154
+ optional bytes in_key = 1;
1155
+ bytes key = 2;
1156
+ // `jstype = JS_STRING` so JS/Web clients receive a string
1157
+ // and don't round counts > 2^53−1 to the nearest
1158
+ // representable Number.
1159
+ uint64 count = 3 [jstype = JS_STRING];
1160
+ }
1161
+
1162
+ message CountEntries {
1163
+ repeated CountEntry entries = 1;
1164
+ }
1165
+
1166
+ // Non-proof count result. Shape is mode-dependent and made
1167
+ // explicit on the wire via the inner `variant` oneof:
1168
+ // * `aggregate_count`: `select=COUNT, group_by=[]` —
1169
+ // single u64 with no per-key breakdown.
1170
+ // * `entries`: `select=COUNT, group_by=[…]` — one
1171
+ // CountEntry per distinct group, in serialized-key order
1172
+ // (subject to the first `order_by` clause's direction and
1173
+ // `limit`).
1174
+ message CountResults {
1175
+ oneof variant {
1176
+ uint64 aggregate_count = 1 [jstype = JS_STRING];
1177
+ CountEntries entries = 2;
1178
+ }
1179
+ }
1180
+
1181
+ // Sum-side analog of `CountEntry` — one per matched key for
1182
+ // `select=SUM, group_by=[...]` queries. `in_key` carries the
1183
+ // outer prefix value for compound `(In, range)` shapes; `key`
1184
+ // carries the terminator value. `sum` is signed because grovedb's
1185
+ // SumTree values are `i64` and sums can in principle be negative
1186
+ // (deliberate i64-overflow signaling — see the sum-tree book
1187
+ // chapter's "Signed `i64` overflow" note). For tip-jar-style
1188
+ // non-negative sums this stays >= 0 in practice.
1189
+ message SumEntry {
1190
+ optional bytes in_key = 1;
1191
+ bytes key = 2;
1192
+ // `jstype = JS_STRING` so JS/Web clients receive a string
1193
+ // and don't round large sums to the nearest Number.
1194
+ sint64 sum = 3 [jstype = JS_STRING];
1195
+ }
1196
+
1197
+ message SumEntries {
1198
+ repeated SumEntry entries = 1;
1199
+ }
1200
+
1201
+ // Non-proof sum result. Same shape as `CountResults` for the
1202
+ // sum surface — the variants mirror count's:
1203
+ // * `aggregate_sum`: `select=SUM, group_by=[]` — single signed
1204
+ // sum with no per-key breakdown.
1205
+ // * `entries`: `select=SUM, group_by=[…]` — one SumEntry per
1206
+ // distinct group.
1207
+ message SumResults {
1208
+ oneof variant {
1209
+ sint64 aggregate_sum = 1 [jstype = JS_STRING];
1210
+ SumEntries entries = 2;
1211
+ }
1212
+ }
1213
+
1214
+ // Average-side analog of `SumEntry` — one per matched key for
1215
+ // `select=AVG, group_by=[…]` queries. Each entry carries BOTH
1216
+ // the count and the sum for its group; the client divides
1217
+ // `sum / count` to compute the actual average.
1218
+ //
1219
+ // Why no `average` field on the wire? Returning the (count, sum)
1220
+ // pair preserves full precision and lets the client pick the
1221
+ // representation it wants (integer-truncated division, floating-
1222
+ // point, decimal). Returning a single pre-computed `average`
1223
+ // would force the server to choose, and any choice loses
1224
+ // information for callers that wanted a different one.
1225
+ //
1226
+ // This shape is produced by grovedb's `AggregateCountAndSumOnRange`
1227
+ // primitive (one root-hash-committed traversal returning both
1228
+ // metrics) which lands as part of grovedb PR 670 alongside the
1229
+ // PCPS (`ProvableCountProvableSumTree`) tree element.
1230
+ message AverageEntry {
1231
+ optional bytes in_key = 1;
1232
+ bytes key = 2;
1233
+ // `jstype = JS_STRING` on both fields so JS/Web clients receive
1234
+ // strings and don't lose precision on counts/sums exceeding
1235
+ // `Number.MAX_SAFE_INTEGER`.
1236
+ uint64 count = 3 [jstype = JS_STRING];
1237
+ sint64 sum = 4 [jstype = JS_STRING];
1238
+ }
1239
+
1240
+ message AverageEntries {
1241
+ repeated AverageEntry entries = 1;
1242
+ }
1243
+
1244
+ // Aggregate average across all matched documents (no group_by).
1245
+ // Same `(count, sum)` shape as a single entry — the client
1246
+ // computes `avg = sum / count` itself.
1247
+ message AverageAggregate {
1248
+ uint64 count = 1 [jstype = JS_STRING];
1249
+ sint64 sum = 2 [jstype = JS_STRING];
1250
+ }
1251
+
1252
+ // Non-proof average result. Same outer shape as
1253
+ // `CountResults` / `SumResults`; the variants mirror them:
1254
+ // * `aggregate_average`: `select=AVG, group_by=[]` — single
1255
+ // `(count, sum)` pair with no per-key breakdown.
1256
+ // * `entries`: `select=AVG, group_by=[…]` — one AverageEntry
1257
+ // per distinct group, each carrying its own `(count, sum)`.
1258
+ message AverageResults {
1259
+ oneof variant {
1260
+ AverageAggregate aggregate_average = 1;
1261
+ AverageEntries entries = 2;
1262
+ }
1263
+ }
1264
+
1265
+ // Non-proof result wrapper. The outer `oneof result` switches
1266
+ // between this and `proof`; this inner oneof switches between
1267
+ // the four non-proof shapes the v1 surface can return.
1268
+ message ResultData {
1269
+ oneof variant {
1270
+ Documents documents = 1;
1271
+ CountResults counts = 2;
1272
+ // Sum-aggregate result. Routed when the request's
1273
+ // `select.function == SUM` and the dispatcher's
1274
+ // [`DriveDocumentSumQuery`] (in rs-drive) returns a
1275
+ // non-proof variant. The schema field name (`sums`) parallels
1276
+ // `counts` above; field numbers stay 1/2/3/4 per the proto-
1277
+ // wire-stability rule (additions only, never renumbers).
1278
+ SumResults sums = 3;
1279
+ // Average-aggregate result. Routed when the request's
1280
+ // `select.function == AVG`. The dispatcher returns the
1281
+ // `(count, sum)` pair grovedb's `AggregateCountAndSumOnRange`
1282
+ // primitive produces in one root-hash-committed traversal;
1283
+ // the client divides to obtain the actual average. See
1284
+ // `book/src/drive/average-index-examples.md` for the design
1285
+ // and the grades-contract worked example.
1286
+ AverageResults averages = 4;
1287
+ }
1288
+ }
1289
+
1290
+ oneof result {
1291
+ ResultData data = 1;
1292
+ Proof proof = 2;
1293
+ }
1294
+ ResponseMetadata metadata = 3;
1295
+ }
1296
+
1297
+ oneof version {
1298
+ GetDocumentsResponseV0 v0 = 1;
1299
+ GetDocumentsResponseV1 v1 = 2;
1300
+ }
592
1301
  }
593
1302
 
594
1303
  message GetIdentityByPublicKeyHashRequest {
@@ -2057,6 +2766,11 @@ message GetRecentAddressBalanceChangesRequest {
2057
2766
  message GetRecentAddressBalanceChangesRequestV0 {
2058
2767
  uint64 start_height = 1 [ jstype = JS_STRING ];
2059
2768
  bool prove = 2;
2769
+ // When true, use exclusive start (RangeAfter) instead of inclusive start
2770
+ // (RangeFrom). The start_height key becomes a boundary node in the proof
2771
+ // rather than a result element, enabling compaction detection via
2772
+ // key_exists_as_boundary.
2773
+ bool start_height_exclusive = 3;
2060
2774
  }
2061
2775
  oneof version { GetRecentAddressBalanceChangesRequestV0 v0 = 1; }
2062
2776
  }
@@ -2125,3 +2839,211 @@ message GetRecentCompactedAddressBalanceChangesResponse {
2125
2839
  }
2126
2840
  oneof version { GetRecentCompactedAddressBalanceChangesResponseV0 v0 = 1; }
2127
2841
  }
2842
+
2843
+ // --- Shielded Pool Queries ---
2844
+
2845
+ message GetShieldedEncryptedNotesRequest {
2846
+ message GetShieldedEncryptedNotesRequestV0 {
2847
+ uint64 start_index = 1;
2848
+ uint32 count = 2;
2849
+ bool prove = 3;
2850
+ }
2851
+ oneof version { GetShieldedEncryptedNotesRequestV0 v0 = 1; }
2852
+ }
2853
+
2854
+ message GetShieldedEncryptedNotesResponse {
2855
+ message GetShieldedEncryptedNotesResponseV0 {
2856
+ message EncryptedNote {
2857
+ bytes nullifier = 1; // 32-byte nullifier (needed for Rho derivation in trial decryption)
2858
+ bytes cmx = 2; // 32-byte extracted note commitment
2859
+ bytes encrypted_note = 3; // encrypted note payload (epk + enc_ciphertext + out_ciphertext)
2860
+ }
2861
+ message EncryptedNotes {
2862
+ repeated EncryptedNote entries = 1;
2863
+ }
2864
+ oneof result {
2865
+ EncryptedNotes encrypted_notes = 1;
2866
+ Proof proof = 2;
2867
+ }
2868
+ ResponseMetadata metadata = 3;
2869
+ }
2870
+ oneof version { GetShieldedEncryptedNotesResponseV0 v0 = 1; }
2871
+ }
2872
+
2873
+ message GetShieldedAnchorsRequest {
2874
+ message GetShieldedAnchorsRequestV0 {
2875
+ bool prove = 1;
2876
+ }
2877
+ oneof version { GetShieldedAnchorsRequestV0 v0 = 1; }
2878
+ }
2879
+
2880
+ message GetShieldedAnchorsResponse {
2881
+ message GetShieldedAnchorsResponseV0 {
2882
+ message Anchors {
2883
+ repeated bytes anchors = 1;
2884
+ }
2885
+ oneof result {
2886
+ Anchors anchors = 1;
2887
+ Proof proof = 2;
2888
+ }
2889
+ ResponseMetadata metadata = 3;
2890
+ }
2891
+ oneof version { GetShieldedAnchorsResponseV0 v0 = 1; }
2892
+ }
2893
+
2894
+ message GetMostRecentShieldedAnchorRequest {
2895
+ message GetMostRecentShieldedAnchorRequestV0 {
2896
+ bool prove = 1;
2897
+ }
2898
+ oneof version { GetMostRecentShieldedAnchorRequestV0 v0 = 1; }
2899
+ }
2900
+
2901
+ message GetMostRecentShieldedAnchorResponse {
2902
+ message GetMostRecentShieldedAnchorResponseV0 {
2903
+ oneof result {
2904
+ bytes anchor = 1;
2905
+ Proof proof = 2;
2906
+ }
2907
+ ResponseMetadata metadata = 3;
2908
+ }
2909
+ oneof version { GetMostRecentShieldedAnchorResponseV0 v0 = 1; }
2910
+ }
2911
+
2912
+ message GetShieldedPoolStateRequest {
2913
+ message GetShieldedPoolStateRequestV0 {
2914
+ bool prove = 1;
2915
+ }
2916
+ oneof version { GetShieldedPoolStateRequestV0 v0 = 1; }
2917
+ }
2918
+
2919
+ message GetShieldedPoolStateResponse {
2920
+ message GetShieldedPoolStateResponseV0 {
2921
+ oneof result {
2922
+ uint64 total_balance = 1 [jstype = JS_STRING];
2923
+ Proof proof = 2;
2924
+ }
2925
+ ResponseMetadata metadata = 3;
2926
+ }
2927
+ oneof version { GetShieldedPoolStateResponseV0 v0 = 1; }
2928
+ }
2929
+
2930
+ message GetShieldedNullifiersRequest {
2931
+ message GetShieldedNullifiersRequestV0 {
2932
+ repeated bytes nullifiers = 1;
2933
+ bool prove = 2;
2934
+ }
2935
+ oneof version { GetShieldedNullifiersRequestV0 v0 = 1; }
2936
+ }
2937
+
2938
+ message GetShieldedNullifiersResponse {
2939
+ message GetShieldedNullifiersResponseV0 {
2940
+ message NullifierStatus {
2941
+ bytes nullifier = 1;
2942
+ bool is_spent = 2;
2943
+ }
2944
+ message NullifierStatuses {
2945
+ repeated NullifierStatus entries = 1;
2946
+ }
2947
+ oneof result {
2948
+ NullifierStatuses nullifier_statuses = 1;
2949
+ Proof proof = 2;
2950
+ }
2951
+ ResponseMetadata metadata = 3;
2952
+ }
2953
+ oneof version { GetShieldedNullifiersResponseV0 v0 = 1; }
2954
+ }
2955
+
2956
+ message GetNullifiersTrunkStateRequest {
2957
+ message GetNullifiersTrunkStateRequestV0 {
2958
+ uint32 pool_type = 1; // ShieldedPoolType enum value (0=credit, 1=main token, 2=individual token)
2959
+ bytes pool_identifier = 2; // 32-byte Identifier, required for pool_type=2
2960
+ }
2961
+ oneof version { GetNullifiersTrunkStateRequestV0 v0 = 1; }
2962
+ }
2963
+
2964
+ message GetNullifiersTrunkStateResponse {
2965
+ message GetNullifiersTrunkStateResponseV0 {
2966
+ Proof proof = 2;
2967
+ ResponseMetadata metadata = 3;
2968
+ }
2969
+ oneof version { GetNullifiersTrunkStateResponseV0 v0 = 1; }
2970
+ }
2971
+
2972
+ message GetNullifiersBranchStateRequest {
2973
+ message GetNullifiersBranchStateRequestV0 {
2974
+ uint32 pool_type = 1;
2975
+ bytes pool_identifier = 2;
2976
+ bytes key = 3;
2977
+ uint32 depth = 4;
2978
+ uint64 checkpoint_height = 5;
2979
+ }
2980
+ oneof version { GetNullifiersBranchStateRequestV0 v0 = 1; }
2981
+ }
2982
+
2983
+ message GetNullifiersBranchStateResponse {
2984
+ message GetNullifiersBranchStateResponseV0 {
2985
+ bytes merk_proof = 2;
2986
+ }
2987
+ oneof version { GetNullifiersBranchStateResponseV0 v0 = 1; }
2988
+ }
2989
+
2990
+ // --- Recent Nullifier Changes ---
2991
+
2992
+ message BlockNullifierChanges {
2993
+ uint64 block_height = 1 [ jstype = JS_STRING ];
2994
+ repeated bytes nullifiers = 2; // Each is 32 bytes
2995
+ }
2996
+
2997
+ message NullifierUpdateEntries {
2998
+ repeated BlockNullifierChanges block_changes = 1;
2999
+ }
3000
+
3001
+ message GetRecentNullifierChangesRequest {
3002
+ message GetRecentNullifierChangesRequestV0 {
3003
+ uint64 start_height = 1 [ jstype = JS_STRING ];
3004
+ bool prove = 2;
3005
+ }
3006
+ oneof version { GetRecentNullifierChangesRequestV0 v0 = 1; }
3007
+ }
3008
+
3009
+ message GetRecentNullifierChangesResponse {
3010
+ message GetRecentNullifierChangesResponseV0 {
3011
+ oneof result {
3012
+ NullifierUpdateEntries nullifier_update_entries = 1;
3013
+ Proof proof = 2;
3014
+ }
3015
+ ResponseMetadata metadata = 3;
3016
+ }
3017
+ oneof version { GetRecentNullifierChangesResponseV0 v0 = 1; }
3018
+ }
3019
+
3020
+ // --- Compacted Nullifier Changes ---
3021
+
3022
+ message CompactedBlockNullifierChanges {
3023
+ uint64 start_block_height = 1 [ jstype = JS_STRING ];
3024
+ uint64 end_block_height = 2 [ jstype = JS_STRING ];
3025
+ repeated bytes nullifiers = 3; // Each is 32 bytes
3026
+ }
3027
+
3028
+ message CompactedNullifierUpdateEntries {
3029
+ repeated CompactedBlockNullifierChanges compacted_block_changes = 1;
3030
+ }
3031
+
3032
+ message GetRecentCompactedNullifierChangesRequest {
3033
+ message GetRecentCompactedNullifierChangesRequestV0 {
3034
+ uint64 start_block_height = 1 [ jstype = JS_STRING ];
3035
+ bool prove = 2;
3036
+ }
3037
+ oneof version { GetRecentCompactedNullifierChangesRequestV0 v0 = 1; }
3038
+ }
3039
+
3040
+ message GetRecentCompactedNullifierChangesResponse {
3041
+ message GetRecentCompactedNullifierChangesResponseV0 {
3042
+ oneof result {
3043
+ CompactedNullifierUpdateEntries compacted_nullifier_update_entries = 1;
3044
+ Proof proof = 2;
3045
+ }
3046
+ ResponseMetadata metadata = 3;
3047
+ }
3048
+ oneof version { GetRecentCompactedNullifierChangesResponseV0 v0 = 1; }
3049
+ }