From c95a40e69977c77f167e763f07c6cc247d7cef65 Mon Sep 17 00:00:00 2001 From: thephez Date: Thu, 27 Aug 2026 14:07:06 -0400 Subject: [PATCH 1/3] feat: add shielded, document aggregate, and token balance queries Add 10 missing SDK query operations to the site: a new Shielded Queries category (poolState, encryptedNotes, anchors, mostRecentAnchor, nullifiers), document aggregates and history (count, sum, average, history), and tokens.identityBalances as Get Token Balances for Identity. All are wired into the proof toggle via their WithProof siblings. Tests: new callEvo dispatch unit suite (proof/non-proof pairs, payload construction, hex and bigint validation, proof-wrapped anchor conversion), a Map result-format case, E2E fixtures and spec tables for all operations with shape-exact validators, and validateResultWithProof support for legitimately null proof data. Sum/average E2E are skipped pending a summable-index contract. Co-Authored-By: Claude Fable 5 --- public/AI_REFERENCE.md | 347 ++++++ public/TYPE_REFERENCE.md | 82 ++ public/api-definitions.json | 312 +++++ public/documentation-check-report.txt | 2 +- public/sdk-operation-catalog.json | 1363 +++++++++++++++++++-- public/src/definitions-data.js | 11 +- public/src/operations.js | 118 +- scripts/generate_docs.py | 86 +- tests/e2e/fixtures/test-data.js | 83 ++ tests/e2e/queries/query-execution.spec.js | 273 ++++- tests/unit/operations-dispatch.test.js | 269 ++++ tests/unit/result-format.test.js | 5 + 12 files changed, 2837 insertions(+), 114 deletions(-) create mode 100644 tests/unit/operations-dispatch.test.js diff --git a/public/AI_REFERENCE.md b/public/AI_REFERENCE.md index 2692a29..1a753cf 100644 --- a/public/AI_REFERENCE.md +++ b/public/AI_REFERENCE.md @@ -534,6 +534,238 @@ const result = await sdk.documents.get( ); ``` +**Get Document Count** - `documents.count` +*Count documents matching a query. Requires an index declaring countable on the queried properties. Returns a map of group key to count; without groupBy the map has a single aggregate entry.* + +Signature: `count(query: wasm.DocumentsQuery): Promise>` + +Parameters: +- `query`: `wasm.DocumentsQuery` (required) + - Type declarations: [`wasm.DocumentsQuery`](TYPE_REFERENCE.md#type-documentsquery) + - `dataContractId`: `IdentifierLike` (required) + - Data contract identifier. + - Type declarations: [`IdentifierLike`](TYPE_REFERENCE.md#type-identifierlike) + - `documentTypeName`: `string` (required) + - Document type name. + - `where`: `DocumentWhereClause[]` (optional) + - Optional filter clauses expressed as [field, operator, value]. + - Type declarations: [`DocumentWhereClause`](TYPE_REFERENCE.md#type-documentwhereclause) + - `orderBy`: `DocumentOrderByClause[]` (optional) + - Optional sorting clauses expressed as [field, direction]. + - Type declarations: [`DocumentOrderByClause`](TYPE_REFERENCE.md#type-documentorderbyclause) + - `limit`: `number` (optional) + - Maximum number of documents to return. + - `startAfter`: `IdentifierLike` (optional) + - Exclusive document ID to resume from. + - Type declarations: [`IdentifierLike`](TYPE_REFERENCE.md#type-identifierlike) + - `startAt`: `IdentifierLike` (optional) + - Inclusive document ID to start from. + - Type declarations: [`IdentifierLike`](TYPE_REFERENCE.md#type-identifierlike) + - `groupBy`: `string[]` (optional) + - Count-query knob: SQL-shaped `GROUP BY` field list. Mirrors +the v1 wire's `group_by: repeated string` directly. Ignored +by the regular document-fetch path. + +- `[]` or omitted → aggregate count (a single row). +- `[""]` where `` matches an `In` + constraint → per-`In`-value entries (PerInValue). +- `[""]` where `` matches a range + constraint → per-distinct-value entries within the range + (RangeDistinct). +- `["", ""]` for compound `In + range` + queries → compound distinct entries. + +Entry direction comes from the first `orderBy` clause's +direction (which also drives walk order on the materialize + +prove path); set `orderBy: [["", "asc"|"desc"]]` +alongside `groupBy: [""]` to control sort. + +Returns: + +- `Promise>` + +Example: +```javascript +// Requires an index declaring countable on the queried properties. +const result = await sdk.documents.count({ + dataContractId: 'BdgTqaTAPYMyhp1WdeWdcvYSgoD7AuJ7tVCaCSXyQgyP', + documentTypeName: 'review', + where: [["resourceId", "==", "identities-names"]], + orderBy: [["resourceId", "asc"]] +}); +``` + +**Get Document Sum** - `documents.sum` +*Sum a numeric document property across documents matching a query. Requires an index declaring summable for the property. Returns a map of group key to sum.* + +Signature: `sum(query: wasm.DocumentsQuery, sumProperty: string): Promise>` + +Parameters: +- `query`: `wasm.DocumentsQuery` (required) + - Type declarations: [`wasm.DocumentsQuery`](TYPE_REFERENCE.md#type-documentsquery) + - `dataContractId`: `IdentifierLike` (required) + - Data contract identifier. + - Type declarations: [`IdentifierLike`](TYPE_REFERENCE.md#type-identifierlike) + - `documentTypeName`: `string` (required) + - Document type name. + - `where`: `DocumentWhereClause[]` (optional) + - Optional filter clauses expressed as [field, operator, value]. + - Type declarations: [`DocumentWhereClause`](TYPE_REFERENCE.md#type-documentwhereclause) + - `orderBy`: `DocumentOrderByClause[]` (optional) + - Optional sorting clauses expressed as [field, direction]. + - Type declarations: [`DocumentOrderByClause`](TYPE_REFERENCE.md#type-documentorderbyclause) + - `limit`: `number` (optional) + - Maximum number of documents to return. + - `startAfter`: `IdentifierLike` (optional) + - Exclusive document ID to resume from. + - Type declarations: [`IdentifierLike`](TYPE_REFERENCE.md#type-identifierlike) + - `startAt`: `IdentifierLike` (optional) + - Inclusive document ID to start from. + - Type declarations: [`IdentifierLike`](TYPE_REFERENCE.md#type-identifierlike) + - `groupBy`: `string[]` (optional) + - Count-query knob: SQL-shaped `GROUP BY` field list. Mirrors +the v1 wire's `group_by: repeated string` directly. Ignored +by the regular document-fetch path. + +- `[]` or omitted → aggregate count (a single row). +- `[""]` where `` matches an `In` + constraint → per-`In`-value entries (PerInValue). +- `[""]` where `` matches a range + constraint → per-distinct-value entries within the range + (RangeDistinct). +- `["", ""]` for compound `In + range` + queries → compound distinct entries. + +Entry direction comes from the first `orderBy` clause's +direction (which also drives walk order on the materialize + +prove path); set `orderBy: [["", "asc"|"desc"]]` +alongside `groupBy: [""]` to control sort. + +- `sumProperty`: `string` (required) + +Returns: + +- `Promise>` + +Example: +```javascript +// The second argument names the numeric document property to sum. +// Requires an index declaring summable for that property; no known +// testnet contract has one yet, so this call shape is illustrative +// and the server will reject it against this contract. +const result = await sdk.documents.sum({ + dataContractId: 'BdgTqaTAPYMyhp1WdeWdcvYSgoD7AuJ7tVCaCSXyQgyP', + documentTypeName: 'review', + where: [["resourceId", "==", "identities-names"]], + orderBy: [["resourceId", "asc"]] +}, 'rating'); +``` + +**Get Document Average** - `documents.average` +*Aggregate a numeric document property across documents matching a query. Requires an index declaring summable for the property. Returns a map of group key to {count, sum} - divide sum by count to obtain the average.* + +Signature: `average(query: wasm.DocumentsQuery, averageProperty: string): Promise>` + +Parameters: +- `query`: `wasm.DocumentsQuery` (required) + - Type declarations: [`wasm.DocumentsQuery`](TYPE_REFERENCE.md#type-documentsquery) + - `dataContractId`: `IdentifierLike` (required) + - Data contract identifier. + - Type declarations: [`IdentifierLike`](TYPE_REFERENCE.md#type-identifierlike) + - `documentTypeName`: `string` (required) + - Document type name. + - `where`: `DocumentWhereClause[]` (optional) + - Optional filter clauses expressed as [field, operator, value]. + - Type declarations: [`DocumentWhereClause`](TYPE_REFERENCE.md#type-documentwhereclause) + - `orderBy`: `DocumentOrderByClause[]` (optional) + - Optional sorting clauses expressed as [field, direction]. + - Type declarations: [`DocumentOrderByClause`](TYPE_REFERENCE.md#type-documentorderbyclause) + - `limit`: `number` (optional) + - Maximum number of documents to return. + - `startAfter`: `IdentifierLike` (optional) + - Exclusive document ID to resume from. + - Type declarations: [`IdentifierLike`](TYPE_REFERENCE.md#type-identifierlike) + - `startAt`: `IdentifierLike` (optional) + - Inclusive document ID to start from. + - Type declarations: [`IdentifierLike`](TYPE_REFERENCE.md#type-identifierlike) + - `groupBy`: `string[]` (optional) + - Count-query knob: SQL-shaped `GROUP BY` field list. Mirrors +the v1 wire's `group_by: repeated string` directly. Ignored +by the regular document-fetch path. + +- `[]` or omitted → aggregate count (a single row). +- `[""]` where `` matches an `In` + constraint → per-`In`-value entries (PerInValue). +- `[""]` where `` matches a range + constraint → per-distinct-value entries within the range + (RangeDistinct). +- `["", ""]` for compound `In + range` + queries → compound distinct entries. + +Entry direction comes from the first `orderBy` clause's +direction (which also drives walk order on the materialize + +prove path); set `orderBy: [["", "asc"|"desc"]]` +alongside `groupBy: [""]` to control sort. + +- `averageProperty`: `string` (required) + +Returns: + +- `Promise>` + +Example: +```javascript +// Returns { count, sum } per entry - divide sum by count for the average. +// Requires an index declaring summable for the aggregated property; no +// known testnet contract has one yet, so this call shape is illustrative +// and the server will reject it against this contract. +const result = await sdk.documents.average({ + dataContractId: 'BdgTqaTAPYMyhp1WdeWdcvYSgoD7AuJ7tVCaCSXyQgyP', + documentTypeName: 'review', + where: [["resourceId", "==", "identities-names"]], + orderBy: [["resourceId", "asc"]] +}, 'rating'); +``` + +**Get Document History** - `documents.history` +*Fetch historical revisions of a document. Requires a document type with history retention enabled (documentsKeepHistory).* + +Signature: `history(query: wasm.DocumentHistoryQuery): Promise>` + +Parameters: +- `query`: `wasm.DocumentHistoryQuery` (required) + - Type declarations: [`wasm.DocumentHistoryQuery`](TYPE_REFERENCE.md#type-documenthistoryquery) + - `dataContractId`: `IdentifierLike` (required) + - Data contract identifier. + - Type declarations: [`IdentifierLike`](TYPE_REFERENCE.md#type-identifierlike) + - `documentTypeName`: `string` (required) + - Document type name. + - `documentId`: `IdentifierLike` (required) + - Document identifier. + - Type declarations: [`IdentifierLike`](TYPE_REFERENCE.md#type-identifierlike) + - `startAtMs`: `number` (optional) + - Millisecond timestamp (exclusive) to start after. + - `limit`: `number` (optional) + - Maximum number of entries to return. + - `offset`: `number` (optional) + - Offset for pagination through the document history. + +Returns: + +- `Promise>` + - Type declarations: [`wasm.Document`](TYPE_REFERENCE.md#type-document) + +Example: +```javascript +// Requires a document type with history retention (documentsKeepHistory). +const result = await sdk.documents.history({ + dataContractId: 'BdgTqaTAPYMyhp1WdeWdcvYSgoD7AuJ7tVCaCSXyQgyP', + documentTypeName: 'review', + documentId: '2kWsepFc3PoHEee97xpJQTfbXtaDU9RHB4KdH52wk8f4', + limit: 10 +}); +``` + #### DPNS Queries **Get Primary Username** - `dpns.username` @@ -1191,6 +1423,28 @@ Example: const result = await sdk.tokens.totalSupply('Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv'); ``` +**Get Token Balances for Identity** - `tokens.identityBalances` +*Fetch one identity's balances across multiple tokens. The result map is keyed by token ID (unlike Get Identities Token Balances, which is keyed by identity ID).* + +Signature: `identityBalances(identityId: wasm.IdentifierLike, tokenIds: wasm.IdentifierLikeArray): Promise>` + +Parameters: +- `identityId`: `wasm.IdentifierLike` (required) + - Type declarations: [`wasm.IdentifierLike`](TYPE_REFERENCE.md#type-identifierlike) + +- `tokenIds`: `wasm.IdentifierLikeArray` (required) + - Type declarations: [`wasm.IdentifierLikeArray`](TYPE_REFERENCE.md#type-identifierlikearray) + +Returns: + +- `Promise>` + +Example: +```javascript +// The result map is keyed by token ID. +const result = await sdk.tokens.identityBalances('5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', ['Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv']); +``` + **Get Token Price by Contract** - `tokens.priceByContract` *Retrieve the price details for a token indexed by contract position.* @@ -1559,6 +1813,99 @@ Example: const result = await sdk.addresses.getMany(['tdash1krt0z5hrcaphyuraxmk2h2ff8nyv5fmncsgf7evf']); ``` +#### Shielded Queries + +**Get Shielded Pool State** - `shielded.poolState` +*Fetch the total balance of the shielded pool in credits. Returns nothing if the pool is empty.* + +Signature: `poolState(): Promise` + +No parameters required. + +Returns: + +- `Promise` + +Example: +```javascript +// Total shielded pool balance in credits (undefined if the pool is empty). +const result = await sdk.shielded.poolState(); +``` + +**Get Shielded Encrypted Notes** - `shielded.encryptedNotes` +*Fetch a page of encrypted shielded notes by global note index.* + +Signature: `encryptedNotes(startIndex: bigint, count: number): Promise` + +Parameters: +- `startIndex`: `bigint` (required) + +- `count`: `number` (required) + +Returns: + +- `Promise` + - Type declarations: [`wasm.ShieldedEncryptedNote`](TYPE_REFERENCE.md#type-shieldedencryptednote) + +Example: +```javascript +const result = await sdk.shielded.encryptedNotes(0n, 10); +``` + +**Get Shielded Anchors** - `shielded.anchors` +*Fetch all valid anchors for building Orchard spend proofs. The SDK returns 32-byte Uint8Array values; this page displays them hex-encoded.* + +Signature: `anchors(): Promise` + +No parameters required. + +Returns: + +- `Promise` + +Example: +```javascript +// Each anchor is a 32-byte Uint8Array. +const result = await sdk.shielded.anchors(); +``` + +**Get Most Recent Shielded Anchor** - `shielded.mostRecentAnchor` +*Fetch the most recent shielded anchor. The SDK returns a 32-byte Uint8Array, or nothing if no anchor exists yet; this page displays it hex-encoded.* + +Signature: `mostRecentAnchor(): Promise` + +No parameters required. + +Returns: + +- `Promise` + +Example: +```javascript +// 32-byte Uint8Array, or undefined if no anchor exists yet. +const result = await sdk.shielded.mostRecentAnchor(); +``` + +**Get Shielded Nullifier Statuses** - `shielded.nullifiers` +*Check whether shielded nullifiers have been spent.* + +Signature: `nullifiers(nullifiers: Uint8Array[]): Promise` + +Parameters: +- `nullifiers`: `Uint8Array[]` (required) + +Returns: + +- `Promise` + - Type declarations: [`wasm.ShieldedNullifierStatus`](TYPE_REFERENCE.md#type-shieldednullifierstatus) + +Example: +```javascript +// Each nullifier must be exactly 32 bytes. +const nullifier = new Uint8Array(32); +return await sdk.shielded.nullifiers([nullifier]); +``` + ## State Transition Operations ### Pattern diff --git a/public/TYPE_REFERENCE.md b/public/TYPE_REFERENCE.md index 7eb8247..c959b0b 100644 --- a/public/TYPE_REFERENCE.md +++ b/public/TYPE_REFERENCE.md @@ -699,6 +699,48 @@ export interface DocumentDeleteOptions { } ``` + +## `DocumentHistoryQuery` + +Source declaration: `wasm-sdk/dist/raw/wasm_sdk.d.ts` + +```typescript +export interface DocumentHistoryQuery { + /** + * Data contract identifier. + */ + dataContractId: IdentifierLike + + /** + * Document type name. + */ + documentTypeName: string; + + /** + * Document identifier. + */ + documentId: IdentifierLike + + /** + * Millisecond timestamp (exclusive) to start after. + * @default 0 + */ + startAtMs?: number; + + /** + * Maximum number of entries to return. + * @default undefined + */ + limit?: number; + + /** + * Offset for pagination through the document history. + * @default undefined + */ + offset?: number; +} +``` + ## `DocumentOrderByClause` @@ -2875,6 +2917,46 @@ export class RewardDistributionMoment { } ``` + +## `ShieldedEncryptedNote` + +Source declaration: `wasm-sdk/dist/raw/wasm_sdk.d.ts` + +```typescript +export class ShieldedEncryptedNote { + private constructor(); + free(): void; + [Symbol.dispose](): void; + static fromJSON(js: object): ShieldedEncryptedNote; + static fromObject(obj: object): ShieldedEncryptedNote; + toJSON(): any; + toObject(): any; + readonly cmx: Uint8Array; + readonly cvNet: Uint8Array; + readonly encryptedNote: Uint8Array; + readonly nullifier: Uint8Array; +} +``` + + +## `ShieldedNullifierStatus` + +Source declaration: `wasm-sdk/dist/raw/wasm_sdk.d.ts` + +```typescript +export class ShieldedNullifierStatus { + private constructor(); + free(): void; + [Symbol.dispose](): void; + static fromJSON(js: object): ShieldedNullifierStatus; + static fromObject(obj: object): ShieldedNullifierStatus; + toJSON(): any; + toObject(): any; + readonly isSpent: boolean; + readonly nullifier: Uint8Array; +} +``` + ## `StateTransitionResult` diff --git a/public/api-definitions.json b/public/api-definitions.json index d947d59..381e25b 100644 --- a/public/api-definitions.json +++ b/public/api-definitions.json @@ -485,6 +485,237 @@ } ], "sdk_method": "documents.get" + }, + "getDocumentCount": { + "label": "Get Document Count", + "description": "Count documents matching a query. Requires an index declaring countable on the queried properties. Returns a map of group key to count; without groupBy the map has a single aggregate entry.", + "inputs": [ + { + "name": "dataContractId", + "type": "text", + "label": "Data Contract ID", + "required": true, + "placeholder": "BdgTqaTAPYMyhp1WdeWdcvYSgoD7AuJ7tVCaCSXyQgyP" + }, + { + "name": "documentTypeName", + "type": "text", + "label": "Document Type", + "required": true, + "placeholder": "review" + }, + { + "name": "where", + "type": "json", + "label": "Where Clause (JSON)", + "placeholder": "[[\"resourceId\", \"==\", \"identities-names\"], [\"rating\", \"between\", [1, 5]]]" + }, + { + "name": "orderBy", + "type": "json", + "label": "Order By (JSON)", + "placeholder": "[[\"rating\", \"asc\"]]" + }, + { + "name": "groupBy", + "type": "array", + "label": "Group By", + "placeholder": "[\"rating\"]", + "help": "Optional GROUP BY field list. Omit for a single aggregate row; name a field matched by an In or range constraint for per-value rows." + }, + { + "name": "limit", + "type": "number", + "label": "Limit" + }, + { + "name": "startAfter", + "type": "text", + "label": "Start After", + "help": "Document ID or index value to resume from." + }, + { + "name": "startAt", + "type": "text", + "label": "Start At", + "help": "Explicit starting index value (JSON string)." + } + ], + "sdk_method": "documents.count" + }, + "getDocumentSum": { + "label": "Get Document Sum", + "description": "Sum a numeric document property across documents matching a query. Requires an index declaring summable for the property. Returns a map of group key to sum.", + "inputs": [ + { + "name": "dataContractId", + "type": "text", + "label": "Data Contract ID", + "required": true, + "placeholder": "BdgTqaTAPYMyhp1WdeWdcvYSgoD7AuJ7tVCaCSXyQgyP" + }, + { + "name": "documentTypeName", + "type": "text", + "label": "Document Type", + "required": true, + "placeholder": "review" + }, + { + "name": "sumProperty", + "type": "text", + "label": "Sum Property", + "required": true, + "placeholder": "rating", + "help": "Name of the numeric document property to sum." + }, + { + "name": "where", + "type": "json", + "label": "Where Clause (JSON)", + "placeholder": "[[\"resourceId\", \"==\", \"identities-names\"], [\"rating\", \"between\", [1, 5]]]" + }, + { + "name": "orderBy", + "type": "json", + "label": "Order By (JSON)", + "placeholder": "[[\"rating\", \"asc\"]]" + }, + { + "name": "groupBy", + "type": "array", + "label": "Group By", + "placeholder": "[\"rating\"]", + "help": "Optional GROUP BY field list. Omit for a single aggregate row; name a field matched by an In or range constraint for per-value rows." + }, + { + "name": "limit", + "type": "number", + "label": "Limit" + }, + { + "name": "startAfter", + "type": "text", + "label": "Start After", + "help": "Document ID or index value to resume from." + }, + { + "name": "startAt", + "type": "text", + "label": "Start At", + "help": "Explicit starting index value (JSON string)." + } + ], + "sdk_method": "documents.sum" + }, + "getDocumentAverage": { + "label": "Get Document Average", + "description": "Aggregate a numeric document property across documents matching a query. Requires an index declaring summable for the property. Returns a map of group key to {count, sum} - divide sum by count to obtain the average.", + "inputs": [ + { + "name": "dataContractId", + "type": "text", + "label": "Data Contract ID", + "required": true, + "placeholder": "BdgTqaTAPYMyhp1WdeWdcvYSgoD7AuJ7tVCaCSXyQgyP" + }, + { + "name": "documentTypeName", + "type": "text", + "label": "Document Type", + "required": true, + "placeholder": "review" + }, + { + "name": "averageProperty", + "type": "text", + "label": "Average Property", + "required": true, + "placeholder": "rating", + "help": "Name of the numeric document property to aggregate." + }, + { + "name": "where", + "type": "json", + "label": "Where Clause (JSON)", + "placeholder": "[[\"resourceId\", \"==\", \"identities-names\"], [\"rating\", \"between\", [1, 5]]]" + }, + { + "name": "orderBy", + "type": "json", + "label": "Order By (JSON)", + "placeholder": "[[\"rating\", \"asc\"]]" + }, + { + "name": "groupBy", + "type": "array", + "label": "Group By", + "placeholder": "[\"rating\"]", + "help": "Optional GROUP BY field list. Omit for a single aggregate row; name a field matched by an In or range constraint for per-value rows." + }, + { + "name": "limit", + "type": "number", + "label": "Limit" + }, + { + "name": "startAfter", + "type": "text", + "label": "Start After", + "help": "Document ID or index value to resume from." + }, + { + "name": "startAt", + "type": "text", + "label": "Start At", + "help": "Explicit starting index value (JSON string)." + } + ], + "sdk_method": "documents.average" + }, + "getDocumentHistory": { + "label": "Get Document History", + "description": "Fetch historical revisions of a document. Requires a document type with history retention enabled (documentsKeepHistory).", + "inputs": [ + { + "name": "dataContractId", + "type": "text", + "label": "Data Contract ID", + "required": true, + "placeholder": "BdgTqaTAPYMyhp1WdeWdcvYSgoD7AuJ7tVCaCSXyQgyP" + }, + { + "name": "documentTypeName", + "type": "text", + "label": "Document Type", + "required": true, + "placeholder": "review" + }, + { + "name": "documentId", + "type": "text", + "label": "Document ID", + "required": true, + "placeholder": "2kWsepFc3PoHEee97xpJQTfbXtaDU9RHB4KdH52wk8f4" + }, + { + "name": "startAtMs", + "type": "number", + "label": "Start At (ms)", + "help": "Millisecond timestamp; only revisions strictly after this time are returned." + }, + { + "name": "limit", + "type": "number", + "label": "Limit" + }, + { + "name": "offset", + "type": "number", + "label": "Offset" + } + ], + "sdk_method": "documents.history" } } }, @@ -1109,6 +1340,27 @@ ], "sdk_method": "tokens.totalSupply" }, + "getTokenBalancesForIdentity": { + "label": "Get Token Balances for Identity", + "description": "Fetch one identity's balances across multiple tokens. The result map is keyed by token ID (unlike Get Identities Token Balances, which is keyed by identity ID).", + "inputs": [ + { + "name": "identityId", + "type": "text", + "label": "Identity ID", + "required": true, + "placeholder": "5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk" + }, + { + "name": "tokenIds", + "type": "array", + "label": "Token IDs", + "required": true, + "placeholder": "[\"Hqyu7v6mLQTKBLCE6qzquZWLGZapAcnPuYGZDLGoDyxV\"]" + } + ], + "sdk_method": "tokens.identityBalances" + }, "getTokenPriceByContract": { "label": "Get Token Price by Contract", "description": "Retrieve the price details for a token indexed by contract position.", @@ -1471,6 +1723,66 @@ "sdk_method": "addresses.getMany" } } + }, + "shielded": { + "label": "Shielded Queries", + "queries": { + "getShieldedPoolState": { + "label": "Get Shielded Pool State", + "description": "Fetch the total balance of the shielded pool in credits. Returns nothing if the pool is empty.", + "inputs": [], + "sdk_method": "shielded.poolState" + }, + "getShieldedEncryptedNotes": { + "label": "Get Shielded Encrypted Notes", + "description": "Fetch a page of encrypted shielded notes by global note index.", + "inputs": [ + { + "name": "startIndex", + "type": "text", + "label": "Start Index", + "value": "0", + "help": "Global note index to start from (non-negative integer)." + }, + { + "name": "count", + "type": "number", + "label": "Count", + "required": true, + "placeholder": "10", + "help": "Number of notes to fetch." + } + ], + "sdk_method": "shielded.encryptedNotes" + }, + "getShieldedAnchors": { + "label": "Get Shielded Anchors", + "description": "Fetch all valid anchors for building Orchard spend proofs. The SDK returns 32-byte Uint8Array values; this page displays them hex-encoded.", + "inputs": [], + "sdk_method": "shielded.anchors" + }, + "getShieldedMostRecentAnchor": { + "label": "Get Most Recent Shielded Anchor", + "description": "Fetch the most recent shielded anchor. The SDK returns a 32-byte Uint8Array, or nothing if no anchor exists yet; this page displays it hex-encoded.", + "inputs": [], + "sdk_method": "shielded.mostRecentAnchor" + }, + "getShieldedNullifiers": { + "label": "Get Shielded Nullifier Statuses", + "description": "Check whether shielded nullifiers have been spent.", + "inputs": [ + { + "name": "nullifiers", + "type": "array", + "label": "Nullifiers", + "required": true, + "placeholder": "[\"a3f1c2d4e5b60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90\"]", + "help": "Hex-encoded 32-byte nullifiers (64 hex characters each; optional 0x prefix)." + } + ], + "sdk_method": "shielded.nullifiers" + } + } } }, "transitions": { diff --git a/public/documentation-check-report.txt b/public/documentation-check-report.txt index c7a2476..3f08769 100644 --- a/public/documentation-check-report.txt +++ b/public/documentation-check-report.txt @@ -1,7 +1,7 @@ ================================================================================ Evo SDK Documentation Check ================================================================================ -Timestamp: 2026-07-23T17:08:43.096666 +Timestamp: 2026-08-27T13:56:40.383516 ✅ All documentation is up to date! ================================================================================ \ No newline at end of file diff --git a/public/sdk-operation-catalog.json b/public/sdk-operation-catalog.json index 1dd9f94..7e38458 100644 --- a/public/sdk-operation-catalog.json +++ b/public/sdk-operation-catalog.json @@ -770,6 +770,374 @@ "wasm.Document" ] }, + { + "key": "getDocumentCount", + "group": "queries", + "category": "document", + "sdkMethod": "documents.count", + "label": "Get Document Count", + "description": "Count documents matching a query. Requires an index declaring countable on the queried properties. Returns a map of group key to count; without groupBy the map has a single aggregate entry.", + "disabled": null, + "signature": "count(query: wasm.DocumentsQuery): Promise>", + "parameters": [ + { + "name": "query", + "type": "wasm.DocumentsQuery", + "optional": false, + "rest": false, + "description": "", + "references": [ + "wasm.DocumentsQuery" + ], + "properties": [ + { + "name": "dataContractId", + "type": "IdentifierLike", + "optional": false, + "description": "Data contract identifier.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "documentTypeName", + "type": "string", + "optional": false, + "description": "Document type name.", + "references": [] + }, + { + "name": "where", + "type": "DocumentWhereClause[]", + "optional": true, + "description": "Optional filter clauses expressed as [field, operator, value].", + "references": [ + "DocumentWhereClause" + ] + }, + { + "name": "orderBy", + "type": "DocumentOrderByClause[]", + "optional": true, + "description": "Optional sorting clauses expressed as [field, direction].", + "references": [ + "DocumentOrderByClause" + ] + }, + { + "name": "limit", + "type": "number", + "optional": true, + "description": "Maximum number of documents to return.", + "references": [] + }, + { + "name": "startAfter", + "type": "IdentifierLike", + "optional": true, + "description": "Exclusive document ID to resume from.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "startAt", + "type": "IdentifierLike", + "optional": true, + "description": "Inclusive document ID to start from.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "groupBy", + "type": "string[]", + "optional": true, + "description": "Count-query knob: SQL-shaped `GROUP BY` field list. Mirrors\nthe v1 wire's `group_by: repeated string` directly. Ignored\nby the regular document-fetch path.\n\n- `[]` or omitted \u2192 aggregate count (a single row).\n- `[\"\"]` where `` matches an `In`\n constraint \u2192 per-`In`-value entries (PerInValue).\n- `[\"\"]` where `` matches a range\n constraint \u2192 per-distinct-value entries within the range\n (RangeDistinct).\n- `[\"\", \"\"]` for compound `In + range`\n queries \u2192 compound distinct entries.\n\nEntry direction comes from the first `orderBy` clause's\ndirection (which also drives walk order on the materialize +\nprove path); set `orderBy: [[\"\", \"asc\"|\"desc\"]]`\nalongside `groupBy: [\"\"]` to control sort.", + "references": [] + } + ] + } + ], + "returnType": "Promise>", + "references": [] + }, + { + "key": "getDocumentSum", + "group": "queries", + "category": "document", + "sdkMethod": "documents.sum", + "label": "Get Document Sum", + "description": "Sum a numeric document property across documents matching a query. Requires an index declaring summable for the property. Returns a map of group key to sum.", + "disabled": null, + "signature": "sum(query: wasm.DocumentsQuery, sumProperty: string): Promise>", + "parameters": [ + { + "name": "query", + "type": "wasm.DocumentsQuery", + "optional": false, + "rest": false, + "description": "", + "references": [ + "wasm.DocumentsQuery" + ], + "properties": [ + { + "name": "dataContractId", + "type": "IdentifierLike", + "optional": false, + "description": "Data contract identifier.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "documentTypeName", + "type": "string", + "optional": false, + "description": "Document type name.", + "references": [] + }, + { + "name": "where", + "type": "DocumentWhereClause[]", + "optional": true, + "description": "Optional filter clauses expressed as [field, operator, value].", + "references": [ + "DocumentWhereClause" + ] + }, + { + "name": "orderBy", + "type": "DocumentOrderByClause[]", + "optional": true, + "description": "Optional sorting clauses expressed as [field, direction].", + "references": [ + "DocumentOrderByClause" + ] + }, + { + "name": "limit", + "type": "number", + "optional": true, + "description": "Maximum number of documents to return.", + "references": [] + }, + { + "name": "startAfter", + "type": "IdentifierLike", + "optional": true, + "description": "Exclusive document ID to resume from.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "startAt", + "type": "IdentifierLike", + "optional": true, + "description": "Inclusive document ID to start from.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "groupBy", + "type": "string[]", + "optional": true, + "description": "Count-query knob: SQL-shaped `GROUP BY` field list. Mirrors\nthe v1 wire's `group_by: repeated string` directly. Ignored\nby the regular document-fetch path.\n\n- `[]` or omitted \u2192 aggregate count (a single row).\n- `[\"\"]` where `` matches an `In`\n constraint \u2192 per-`In`-value entries (PerInValue).\n- `[\"\"]` where `` matches a range\n constraint \u2192 per-distinct-value entries within the range\n (RangeDistinct).\n- `[\"\", \"\"]` for compound `In + range`\n queries \u2192 compound distinct entries.\n\nEntry direction comes from the first `orderBy` clause's\ndirection (which also drives walk order on the materialize +\nprove path); set `orderBy: [[\"\", \"asc\"|\"desc\"]]`\nalongside `groupBy: [\"\"]` to control sort.", + "references": [] + } + ] + }, + { + "name": "sumProperty", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise>", + "references": [] + }, + { + "key": "getDocumentAverage", + "group": "queries", + "category": "document", + "sdkMethod": "documents.average", + "label": "Get Document Average", + "description": "Aggregate a numeric document property across documents matching a query. Requires an index declaring summable for the property. Returns a map of group key to {count, sum} - divide sum by count to obtain the average.", + "disabled": null, + "signature": "average(query: wasm.DocumentsQuery, averageProperty: string): Promise>", + "parameters": [ + { + "name": "query", + "type": "wasm.DocumentsQuery", + "optional": false, + "rest": false, + "description": "", + "references": [ + "wasm.DocumentsQuery" + ], + "properties": [ + { + "name": "dataContractId", + "type": "IdentifierLike", + "optional": false, + "description": "Data contract identifier.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "documentTypeName", + "type": "string", + "optional": false, + "description": "Document type name.", + "references": [] + }, + { + "name": "where", + "type": "DocumentWhereClause[]", + "optional": true, + "description": "Optional filter clauses expressed as [field, operator, value].", + "references": [ + "DocumentWhereClause" + ] + }, + { + "name": "orderBy", + "type": "DocumentOrderByClause[]", + "optional": true, + "description": "Optional sorting clauses expressed as [field, direction].", + "references": [ + "DocumentOrderByClause" + ] + }, + { + "name": "limit", + "type": "number", + "optional": true, + "description": "Maximum number of documents to return.", + "references": [] + }, + { + "name": "startAfter", + "type": "IdentifierLike", + "optional": true, + "description": "Exclusive document ID to resume from.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "startAt", + "type": "IdentifierLike", + "optional": true, + "description": "Inclusive document ID to start from.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "groupBy", + "type": "string[]", + "optional": true, + "description": "Count-query knob: SQL-shaped `GROUP BY` field list. Mirrors\nthe v1 wire's `group_by: repeated string` directly. Ignored\nby the regular document-fetch path.\n\n- `[]` or omitted \u2192 aggregate count (a single row).\n- `[\"\"]` where `` matches an `In`\n constraint \u2192 per-`In`-value entries (PerInValue).\n- `[\"\"]` where `` matches a range\n constraint \u2192 per-distinct-value entries within the range\n (RangeDistinct).\n- `[\"\", \"\"]` for compound `In + range`\n queries \u2192 compound distinct entries.\n\nEntry direction comes from the first `orderBy` clause's\ndirection (which also drives walk order on the materialize +\nprove path); set `orderBy: [[\"\", \"asc\"|\"desc\"]]`\nalongside `groupBy: [\"\"]` to control sort.", + "references": [] + } + ] + }, + { + "name": "averageProperty", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise>", + "references": [] + }, + { + "key": "getDocumentHistory", + "group": "queries", + "category": "document", + "sdkMethod": "documents.history", + "label": "Get Document History", + "description": "Fetch historical revisions of a document. Requires a document type with history retention enabled (documentsKeepHistory).", + "disabled": null, + "signature": "history(query: wasm.DocumentHistoryQuery): Promise>", + "parameters": [ + { + "name": "query", + "type": "wasm.DocumentHistoryQuery", + "optional": false, + "rest": false, + "description": "", + "references": [ + "wasm.DocumentHistoryQuery" + ], + "properties": [ + { + "name": "dataContractId", + "type": "IdentifierLike", + "optional": false, + "description": "Data contract identifier.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "documentTypeName", + "type": "string", + "optional": false, + "description": "Document type name.", + "references": [] + }, + { + "name": "documentId", + "type": "IdentifierLike", + "optional": false, + "description": "Document identifier.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "startAtMs", + "type": "number", + "optional": true, + "description": "Millisecond timestamp (exclusive) to start after.", + "references": [] + }, + { + "name": "limit", + "type": "number", + "optional": true, + "description": "Maximum number of entries to return.", + "references": [] + }, + { + "name": "offset", + "type": "number", + "optional": true, + "description": "Offset for pagination through the document history.", + "references": [] + } + ] + } + ], + "returnType": "Promise>", + "references": [ + "wasm.Document" + ] + }, { "key": "getDpnsUsername", "group": "queries", @@ -1831,6 +2199,42 @@ "wasm.TokenTotalSupply" ] }, + { + "key": "getTokenBalancesForIdentity", + "group": "queries", + "category": "token", + "sdkMethod": "tokens.identityBalances", + "label": "Get Token Balances for Identity", + "description": "Fetch one identity's balances across multiple tokens. The result map is keyed by token ID (unlike Get Identities Token Balances, which is keyed by identity ID).", + "disabled": null, + "signature": "identityBalances(identityId: wasm.IdentifierLike, tokenIds: wasm.IdentifierLikeArray): Promise>", + "parameters": [ + { + "name": "identityId", + "type": "wasm.IdentifierLike", + "optional": false, + "rest": false, + "description": "", + "references": [ + "wasm.IdentifierLike" + ], + "properties": [] + }, + { + "name": "tokenIds", + "type": "wasm.IdentifierLikeArray", + "optional": false, + "rest": false, + "description": "", + "references": [ + "wasm.IdentifierLikeArray" + ], + "properties": [] + } + ], + "returnType": "Promise>", + "references": [] + }, { "key": "getTokenPriceByContract", "group": "queries", @@ -2404,30 +2808,128 @@ ] }, { - "key": "getPlatformAddresses", + "key": "getPlatformAddresses", + "group": "queries", + "category": "address", + "sdkMethod": "addresses.getMany", + "label": "Get Multiple Platform Addresses", + "description": "Fetch information about multiple Platform addresses.", + "disabled": null, + "signature": "getMany(addresses: wasm.PlatformAddressLikeArray): Promise>", + "parameters": [ + { + "name": "addresses", + "type": "wasm.PlatformAddressLikeArray", + "optional": false, + "rest": false, + "description": "- Array of platform addresses to query", + "references": [ + "wasm.PlatformAddressLikeArray" + ], + "properties": [] + } + ], + "returnType": "Promise>", + "references": [ + "wasm.PlatformAddressInfo" + ] + }, + { + "key": "getShieldedPoolState", + "group": "queries", + "category": "shielded", + "sdkMethod": "shielded.poolState", + "label": "Get Shielded Pool State", + "description": "Fetch the total balance of the shielded pool in credits. Returns nothing if the pool is empty.", + "disabled": null, + "signature": "poolState(): Promise", + "parameters": [], + "returnType": "Promise", + "references": [] + }, + { + "key": "getShieldedEncryptedNotes", + "group": "queries", + "category": "shielded", + "sdkMethod": "shielded.encryptedNotes", + "label": "Get Shielded Encrypted Notes", + "description": "Fetch a page of encrypted shielded notes by global note index.", + "disabled": null, + "signature": "encryptedNotes(startIndex: bigint, count: number): Promise", + "parameters": [ + { + "name": "startIndex", + "type": "bigint", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "count", + "type": "number", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise", + "references": [ + "wasm.ShieldedEncryptedNote" + ] + }, + { + "key": "getShieldedAnchors", + "group": "queries", + "category": "shielded", + "sdkMethod": "shielded.anchors", + "label": "Get Shielded Anchors", + "description": "Fetch all valid anchors for building Orchard spend proofs. The SDK returns 32-byte Uint8Array values; this page displays them hex-encoded.", + "disabled": null, + "signature": "anchors(): Promise", + "parameters": [], + "returnType": "Promise", + "references": [] + }, + { + "key": "getShieldedMostRecentAnchor", "group": "queries", - "category": "address", - "sdkMethod": "addresses.getMany", - "label": "Get Multiple Platform Addresses", - "description": "Fetch information about multiple Platform addresses.", + "category": "shielded", + "sdkMethod": "shielded.mostRecentAnchor", + "label": "Get Most Recent Shielded Anchor", + "description": "Fetch the most recent shielded anchor. The SDK returns a 32-byte Uint8Array, or nothing if no anchor exists yet; this page displays it hex-encoded.", "disabled": null, - "signature": "getMany(addresses: wasm.PlatformAddressLikeArray): Promise>", + "signature": "mostRecentAnchor(): Promise", + "parameters": [], + "returnType": "Promise", + "references": [] + }, + { + "key": "getShieldedNullifiers", + "group": "queries", + "category": "shielded", + "sdkMethod": "shielded.nullifiers", + "label": "Get Shielded Nullifier Statuses", + "description": "Check whether shielded nullifiers have been spent.", + "disabled": null, + "signature": "nullifiers(nullifiers: Uint8Array[]): Promise", "parameters": [ { - "name": "addresses", - "type": "wasm.PlatformAddressLikeArray", + "name": "nullifiers", + "type": "Uint8Array[]", "optional": false, "rest": false, - "description": "- Array of platform addresses to query", - "references": [ - "wasm.PlatformAddressLikeArray" - ], + "description": "", + "references": [], "properties": [] } ], - "returnType": "Promise>", + "returnType": "Promise", "references": [ - "wasm.PlatformAddressInfo" + "wasm.ShieldedNullifierStatus" ] }, { @@ -5719,7 +6221,250 @@ "description": "", "parameters": [ { - "name": "contractId", + "name": "contractId", + "type": "wasm.IdentifierLike", + "optional": false, + "rest": false, + "description": "", + "references": [ + "wasm.IdentifierLike" + ], + "properties": [] + } + ], + "returnType": "Promise", + "returnReferences": [ + "wasm.DataContract" + ], + "references": [ + "wasm.DataContract" + ], + "variants": [ + { + "key": "getDataContract", + "group": "queries", + "category": "dataContract" + } + ], + "source": "dist/contracts/facade.d.ts" + }, + "contracts.getHistory": { + "sdkMethod": "contracts.getHistory", + "signature": "getHistory(query: wasm.DataContractHistoryQuery): Promise>", + "description": "", + "parameters": [ + { + "name": "query", + "type": "wasm.DataContractHistoryQuery", + "optional": false, + "rest": false, + "description": "", + "references": [ + "wasm.DataContractHistoryQuery" + ], + "properties": [ + { + "name": "dataContractId", + "type": "IdentifierLike", + "optional": false, + "description": "Data contract identifier.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "limit", + "type": "number", + "optional": true, + "description": "Maximum number of entries to return.", + "references": [] + }, + { + "name": "startAtMs", + "type": "number", + "optional": true, + "description": "Millisecond timestamp (inclusive) to start from.", + "references": [] + } + ] + } + ], + "returnType": "Promise>", + "returnReferences": [ + "wasm.DataContract" + ], + "references": [ + "wasm.DataContract" + ], + "variants": [ + { + "key": "getDataContractHistory", + "group": "queries", + "category": "dataContract" + } + ], + "source": "dist/contracts/facade.d.ts" + }, + "contracts.getMany": { + "sdkMethod": "contracts.getMany", + "signature": "getMany(contractIds: wasm.IdentifierLikeArray): Promise>", + "description": "", + "parameters": [ + { + "name": "contractIds", + "type": "wasm.IdentifierLikeArray", + "optional": false, + "rest": false, + "description": "", + "references": [ + "wasm.IdentifierLikeArray" + ], + "properties": [] + } + ], + "returnType": "Promise>", + "returnReferences": [ + "wasm.DataContract" + ], + "references": [ + "wasm.DataContract" + ], + "variants": [ + { + "key": "getDataContracts", + "group": "queries", + "category": "dataContract" + } + ], + "source": "dist/contracts/facade.d.ts" + }, + "documents.query": { + "sdkMethod": "documents.query", + "signature": "query(query: wasm.DocumentsQuery): Promise>", + "description": "", + "parameters": [ + { + "name": "query", + "type": "wasm.DocumentsQuery", + "optional": false, + "rest": false, + "description": "", + "references": [ + "wasm.DocumentsQuery" + ], + "properties": [ + { + "name": "dataContractId", + "type": "IdentifierLike", + "optional": false, + "description": "Data contract identifier.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "documentTypeName", + "type": "string", + "optional": false, + "description": "Document type name.", + "references": [] + }, + { + "name": "where", + "type": "DocumentWhereClause[]", + "optional": true, + "description": "Optional filter clauses expressed as [field, operator, value].", + "references": [ + "DocumentWhereClause" + ] + }, + { + "name": "orderBy", + "type": "DocumentOrderByClause[]", + "optional": true, + "description": "Optional sorting clauses expressed as [field, direction].", + "references": [ + "DocumentOrderByClause" + ] + }, + { + "name": "limit", + "type": "number", + "optional": true, + "description": "Maximum number of documents to return.", + "references": [] + }, + { + "name": "startAfter", + "type": "IdentifierLike", + "optional": true, + "description": "Exclusive document ID to resume from.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "startAt", + "type": "IdentifierLike", + "optional": true, + "description": "Inclusive document ID to start from.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "groupBy", + "type": "string[]", + "optional": true, + "description": "Count-query knob: SQL-shaped `GROUP BY` field list. Mirrors\nthe v1 wire's `group_by: repeated string` directly. Ignored\nby the regular document-fetch path.\n\n- `[]` or omitted \u2192 aggregate count (a single row).\n- `[\"\"]` where `` matches an `In`\n constraint \u2192 per-`In`-value entries (PerInValue).\n- `[\"\"]` where `` matches a range\n constraint \u2192 per-distinct-value entries within the range\n (RangeDistinct).\n- `[\"\", \"\"]` for compound `In + range`\n queries \u2192 compound distinct entries.\n\nEntry direction comes from the first `orderBy` clause's\ndirection (which also drives walk order on the materialize +\nprove path); set `orderBy: [[\"\", \"asc\"|\"desc\"]]`\nalongside `groupBy: [\"\"]` to control sort.", + "references": [] + } + ] + } + ], + "returnType": "Promise>", + "returnReferences": [ + "wasm.Document" + ], + "references": [ + "wasm.Document" + ], + "variants": [ + { + "key": "getDocuments", + "group": "queries", + "category": "document" + } + ], + "source": "dist/documents/facade.d.ts" + }, + "documents.get": { + "sdkMethod": "documents.get", + "signature": "get(contractId: wasm.IdentifierLike, type: string, documentId: wasm.IdentifierLike): Promise", + "description": "", + "parameters": [ + { + "name": "contractId", + "type": "wasm.IdentifierLike", + "optional": false, + "rest": false, + "description": "", + "references": [ + "wasm.IdentifierLike" + ], + "properties": [] + }, + { + "name": "type", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "documentId", "type": "wasm.IdentifierLike", "optional": false, "rest": false, @@ -5730,35 +6475,35 @@ "properties": [] } ], - "returnType": "Promise", + "returnType": "Promise", "returnReferences": [ - "wasm.DataContract" + "wasm.Document" ], "references": [ - "wasm.DataContract" + "wasm.Document" ], "variants": [ { - "key": "getDataContract", + "key": "getDocument", "group": "queries", - "category": "dataContract" + "category": "document" } ], - "source": "dist/contracts/facade.d.ts" + "source": "dist/documents/facade.d.ts" }, - "contracts.getHistory": { - "sdkMethod": "contracts.getHistory", - "signature": "getHistory(query: wasm.DataContractHistoryQuery): Promise>", + "documents.count": { + "sdkMethod": "documents.count", + "signature": "count(query: wasm.DocumentsQuery): Promise>", "description": "", "parameters": [ { "name": "query", - "type": "wasm.DataContractHistoryQuery", + "type": "wasm.DocumentsQuery", "optional": false, "rest": false, "description": "", "references": [ - "wasm.DataContractHistoryQuery" + "wasm.DocumentsQuery" ], "properties": [ { @@ -5770,75 +6515,186 @@ "IdentifierLike" ] }, + { + "name": "documentTypeName", + "type": "string", + "optional": false, + "description": "Document type name.", + "references": [] + }, + { + "name": "where", + "type": "DocumentWhereClause[]", + "optional": true, + "description": "Optional filter clauses expressed as [field, operator, value].", + "references": [ + "DocumentWhereClause" + ] + }, + { + "name": "orderBy", + "type": "DocumentOrderByClause[]", + "optional": true, + "description": "Optional sorting clauses expressed as [field, direction].", + "references": [ + "DocumentOrderByClause" + ] + }, { "name": "limit", "type": "number", "optional": true, - "description": "Maximum number of entries to return.", + "description": "Maximum number of documents to return.", "references": [] }, { - "name": "startAtMs", - "type": "number", + "name": "startAfter", + "type": "IdentifierLike", "optional": true, - "description": "Millisecond timestamp (inclusive) to start from.", + "description": "Exclusive document ID to resume from.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "startAt", + "type": "IdentifierLike", + "optional": true, + "description": "Inclusive document ID to start from.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "groupBy", + "type": "string[]", + "optional": true, + "description": "Count-query knob: SQL-shaped `GROUP BY` field list. Mirrors\nthe v1 wire's `group_by: repeated string` directly. Ignored\nby the regular document-fetch path.\n\n- `[]` or omitted \u2192 aggregate count (a single row).\n- `[\"\"]` where `` matches an `In`\n constraint \u2192 per-`In`-value entries (PerInValue).\n- `[\"\"]` where `` matches a range\n constraint \u2192 per-distinct-value entries within the range\n (RangeDistinct).\n- `[\"\", \"\"]` for compound `In + range`\n queries \u2192 compound distinct entries.\n\nEntry direction comes from the first `orderBy` clause's\ndirection (which also drives walk order on the materialize +\nprove path); set `orderBy: [[\"\", \"asc\"|\"desc\"]]`\nalongside `groupBy: [\"\"]` to control sort.", "references": [] } ] } ], - "returnType": "Promise>", - "returnReferences": [ - "wasm.DataContract" - ], - "references": [ - "wasm.DataContract" - ], + "returnType": "Promise>", + "returnReferences": [], + "references": [], "variants": [ { - "key": "getDataContractHistory", + "key": "getDocumentCount", "group": "queries", - "category": "dataContract" + "category": "document" } ], - "source": "dist/contracts/facade.d.ts" + "source": "dist/documents/facade.d.ts" }, - "contracts.getMany": { - "sdkMethod": "contracts.getMany", - "signature": "getMany(contractIds: wasm.IdentifierLikeArray): Promise>", + "documents.sum": { + "sdkMethod": "documents.sum", + "signature": "sum(query: wasm.DocumentsQuery, sumProperty: string): Promise>", "description": "", "parameters": [ { - "name": "contractIds", - "type": "wasm.IdentifierLikeArray", + "name": "query", + "type": "wasm.DocumentsQuery", "optional": false, "rest": false, "description": "", "references": [ - "wasm.IdentifierLikeArray" + "wasm.DocumentsQuery" ], + "properties": [ + { + "name": "dataContractId", + "type": "IdentifierLike", + "optional": false, + "description": "Data contract identifier.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "documentTypeName", + "type": "string", + "optional": false, + "description": "Document type name.", + "references": [] + }, + { + "name": "where", + "type": "DocumentWhereClause[]", + "optional": true, + "description": "Optional filter clauses expressed as [field, operator, value].", + "references": [ + "DocumentWhereClause" + ] + }, + { + "name": "orderBy", + "type": "DocumentOrderByClause[]", + "optional": true, + "description": "Optional sorting clauses expressed as [field, direction].", + "references": [ + "DocumentOrderByClause" + ] + }, + { + "name": "limit", + "type": "number", + "optional": true, + "description": "Maximum number of documents to return.", + "references": [] + }, + { + "name": "startAfter", + "type": "IdentifierLike", + "optional": true, + "description": "Exclusive document ID to resume from.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "startAt", + "type": "IdentifierLike", + "optional": true, + "description": "Inclusive document ID to start from.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "groupBy", + "type": "string[]", + "optional": true, + "description": "Count-query knob: SQL-shaped `GROUP BY` field list. Mirrors\nthe v1 wire's `group_by: repeated string` directly. Ignored\nby the regular document-fetch path.\n\n- `[]` or omitted \u2192 aggregate count (a single row).\n- `[\"\"]` where `` matches an `In`\n constraint \u2192 per-`In`-value entries (PerInValue).\n- `[\"\"]` where `` matches a range\n constraint \u2192 per-distinct-value entries within the range\n (RangeDistinct).\n- `[\"\", \"\"]` for compound `In + range`\n queries \u2192 compound distinct entries.\n\nEntry direction comes from the first `orderBy` clause's\ndirection (which also drives walk order on the materialize +\nprove path); set `orderBy: [[\"\", \"asc\"|\"desc\"]]`\nalongside `groupBy: [\"\"]` to control sort.", + "references": [] + } + ] + }, + { + "name": "sumProperty", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], "properties": [] } ], - "returnType": "Promise>", - "returnReferences": [ - "wasm.DataContract" - ], - "references": [ - "wasm.DataContract" - ], + "returnType": "Promise>", + "returnReferences": [], + "references": [], "variants": [ { - "key": "getDataContracts", + "key": "getDocumentSum", "group": "queries", - "category": "dataContract" + "category": "document" } ], - "source": "dist/contracts/facade.d.ts" + "source": "dist/documents/facade.d.ts" }, - "documents.query": { - "sdkMethod": "documents.query", - "signature": "query(query: wasm.DocumentsQuery): Promise>", + "documents.average": { + "sdkMethod": "documents.average", + "signature": "average(query: wasm.DocumentsQuery, averageProperty: string): Promise>", "description": "", "parameters": [ { @@ -5918,62 +6774,94 @@ "references": [] } ] + }, + { + "name": "averageProperty", + "type": "string", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] } ], - "returnType": "Promise>", - "returnReferences": [ - "wasm.Document" - ], - "references": [ - "wasm.Document" - ], + "returnType": "Promise>", + "returnReferences": [], + "references": [], "variants": [ { - "key": "getDocuments", + "key": "getDocumentAverage", "group": "queries", "category": "document" } ], "source": "dist/documents/facade.d.ts" }, - "documents.get": { - "sdkMethod": "documents.get", - "signature": "get(contractId: wasm.IdentifierLike, type: string, documentId: wasm.IdentifierLike): Promise", + "documents.history": { + "sdkMethod": "documents.history", + "signature": "history(query: wasm.DocumentHistoryQuery): Promise>", "description": "", "parameters": [ { - "name": "contractId", - "type": "wasm.IdentifierLike", - "optional": false, - "rest": false, - "description": "", - "references": [ - "wasm.IdentifierLike" - ], - "properties": [] - }, - { - "name": "type", - "type": "string", - "optional": false, - "rest": false, - "description": "", - "references": [], - "properties": [] - }, - { - "name": "documentId", - "type": "wasm.IdentifierLike", + "name": "query", + "type": "wasm.DocumentHistoryQuery", "optional": false, "rest": false, "description": "", "references": [ - "wasm.IdentifierLike" + "wasm.DocumentHistoryQuery" ], - "properties": [] + "properties": [ + { + "name": "dataContractId", + "type": "IdentifierLike", + "optional": false, + "description": "Data contract identifier.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "documentTypeName", + "type": "string", + "optional": false, + "description": "Document type name.", + "references": [] + }, + { + "name": "documentId", + "type": "IdentifierLike", + "optional": false, + "description": "Document identifier.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "startAtMs", + "type": "number", + "optional": true, + "description": "Millisecond timestamp (exclusive) to start after.", + "references": [] + }, + { + "name": "limit", + "type": "number", + "optional": true, + "description": "Maximum number of entries to return.", + "references": [] + }, + { + "name": "offset", + "type": "number", + "optional": true, + "description": "Offset for pagination through the document history.", + "references": [] + } + ] } ], - "returnType": "Promise", + "returnType": "Promise>", "returnReferences": [ "wasm.Document" ], @@ -5982,7 +6870,7 @@ ], "variants": [ { - "key": "getDocument", + "key": "getDocumentHistory", "group": "queries", "category": "document" } @@ -7184,6 +8072,46 @@ ], "source": "dist/tokens/facade.d.ts" }, + "tokens.identityBalances": { + "sdkMethod": "tokens.identityBalances", + "signature": "identityBalances(identityId: wasm.IdentifierLike, tokenIds: wasm.IdentifierLikeArray): Promise>", + "description": "", + "parameters": [ + { + "name": "identityId", + "type": "wasm.IdentifierLike", + "optional": false, + "rest": false, + "description": "", + "references": [ + "wasm.IdentifierLike" + ], + "properties": [] + }, + { + "name": "tokenIds", + "type": "wasm.IdentifierLikeArray", + "optional": false, + "rest": false, + "description": "", + "references": [ + "wasm.IdentifierLikeArray" + ], + "properties": [] + } + ], + "returnType": "Promise>", + "returnReferences": [], + "references": [], + "variants": [ + { + "key": "getTokenBalancesForIdentity", + "group": "queries", + "category": "token" + } + ], + "source": "dist/tokens/facade.d.ts" + }, "tokens.priceByContract": { "sdkMethod": "tokens.priceByContract", "signature": "priceByContract(contractId: wasm.IdentifierLike, tokenPosition: number): Promise", @@ -7873,6 +8801,128 @@ ], "source": "dist/addresses/facade.d.ts" }, + "shielded.poolState": { + "sdkMethod": "shielded.poolState", + "signature": "poolState(): Promise", + "description": "", + "parameters": [], + "returnType": "Promise", + "returnReferences": [], + "references": [], + "variants": [ + { + "key": "getShieldedPoolState", + "group": "queries", + "category": "shielded" + } + ], + "source": "dist/shielded/facade.d.ts" + }, + "shielded.encryptedNotes": { + "sdkMethod": "shielded.encryptedNotes", + "signature": "encryptedNotes(startIndex: bigint, count: number): Promise", + "description": "", + "parameters": [ + { + "name": "startIndex", + "type": "bigint", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + }, + { + "name": "count", + "type": "number", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise", + "returnReferences": [ + "wasm.ShieldedEncryptedNote" + ], + "references": [ + "wasm.ShieldedEncryptedNote" + ], + "variants": [ + { + "key": "getShieldedEncryptedNotes", + "group": "queries", + "category": "shielded" + } + ], + "source": "dist/shielded/facade.d.ts" + }, + "shielded.anchors": { + "sdkMethod": "shielded.anchors", + "signature": "anchors(): Promise", + "description": "", + "parameters": [], + "returnType": "Promise", + "returnReferences": [], + "references": [], + "variants": [ + { + "key": "getShieldedAnchors", + "group": "queries", + "category": "shielded" + } + ], + "source": "dist/shielded/facade.d.ts" + }, + "shielded.mostRecentAnchor": { + "sdkMethod": "shielded.mostRecentAnchor", + "signature": "mostRecentAnchor(): Promise", + "description": "", + "parameters": [], + "returnType": "Promise", + "returnReferences": [], + "references": [], + "variants": [ + { + "key": "getShieldedMostRecentAnchor", + "group": "queries", + "category": "shielded" + } + ], + "source": "dist/shielded/facade.d.ts" + }, + "shielded.nullifiers": { + "sdkMethod": "shielded.nullifiers", + "signature": "nullifiers(nullifiers: Uint8Array[]): Promise", + "description": "Each nullifier must be a `Uint8Array` of exactly 32 bytes; otherwise\nthe underlying call rejects with `InvalidArgument`.", + "parameters": [ + { + "name": "nullifiers", + "type": "Uint8Array[]", + "optional": false, + "rest": false, + "description": "", + "references": [], + "properties": [] + } + ], + "returnType": "Promise", + "returnReferences": [ + "wasm.ShieldedNullifierStatus" + ], + "references": [ + "wasm.ShieldedNullifierStatus" + ], + "variants": [ + { + "key": "getShieldedNullifiers", + "group": "queries", + "category": "shielded" + } + ], + "source": "dist/shielded/facade.d.ts" + }, "identities.create": { "sdkMethod": "identities.create", "signature": "create(options: wasm.IdentityCreateOptions): Promise", @@ -11574,6 +12624,63 @@ ], "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" }, + "DocumentHistoryQuery": { + "kind": "InterfaceDeclaration", + "anchor": "type-documenthistoryquery", + "declaration": "export interface DocumentHistoryQuery {\n /**\n * Data contract identifier.\n */\n dataContractId: IdentifierLike\n\n /**\n * Document type name.\n */\n documentTypeName: string;\n\n /**\n * Document identifier.\n */\n documentId: IdentifierLike\n\n /**\n * Millisecond timestamp (exclusive) to start after.\n * @default 0\n */\n startAtMs?: number;\n\n /**\n * Maximum number of entries to return.\n * @default undefined\n */\n limit?: number;\n\n /**\n * Offset for pagination through the document history.\n * @default undefined\n */\n offset?: number;\n}", + "properties": [ + { + "name": "dataContractId", + "type": "IdentifierLike", + "optional": false, + "description": "Data contract identifier.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "documentTypeName", + "type": "string", + "optional": false, + "description": "Document type name.", + "references": [] + }, + { + "name": "documentId", + "type": "IdentifierLike", + "optional": false, + "description": "Document identifier.", + "references": [ + "IdentifierLike" + ] + }, + { + "name": "startAtMs", + "type": "number", + "optional": true, + "description": "Millisecond timestamp (exclusive) to start after.", + "references": [] + }, + { + "name": "limit", + "type": "number", + "optional": true, + "description": "Maximum number of entries to return.", + "references": [] + }, + { + "name": "offset", + "type": "number", + "optional": true, + "description": "Offset for pagination through the document history.", + "references": [] + } + ], + "references": [ + "IdentifierLike" + ], + "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" + }, "DocumentOrderByClause": { "kind": "TypeAliasDeclaration", "anchor": "type-documentorderbyclause", @@ -14690,6 +15797,70 @@ "references": [], "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" }, + "ShieldedEncryptedNote": { + "kind": "ClassDeclaration", + "anchor": "type-shieldedencryptednote", + "declaration": "export class ShieldedEncryptedNote {\n private constructor();\n free(): void;\n [Symbol.dispose](): void;\n static fromJSON(js: object): ShieldedEncryptedNote;\n static fromObject(obj: object): ShieldedEncryptedNote;\n toJSON(): any;\n toObject(): any;\n readonly cmx: Uint8Array;\n readonly cvNet: Uint8Array;\n readonly encryptedNote: Uint8Array;\n readonly nullifier: Uint8Array;\n}", + "properties": [ + { + "name": "cmx", + "type": "Uint8Array", + "optional": false, + "description": "", + "references": [] + }, + { + "name": "cvNet", + "type": "Uint8Array", + "optional": false, + "description": "", + "references": [] + }, + { + "name": "encryptedNote", + "type": "Uint8Array", + "optional": false, + "description": "", + "references": [] + }, + { + "name": "nullifier", + "type": "Uint8Array", + "optional": false, + "description": "", + "references": [] + } + ], + "references": [ + "ShieldedEncryptedNote" + ], + "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" + }, + "ShieldedNullifierStatus": { + "kind": "ClassDeclaration", + "anchor": "type-shieldednullifierstatus", + "declaration": "export class ShieldedNullifierStatus {\n private constructor();\n free(): void;\n [Symbol.dispose](): void;\n static fromJSON(js: object): ShieldedNullifierStatus;\n static fromObject(obj: object): ShieldedNullifierStatus;\n toJSON(): any;\n toObject(): any;\n readonly isSpent: boolean;\n readonly nullifier: Uint8Array;\n}", + "properties": [ + { + "name": "isSpent", + "type": "boolean", + "optional": false, + "description": "", + "references": [] + }, + { + "name": "nullifier", + "type": "Uint8Array", + "optional": false, + "description": "", + "references": [] + } + ], + "references": [ + "ShieldedNullifierStatus" + ], + "source": "wasm-sdk/dist/raw/wasm_sdk.d.ts" + }, "StateTransitionResult": { "kind": "ClassDeclaration", "anchor": "type-statetransitionresult", diff --git a/public/src/definitions-data.js b/public/src/definitions-data.js index 07f0ca3..9f53035 100644 --- a/public/src/definitions-data.js +++ b/public/src/definitions-data.js @@ -9,7 +9,7 @@ export const SUPPORTED_QUERIES = new Set([ // Data Contracts 'getDataContract', 'getDataContractHistory', 'getDataContracts', // Documents - 'getDocuments', 'getDocument', + 'getDocuments', 'getDocument', 'getDocumentCount', 'getDocumentSum', 'getDocumentAverage', 'getDocumentHistory', // Epoch 'getEpochsInfo', 'getCurrentEpoch', 'getFinalizedEpochInfos', 'getEvonodesProposedEpochBlocksByIds', 'getEvonodesProposedEpochBlocksByRange', // Voting & Contested Resources @@ -19,7 +19,9 @@ export const SUPPORTED_QUERIES = new Set([ 'getProtocolVersionUpgradeState', 'getProtocolVersionUpgradeVoteStatus', // Tokens 'getTokenStatuses', 'getTokenDirectPurchasePrices', 'getTokenContractInfo', 'getTokenPerpetualDistributionLastClaim', - 'getTokenTotalSupply', 'getTokenPriceByContract', + 'getTokenTotalSupply', 'getTokenBalancesForIdentity', 'getTokenPriceByContract', + // Shielded + 'getShieldedPoolState', 'getShieldedEncryptedNotes', 'getShieldedAnchors', 'getShieldedMostRecentAnchor', 'getShieldedNullifiers', // Groups 'getGroupInfo', 'getGroupInfos', 'getGroupMembers', 'getGroupActions', 'getGroupActionSigners', 'getIdentityGroups', 'getGroupsDataContracts', @@ -136,6 +138,7 @@ export const PROOF_CAPABLE = new Set([ 'getIdentitiesTokenInfos', // Data Contracts & Documents 'getDataContract', 'getDataContractHistory', 'getDataContracts', 'getDocuments', 'getDocument', + 'getDocumentCount', 'getDocumentSum', 'getDocumentAverage', 'getDocumentHistory', // DPNS 'getDpnsUsername', 'getDpnsUsernames', 'getDpnsUsernameByName', // Epoch @@ -149,7 +152,9 @@ export const PROOF_CAPABLE = new Set([ // Tokens 'getTokenStatuses', 'getTokenDirectPurchasePrices', 'getTokenContractInfo', 'getTokenPerpetualDistributionLastClaim', 'getTokenTotalSupply', 'getIdentitiesTokenInfos', 'getIdentityTokenInfos', - 'getIdentitiesTokenBalances', 'getIdentityTokenBalances', + 'getIdentitiesTokenBalances', 'getIdentityTokenBalances', 'getTokenBalancesForIdentity', + // Shielded + 'getShieldedPoolState', 'getShieldedEncryptedNotes', 'getShieldedAnchors', 'getShieldedMostRecentAnchor', 'getShieldedNullifiers', // Groups 'getGroupInfo', 'getGroupInfos', 'getGroupMembers', 'getGroupActions', 'getGroupActionSigners', 'getIdentityGroups', 'getGroupsDataContracts', diff --git a/public/src/operations.js b/public/src/operations.js index efc158a..66c1535 100644 --- a/public/src/operations.js +++ b/public/src/operations.js @@ -39,6 +39,48 @@ export async function callEvo(client, groupKey, itemKey, defs, args, useProof, e } }; + const hexToBytes = (value, label = 'value') => { + const hex = String(value ?? '').trim().replace(/^0x/i, ''); + if (!/^[0-9a-fA-F]{64}$/.test(hex)) { + throw new Error(`Invalid ${label}: expected 64 hexadecimal characters (32 bytes).`); + } + const bytes = new Uint8Array(32); + for (let i = 0; i < 32; i += 1) { + bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16); + } + return bytes; + }; + + const bytesToHex = (bytes) => Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join(''); + + // Anchors arrive as raw 32-byte values (a single one for mostRecentAnchor, + // an array for anchors); hex-encode them for display while keeping proof + // metadata wrappers intact. + const anchorsToHex = (result) => { + const convert = (data) => { + if (data === undefined || data === null) return data; + return Array.isArray(data) ? data.map(bytesToHex) : bytesToHex(data); + }; + if (result && typeof result === 'object' && 'data' in result && 'metadata' in result && 'proof' in result) { + return { data: convert(result.data), metadata: result.metadata, proof: result.proof }; + } + return convert(result); + }; + + const documentsQueryPayload = () => { + const groupBy = toStringArray(n.groupBy); + return { + dataContractId: n.dataContractId || n.contractId, + documentTypeName: n.documentTypeName || n.documentType, + where: parseJson(n.where, 'Where'), + orderBy: parseJson(n.orderBy, 'Order By'), + limit: n.limit ?? undefined, + startAfter: n.startAfter ?? undefined, + startAt: n.startAt ?? undefined, + ...(groupBy.length ? { groupBy } : {}), + }; + }; + switch (itemKey) { // Identity queries case 'getIdentity': @@ -130,21 +172,42 @@ export async function callEvo(client, groupKey, itemKey, defs, args, useProof, e return useProof ? c.contracts.getManyWithProof(n.ids) : c.contracts.getMany(n.ids); // Documents case 'getDocuments': { - const payload = { - dataContractId: n.dataContractId || n.contractId, - documentTypeName: n.documentTypeName || n.documentType, - where: parseJson(n.where, 'Where'), - orderBy: parseJson(n.orderBy, 'Order By'), - limit: n.limit ?? undefined, - startAfter: n.startAfter ?? undefined, - startAt: n.startAt ?? undefined, - }; + const payload = documentsQueryPayload(); return useProof ? c.documents.queryWithProof(payload) : c.documents.query(payload); } case 'getDocument': return useProof ? c.documents.getWithProof(n.dataContractId || n.contractId, n.documentTypeName || n.documentType, n.documentId) : c.documents.get(n.dataContractId || n.contractId, n.documentTypeName || n.documentType, n.documentId); + case 'getDocumentCount': { + const payload = documentsQueryPayload(); + return useProof ? c.documents.countWithProof(payload) : c.documents.count(payload); + } + case 'getDocumentSum': { + if (!n.sumProperty) { + throw new Error('Sum property is required.'); + } + const payload = documentsQueryPayload(); + return useProof ? c.documents.sumWithProof(payload, n.sumProperty) : c.documents.sum(payload, n.sumProperty); + } + case 'getDocumentAverage': { + if (!n.averageProperty) { + throw new Error('Average property is required.'); + } + const payload = documentsQueryPayload(); + return useProof ? c.documents.averageWithProof(payload, n.averageProperty) : c.documents.average(payload, n.averageProperty); + } + case 'getDocumentHistory': { + const payload = { + dataContractId: n.dataContractId, + documentTypeName: n.documentTypeName, + documentId: n.documentId, + startAtMs: n.startAtMs ?? undefined, + limit: n.limit ?? undefined, + offset: n.offset ?? undefined, + }; + return useProof ? c.documents.historyWithProof(payload) : c.documents.history(payload); + } // DPNS case 'getDpnsUsername': return useProof ? c.dpns.usernameWithProof(n.identityId) : c.dpns.username(n.identityId); @@ -235,6 +298,10 @@ export async function callEvo(client, groupKey, itemKey, defs, args, useProof, e return useProof ? c.tokens.perpetualDistributionLastClaimWithProof(n.identityId, n.tokenId) : c.tokens.perpetualDistributionLastClaim(n.identityId, n.tokenId); case 'getTokenTotalSupply': return useProof ? c.tokens.totalSupplyWithProof(n.tokenId) : c.tokens.totalSupply(n.tokenId); + case 'getTokenBalancesForIdentity': { + const tokenIds = toStringArray(n.tokenIds); + return useProof ? c.tokens.identityBalancesWithProof(n.identityId, tokenIds) : c.tokens.identityBalances(n.identityId, tokenIds); + } case 'getTokenPriceByContract': { const contractId = n.dataContractId || n.contractId; const tokenPosition = toNumber(n.tokenPosition, 0); @@ -436,6 +503,39 @@ export async function callEvo(client, groupKey, itemKey, defs, args, useProof, e return useProof ? c.addresses.getManyWithProof(addresses) : c.addresses.getMany(addresses); } + // Shielded pool queries + case 'getShieldedPoolState': + return useProof ? c.shielded.poolStateWithProof() : c.shielded.poolState(); + case 'getShieldedEncryptedNotes': { + const startIndexRaw = (n.startIndex === undefined || n.startIndex === null || n.startIndex === '') + ? '0' + : String(n.startIndex).trim(); + if (!/^\d+$/.test(startIndexRaw)) { + throw new Error('Start index must be a non-negative integer.'); + } + const count = toNumber(n.count); + if (count === null || !Number.isInteger(count) || count <= 0) { + throw new Error('Count must be a positive integer.'); + } + const startIndex = BigInt(startIndexRaw); + return useProof ? c.shielded.encryptedNotesWithProof(startIndex, count) : c.shielded.encryptedNotes(startIndex, count); + } + case 'getShieldedAnchors': { + const result = useProof ? await c.shielded.anchorsWithProof() : await c.shielded.anchors(); + return anchorsToHex(result); + } + case 'getShieldedMostRecentAnchor': { + const result = useProof ? await c.shielded.mostRecentAnchorWithProof() : await c.shielded.mostRecentAnchor(); + return anchorsToHex(result); + } + case 'getShieldedNullifiers': { + const nullifiers = toStringArray(n.nullifiers).map((value, i) => hexToBytes(value, `nullifier #${i + 1}`)); + if (!nullifiers.length) { + throw new Error('At least one nullifier is required.'); + } + return useProof ? c.shielded.nullifiersWithProof(nullifiers) : c.shielded.nullifiers(nullifiers); + } + default: throw new Error(`Operation ${itemKey} is not supported in the demo UI.`); } diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index becc7c8..0104f35 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -65,6 +65,12 @@ def rewrite_wasm_wrapper(wasm_file: Path) -> None: 'token_id': 'Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv', 'document_type': 'domain', 'document_id': '7NYmEKQsYtniQRUmxwdPGeVcirMoPh5ZPyAKz8BWFy3r', + # DashRate demo contract: review type has countable indices on + # [resourceId] and [resourceId, rating] plus documentsKeepHistory. + 'aggregate_contract_id': 'BdgTqaTAPYMyhp1WdeWdcvYSgoD7AuJ7tVCaCSXyQgyP', + 'aggregate_document_type': 'review', + 'aggregate_resource_id': 'identities-names', + 'history_document_id': '2kWsepFc3PoHEee97xpJQTfbXtaDU9RHB4KdH52wk8f4', 'username': 'alice', 'epoch': 8635, 'platform_address': 'tdash1krt0z5hrcaphyuraxmk2h2ff8nyv5fmncsgf7evf', @@ -296,6 +302,48 @@ def evo_example_for_query(key: str, inputs: List[dict]): '{data['document_id']}' ) """), + 'getDocumentCount': example(f""" + // Requires an index declaring countable on the queried properties. + return await sdk.documents.count({{ + dataContractId: '{data['aggregate_contract_id']}', + documentTypeName: '{data['aggregate_document_type']}', + where: [["resourceId", "==", "{data['aggregate_resource_id']}"]], + orderBy: [["resourceId", "asc"]] + }}) + """), + 'getDocumentSum': example(f""" + // The second argument names the numeric document property to sum. + // Requires an index declaring summable for that property; no known + // testnet contract has one yet, so this call shape is illustrative + // and the server will reject it against this contract. + return await sdk.documents.sum({{ + dataContractId: '{data['aggregate_contract_id']}', + documentTypeName: '{data['aggregate_document_type']}', + where: [["resourceId", "==", "{data['aggregate_resource_id']}"]], + orderBy: [["resourceId", "asc"]] + }}, 'rating') + """), + 'getDocumentAverage': example(f""" + // Returns {{ count, sum }} per entry - divide sum by count for the average. + // Requires an index declaring summable for the aggregated property; no + // known testnet contract has one yet, so this call shape is illustrative + // and the server will reject it against this contract. + return await sdk.documents.average({{ + dataContractId: '{data['aggregate_contract_id']}', + documentTypeName: '{data['aggregate_document_type']}', + where: [["resourceId", "==", "{data['aggregate_resource_id']}"]], + orderBy: [["resourceId", "asc"]] + }}, 'rating') + """), + 'getDocumentHistory': example(f""" + // Requires a document type with history retention (documentsKeepHistory). + return await sdk.documents.history({{ + dataContractId: '{data['aggregate_contract_id']}', + documentTypeName: '{data['aggregate_document_type']}', + documentId: '{data['history_document_id']}', + limit: 10 + }}) + """), 'getDpnsUsername': example(f""" return await sdk.dpns.username('{data['identity_id']}') """), @@ -431,6 +479,10 @@ def evo_example_for_query(key: str, inputs: List[dict]): 'getTokenTotalSupply': example(f""" return await sdk.tokens.totalSupply('{data['token_id']}') """), + 'getTokenBalancesForIdentity': example(f""" + // The result map is keyed by token ID. + return await sdk.tokens.identityBalances('{data['identity_id']}', ['{data['token_id']}']) + """), 'getGroupInfo': example(f""" return await sdk.group.info('{data['group_contract_id']}', 0) """), @@ -510,6 +562,27 @@ def evo_example_for_query(key: str, inputs: List[dict]): 'getIdentityByNonUniquePublicKeyHash': example(f""" return await sdk.identities.byNonUniquePublicKeyHash('{data['public_key_hash_non_unique']}') """), + # Shielded pool + 'getShieldedPoolState': example(""" + // Total shielded pool balance in credits (undefined if the pool is empty). + return await sdk.shielded.poolState() + """), + 'getShieldedEncryptedNotes': example(""" + return await sdk.shielded.encryptedNotes(0n, 10) + """), + 'getShieldedAnchors': example(""" + // Each anchor is a 32-byte Uint8Array. + return await sdk.shielded.anchors() + """), + 'getShieldedMostRecentAnchor': example(""" + // 32-byte Uint8Array, or undefined if no anchor exists yet. + return await sdk.shielded.mostRecentAnchor() + """), + 'getShieldedNullifiers': example(""" + // Each nullifier must be exactly 32 bytes. + const nullifier = new Uint8Array(32); + return await sdk.shielded.nullifiers([nullifier]) + """), } return examples.get(key) @@ -646,9 +719,16 @@ def format_example(code: str, header: str, add_return: bool) -> str: body_lines = formatted.split('\n') if add_return and body_lines: - first_line = body_lines[0].lstrip() - if first_line and not first_line.startswith('return'): - body_lines[0] = f'return {first_line}' + # Skip leading comment lines so the return lands on the expression. + first_index = 0 + while first_index < len(body_lines) and ( + not body_lines[first_index].strip() or body_lines[first_index].lstrip().startswith('//') + ): + first_index += 1 + if first_index < len(body_lines): + first_line = body_lines[first_index].lstrip() + if first_line and not first_line.startswith(('return', 'const ', 'let ', 'var ', 'import ')): + body_lines[first_index] = f'return {first_line}' def replace_client(line: str) -> str: return line.replace('client', 'sdk') diff --git a/tests/e2e/fixtures/test-data.js b/tests/e2e/fixtures/test-data.js index b81198b..baa20b6 100644 --- a/tests/e2e/fixtures/test-data.js +++ b/tests/e2e/fixtures/test-data.js @@ -247,6 +247,53 @@ const testData = { documentId: "7NYmEKQsYtniQRUmxwdPGeVcirMoPh5ZPyAKz8BWFy3r" } ] + }, + // DashRate demo contract: review type has countable indices on + // [resourceId] and [resourceId, rating] plus documentsKeepHistory. + getDocumentCount: { + testnet: [ + { + dataContractId: "BdgTqaTAPYMyhp1WdeWdcvYSgoD7AuJ7tVCaCSXyQgyP", + documentTypeName: "review", + where: '[["resourceId", "==", "identities-names"]]', + orderBy: '[["resourceId", "asc"]]' + } + ] + }, + // sum/average require an index declaring `summable` for the property; + // no known testnet contract has one (see dashpay/platform#3960), so the + // spec skips these until such a contract exists. + getDocumentSum: { + testnet: [ + { + dataContractId: "BdgTqaTAPYMyhp1WdeWdcvYSgoD7AuJ7tVCaCSXyQgyP", + documentTypeName: "review", + where: '[["resourceId", "==", "identities-names"]]', + orderBy: '[["resourceId", "asc"]]', + sumProperty: "rating" + } + ] + }, + getDocumentAverage: { + testnet: [ + { + dataContractId: "BdgTqaTAPYMyhp1WdeWdcvYSgoD7AuJ7tVCaCSXyQgyP", + documentTypeName: "review", + where: '[["resourceId", "==", "identities-names"]]', + orderBy: '[["resourceId", "asc"]]', + averageProperty: "rating" + } + ] + }, + getDocumentHistory: { + testnet: [ + { + dataContractId: "BdgTqaTAPYMyhp1WdeWdcvYSgoD7AuJ7tVCaCSXyQgyP", + documentTypeName: "review", + documentId: "2kWsepFc3PoHEee97xpJQTfbXtaDU9RHB4KdH52wk8f4", + limit: 5 + } + ] } }, @@ -445,6 +492,14 @@ const testData = { } ] }, + getTokenBalancesForIdentity: { + testnet: [ + { + identityId: "5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk", + tokenIds: ["Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv"] + } + ] + }, getTokenPriceByContract: { testnet: [ { @@ -598,6 +653,34 @@ const testData = { } ] } + }, + + shielded: { + getShieldedPoolState: { + testnet: [{}] // No parameters needed + }, + getShieldedEncryptedNotes: { + testnet: [ + { + startIndex: "0", + count: 5 + } + ] + }, + getShieldedAnchors: { + testnet: [{}] + }, + getShieldedMostRecentAnchor: { + testnet: [{}] + }, + getShieldedNullifiers: { + testnet: [ + { + // Any well-formed 32-byte value works; unspent ones return isSpent: false + nullifiers: ["0000000000000000000000000000000000000000000000000000000000000000"] + } + ] + } } }, diff --git a/tests/e2e/queries/query-execution.spec.js b/tests/e2e/queries/query-execution.spec.js index 6de404a..a85f11d 100644 --- a/tests/e2e/queries/query-execution.spec.js +++ b/tests/e2e/queries/query-execution.spec.js @@ -1,6 +1,7 @@ const { test, expect } = require('@playwright/test'); const { EvoSdkPage } = require('../utils/sdk-page'); const { ParameterInjector } = require('../utils/parameter-injector'); +const { getTestParameters } = require('../fixtures/test-data'); /** * Helper function to execute a query with proof toggle enabled @@ -120,8 +121,12 @@ function validateProofContent(resultData) { /** * Helper function to validate result with proof * @param {Object} result - The query result object + * @param {Object} [options] + * @param {boolean} [options.allowNullData] - Accept data: null (for queries + * declared ProofMetadataResponseTyped); proof metadata is still + * validated in full. */ -function validateResultWithProof(result) { +function validateResultWithProof(result, { allowNullData = false } = {}) { expect(result.success).toBe(true); expect(result.result).toBeDefined(); @@ -129,7 +134,9 @@ function validateResultWithProof(result) { const resultData = JSON.parse(result.result); // All proof responses must have a data property expect(resultData).toHaveProperty('data'); - expect(resultData.data).not.toBeNull(); + if (!allowNullData) { + expect(resultData.data).not.toBeNull(); + } expect(resultData.data).toBeDefined(); expect(resultData).toHaveProperty('metadata'); expect(resultData).toHaveProperty('proof'); @@ -637,6 +644,112 @@ test.describe('Evo SDK Query Execution Tests', () => { validateDocumentResult(result.result); } }); + + // Aggregate and history queries run against the DashRate demo contract + // (countable indices on [resourceId] and [resourceId, rating], plus + // documentsKeepHistory on the review type). + const documentAggregateQueries = [ + { + name: 'getDocumentCount', + hasProofSupport: true, + needsParameters: true, + validateFn: (result, isProofMode = false) => { + const parsed = JSON.parse(result); + // Map of group key to count; bigint counts serialize as strings. + // The fixture omits groupBy, so exactly one aggregate entry comes back. + const counts = isProofMode ? parsed.data : parsed; + expect(typeof counts === 'object').toBe(true); + const values = Object.values(counts); + expect(values).toHaveLength(1); + for (const count of values) { + expect(count).toMatch(/^\d+$/); + } + } + }, + { + // sum/average need an index declaring `summable` for the property; no + // known testnet contract has one (see dashpay/platform#3960). + name: 'getDocumentSum', + skip: true, + hasProofSupport: true, + needsParameters: true, + validateFn: () => {} + }, + { + name: 'getDocumentAverage', + skip: true, + hasProofSupport: true, + needsParameters: true, + validateFn: () => {} + }, + { + name: 'getDocumentHistory', + hasProofSupport: true, + needsParameters: true, + validateFn: (result, isProofMode = false) => { + const parsed = JSON.parse(result); + // Map of bigint revision timestamp (stringified) to document + const history = isProofMode ? parsed.data : parsed; + expect(typeof history === 'object').toBe(true); + const keys = Object.keys(history); + expect(keys.length).toBeGreaterThan(0); + for (const key of keys) { + expect(key).toMatch(/^\d+$/); + } + } + } + ]; + + documentAggregateQueries.forEach(({ name, hasProofSupport, needsParameters, skip, validateFn }) => { + test.describe(`${name} query (parameterized)`, () => { + if (skip) { + test.skip('without proof info', async () => { + // Requires a testnet contract with a summable index + }); + test.skip('with proof info', async () => { + // Requires a testnet contract with a summable index + }); + return; + } + + test('without proof info', async () => { + await evoSdkPage.setupQuery('document', name); + await evoSdkPage.disableProofInfo(); + + if (needsParameters) { + const success = await parameterInjector.injectParameters('document', name, 'testnet'); + expect(success).toBe(true); + } + + const result = await evoSdkPage.executeQueryAndGetResult(); + validateBasicQueryResult(result); + validateResultWithoutProof(result); + validateFn(result.result, false); + }); + + if (hasProofSupport) { + test('with proof info', async () => { + const { result, proofEnabled } = await executeQueryWithProof( + evoSdkPage, + parameterInjector, + 'document', + name, + 'testnet' + ); + + validateBasicQueryResult(result); + + if (proofEnabled) { + validateResultWithProof(result); + validateFn(result.result, true); + } else { + validateResultWithoutProof(result); + validateFn(result.result, false); + } + }); + } + }); + }); }); test.describe('System Queries', () => { @@ -1018,6 +1131,24 @@ test.describe('Evo SDK Query Execution Tests', () => { expect(typeof supplyData === 'object').toBe(true); expect(supplyData).toHaveProperty('totalSupply'); } + }, + { + name: 'getTokenBalancesForIdentity', + hasProofSupport: true, + needsParameters: true, + validateFn: (result) => { + expect(() => JSON.parse(result)).not.toThrow(); + // Map keyed by token ID with bigint balances serialized as strings. + // The requested token must be present (proof-mode keys are base58 too). + const balances = JSON.parse(result); + expect(typeof balances === 'object').toBe(true); + const { tokenIds } = getTestParameters('token', 'getTokenBalancesForIdentity', 'testnet'); + expect(Object.keys(balances)).toContain(tokenIds[0]); + for (const balance of Object.values(balances)) { + expect(typeof balance).toBe('string'); + expect(balance).toMatch(/^\d+$/); + } + } } ]; @@ -1414,6 +1545,144 @@ test.describe('Evo SDK Query Execution Tests', () => { }); }); + test.describe('Shielded Queries', () => { + // Shielded pool data on testnet may be sparse; validators accept legitimate + // empty responses ("Completed (no result returned)") for the nullable queries. + const NO_RESULT = 'Completed (no result returned)'; + // Non-proof scalar results render as the bare formatted string + // (result-format.js returns top-level strings verbatim); proof results are + // a JSON object whose `data` field carries the converted value. Validators + // receive the raw result text plus the mode and assert the exact shape. + const shieldedQueries = [ + { + name: 'getShieldedPoolState', + hasProofSupport: true, + needsParameters: false, + // Declared as ProofMetadataResponseTyped: null when the pool is empty + allowsNullData: true, + validateFn: (result, isProofMode = false) => { + expect(result).toBeDefined(); + if (isProofMode) { + // Balance is a bigint, serialized as a decimal string in data + const parsed = JSON.parse(result); + if (parsed.data === null) return; // empty pool + expect(typeof parsed.data).toBe('string'); + expect(parsed.data).toMatch(/^\d+$/); + } else { + if (result === NO_RESULT) return; // empty pool + expect(result).toMatch(/^\d+$/); + } + } + }, + { + name: 'getShieldedEncryptedNotes', + hasProofSupport: true, + needsParameters: true, + validateFn: (result, isProofMode = false) => { + const parsed = JSON.parse(result); + const notes = isProofMode ? parsed.data : parsed; + expect(Array.isArray(notes)).toBe(true); + for (const note of notes) { + // ShieldedEncryptedNote.toJSON(): base64-encoded byte fields + expect(typeof note.cmx).toBe('string'); + expect(typeof note.nullifier).toBe('string'); + expect(typeof note.cvNet).toBe('string'); + expect(typeof note.encryptedNote).toBe('string'); + } + } + }, + { + name: 'getShieldedAnchors', + hasProofSupport: true, + needsParameters: false, + validateFn: (result, isProofMode = false) => { + const parsed = JSON.parse(result); + const anchors = isProofMode ? parsed.data : parsed; + // The site hex-encodes each 32-byte anchor in both modes + expect(Array.isArray(anchors)).toBe(true); + for (const anchor of anchors) { + expect(anchor).toMatch(/^[0-9a-f]{64}$/); + } + } + }, + { + name: 'getShieldedMostRecentAnchor', + hasProofSupport: true, + needsParameters: false, + // Declared as ProofMetadataResponseTyped: null when no anchor exists + allowsNullData: true, + validateFn: (result, isProofMode = false) => { + expect(result).toBeDefined(); + if (isProofMode) { + const parsed = JSON.parse(result); + if (parsed.data === null) return; // no anchor yet + expect(parsed.data).toMatch(/^[0-9a-f]{64}$/); + } else { + if (result === NO_RESULT) return; // no anchor yet + expect(result).toMatch(/^[0-9a-f]{64}$/); + } + } + }, + { + name: 'getShieldedNullifiers', + hasProofSupport: true, + needsParameters: true, + validateFn: (result, isProofMode = false) => { + const parsed = JSON.parse(result); + const statuses = isProofMode ? parsed.data : parsed; + expect(Array.isArray(statuses)).toBe(true); + expect(statuses.length).toBeGreaterThan(0); + for (const status of statuses) { + // ShieldedNullifierStatus.toJSON(): base64 nullifier + spent flag + expect(typeof status.nullifier).toBe('string'); + expect(typeof status.isSpent).toBe('boolean'); + } + } + } + ]; + + shieldedQueries.forEach(({ name, hasProofSupport, needsParameters, allowsNullData, validateFn }) => { + test.describe(`${name} query (parameterized)`, () => { + test('without proof info', async () => { + await evoSdkPage.setupQuery('shielded', name); + await evoSdkPage.disableProofInfo(); + + if (needsParameters) { + const success = await parameterInjector.injectParameters('shielded', name, 'testnet'); + expect(success).toBe(true); + } + + const result = await evoSdkPage.executeQueryAndGetResult(); + validateBasicQueryResult(result); + validateResultWithoutProof(result); + validateFn(result.result, false); + }); + + if (hasProofSupport) { + test('with proof info', async () => { + const { result, proofEnabled } = await executeQueryWithProof( + evoSdkPage, + parameterInjector, + 'shielded', + name, + 'testnet' + ); + + validateBasicQueryResult(result); + + if (proofEnabled) { + validateResultWithProof(result, { allowNullData: allowsNullData }); + validateFn(result.result, true); + } else { + validateResultWithoutProof(result); + validateFn(result.result, false); + } + }); + } + }); + }); + }); + test.describe('Error Handling', () => { test('should handle invalid identity ID gracefully', async () => { await evoSdkPage.setupQuery('identity', 'getIdentity'); diff --git a/tests/unit/operations-dispatch.test.js b/tests/unit/operations-dispatch.test.js new file mode 100644 index 0000000..eac8f59 --- /dev/null +++ b/tests/unit/operations-dispatch.test.js @@ -0,0 +1,269 @@ +import { describe, it, expect, vi, beforeAll } from 'vitest'; + +// Focused dispatch coverage for the callEvo() query switch: these operations +// convert form inputs (hex strings, bigint text, JSON clauses) before calling +// the SDK, and that conversion is otherwise only exercised by network-bound +// E2E tests. + +// operations.js transitively imports state.js, which touches `document` at +// import time; stub a minimal shim before dynamically importing (same +// approach as dynamic-handlers.test.js). +let callEvo; + +beforeAll(async () => { + vi.stubGlobal('document', { + getElementById: () => null, + querySelectorAll: () => [], + }); + ({ callEvo } = await import('../../public/src/operations.js')); +}); + +const HEX_A = 'a3f1c2d4e5b60718293a4b5c6d7e8f90a1b2c3d4e5f60718293a4b5c6d7e8f90'; + +function makeClient() { + return { + documents: { + count: vi.fn().mockResolvedValue('count'), + countWithProof: vi.fn().mockResolvedValue('countProof'), + sum: vi.fn().mockResolvedValue('sum'), + sumWithProof: vi.fn().mockResolvedValue('sumProof'), + average: vi.fn().mockResolvedValue('average'), + averageWithProof: vi.fn().mockResolvedValue('averageProof'), + history: vi.fn().mockResolvedValue('history'), + historyWithProof: vi.fn().mockResolvedValue('historyProof'), + }, + tokens: { + identityBalances: vi.fn().mockResolvedValue('balances'), + identityBalancesWithProof: vi.fn().mockResolvedValue('balancesProof'), + }, + shielded: { + poolState: vi.fn().mockResolvedValue(123n), + poolStateWithProof: vi.fn().mockResolvedValue('poolProof'), + encryptedNotes: vi.fn().mockResolvedValue([]), + encryptedNotesWithProof: vi.fn().mockResolvedValue('notesProof'), + anchors: vi.fn().mockResolvedValue([]), + anchorsWithProof: vi.fn().mockResolvedValue([]), + mostRecentAnchor: vi.fn().mockResolvedValue(undefined), + mostRecentAnchorWithProof: vi.fn().mockResolvedValue(undefined), + nullifiers: vi.fn().mockResolvedValue([]), + nullifiersWithProof: vi.fn().mockResolvedValue([]), + }, + }; +} + +function run(client, itemKey, values = {}, useProof = false) { + return callEvo(client, 'queries', itemKey, [], [], useProof, values); +} + +describe('document aggregate dispatch', () => { + const baseInputs = { + dataContractId: 'contract-id', + documentTypeName: 'domain', + where: '[["normalizedParentDomainName", "==", "dash"]]', + orderBy: '[["normalizedLabel", "asc"]]', + limit: 5, + }; + const expectedPayload = { + dataContractId: 'contract-id', + documentTypeName: 'domain', + where: [['normalizedParentDomainName', '==', 'dash']], + orderBy: [['normalizedLabel', 'asc']], + limit: 5, + startAfter: undefined, + startAt: undefined, + }; + + it('getDocumentCount builds the query payload and dispatches by proof mode', async () => { + const c = makeClient(); + await run(c, 'getDocumentCount', baseInputs); + expect(c.documents.count).toHaveBeenCalledWith(expectedPayload); + await run(c, 'getDocumentCount', baseInputs, true); + expect(c.documents.countWithProof).toHaveBeenCalledWith(expectedPayload); + }); + + it('includes groupBy only when values are provided', async () => { + const c = makeClient(); + await run(c, 'getDocumentCount', { ...baseInputs, groupBy: ['normalizedParentDomainName'] }); + expect(c.documents.count).toHaveBeenCalledWith({ + ...expectedPayload, + groupBy: ['normalizedParentDomainName'], + }); + await run(c, 'getDocumentCount', { ...baseInputs, groupBy: [] }); + expect(c.documents.count).toHaveBeenLastCalledWith(expectedPayload); + }); + + it('getDocumentSum passes the property as a positional argument', async () => { + const c = makeClient(); + await run(c, 'getDocumentSum', { ...baseInputs, sumProperty: 'amount' }); + expect(c.documents.sum).toHaveBeenCalledWith(expectedPayload, 'amount'); + await run(c, 'getDocumentSum', { ...baseInputs, sumProperty: 'amount' }, true); + expect(c.documents.sumWithProof).toHaveBeenCalledWith(expectedPayload, 'amount'); + }); + + it('getDocumentSum requires the property', async () => { + await expect(run(makeClient(), 'getDocumentSum', baseInputs)).rejects.toThrow('Sum property is required'); + }); + + it('getDocumentAverage passes the property as a positional argument', async () => { + const c = makeClient(); + await run(c, 'getDocumentAverage', { ...baseInputs, averageProperty: 'amount' }); + expect(c.documents.average).toHaveBeenCalledWith(expectedPayload, 'amount'); + await run(c, 'getDocumentAverage', { ...baseInputs, averageProperty: 'amount' }, true); + expect(c.documents.averageWithProof).toHaveBeenCalledWith(expectedPayload, 'amount'); + }); + + it('getDocumentAverage requires the property', async () => { + await expect(run(makeClient(), 'getDocumentAverage', baseInputs)).rejects.toThrow('Average property is required'); + }); + + it('rejects invalid where JSON with a labeled error', async () => { + await expect(run(makeClient(), 'getDocumentCount', { ...baseInputs, where: '{not json' })) + .rejects.toThrow('Invalid JSON in Where'); + }); + + it('getDocumentHistory builds its payload with optional fields omitted', async () => { + const c = makeClient(); + await run(c, 'getDocumentHistory', { + dataContractId: 'contract-id', + documentTypeName: 'domain', + documentId: 'doc-id', + }); + expect(c.documents.history).toHaveBeenCalledWith({ + dataContractId: 'contract-id', + documentTypeName: 'domain', + documentId: 'doc-id', + startAtMs: undefined, + limit: undefined, + offset: undefined, + }); + await run(c, 'getDocumentHistory', { + dataContractId: 'contract-id', + documentTypeName: 'domain', + documentId: 'doc-id', + startAtMs: 1700000000000, + limit: 3, + offset: 1, + }, true); + expect(c.documents.historyWithProof).toHaveBeenCalledWith({ + dataContractId: 'contract-id', + documentTypeName: 'domain', + documentId: 'doc-id', + startAtMs: 1700000000000, + limit: 3, + offset: 1, + }); + }); +}); + +describe('getTokenBalancesForIdentity dispatch', () => { + it('passes identity id and filtered token ids in both proof modes', async () => { + const c = makeClient(); + await run(c, 'getTokenBalancesForIdentity', { identityId: 'id-1', tokenIds: ['tok-1', '', null, 'tok-2'] }); + expect(c.tokens.identityBalances).toHaveBeenCalledWith('id-1', ['tok-1', 'tok-2']); + await run(c, 'getTokenBalancesForIdentity', { identityId: 'id-1', tokenIds: ['tok-1'] }, true); + expect(c.tokens.identityBalancesWithProof).toHaveBeenCalledWith('id-1', ['tok-1']); + }); +}); + +describe('shielded dispatch', () => { + it('getShieldedPoolState dispatches by proof mode', async () => { + const c = makeClient(); + await expect(run(c, 'getShieldedPoolState')).resolves.toBe(123n); + await run(c, 'getShieldedPoolState', {}, true); + expect(c.shielded.poolStateWithProof).toHaveBeenCalled(); + }); + + it('getShieldedEncryptedNotes converts startIndex to a BigInt without precision loss', async () => { + const c = makeClient(); + const huge = '18446744073709551615'; // exceeds Number.MAX_SAFE_INTEGER + await run(c, 'getShieldedEncryptedNotes', { startIndex: huge, count: 10 }); + expect(c.shielded.encryptedNotes).toHaveBeenCalledWith(18446744073709551615n, 10); + }); + + it('getShieldedEncryptedNotes defaults a blank startIndex to 0n', async () => { + const c = makeClient(); + await run(c, 'getShieldedEncryptedNotes', { startIndex: '', count: 5 }); + expect(c.shielded.encryptedNotes).toHaveBeenCalledWith(0n, 5); + await run(c, 'getShieldedEncryptedNotes', { count: 5 }, true); + expect(c.shielded.encryptedNotesWithProof).toHaveBeenCalledWith(0n, 5); + }); + + it('getShieldedEncryptedNotes rejects non-integer inputs', async () => { + await expect(run(makeClient(), 'getShieldedEncryptedNotes', { startIndex: '-1', count: 5 })) + .rejects.toThrow('Start index must be a non-negative integer'); + await expect(run(makeClient(), 'getShieldedEncryptedNotes', { startIndex: '1.5', count: 5 })) + .rejects.toThrow('Start index must be a non-negative integer'); + await expect(run(makeClient(), 'getShieldedEncryptedNotes', { count: 0 })) + .rejects.toThrow('Count must be a positive integer'); + await expect(run(makeClient(), 'getShieldedEncryptedNotes', { count: 2.5 })) + .rejects.toThrow('Count must be a positive integer'); + }); + + it('getShieldedNullifiers converts hex strings (with or without 0x) to 32-byte arrays', async () => { + const c = makeClient(); + await run(c, 'getShieldedNullifiers', { nullifiers: [HEX_A, `0x${HEX_A}`] }); + const [nullifiers] = c.shielded.nullifiers.mock.calls[0]; + expect(nullifiers).toHaveLength(2); + for (const bytes of nullifiers) { + expect(bytes).toBeInstanceOf(Uint8Array); + expect(bytes).toHaveLength(32); + expect(bytes[0]).toBe(0xa3); + expect(bytes[31]).toBe(0x90); + } + await run(c, 'getShieldedNullifiers', { nullifiers: [HEX_A] }, true); + expect(c.shielded.nullifiersWithProof).toHaveBeenCalled(); + }); + + it('getShieldedNullifiers rejects invalid input', async () => { + await expect(run(makeClient(), 'getShieldedNullifiers', { nullifiers: ['zz'.repeat(32)] })) + .rejects.toThrow('expected 64 hexadecimal characters'); + await expect(run(makeClient(), 'getShieldedNullifiers', { nullifiers: [HEX_A.slice(0, 62)] })) + .rejects.toThrow('expected 64 hexadecimal characters'); + await expect(run(makeClient(), 'getShieldedNullifiers', { nullifiers: [] })) + .rejects.toThrow('At least one nullifier is required'); + }); + + it('getShieldedAnchors hex-encodes raw results', async () => { + const c = makeClient(); + c.shielded.anchors.mockResolvedValue([ + new Uint8Array(32).fill(0xab), + new Uint8Array(32).fill(0x01), + ]); + await expect(run(c, 'getShieldedAnchors')).resolves.toEqual([ + 'ab'.repeat(32), + '01'.repeat(32), + ]); + }); + + it('getShieldedAnchors converts proof-wrapped data while preserving metadata and proof', async () => { + const c = makeClient(); + const metadata = { height: 1 }; + const proof = { quorumHash: 'abc' }; + c.shielded.anchorsWithProof.mockResolvedValue({ + data: [new Uint8Array(32).fill(0xcd)], + metadata, + proof, + }); + await expect(run(c, 'getShieldedAnchors', {}, true)).resolves.toEqual({ + data: ['cd'.repeat(32)], + metadata, + proof, + }); + }); + + it('getShieldedMostRecentAnchor hex-encodes a single anchor and passes undefined through', async () => { + const c = makeClient(); + c.shielded.mostRecentAnchor.mockResolvedValue(new Uint8Array(32).fill(0x0f)); + await expect(run(c, 'getShieldedMostRecentAnchor')).resolves.toBe('0f'.repeat(32)); + + c.shielded.mostRecentAnchor.mockResolvedValue(undefined); + await expect(run(c, 'getShieldedMostRecentAnchor')).resolves.toBeUndefined(); + + c.shielded.mostRecentAnchorWithProof.mockResolvedValue({ data: null, metadata: {}, proof: {} }); + await expect(run(c, 'getShieldedMostRecentAnchor', {}, true)).resolves.toEqual({ + data: null, + metadata: {}, + proof: {}, + }); + }); +}); diff --git a/tests/unit/result-format.test.js b/tests/unit/result-format.test.js index ca271b1..ffad5b1 100644 --- a/tests/unit/result-format.test.js +++ b/tests/unit/result-format.test.js @@ -70,6 +70,11 @@ describe('formatResult — Map handling', () => { const m = new Map([['bal', 5n]]); expect(formatResult(m)).toBe('{\n "bal": "5"\n}'); }); + + it('stringifies bigint Map keys (documents.history shape)', () => { + const m = new Map([[18446744073709551615n, { revision: 1 }]]); + expect(formatResult(m)).toBe('{\n "18446744073709551615": {\n "revision": 1\n }\n}'); + }); }); describe('formatResult — circular references', () => { From 78ea7cbbe2fd1e84e08e75aed41c0b9ec4752f31 Mon Sep 17 00:00:00 2001 From: thephez Date: Thu, 27 Aug 2026 14:13:54 -0400 Subject: [PATCH 2/3] fix: validate history documents and correct startAfter/startAt help text Validate every document returned by the getDocumentHistory E2E test with validateSingleDocument() so entries like { "123": null } can no longer pass on numeric keys alone. Correct the startAfter/startAt help text on getDocuments and the document aggregate queries to match the DocumentsQuery declaration: both are document IDs (exclusive/inclusive), not index values. Co-Authored-By: Claude Fable 5 --- public/api-definitions.json | 16 ++++++++-------- public/documentation-check-report.txt | 2 +- tests/e2e/queries/query-execution.spec.js | 1 + 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/public/api-definitions.json b/public/api-definitions.json index 381e25b..0b4044f 100644 --- a/public/api-definitions.json +++ b/public/api-definitions.json @@ -447,13 +447,13 @@ "name": "startAfter", "type": "text", "label": "Start After", - "help": "Document ID or index value to resume from." + "help": "Exclusive document ID to resume from." }, { "name": "startAt", "type": "text", "label": "Start At", - "help": "Explicit starting index value (JSON string)." + "help": "Inclusive document ID to start from." } ], "sdk_method": "documents.query" @@ -532,13 +532,13 @@ "name": "startAfter", "type": "text", "label": "Start After", - "help": "Document ID or index value to resume from." + "help": "Exclusive document ID to resume from." }, { "name": "startAt", "type": "text", "label": "Start At", - "help": "Explicit starting index value (JSON string)." + "help": "Inclusive document ID to start from." } ], "sdk_method": "documents.count" @@ -597,13 +597,13 @@ "name": "startAfter", "type": "text", "label": "Start After", - "help": "Document ID or index value to resume from." + "help": "Exclusive document ID to resume from." }, { "name": "startAt", "type": "text", "label": "Start At", - "help": "Explicit starting index value (JSON string)." + "help": "Inclusive document ID to start from." } ], "sdk_method": "documents.sum" @@ -662,13 +662,13 @@ "name": "startAfter", "type": "text", "label": "Start After", - "help": "Document ID or index value to resume from." + "help": "Exclusive document ID to resume from." }, { "name": "startAt", "type": "text", "label": "Start At", - "help": "Explicit starting index value (JSON string)." + "help": "Inclusive document ID to start from." } ], "sdk_method": "documents.average" diff --git a/public/documentation-check-report.txt b/public/documentation-check-report.txt index 3f08769..717fb07 100644 --- a/public/documentation-check-report.txt +++ b/public/documentation-check-report.txt @@ -1,7 +1,7 @@ ================================================================================ Evo SDK Documentation Check ================================================================================ -Timestamp: 2026-08-27T13:56:40.383516 +Timestamp: 2026-08-27T14:10:15.480441 ✅ All documentation is up to date! ================================================================================ \ No newline at end of file diff --git a/tests/e2e/queries/query-execution.spec.js b/tests/e2e/queries/query-execution.spec.js index a85f11d..9d4588c 100644 --- a/tests/e2e/queries/query-execution.spec.js +++ b/tests/e2e/queries/query-execution.spec.js @@ -696,6 +696,7 @@ test.describe('Evo SDK Query Execution Tests', () => { for (const key of keys) { expect(key).toMatch(/^\d+$/); } + Object.values(history).forEach(doc => validateSingleDocument(doc)); } } ]; From b12bfa5451db67d749104aca9f5c3ba61cee097a Mon Sep 17 00:00:00 2001 From: thephez Date: Thu, 27 Aug 2026 14:22:24 -0400 Subject: [PATCH 3/3] feat: render Markdown subset in parameter descriptions Convert the small Markdown subset used by SDK declaration JSDoc (paragraphs, bullet lists, and inline code spans) into HTML when generating docs.html, instead of emitting it as raw escaped text in a single paragraph. Add matching styles for the new description list and code spans. --- public/docs.css | 23 +++++++++++++++++++ scripts/generate_docs.py | 49 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/public/docs.css b/public/docs.css index 63cabe8..b147637 100644 --- a/public/docs.css +++ b/public/docs.css @@ -393,6 +393,29 @@ h3 { margin: 5px 0 0; } +.parameter-description code, +.parameter-description-list code { + color: #526684; + font-size: 0.85em; +} + +.parameter-description-list { + color: #71829d; + display: grid; + font-size: 0.88rem; + gap: 5px; + list-style: none; + margin: 7px 0 0 10px; + padding: 0; +} + +.parameter-description-list li { + background: #f7f9fc; + border-left: 2px solid #b9cae3; + border-radius: 0 4px 4px 0; + padding: 6px 10px; +} + .parameter-properties { border-left: 2px solid #e2e9f3; margin: 10px 0 0 8px; diff --git a/scripts/generate_docs.py b/scripts/generate_docs.py index 0104f35..37184ab 100644 --- a/scripts/generate_docs.py +++ b/scripts/generate_docs.py @@ -638,6 +638,51 @@ def safe_value(text) -> str: return escape(str(text), quote=False) +def render_description_html(text, css_class: str = 'parameter-description') -> str: + """Render the small Markdown subset used by declaration JSDoc.""" + if not text: + return '' + + def render_inline(value: str) -> str: + escaped = safe_value(value) + return re.sub(r'`([^`]+)`', r'\1', escaped) + + blocks: List[str] = [] + paragraph: List[str] = [] + list_items: List[List[str]] = [] + + def flush_paragraph() -> None: + if paragraph: + blocks.append(f'

{render_inline(" ".join(paragraph))}

') + paragraph.clear() + + def flush_list() -> None: + if list_items: + items = ''.join( + f'
  • {render_inline(" ".join(item))}
  • ' + for item in list_items + ) + blocks.append(f'
      {items}
    ') + list_items.clear() + + for raw_line in str(text).splitlines(): + line = raw_line.strip() + if not line: + flush_paragraph() + flush_list() + elif line.startswith('- '): + flush_paragraph() + list_items.append([line[2:].strip()]) + elif list_items: + list_items[-1].append(line) + else: + paragraph.append(line) + + flush_paragraph() + flush_list() + return '\n'.join(blocks) + + def render_parameter(param: dict) -> str: name = safe_value(param.get('name') or 'Parameter') param_type = str(param.get('type', 'text')) @@ -658,7 +703,7 @@ def render_parameter(param: dict) -> str: description = param.get('description') if description: - lines.append(f'

    {safe_value(description)}

    ') + lines.append(textwrap.indent(render_description_html(description), ' ')) properties = param.get('properties') or [] if properties: @@ -680,7 +725,7 @@ def render_parameter(param: dict) -> str: ' ', ]) if prop.get('description'): - lines.append(f'

    {safe_value(prop["description"])}

    ') + lines.append(textwrap.indent(render_description_html(prop['description']), ' ')) lines.append(' ') lines.append(' ')