From 4f083e59cc025f06518735186092b4c14306d808 Mon Sep 17 00:00:00 2001 From: Alex Pitsikoulis Date: Fri, 15 Aug 2025 15:41:09 -0700 Subject: [PATCH 01/14] wip: started upgrade to record provider interface and started record scanner implementation of said interface --- sdk/src/record-provider.ts | 140 +++++++++++++++++++---- sdk/src/record-scanner.ts | 229 +++++++++++++++++++++++++++++++++++++ 2 files changed, 346 insertions(+), 23 deletions(-) create mode 100644 sdk/src/record-scanner.ts diff --git a/sdk/src/record-provider.ts b/sdk/src/record-provider.ts index 5f6afdc73..0f2f245ac 100644 --- a/sdk/src/record-provider.ts +++ b/sdk/src/record-provider.ts @@ -8,9 +8,60 @@ import { AleoNetworkClient } from "./network-client.js"; * implementations. */ interface RecordSearchParams { + unspent: boolean; [key: string]: any; // This allows for arbitrary keys with any type values } +type RecordsResponseFilter = { + program: boolean; + record: boolean; + function: boolean; + transition: boolean; + blockHeight: boolean; + transactionId: boolean; + transitionId: boolean; + ioIndex: boolean; +} + +type EncryptedRecord = { + commitment: string; + checksum?: string; + blockHeight?: number; + programName?: string; + functionName?: string; + outputIndex?: number; + owner?: string; + recordCiphertext?: string; + recordName?: string; + recordNonce?: string; + transactionId?: string; + transitionId?: string; + transactionIndex?: number; + transitionIndex?: number; +} + +type OwnedRecord = { + blockHeight?: number; + commitment?: string; + functionName?: string; + outputIndex?: number; + owner?: string; + programName?: string; + recordCiphertext?: string; + recordPlaintext?: string; + recordName?: string; + spent?: boolean; + tag?: string; + transactionId?: string; + transitionId?: string; + transactionIndex?: number; + transitionIndex?: number; +} + +interface RecordResponse { + owner?: string; +} + /** * Interface for a record provider. A record provider is used to find records for use in deployment and execution * transactions on the Aleo Network. A default implementation is provided by the NetworkRecordProvider class. However, @@ -18,7 +69,11 @@ interface RecordSearchParams { * implementing this interface. */ interface RecordProvider { - account: Account + getEncryptedRecords(recordsFilter: RecordSearchParams, responseFilter: RecordsResponseFilter): Promise; + + serialNumbersExist(serialNumbers: string[]): Promise>; + + tagsExist(tags: string[]): Promise>; /** * Find a credits.aleo record with a given number of microcredits from the chosen provider @@ -42,7 +97,7 @@ interface RecordProvider { * const programManager = new ProgramManager("https://api.explorer.provable.com/v1", keyProvider, recordProvider); * programManager.transfer(1, "aleo166q6ww6688cug7qxwe7nhctjpymydwzy2h7rscfmatqmfwnjvggqcad0at", "public", 0.5); */ - findCreditsRecord(microcredits: number, unspent: boolean, nonces?: string[], searchParameters?: RecordSearchParams): Promise; + findCreditsRecord(microcredits: number, searchParameters: RecordSearchParams, nonces?: string[]): Promise; /** * Find a list of credit.aleo records with a given number of microcredits from the chosen provider @@ -68,7 +123,7 @@ interface RecordProvider { * const programManager = new ProgramManager("https://api.explorer.provable.com/v1", keyProvider, recordProvider); * programManager.transfer(1, "aleo166q6ww6688cug7qxwe7nhctjpymydwzy2h7rscfmatqmfwnjvggqcad0at", "public", 0.5); */ - findCreditsRecords(microcreditAmounts: number[], unspent: boolean, nonces?: string[], searchParameters?: RecordSearchParams): Promise; + findCreditsRecords(microcreditAmounts: number[], searchParameters: RecordSearchParams, nonces?: string[]): Promise; /** * Find an arbitrary record @@ -101,7 +156,7 @@ interface RecordProvider { * * const record = await recordProvider.findRecord(true, [], params); */ - findRecord(unspent: boolean, nonces?: string[], searchParameters?: RecordSearchParams): Promise; + findRecord(searchParameters: RecordSearchParams, filterFn?: (record: RecordPlaintext) => boolean, nonces?: string[]): Promise; /** * Find multiple records from arbitrary programs @@ -135,7 +190,7 @@ interface RecordProvider { * const params = new CustomRecordSearch(0, 100, 5000, 2, "credits.aleo", "credits"); * const records = await recordProvider.findRecord(true, [], params); */ - findRecords(unspent: boolean, nonces?: string[], searchParameters?: RecordSearchParams): Promise; + findRecords(searchParameters?: RecordSearchParams, filterFn?: (record: RecordPlaintext) => boolean, nonces?: string[],): Promise; } /** @@ -187,7 +242,7 @@ class NetworkRecordProvider implements RecordProvider { * programManager.transfer(1, "aleo166q6ww6688cug7qxwe7nhctjpymydwzy2h7rscfmatqmfwnjvggqcad0at", "public", 0.5); * * */ - async findCreditsRecords(microcredits: number[], unspent: boolean, nonces?: string[], searchParameters?: RecordSearchParams): Promise { + async findCreditsRecords(microcredits: number[], searchParameters: RecordSearchParams, nonces?: string[]): Promise { let startHeight = 0; let endHeight = 0; let maxAmount = undefined; @@ -208,10 +263,6 @@ class NetworkRecordProvider implements RecordProvider { if ("maxAmount" in searchParameters && typeof searchParameters["maxAmount"] == "number") { maxAmount = searchParameters["maxAmount"]; } - - if ("unspent" in searchParameters && typeof searchParameters["unspent"] == "boolean") { - unspent = searchParameters["unspent"] - } } // If the end height is not specified, use the current block height @@ -225,7 +276,15 @@ class NetworkRecordProvider implements RecordProvider { logAndThrow("Start height must be less than end height"); } - return await this.networkClient.findRecords(startHeight, endHeight, unspent, ["credits.aleo"], microcredits, maxAmount, nonces, this.account.privateKey()); + const recordsPts = await this.networkClient.findRecords(startHeight, endHeight, searchParameters.unspent, ["credits.aleo"], microcredits, maxAmount, nonces, this.account.privateKey()); + return recordsPts.map((record) => ({ + commitment: record.commitment().toString(), + owner: record.owner().toString(), + programName: 'credits.aleo', + recordName: 'credits', + recordPlaintext: record.to_string(), + tag: record.tag().toString(), + })); } /** @@ -255,11 +314,11 @@ class NetworkRecordProvider implements RecordProvider { * const programManager = new ProgramManager("https://api.explorer.provable.com/v1", keyProvider, recordProvider); * programManager.transfer(1, "aleo166q6ww6688cug7qxwe7nhctjpymydwzy2h7rscfmatqmfwnjvggqcad0at", "public", 0.5); */ - async findCreditsRecord(microcredits: number, unspent: boolean, nonces?: string[], searchParameters?: RecordSearchParams): Promise { + async findCreditsRecord(microcredits: number, searchParameters: RecordSearchParams,nonces?: string[]): Promise { let records = null; try { - records = await this.findCreditsRecords([microcredits], unspent, nonces, searchParameters); + records = await this.findCreditsRecords([microcredits], searchParameters, nonces); } catch (e) { console.log("No records found with error:", e); } @@ -275,14 +334,27 @@ class NetworkRecordProvider implements RecordProvider { /** * Find an arbitrary record. WARNING: This function is not implemented yet and will throw an error. */ - async findRecord(unspent: boolean, nonces?: string[], searchParameters?: RecordSearchParams): Promise { - throw new Error("Not implemented"); + async findRecord(searchParameters: RecordSearchParams, filterFn?: (record: RecordPlaintext) => boolean, nonces?: string[]): Promise { + let records; + + try { + records = await this.findRecords(searchParameters, filterFn, nonces); + } catch (e) { + console.log("No records found with error:", e); + } + + if (records && records.length > 0) { + return records[0]; + } + + console.error("Record not found with error:", records); + throw new Error("Record not found"); } /** * Find multiple records from a specified program. */ - async findRecords(unspent: boolean, nonces?: string[], searchParameters?: RecordSearchParams): Promise { + async findRecords(searchParameters: RecordSearchParams, filterFn?: (record: RecordPlaintext) => boolean, nonces?: string[]): Promise { let startHeight = 0; let endHeight = 0; let amounts = undefined; @@ -317,10 +389,6 @@ class NetworkRecordProvider implements RecordProvider { if ("programs" in searchParameters && Array.isArray(searchParameters["programs"]) && searchParameters["programs"].every((item: any) => typeof item === "string")) { programs = searchParameters["programs"]; } - - if ("unspent" in searchParameters && typeof searchParameters["unspent"] == "boolean") { - unspent = searchParameters["unspent"] - } } // If the end height is not specified, use the current block height @@ -334,9 +402,26 @@ class NetworkRecordProvider implements RecordProvider { logAndThrow("Start height must be less than end height"); } - return await this.networkClient.findRecords(startHeight, endHeight, unspent, programs, amounts, maxAmount, nonces, this.account.privateKey()); + const recordPts = await this.networkClient.findRecords(startHeight, endHeight, searchParameters.unspent, programs, amounts, maxAmount, nonces, this.account.privateKey()); + return recordPts.map((record) => ({ + commitment: record.commitment().toString(), + owner: record.owner().toString(), + programName: record.program().toString(), + recordName: record.name().toString(), + })); } + async getEncryptedRecords(recordsFilter: RecordSearchParams, responseFilter: RecordsResponseFilter): Promise { + throw new Error("Not implemented"); + } + + async serialNumbersExist(serialNumbers: string[]): Promise> { + throw new Error("Not implemented"); + } + + async tagsExist(tags: string[]): Promise> { + throw new Error("Not implemented"); + } } /** @@ -360,10 +445,19 @@ class NetworkRecordProvider implements RecordProvider { class BlockHeightSearch implements RecordSearchParams { startHeight: number; endHeight: number; - constructor(startHeight: number, endHeight: number) { + unspent: boolean; + constructor(startHeight: number, endHeight: number, unspent: boolean) { this.startHeight = startHeight; this.endHeight = endHeight; + this.unspent = unspent; } } -export { BlockHeightSearch, NetworkRecordProvider, RecordProvider, RecordSearchParams}; +export { + BlockHeightSearch, + EncryptedRecord, + OwnedRecord, + RecordProvider, + RecordSearchParams, + RecordsResponseFilter, +}; diff --git a/sdk/src/record-scanner.ts b/sdk/src/record-scanner.ts new file mode 100644 index 000000000..02356bad2 --- /dev/null +++ b/sdk/src/record-scanner.ts @@ -0,0 +1,229 @@ +import { Account } from "./account"; +import { EncryptedRecord, OwnedRecord, RecordProvider, RecordSearchParams, RecordsResponseFilter } from "./record-provider"; +import { RecordPlaintext } from "./wasm"; + +type RegistrationRequest = { + viewKey: string; + start: number; +} + +interface RecordsFilter extends RecordSearchParams { + start: number; + end?: number; + program?: string; + record?: string; + function?: string; +} + +interface OwnedFilter extends RecordSearchParams { + decrypt?: boolean; + filter?: RecordsFilter; + responseFilter?: RecordsResponseFilter; +} + +interface OwnedFilterWithUuid extends OwnedFilter { + uuid: string; +} + +class RecordScanner implements RecordProvider { + readonly url: string; + private account?: Account; + private uuid?: string; + + constructor(url: string, account?: Account) { + this.account = account; + this.url = url; + } + + async setAccount(account: Account): Promise { + this.account = account; + } + + async register(startBlock: number): Promise { + let request: RegistrationRequest; + if (!this.account) { + throw new Error("Account not set"); + } else { + request = { + viewKey: this.account.viewKey(), + start: startBlock, + }; + } + + try { + const response = await this.recordScannerServiceRequest( + new Request(`${this.url}/register`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(request), + }) + ); + + const data = await response.json(); + this.uuid = data.uuid; + } catch (error) { + console.error(`Failed to register view key: ${error}`); + throw error; + } + } + + async getEncryptedRecords(recordsFilter: RecordsFilter, responseFilter: RecordsResponseFilter): Promise { + try { + const response = await this.recordScannerServiceRequest( + new Request(`${this.url}/records/encrypted?${this.buildQueryString(recordsFilter, responseFilter)}`, { + method: "GET", + headers: { "Content-Type": "application/json" }, + }), + ); + + const data = await response.json(); + return data.records; + } catch (error) { + console.error(`Failed to get encrypted records: ${error}`); + throw error; + } + } + + async serialNumbersExist(serialNumbers: string[]): Promise> { + try { + const response = await this.recordScannerServiceRequest( + new Request(`${this.url}/records/sns`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(serialNumbers), + }), + ); + + return await response.json(); + } catch (error) { + console.error(`Failed to check if serial numbers exist: ${error}`); + throw error; + } + } + + async tagsExist(tags: string[]): Promise> { + try { + const response = await this.recordScannerServiceRequest( + new Request(`${this.url}/records/tags`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(tags), + }), + ); + + return await response.json(); + } catch (error) { + console.error(`Failed to check if tags exist: ${error}`); + throw error; + } + } + + async findRecord(searchParameters: OwnedFilter, filterFn?: (record: RecordPlaintext) => boolean): Promise { + try { + const records = await this.findRecords(searchParameters, filterFn); + + if (records.length > 0) { + return records[0]; + } + + throw new Error("Record not found"); + } catch (error) { + console.error(`Failed to find record: ${error}`); + throw error; + } + } + + async findRecords(searchParameters: OwnedFilter, filterFn?: (record: RecordPlaintext) => boolean): Promise { + if (!this.uuid) { + throw new Error("Not registered"); + } + + const filterWithUuid: OwnedFilterWithUuid = { + ...searchParameters, + uuid: this.uuid, + }; + + try { + const response = await this.recordScannerServiceRequest( + new Request(`${this.url}/records/owned`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(filterWithUuid), + }), + ); + + const records = await response.json(); + return filterFn ? records.filter(filterFn) : records; + } catch (error) { + console.error(`Failed to get owned records: ${error}`); + throw error; + } + } + + async findCreditsRecord(microcredits: number, searchParameters: OwnedFilter, nonces?: string[]): Promise { + try { + const records = await this.findRecords({ + ...searchParameters, + program: "credits.aleo", + record: "credits", + decrypt: true, + }); + + const record = records.find(record => { + const plaintext = RecordPlaintext.fromString(record.recordPlaintext); + const amount = plaintext.getMember("microcredits").toString(); + return amount === `${microcredits}u64`; + }); + + if (!record) { + throw new Error("Record not found"); + } + + return record; + } catch (error) { + console.error(`Failed to find credits record: ${error}`); + throw error; + } + } + + async findCreditsRecords(microcreditAmounts: number[], searchParameters: OwnedFilter, nonces?: string[]): Promise { + try { + const records = await this.findRecords({ + ...searchParameters, + program: "credits.aleo", + record: "credits", + decrypt: true, + }); + return records.filter(record => { + const plaintext = RecordPlaintext.fromString(record.recordPlaintext); + const amount = plaintext.getMember("microcredits").toString(); + return microcreditAmounts.includes(parseInt(amount.replace("u64", ""))); + }); + } catch (error) { + console.error(`Failed to find credits records: ${error}`); + throw error; + } + } + + + private async recordScannerServiceRequest(req: Request): Promise { + try { + const response = await fetch(req); + + if (!response.ok) { + throw new Error(await response.text() ?? `Request to ${req.url} failed with status ${response.status}`); + } + + return response; + } catch (error) { + console.error(`Failed to make request to ${req.url}: ${error}`); + throw error; + } + } + + private buildQueryString(recordsFilter: RecordSearchParams, responseFilter: RecordsResponseFilter): string { + return Object.entries({ ...recordsFilter, ...responseFilter }) + .map(([key, value]) => `${key}=${value}`) + .join("&"); + } +} \ No newline at end of file From 83fbe5444c0134b4d4c20e6d3aa30853f904dd39 Mon Sep 17 00:00:00 2001 From: Alex Pitsikoulis Date: Mon, 18 Aug 2025 15:12:02 -0700 Subject: [PATCH 02/14] moved record provider types to models dir. moved nonces out of record provider method signatures into RecordSearchParams interface. added jsdoc comments to record scanner --- .../models/record-provider/encryptedRecord.ts | 16 ++ sdk/src/models/record-provider/ownedRecord.ts | 17 ++ .../record-provider/recordSearchParams.ts | 9 + .../record-provider/recordsResponseFilter.ts | 10 + sdk/src/models/record-scanner/ownedFilter.ts | 10 + .../models/record-scanner/recordsFilter.ts | 9 + .../record-scanner/registrationRequest.ts | 4 + sdk/src/record-provider.ts | 212 ++++++++---------- sdk/src/record-scanner.ts | 155 +++++++++---- 9 files changed, 281 insertions(+), 161 deletions(-) create mode 100644 sdk/src/models/record-provider/encryptedRecord.ts create mode 100644 sdk/src/models/record-provider/ownedRecord.ts create mode 100644 sdk/src/models/record-provider/recordSearchParams.ts create mode 100644 sdk/src/models/record-provider/recordsResponseFilter.ts create mode 100644 sdk/src/models/record-scanner/ownedFilter.ts create mode 100644 sdk/src/models/record-scanner/recordsFilter.ts create mode 100644 sdk/src/models/record-scanner/registrationRequest.ts diff --git a/sdk/src/models/record-provider/encryptedRecord.ts b/sdk/src/models/record-provider/encryptedRecord.ts new file mode 100644 index 000000000..5b579e5a8 --- /dev/null +++ b/sdk/src/models/record-provider/encryptedRecord.ts @@ -0,0 +1,16 @@ +export type EncryptedRecord = { + commitment: string; + checksum?: string; + blockHeight?: number; + programName?: string; + functionName?: string; + outputIndex?: number; + owner?: string; + recordCiphertext?: string; + recordName?: string; + recordNonce?: string; + transactionId?: string; + transitionId?: string; + transactionIndex?: number; + transitionIndex?: number; +} \ No newline at end of file diff --git a/sdk/src/models/record-provider/ownedRecord.ts b/sdk/src/models/record-provider/ownedRecord.ts new file mode 100644 index 000000000..7c8b04a20 --- /dev/null +++ b/sdk/src/models/record-provider/ownedRecord.ts @@ -0,0 +1,17 @@ +export type OwnedRecord = { + blockHeight?: number; + commitment?: string; + functionName?: string; + outputIndex?: number; + owner?: string; + programName?: string; + recordCiphertext?: string; + recordPlaintext?: string; + recordName?: string; + spent?: boolean; + tag?: string; + transactionId?: string; + transitionId?: string; + transactionIndex?: number; + transitionIndex?: number; +} \ No newline at end of file diff --git a/sdk/src/models/record-provider/recordSearchParams.ts b/sdk/src/models/record-provider/recordSearchParams.ts new file mode 100644 index 000000000..879d2fdbf --- /dev/null +++ b/sdk/src/models/record-provider/recordSearchParams.ts @@ -0,0 +1,9 @@ +/** + * Interface for record search parameters. This allows for arbitrary search parameters to be passed to record provider + * implementations. + */ +export interface RecordSearchParams { + unspent: boolean; + nonces?: string[]; + [key: string]: any; // This allows for arbitrary keys with any type values +} \ No newline at end of file diff --git a/sdk/src/models/record-provider/recordsResponseFilter.ts b/sdk/src/models/record-provider/recordsResponseFilter.ts new file mode 100644 index 000000000..3fa0dd461 --- /dev/null +++ b/sdk/src/models/record-provider/recordsResponseFilter.ts @@ -0,0 +1,10 @@ +export type RecordsResponseFilter = { + program: boolean; + record: boolean; + function: boolean; + transition: boolean; + blockHeight: boolean; + transactionId: boolean; + transitionId: boolean; + ioIndex: boolean; +} \ No newline at end of file diff --git a/sdk/src/models/record-scanner/ownedFilter.ts b/sdk/src/models/record-scanner/ownedFilter.ts new file mode 100644 index 000000000..3eaf9078b --- /dev/null +++ b/sdk/src/models/record-scanner/ownedFilter.ts @@ -0,0 +1,10 @@ +import { RecordSearchParams } from "../record-provider/recordSearchParams"; +import { RecordsFilter } from "./recordsFilter"; +import { RecordsResponseFilter } from "../record-provider/recordsResponseFilter"; + +export interface OwnedFilter extends RecordSearchParams { + decrypt?: boolean; + filter?: RecordsFilter; + responseFilter?: RecordsResponseFilter; + uuid?: string; +} \ No newline at end of file diff --git a/sdk/src/models/record-scanner/recordsFilter.ts b/sdk/src/models/record-scanner/recordsFilter.ts new file mode 100644 index 000000000..766834976 --- /dev/null +++ b/sdk/src/models/record-scanner/recordsFilter.ts @@ -0,0 +1,9 @@ +import { RecordSearchParams } from "../record-provider/recordSearchParams"; + +export interface RecordsFilter extends RecordSearchParams { + start: number; + end?: number; + program?: string; + record?: string; + function?: string; +} \ No newline at end of file diff --git a/sdk/src/models/record-scanner/registrationRequest.ts b/sdk/src/models/record-scanner/registrationRequest.ts new file mode 100644 index 000000000..5dfaf93a7 --- /dev/null +++ b/sdk/src/models/record-scanner/registrationRequest.ts @@ -0,0 +1,4 @@ +export type RegistrationRequest = { + viewKey: string; + start: number; +} \ No newline at end of file diff --git a/sdk/src/record-provider.ts b/sdk/src/record-provider.ts index 0f2f245ac..04d2ed615 100644 --- a/sdk/src/record-provider.ts +++ b/sdk/src/record-provider.ts @@ -1,66 +1,10 @@ -import { RecordPlaintext } from "./wasm.js"; import { logAndThrow } from "./utils.js"; import { Account } from "./account.js"; import { AleoNetworkClient } from "./network-client.js"; - -/** - * Interface for record search parameters. This allows for arbitrary search parameters to be passed to record provider - * implementations. - */ -interface RecordSearchParams { - unspent: boolean; - [key: string]: any; // This allows for arbitrary keys with any type values -} - -type RecordsResponseFilter = { - program: boolean; - record: boolean; - function: boolean; - transition: boolean; - blockHeight: boolean; - transactionId: boolean; - transitionId: boolean; - ioIndex: boolean; -} - -type EncryptedRecord = { - commitment: string; - checksum?: string; - blockHeight?: number; - programName?: string; - functionName?: string; - outputIndex?: number; - owner?: string; - recordCiphertext?: string; - recordName?: string; - recordNonce?: string; - transactionId?: string; - transitionId?: string; - transactionIndex?: number; - transitionIndex?: number; -} - -type OwnedRecord = { - blockHeight?: number; - commitment?: string; - functionName?: string; - outputIndex?: number; - owner?: string; - programName?: string; - recordCiphertext?: string; - recordPlaintext?: string; - recordName?: string; - spent?: boolean; - tag?: string; - transactionId?: string; - transitionId?: string; - transactionIndex?: number; - transitionIndex?: number; -} - -interface RecordResponse { - owner?: string; -} +import { RecordsResponseFilter } from "./models/record-provider/recordsResponseFilter.js"; +import { EncryptedRecord } from "./models/record-provider/encryptedRecord.js"; +import { OwnedRecord } from "./models/record-provider/ownedRecord.js"; +import { RecordSearchParams } from "./models/record-provider/recordSearchParams.js"; /** * Interface for a record provider. A record provider is used to find records for use in deployment and execution @@ -69,68 +13,81 @@ interface RecordResponse { * implementing this interface. */ interface RecordProvider { - getEncryptedRecords(recordsFilter: RecordSearchParams, responseFilter: RecordsResponseFilter): Promise; + /** + * Find encrypted records from the chosen provider + * + * @param {RecordSearchParams} recordsFilter The filter to use to find the records + * @param {RecordsResponseFilter} responseFilter The filter to use to filter the response + * @returns {Promise} The encrypted records + */ + encryptedRecords(recordsFilter: RecordSearchParams, responseFilter: RecordsResponseFilter): Promise; - serialNumbersExist(serialNumbers: string[]): Promise>; + /** + * Check if a list of serial numbers exist in the chosen provider + * + * @param {string[]} serialNumbers The serial numbers to check + * @returns {Promise>} A record of serial numbers and whether they exist + */ + checkSerialNumbers(serialNumbers: string[]): Promise>; - tagsExist(tags: string[]): Promise>; + /** + * Check if a list of tags exist in the chosen provider + * + * @param {string[]} tags The tags to check + * @returns {Promise>} A record of tags and whether they exist + */ + checkTags(tags: string[]): Promise>; /** * Find a credits.aleo record with a given number of microcredits from the chosen provider * * @param {number} microcredits The number of microcredits to search for - * @param {boolean} unspent Whether or not the record is unspent - * @param {string[]} nonces Nonces of records already found so they are not found again * @param {RecordSearchParams} searchParameters Additional parameters to search for - * @returns {Promise} The record if found, otherwise an error + * @returns {Promise} The record if one is found * * @example * // A class implementing record provider can be used to find a record with a given number of microcredits - * const record = await recordProvider.findCreditsRecord(5000, true, []); + * const record = await recordProvider.findCreditsRecord(5000, { unspent: true, nonces: [] }); * * // When a record is found but not yet used, its nonce should be added to the nonces array so that it is not * // found again if a subsequent search is performed - * const record2 = await recordProvider.findCreditsRecord(5000, true, [record.nonce()]); + * const record2 = await recordProvider.findCreditsRecord(5000, { unspent: true, nonces: [record.nonce()] }); * * // When the program manager is initialized with the record provider it will be used to find automatically find * // fee records and amount records for value transfers so that they do not need to be specified manually * const programManager = new ProgramManager("https://api.explorer.provable.com/v1", keyProvider, recordProvider); * programManager.transfer(1, "aleo166q6ww6688cug7qxwe7nhctjpymydwzy2h7rscfmatqmfwnjvggqcad0at", "public", 0.5); */ - findCreditsRecord(microcredits: number, searchParameters: RecordSearchParams, nonces?: string[]): Promise; + findCreditsRecord(microcredits: number, searchParameters: RecordSearchParams): Promise; /** * Find a list of credit.aleo records with a given number of microcredits from the chosen provider * - * @param {number} microcreditAmounts A list of separate microcredit amounts to search for (e.g. [5000, 100000]) - * @param {boolean} unspent Whether or not the record is unspent - * @param {string[]} nonces Nonces of records already found so that they are not found again + * @param {number[]} microcreditAmounts A list of separate microcredit amounts to search for (e.g. [5000, 100000]) * @param {RecordSearchParams} searchParameters Additional parameters to search for - * @returns {Promise} A list of records with a value greater or equal to the amounts specified if such records exist, otherwise an error + * @returns {Promise} A list of records with a value greater or equal to the amounts specified if such records exist, otherwise an error * * @example * // A class implementing record provider can be used to find a record with a given number of microcredits - * const records = await recordProvider.findCreditsRecords([5000, 5000], true, []); + * const records = await recordProvider.findCreditsRecords([5000, 5000], { unspent: true, nonces: [] }); * * // When a record is found but not yet used, it's nonce should be added to the nonces array so that it is not * // found again if a subsequent search is performed * const nonces = []; * records.forEach(record => { nonces.push(record.nonce()) }); - * const records2 = await recordProvider.findCreditsRecord(5000, true, nonces); + * const records2 = await recordProvider.findCreditsRecord(5000, { unspent: true, nonces }); * * // When the program manager is initialized with the record provider it will be used to find automatically find * // fee records and amount records for value transfers so that they do not need to be specified manually * const programManager = new ProgramManager("https://api.explorer.provable.com/v1", keyProvider, recordProvider); * programManager.transfer(1, "aleo166q6ww6688cug7qxwe7nhctjpymydwzy2h7rscfmatqmfwnjvggqcad0at", "public", 0.5); */ - findCreditsRecords(microcreditAmounts: number[], searchParameters: RecordSearchParams, nonces?: string[]): Promise; + findCreditsRecords(microcreditAmounts: number[], searchParameters: RecordSearchParams): Promise; /** * Find an arbitrary record - * @param {boolean} unspent Whether or not the record is unspent - * @param {string[]} nonces Nonces of records already found so that they are not found again * @param {RecordSearchParams} searchParameters Additional parameters to search for - * @returns {Promise} The record if found, otherwise an error + * @returns {Promise} The record if found, otherwise an error * * @example * // The RecordSearchParams interface can be used to create parameters for custom record searches which can then @@ -143,29 +100,41 @@ interface RecordProvider { * amount: number; * program: string; * recordName: string; - * constructor(startHeight: number, endHeight: number, credits: number, maxRecords: number, programName: string, recordName: string) { + * nonces: string[]; + * unspent: boolean; + * constructor( + * startHeight: number, + * endHeight: number, + * credits: number, + * maxRecords: number, + * programName: string, + * recordName: string, + * nonces: string[], + * unspent: boolean + * ) { * this.startHeight = startHeight; * this.endHeight = endHeight; * this.amount = amount; * this.program = programName; * this.recordName = recordName; + * this.nonces = nonces; + * this.unspent = unspent; * } * } * - * const params = new CustomRecordSearch(0, 100, 5000, "credits.aleo", "credits"); + * const params = new CustomRecordSearch(0, 100, 5000, "credits.aleo", "credits", [], true); * - * const record = await recordProvider.findRecord(true, [], params); + * const record = await recordProvider.findRecord(params); */ - findRecord(searchParameters: RecordSearchParams, filterFn?: (record: RecordPlaintext) => boolean, nonces?: string[]): Promise; + findRecord(searchParameters: RecordSearchParams): Promise; /** * Find multiple records from arbitrary programs * - * @param {boolean} unspent Whether or not the record is unspent - * @param {string[]} nonces Nonces of records already found so that they are not found again * @param {RecordSearchParams} searchParameters Additional parameters to search for - * @returns {Promise} The record if found, otherwise an error + * @returns {Promise} The records if found, otherwise an error * + * @example * // The RecordSearchParams interface can be used to create parameters for custom record searches which can then * // be passed to the record provider. An example of how this would be done for the credits.aleo program is shown * // below. @@ -173,24 +142,35 @@ interface RecordProvider { * class CustomRecordSearch implements RecordSearchParams { * startHeight: number; * endHeight: number; - * amount: number; - * maxRecords: number; - * programName: string; + * credits: number; + * program: string; * recordName: string; - * constructor(startHeight: number, endHeight: number, credits: number, maxRecords: number, programName: string, recordName: string) { + * nonces: string[]; + * unspent: boolean; + * constructor( + * startHeight: number, + * endHeight: number, + * credits: number, + * maxRecords: number, + * programName: string, + * recordName: string, + * nonces: string[], + * unspent: boolean + * ) { * this.startHeight = startHeight; * this.endHeight = endHeight; - * this.amount = amount; - * this.maxRecords = maxRecords; - * this.programName = programName; + * this.credits = credits; + * this.program = programName; * this.recordName = recordName; + * this.nonces = nonces; + * this.unspent = unspent; * } * } * * const params = new CustomRecordSearch(0, 100, 5000, 2, "credits.aleo", "credits"); * const records = await recordProvider.findRecord(true, [], params); */ - findRecords(searchParameters?: RecordSearchParams, filterFn?: (record: RecordPlaintext) => boolean, nonces?: string[],): Promise; + findRecords(searchParameters: RecordSearchParams): Promise; } /** @@ -218,10 +198,8 @@ class NetworkRecordProvider implements RecordProvider { * Find a list of credit records with a given number of microcredits by via the official Aleo API * * @param {number[]} microcredits The number of microcredits to search for - * @param {boolean} unspent Whether or not the record is unspent - * @param {string[]} nonces Nonces of records already found so that they are not found again * @param {RecordSearchParams} searchParameters Additional parameters to search for - * @returns {Promise} The record if found, otherwise an error + * @returns {Promise} The records if found, otherwise an error * * @example * // Create a new NetworkRecordProvider @@ -230,11 +208,11 @@ class NetworkRecordProvider implements RecordProvider { * const recordProvider = new NetworkRecordProvider(account, networkClient); * * // The record provider can be used to find records with a given number of microcredits - * const record = await recordProvider.findCreditsRecord(5000, true, []); + * const record = await recordProvider.findCreditsRecord(5000, { unspent: true, nonces: [] }); * * // When a record is found but not yet used, it's nonce should be added to the nonces parameter so that it is not * // found again if a subsequent search is performed - * const records = await recordProvider.findCreditsRecords(5000, true, [record.nonce()]); + * const records = await recordProvider.findCreditsRecords(5000, { unspent: true, nonces: [record.nonce()] }); * * // When the program manager is initialized with the record provider it will be used to find automatically find * // fee records and amount records for value transfers so that they do not need to be specified manually @@ -242,7 +220,7 @@ class NetworkRecordProvider implements RecordProvider { * programManager.transfer(1, "aleo166q6ww6688cug7qxwe7nhctjpymydwzy2h7rscfmatqmfwnjvggqcad0at", "public", 0.5); * * */ - async findCreditsRecords(microcredits: number[], searchParameters: RecordSearchParams, nonces?: string[]): Promise { + async findCreditsRecords(microcredits: number[], searchParameters: RecordSearchParams): Promise { let startHeight = 0; let endHeight = 0; let maxAmount = undefined; @@ -276,7 +254,7 @@ class NetworkRecordProvider implements RecordProvider { logAndThrow("Start height must be less than end height"); } - const recordsPts = await this.networkClient.findRecords(startHeight, endHeight, searchParameters.unspent, ["credits.aleo"], microcredits, maxAmount, nonces, this.account.privateKey()); + const recordsPts = await this.networkClient.findRecords(startHeight, endHeight, searchParameters.unspent, ["credits.aleo"], microcredits, maxAmount, searchParameters.nonces, this.account.privateKey()); return recordsPts.map((record) => ({ commitment: record.commitment().toString(), owner: record.owner().toString(), @@ -291,10 +269,8 @@ class NetworkRecordProvider implements RecordProvider { * Find a credit record with a given number of microcredits by via the official Aleo API * * @param {number} microcredits The number of microcredits to search for - * @param {boolean} unspent Whether or not the record is unspent - * @param {string[]} nonces Nonces of records already found so that they are not found again * @param {RecordSearchParams} searchParameters Additional parameters to search for - * @returns {Promise} The record if found, otherwise an error + * @returns {Promise} The record if found, otherwise an error * * @example * // Create a new NetworkRecordProvider @@ -303,22 +279,22 @@ class NetworkRecordProvider implements RecordProvider { * const recordProvider = new NetworkRecordProvider(account, networkClient); * * // The record provider can be used to find records with a given number of microcredits - * const record = await recordProvider.findCreditsRecord(5000, true, []); + * const record = await recordProvider.findCreditsRecord(5000, { unspent: true, nonces: [] }); * * // When a record is found but not yet used, it's nonce should be added to the nonces parameter so that it is not * // found again if a subsequent search is performed - * const records = await recordProvider.findCreditsRecords(5000, true, [record.nonce()]); + * const records = await recordProvider.findCreditsRecords(5000, { unspent: true, nonces: [record.nonce()] }); * * // When the program manager is initialized with the record provider it will be used to find automatically find * // fee records and amount records for value transfers so that they do not need to be specified manually * const programManager = new ProgramManager("https://api.explorer.provable.com/v1", keyProvider, recordProvider); * programManager.transfer(1, "aleo166q6ww6688cug7qxwe7nhctjpymydwzy2h7rscfmatqmfwnjvggqcad0at", "public", 0.5); */ - async findCreditsRecord(microcredits: number, searchParameters: RecordSearchParams,nonces?: string[]): Promise { + async findCreditsRecord(microcredits: number, searchParameters: RecordSearchParams): Promise { let records = null; try { - records = await this.findCreditsRecords([microcredits], searchParameters, nonces); + records = await this.findCreditsRecords([microcredits], searchParameters); } catch (e) { console.log("No records found with error:", e); } @@ -334,11 +310,11 @@ class NetworkRecordProvider implements RecordProvider { /** * Find an arbitrary record. WARNING: This function is not implemented yet and will throw an error. */ - async findRecord(searchParameters: RecordSearchParams, filterFn?: (record: RecordPlaintext) => boolean, nonces?: string[]): Promise { + async findRecord(searchParameters: RecordSearchParams): Promise { let records; try { - records = await this.findRecords(searchParameters, filterFn, nonces); + records = await this.findRecords(searchParameters); } catch (e) { console.log("No records found with error:", e); } @@ -354,7 +330,7 @@ class NetworkRecordProvider implements RecordProvider { /** * Find multiple records from a specified program. */ - async findRecords(searchParameters: RecordSearchParams, filterFn?: (record: RecordPlaintext) => boolean, nonces?: string[]): Promise { + async findRecords(searchParameters: RecordSearchParams): Promise { let startHeight = 0; let endHeight = 0; let amounts = undefined; @@ -378,10 +354,6 @@ class NetworkRecordProvider implements RecordProvider { maxAmount = searchParameters["maxAmount"]; } - if ("nonces" in searchParameters && Array.isArray(searchParameters["nonces"]) && searchParameters["nonces"].every((item: any) => typeof item === "string")) { - nonces = searchParameters["nonces"]; - } - if ("program" in searchParameters && typeof searchParameters["program"] == "string") { programs = [searchParameters["program"]]; } @@ -402,7 +374,7 @@ class NetworkRecordProvider implements RecordProvider { logAndThrow("Start height must be less than end height"); } - const recordPts = await this.networkClient.findRecords(startHeight, endHeight, searchParameters.unspent, programs, amounts, maxAmount, nonces, this.account.privateKey()); + const recordPts = await this.networkClient.findRecords(startHeight, endHeight, searchParameters.unspent, programs, amounts, maxAmount, searchParameters.nonces, this.account.privateKey()); return recordPts.map((record) => ({ commitment: record.commitment().toString(), owner: record.owner().toString(), @@ -411,15 +383,15 @@ class NetworkRecordProvider implements RecordProvider { })); } - async getEncryptedRecords(recordsFilter: RecordSearchParams, responseFilter: RecordsResponseFilter): Promise { + async encryptedRecords(recordsFilter: RecordSearchParams, responseFilter: RecordsResponseFilter): Promise { throw new Error("Not implemented"); } - async serialNumbersExist(serialNumbers: string[]): Promise> { + async checkSerialNumbers(serialNumbers: string[]): Promise> { throw new Error("Not implemented"); } - async tagsExist(tags: string[]): Promise> { + async checkTags(tags: string[]): Promise> { throw new Error("Not implemented"); } } @@ -439,7 +411,7 @@ class NetworkRecordProvider implements RecordProvider { * * // The record provider can be used to find records with a given number of microcredits and the block height search * // can be used to find records within a given block height range - * const record = await recordProvider.findCreditsRecord(5000, true, [], params); + * const record = await recordProvider.findCreditsRecord(5000, { unspent: true, nonces: [], ...params }); * */ class BlockHeightSearch implements RecordSearchParams { diff --git a/sdk/src/record-scanner.ts b/sdk/src/record-scanner.ts index 02356bad2..9b0fbc0f5 100644 --- a/sdk/src/record-scanner.ts +++ b/sdk/src/record-scanner.ts @@ -1,30 +1,38 @@ import { Account } from "./account"; import { EncryptedRecord, OwnedRecord, RecordProvider, RecordSearchParams, RecordsResponseFilter } from "./record-provider"; import { RecordPlaintext } from "./wasm"; +import { RegistrationRequest } from "./models/record-scanner/registrationRequest"; +import { RecordsFilter } from "./models/record-scanner/recordsFilter"; +import { OwnedFilter } from "./models/record-scanner/ownedFilter"; -type RegistrationRequest = { - viewKey: string; - start: number; -} - -interface RecordsFilter extends RecordSearchParams { - start: number; - end?: number; - program?: string; - record?: string; - function?: string; -} - -interface OwnedFilter extends RecordSearchParams { - decrypt?: boolean; - filter?: RecordsFilter; - responseFilter?: RecordsResponseFilter; -} - -interface OwnedFilterWithUuid extends OwnedFilter { - uuid: string; -} - +/** + * RecordScanner is a RecordProvider implementation that uses the record scanner service to find records. + * + * @example + * const account = new Account({ privateKey: 'APrivateKey1...' }); + * + * const recordScanner = new RecordScanner("https://record-scanner.aleo.org"); + * recordScanner.setAccount(account); + * await recordScanner.register(0); + * + * const filter = { + * start: 0, + * end: 100, + * program: "credits.aleo", + * record: "credits", + * }; + * + * const responseFilter = { + * program: true, + * record: true, + * function: true, + * transition: true, + * blockHeight: true, + * transactionId: true, + * }; + * + * const records = await recordScanner.findRecords({ filter, responseFilter }); + */ class RecordScanner implements RecordProvider { readonly url: string; private account?: Account; @@ -35,10 +43,21 @@ class RecordScanner implements RecordProvider { this.url = url; } + /** + * Set the account to use for the record scanner. + * + * @param {Account} account The account to use for the record scanner. + */ async setAccount(account: Account): Promise { + this.uuid = undefined; this.account = account; } + /** + * Register the account with the record scanner service. + * + * @param {number} startBlock The block height to start scanning from. + */ async register(startBlock: number): Promise { let request: RegistrationRequest; if (!this.account) { @@ -67,7 +86,14 @@ class RecordScanner implements RecordProvider { } } - async getEncryptedRecords(recordsFilter: RecordsFilter, responseFilter: RecordsResponseFilter): Promise { + /** + * Get encrypted records from the record scanner service. + * + * @param {RecordsFilter} recordsFilter The filter to use to find the records + * @param {RecordsResponseFilter} responseFilter The filter to use to filter the response + * @returns {Promise} The encrypted records + */ + async encryptedRecords(recordsFilter: RecordsFilter, responseFilter: RecordsResponseFilter): Promise { try { const response = await this.recordScannerServiceRequest( new Request(`${this.url}/records/encrypted?${this.buildQueryString(recordsFilter, responseFilter)}`, { @@ -84,7 +110,13 @@ class RecordScanner implements RecordProvider { } } - async serialNumbersExist(serialNumbers: string[]): Promise> { + /** + * Check if a list of serial numbers exist in the record scanner service. + * + * @param {string[]} serialNumbers The serial numbers to check + * @returns {Promise>} A record of serial numbers and whether they exist + */ + async checkSerialNumbers(serialNumbers: string[]): Promise> { try { const response = await this.recordScannerServiceRequest( new Request(`${this.url}/records/sns`, { @@ -101,7 +133,13 @@ class RecordScanner implements RecordProvider { } } - async tagsExist(tags: string[]): Promise> { + /** + * Check if a list of tags exist in the record scanner service. + * + * @param {string[]} tags The tags to check + * @returns {Promise>} A record of tags and whether they exist + */ + async checkTags(tags: string[]): Promise> { try { const response = await this.recordScannerServiceRequest( new Request(`${this.url}/records/tags`, { @@ -118,9 +156,15 @@ class RecordScanner implements RecordProvider { } } - async findRecord(searchParameters: OwnedFilter, filterFn?: (record: RecordPlaintext) => boolean): Promise { + /** + * Find a record in the record scanner service. + * + * @param {OwnedFilter} searchParameters The filter to use to find the record + * @returns {Promise} The record + */ + async findRecord(searchParameters: OwnedFilter): Promise { try { - const records = await this.findRecords(searchParameters, filterFn); + const records = await this.findRecords(searchParameters); if (records.length > 0) { return records[0]; @@ -133,34 +177,43 @@ class RecordScanner implements RecordProvider { } } - async findRecords(searchParameters: OwnedFilter, filterFn?: (record: RecordPlaintext) => boolean): Promise { + /** + * Find records in the record scanner service. + * + * @param {OwnedFilter} filter The filter to use to find the records + * @returns {Promise} The records + */ + async findRecords(filter: OwnedFilter): Promise { if (!this.uuid) { throw new Error("Not registered"); } - const filterWithUuid: OwnedFilterWithUuid = { - ...searchParameters, - uuid: this.uuid, - }; + filter.uuid = this.uuid; try { const response = await this.recordScannerServiceRequest( new Request(`${this.url}/records/owned`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify(filterWithUuid), + body: JSON.stringify(filter), }), ); - const records = await response.json(); - return filterFn ? records.filter(filterFn) : records; + return await response.json(); } catch (error) { console.error(`Failed to get owned records: ${error}`); throw error; } } - async findCreditsRecord(microcredits: number, searchParameters: OwnedFilter, nonces?: string[]): Promise { + /** + * Find a credits record in the record scanner service. + * + * @param {number} microcredits The amount of microcredits to find + * @param {OwnedFilter} searchParameters The filter to use to find the record + * @returns {Promise} The record + */ + async findCreditsRecord(microcredits: number, searchParameters: OwnedFilter): Promise { try { const records = await this.findRecords({ ...searchParameters, @@ -171,8 +224,9 @@ class RecordScanner implements RecordProvider { const record = records.find(record => { const plaintext = RecordPlaintext.fromString(record.recordPlaintext); - const amount = plaintext.getMember("microcredits").toString(); - return amount === `${microcredits}u64`; + const amountStr = plaintext.getMember("microcredits").toString(); + const amount = parseInt(amountStr.replace("u64", "")); + return amount >= microcredits; }); if (!record) { @@ -186,7 +240,14 @@ class RecordScanner implements RecordProvider { } } - async findCreditsRecords(microcreditAmounts: number[], searchParameters: OwnedFilter, nonces?: string[]): Promise { + /** + * Find credits records in the record scanner service. + * + * @param {number[]} microcreditAmounts The amounts of microcredits to find + * @param {OwnedFilter} searchParameters The filter to use to find the records + * @returns {Promise} The records + */ + async findCreditsRecords(microcreditAmounts: number[], searchParameters: OwnedFilter): Promise { try { const records = await this.findRecords({ ...searchParameters, @@ -205,7 +266,12 @@ class RecordScanner implements RecordProvider { } } - + /** + * Wrapper function to make a request to the record scanner service and handle any errors + * + * @param {Request} req The request to make + * @returns {Promise} The response + */ private async recordScannerServiceRequest(req: Request): Promise { try { const response = await fetch(req); @@ -221,6 +287,13 @@ class RecordScanner implements RecordProvider { } } + /** + * Helper function to build a query string from the records filter and response filter. + * + * @param {RecordSearchParams} recordsFilter The filter to use to find the records + * @param {RecordsResponseFilter} responseFilter The filter to use to filter the response + * @returns {string} The query string + */ private buildQueryString(recordsFilter: RecordSearchParams, responseFilter: RecordsResponseFilter): string { return Object.entries({ ...recordsFilter, ...responseFilter }) .map(([key, value]) => `${key}=${value}`) From 9170df500807f8909e02fd494495136ff6196491 Mon Sep 17 00:00:00 2001 From: Alex Pitsikoulis Date: Mon, 18 Aug 2025 16:03:58 -0700 Subject: [PATCH 03/14] added jsdoc comments to record provider/scanner types --- .../models/record-provider/encryptedRecord.ts | 21 ++++++ sdk/src/models/record-provider/ownedRecord.ts | 22 ++++++ .../record-provider/recordSearchParams.ts | 10 +++ .../record-provider/recordsResponseFilter.ts | 16 +++++ sdk/src/models/record-scanner/ownedFilter.ts | 14 ++++ .../models/record-scanner/recordsFilter.ts | 11 +++ .../record-scanner/registrationRequest.ts | 9 +++ sdk/src/record-provider.ts | 72 ++++++++++--------- sdk/src/record-scanner.ts | 71 +++++++++--------- 9 files changed, 178 insertions(+), 68 deletions(-) diff --git a/sdk/src/models/record-provider/encryptedRecord.ts b/sdk/src/models/record-provider/encryptedRecord.ts index 5b579e5a8..9e34d16e9 100644 --- a/sdk/src/models/record-provider/encryptedRecord.ts +++ b/sdk/src/models/record-provider/encryptedRecord.ts @@ -1,3 +1,24 @@ +/** + * EncryptedRecord is a type that represents information about an encrypted record. + * + * @example + * const encryptedRecord: EncryptedRecord = { + * commitment: "...", + * checksum: "...", + * blockHeight: 123456, + * programName: "...", + * functionName: "...", + * outputIndex: 0, + * owner: "...", + * recordCiphertext: "...", + * recordName: "...", + * recordNonce: "...", + * transactionId: "...", + * transitionId: "...", + * transactionIndex: 0, + * transitionIndex: 0, + * } + */ export type EncryptedRecord = { commitment: string; checksum?: string; diff --git a/sdk/src/models/record-provider/ownedRecord.ts b/sdk/src/models/record-provider/ownedRecord.ts index 7c8b04a20..ae2ca1de5 100644 --- a/sdk/src/models/record-provider/ownedRecord.ts +++ b/sdk/src/models/record-provider/ownedRecord.ts @@ -1,3 +1,25 @@ +/** + * OwnedRecord is a type that represents information about an owned record that is found on chain. + * + * @example + * const ownedRecord: OwnedRecord = { + * blockHeight: 123456, + * commitment: "...", + * functionName: "...", + * outputIndex: 0, + * owner: "...", + * programName: "...", + * recordCiphertext: "...", + * recordPlaintext: "...", + * recordName: "...", + * spent: true, + * tag: "...", + * transactionId: "...", + * transitionId: "...", + * transactionIndex: 0, + * transitionIndex: 0, + * } + */ export type OwnedRecord = { blockHeight?: number; commitment?: string; diff --git a/sdk/src/models/record-provider/recordSearchParams.ts b/sdk/src/models/record-provider/recordSearchParams.ts index 879d2fdbf..1b7dd62df 100644 --- a/sdk/src/models/record-provider/recordSearchParams.ts +++ b/sdk/src/models/record-provider/recordSearchParams.ts @@ -1,6 +1,16 @@ /** * Interface for record search parameters. This allows for arbitrary search parameters to be passed to record provider * implementations. + * + * @example + * const recordSearchParams: RecordSearchParams = { + * // Declared fields + * unspent: true, + * nonces: ["..."], + * // Arbitrary fields + * startHeight: 123456, + * programName: "..." + * } */ export interface RecordSearchParams { unspent: boolean; diff --git a/sdk/src/models/record-provider/recordsResponseFilter.ts b/sdk/src/models/record-provider/recordsResponseFilter.ts index 3fa0dd461..105456efd 100644 --- a/sdk/src/models/record-provider/recordsResponseFilter.ts +++ b/sdk/src/models/record-provider/recordsResponseFilter.ts @@ -1,3 +1,19 @@ +/** + * RecordsResponseFilter is a type that represents a filter for the response from a record provider. + * A `true` value for a field in the filter will include that field in the response. + * + * @example + * const recordsResponseFilter: RecordsResponseFilter = { + * program: true, + * record: true, + * function: true, + * transition: true, + * blockHeight: true, + * transactionId: true, + * transitionId: true, + * ioIndex: true, + * } + */ export type RecordsResponseFilter = { program: boolean; record: boolean; diff --git a/sdk/src/models/record-scanner/ownedFilter.ts b/sdk/src/models/record-scanner/ownedFilter.ts index 3eaf9078b..56a01f43c 100644 --- a/sdk/src/models/record-scanner/ownedFilter.ts +++ b/sdk/src/models/record-scanner/ownedFilter.ts @@ -2,6 +2,20 @@ import { RecordSearchParams } from "../record-provider/recordSearchParams"; import { RecordsFilter } from "./recordsFilter"; import { RecordsResponseFilter } from "../record-provider/recordsResponseFilter"; +/** + * OwnedFilter is an extension of RecordSearchParams that represents a filter for scanning owned records. + * + * @example + * const ownedFilter: OwnedFilter = { + * unspent: true, + * nonces: ["..."], + * decrypt: true, + * filter: { + * program: "...", + * record: "...", + * }, + * } + */ export interface OwnedFilter extends RecordSearchParams { decrypt?: boolean; filter?: RecordsFilter; diff --git a/sdk/src/models/record-scanner/recordsFilter.ts b/sdk/src/models/record-scanner/recordsFilter.ts index 766834976..d4fb93303 100644 --- a/sdk/src/models/record-scanner/recordsFilter.ts +++ b/sdk/src/models/record-scanner/recordsFilter.ts @@ -1,5 +1,16 @@ import { RecordSearchParams } from "../record-provider/recordSearchParams"; +/** + * RecordsFilter is an extension of RecordSearchParams that represents a filter for scanning encrypted or owned records. + * + * @example + * const recordsFilter: RecordsFilter = { + * start: 0, + * end: 100, + * program: "...", + * record: "...", + * } + */ export interface RecordsFilter extends RecordSearchParams { start: number; end?: number; diff --git a/sdk/src/models/record-scanner/registrationRequest.ts b/sdk/src/models/record-scanner/registrationRequest.ts index 5dfaf93a7..ebd4f54ea 100644 --- a/sdk/src/models/record-scanner/registrationRequest.ts +++ b/sdk/src/models/record-scanner/registrationRequest.ts @@ -1,3 +1,12 @@ +/** + * RegistrationRequest is a type that represents a request to register an account's view key with a record scanning service. + * + * @example + * const registrationRequest: RegistrationRequest = { + * viewKey: "...", + * start: 123456, + * } + */ export type RegistrationRequest = { viewKey: string; start: number; diff --git a/sdk/src/record-provider.ts b/sdk/src/record-provider.ts index 04d2ed615..6833551f0 100644 --- a/sdk/src/record-provider.ts +++ b/sdk/src/record-provider.ts @@ -1,10 +1,10 @@ -import { logAndThrow } from "./utils.js"; import { Account } from "./account.js"; import { AleoNetworkClient } from "./network-client.js"; -import { RecordsResponseFilter } from "./models/record-provider/recordsResponseFilter.js"; import { EncryptedRecord } from "./models/record-provider/encryptedRecord.js"; +import { logAndThrow } from "./utils.js"; import { OwnedRecord } from "./models/record-provider/ownedRecord.js"; import { RecordSearchParams } from "./models/record-provider/recordSearchParams.js"; +import { RecordsResponseFilter } from "./models/record-provider/recordsResponseFilter.js"; /** * Interface for a record provider. A record provider is used to find records for use in deployment and execution @@ -14,36 +14,41 @@ import { RecordSearchParams } from "./models/record-provider/recordSearchParams. */ interface RecordProvider { /** - * Find encrypted records from the chosen provider + * The account used to search for records. + */ + account?: Account; + + /** + * Find encrypted records from the chosen provider. * - * @param {RecordSearchParams} recordsFilter The filter to use to find the records - * @param {RecordsResponseFilter} responseFilter The filter to use to filter the response - * @returns {Promise} The encrypted records + * @param {RecordSearchParams} recordsFilter The filter used to find the records. + * @param {RecordsResponseFilter} responseFilter The filter used to filter the response. + * @returns {Promise} The encrypted records. */ encryptedRecords(recordsFilter: RecordSearchParams, responseFilter: RecordsResponseFilter): Promise; /** * Check if a list of serial numbers exist in the chosen provider * - * @param {string[]} serialNumbers The serial numbers to check - * @returns {Promise>} A record of serial numbers and whether they exist + * @param {string[]} serialNumbers The serial numbers to check. + * @returns {Promise>} Map of Aleo Record serial numbers and whether they appeared in any inputs on chain. If boolean corresponding to the Serial Number has a true value, that Record is considered spent by the Aleo Network. */ checkSerialNumbers(serialNumbers: string[]): Promise>; /** * Check if a list of tags exist in the chosen provider * - * @param {string[]} tags The tags to check - * @returns {Promise>} A record of tags and whether they exist + * @param {string[]} tags The tags to check. + * @returns {Promise>} Map of Aleo Record tags and whether they appeared in any inputs on chain. If boolean corresponding to the tag has a true value, that Record is considered spent by the Aleo Network. */ checkTags(tags: string[]): Promise>; /** * Find a credits.aleo record with a given number of microcredits from the chosen provider * - * @param {number} microcredits The number of microcredits to search for - * @param {RecordSearchParams} searchParameters Additional parameters to search for - * @returns {Promise} The record if one is found + * @param {number} microcredits The number of microcredits to search for. + * @param {RecordSearchParams} searchParameters Additional parameters to search for. + * @returns {Promise} The record if one is found. * * @example * // A class implementing record provider can be used to find a record with a given number of microcredits @@ -63,9 +68,9 @@ interface RecordProvider { /** * Find a list of credit.aleo records with a given number of microcredits from the chosen provider * - * @param {number[]} microcreditAmounts A list of separate microcredit amounts to search for (e.g. [5000, 100000]) - * @param {RecordSearchParams} searchParameters Additional parameters to search for - * @returns {Promise} A list of records with a value greater or equal to the amounts specified if such records exist, otherwise an error + * @param {number[]} microcreditAmounts A list of separate microcredit amounts to search for (e.g. [5000, 100000]). + * @param {RecordSearchParams} searchParameters Additional parameters to search for. + * @returns {Promise} A list of records with a value greater or equal to the amounts specified if such records exist, otherwise an error. * * @example * // A class implementing record provider can be used to find a record with a given number of microcredits @@ -86,8 +91,8 @@ interface RecordProvider { /** * Find an arbitrary record - * @param {RecordSearchParams} searchParameters Additional parameters to search for - * @returns {Promise} The record if found, otherwise an error + * @param {RecordSearchParams} searchParameters Additional parameters to search for. + * @returns {Promise} The record if found, otherwise an error. * * @example * // The RecordSearchParams interface can be used to create parameters for custom record searches which can then @@ -131,8 +136,8 @@ interface RecordProvider { /** * Find multiple records from arbitrary programs * - * @param {RecordSearchParams} searchParameters Additional parameters to search for - * @returns {Promise} The records if found, otherwise an error + * @param {RecordSearchParams} searchParameters Additional parameters to search for. + * @returns {Promise} The records if found, otherwise an error. * * @example * // The RecordSearchParams interface can be used to create parameters for custom record searches which can then @@ -188,7 +193,7 @@ class NetworkRecordProvider implements RecordProvider { /** * Set the account used to search for records * - * @param {Account} account The account to use for searching for records + * @param {Account} account The account used to use for searching for records. */ setAccount(account: Account) { this.account = account; @@ -197,9 +202,9 @@ class NetworkRecordProvider implements RecordProvider { /** * Find a list of credit records with a given number of microcredits by via the official Aleo API * - * @param {number[]} microcredits The number of microcredits to search for - * @param {RecordSearchParams} searchParameters Additional parameters to search for - * @returns {Promise} The records if found, otherwise an error + * @param {number[]} microcredits The number of microcredits to search for. + * @param {RecordSearchParams} searchParameters Additional parameters to search for. + * @returns {Promise} The records if found, otherwise an error. * * @example * // Create a new NetworkRecordProvider @@ -268,9 +273,9 @@ class NetworkRecordProvider implements RecordProvider { /** * Find a credit record with a given number of microcredits by via the official Aleo API * - * @param {number} microcredits The number of microcredits to search for - * @param {RecordSearchParams} searchParameters Additional parameters to search for - * @returns {Promise} The record if found, otherwise an error + * @param {number} microcredits The number of microcredits to search for. + * @param {RecordSearchParams} searchParameters Additional parameters to search for. + * @returns {Promise} The record if found, otherwise an error. * * @example * // Create a new NetworkRecordProvider @@ -363,13 +368,13 @@ class NetworkRecordProvider implements RecordProvider { } } - // If the end height is not specified, use the current block height + // If the end height is not specified, use the current block height. if (endHeight == 0) { const end = await this.networkClient.getLatestHeight(); endHeight = end; } - // If the start height is greater than the end height, throw an error + // If the start height is greater than the end height, throw an error. if (startHeight >= endHeight) { logAndThrow("Start height must be less than end height"); } @@ -425,11 +430,8 @@ class BlockHeightSearch implements RecordSearchParams { } } -export { +export { BlockHeightSearch, - EncryptedRecord, - OwnedRecord, + NetworkRecordProvider, RecordProvider, - RecordSearchParams, - RecordsResponseFilter, -}; +}; \ No newline at end of file diff --git a/sdk/src/record-scanner.ts b/sdk/src/record-scanner.ts index 9b0fbc0f5..e18dacc3c 100644 --- a/sdk/src/record-scanner.ts +++ b/sdk/src/record-scanner.ts @@ -1,9 +1,13 @@ import { Account } from "./account"; -import { EncryptedRecord, OwnedRecord, RecordProvider, RecordSearchParams, RecordsResponseFilter } from "./record-provider"; +import { EncryptedRecord } from "./models/record-provider/encryptedRecord"; +import { OwnedFilter } from "./models/record-scanner/ownedFilter"; +import { OwnedRecord } from "./models/record-provider/ownedRecord"; +import { RecordProvider } from "./record-provider"; import { RecordPlaintext } from "./wasm"; -import { RegistrationRequest } from "./models/record-scanner/registrationRequest"; +import { RecordSearchParams } from "./models/record-provider/recordSearchParams"; import { RecordsFilter } from "./models/record-scanner/recordsFilter"; -import { OwnedFilter } from "./models/record-scanner/ownedFilter"; +import { RecordsResponseFilter } from "./models/record-provider/recordsResponseFilter"; +import { RegistrationRequest } from "./models/record-scanner/registrationRequest"; /** * RecordScanner is a RecordProvider implementation that uses the record scanner service to find records. @@ -34,8 +38,8 @@ import { OwnedFilter } from "./models/record-scanner/ownedFilter"; * const records = await recordScanner.findRecords({ filter, responseFilter }); */ class RecordScanner implements RecordProvider { + account?: Account; readonly url: string; - private account?: Account; private uuid?: string; constructor(url: string, account?: Account) { @@ -57,6 +61,7 @@ class RecordScanner implements RecordProvider { * Register the account with the record scanner service. * * @param {number} startBlock The block height to start scanning from. + * @returns {Promise} A promise that resolves when the account is registered. */ async register(startBlock: number): Promise { let request: RegistrationRequest; @@ -70,7 +75,7 @@ class RecordScanner implements RecordProvider { } try { - const response = await this.recordScannerServiceRequest( + const response = await this.request( new Request(`${this.url}/register`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -89,13 +94,13 @@ class RecordScanner implements RecordProvider { /** * Get encrypted records from the record scanner service. * - * @param {RecordsFilter} recordsFilter The filter to use to find the records - * @param {RecordsResponseFilter} responseFilter The filter to use to filter the response - * @returns {Promise} The encrypted records + * @param {RecordsFilter} recordsFilter The filter to use to find the records. + * @param {RecordsResponseFilter} responseFilter The filter to use to filter the response. + * @returns {Promise} The encrypted records. */ async encryptedRecords(recordsFilter: RecordsFilter, responseFilter: RecordsResponseFilter): Promise { try { - const response = await this.recordScannerServiceRequest( + const response = await this.request( new Request(`${this.url}/records/encrypted?${this.buildQueryString(recordsFilter, responseFilter)}`, { method: "GET", headers: { "Content-Type": "application/json" }, @@ -113,12 +118,12 @@ class RecordScanner implements RecordProvider { /** * Check if a list of serial numbers exist in the record scanner service. * - * @param {string[]} serialNumbers The serial numbers to check - * @returns {Promise>} A record of serial numbers and whether they exist + * @param {string[]} serialNumbers The serial numbers to check. + * @returns {Promise>} Map of Aleo Record serial numbers and whether they appeared in any inputs on chain. If boolean corresponding to the Serial Number has a true value, that Record is considered spent by the Aleo Network. */ async checkSerialNumbers(serialNumbers: string[]): Promise> { try { - const response = await this.recordScannerServiceRequest( + const response = await this.request( new Request(`${this.url}/records/sns`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -136,12 +141,12 @@ class RecordScanner implements RecordProvider { /** * Check if a list of tags exist in the record scanner service. * - * @param {string[]} tags The tags to check - * @returns {Promise>} A record of tags and whether they exist + * @param {string[]} tags The tags to check. + * @returns {Promise>} Map of Aleo Record tags and whether they appeared in any inputs on chain. If boolean corresponding to the tag has a true value, that Record is considered spent by the Aleo Network. */ async checkTags(tags: string[]): Promise> { try { - const response = await this.recordScannerServiceRequest( + const response = await this.request( new Request(`${this.url}/records/tags`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -159,8 +164,8 @@ class RecordScanner implements RecordProvider { /** * Find a record in the record scanner service. * - * @param {OwnedFilter} searchParameters The filter to use to find the record - * @returns {Promise} The record + * @param {OwnedFilter} searchParameters The filter to use to find the record. + * @returns {Promise} The record. */ async findRecord(searchParameters: OwnedFilter): Promise { try { @@ -180,8 +185,8 @@ class RecordScanner implements RecordProvider { /** * Find records in the record scanner service. * - * @param {OwnedFilter} filter The filter to use to find the records - * @returns {Promise} The records + * @param {OwnedFilter} filter The filter to use to find the records. + * @returns {Promise} The records. */ async findRecords(filter: OwnedFilter): Promise { if (!this.uuid) { @@ -191,7 +196,7 @@ class RecordScanner implements RecordProvider { filter.uuid = this.uuid; try { - const response = await this.recordScannerServiceRequest( + const response = await this.request( new Request(`${this.url}/records/owned`, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -209,9 +214,9 @@ class RecordScanner implements RecordProvider { /** * Find a credits record in the record scanner service. * - * @param {number} microcredits The amount of microcredits to find - * @param {OwnedFilter} searchParameters The filter to use to find the record - * @returns {Promise} The record + * @param {number} microcredits The amount of microcredits to find. + * @param {OwnedFilter} searchParameters The filter to use to find the record. + * @returns {Promise} The record. */ async findCreditsRecord(microcredits: number, searchParameters: OwnedFilter): Promise { try { @@ -241,10 +246,10 @@ class RecordScanner implements RecordProvider { } /** - * Find credits records in the record scanner service. + * Find credits records using a record scanning service. * - * @param {number[]} microcreditAmounts The amounts of microcredits to find - * @param {OwnedFilter} searchParameters The filter to use to find the records + * @param {number[]} microcreditAmounts The amounts of microcredits to find. + * @param {OwnedFilter} searchParameters The filter to use to find the records. * @returns {Promise} The records */ async findCreditsRecords(microcreditAmounts: number[], searchParameters: OwnedFilter): Promise { @@ -267,12 +272,12 @@ class RecordScanner implements RecordProvider { } /** - * Wrapper function to make a request to the record scanner service and handle any errors + * Wrapper function to make a request to the record scanner service and handle any errors. * - * @param {Request} req The request to make - * @returns {Promise} The response + * @param {Request} req The request to make. + * @returns {Promise} The response. */ - private async recordScannerServiceRequest(req: Request): Promise { + private async request(req: Request): Promise { try { const response = await fetch(req); @@ -290,9 +295,9 @@ class RecordScanner implements RecordProvider { /** * Helper function to build a query string from the records filter and response filter. * - * @param {RecordSearchParams} recordsFilter The filter to use to find the records - * @param {RecordsResponseFilter} responseFilter The filter to use to filter the response - * @returns {string} The query string + * @param {RecordSearchParams} recordsFilter The filter to use to find the records. + * @param {RecordsResponseFilter} responseFilter The filter to use to filter the response. + * @returns {string} The query string. */ private buildQueryString(recordsFilter: RecordSearchParams, responseFilter: RecordsResponseFilter): string { return Object.entries({ ...recordsFilter, ...responseFilter }) From 8a4df53b5c0e7b23cfb3005b41e349f309be348e Mon Sep 17 00:00:00 2001 From: Alex Pitsikoulis Date: Tue, 19 Aug 2025 12:33:59 -0700 Subject: [PATCH 04/14] fixed failing record provider tests --- sdk/src/browser.ts | 4 +- sdk/src/program-manager.ts | 66 +++++++++++------------- sdk/src/record-provider.ts | 19 +++---- sdk/src/record-scanner.ts | 6 +-- sdk/tests/record-provider.integration.ts | 12 +++-- sdk/tests/record-provider.test.ts | 2 +- 6 files changed, 51 insertions(+), 58 deletions(-) diff --git a/sdk/src/browser.ts b/sdk/src/browser.ts index a22740e4f..f97f7cbf5 100644 --- a/sdk/src/browser.ts +++ b/sdk/src/browser.ts @@ -45,8 +45,10 @@ import { BlockHeightSearch, NetworkRecordProvider, RecordProvider, - RecordSearchParams, } from "./record-provider.js"; +import { + RecordSearchParams, +} from "./models/record-provider/recordSearchParams.js"; // @TODO: This function is no longer needed, remove it. async function initializeWasm() { diff --git a/sdk/src/program-manager.ts b/sdk/src/program-manager.ts index 1474c213d..3231c6fce 100644 --- a/sdk/src/program-manager.ts +++ b/sdk/src/program-manager.ts @@ -1,7 +1,8 @@ import { Account } from "./account.js"; import { AleoNetworkClient, AleoNetworkClientOptions, ProgramImports } from "./network-client.js"; import { ImportedPrograms, ImportedVerifyingKeys } from "./models/imports.js"; -import { RecordProvider, RecordSearchParams } from "./record-provider.js"; +import { RecordProvider } from "./record-provider.js"; +import { RecordSearchParams } from "./models/record-provider/recordSearchParams.js"; import { AleoKeyProvider, @@ -35,6 +36,7 @@ import { } from "./constants.js"; import { logAndThrow } from "./utils.js"; +import { OwnedRecord } from "./models/record-provider/ownedRecord.js"; /** * Represents the options for executing a transaction in the Aleo network. @@ -353,14 +355,12 @@ class ProgramManager { // Get the fee record from the account if it is not provided in the parameters try { feeRecord = privateFee - ? ( - await this.getCreditsRecord( + ? RecordPlaintext.fromString((await this.getCreditsRecord( priorityFee, [], feeRecord, recordSearchParams, - ) - ) + )).recordPlaintext?? '') : undefined; } catch (e: any) { logAndThrow( @@ -583,14 +583,12 @@ class ProgramManager { // Get the fee record from the account if it is not provided in the parameters try { feeRecord = privateFee - ? ( - await this.getCreditsRecord( + ? RecordPlaintext.fromString((await this.getCreditsRecord( priorityFee, [], feeRecord, recordSearchParams, - ) - ) + )).recordPlaintext?? '') : undefined; } catch (e: any) { logAndThrow( @@ -976,14 +974,12 @@ class ProgramManager { // Get the fee record from the account if it is not provided in the parameters. try { feeRecord = privateFee - ? ( - await this.getCreditsRecord( + ? RecordPlaintext.fromString((await this.getCreditsRecord( priorityFee, [], feeRecord, recordSearchParams, - ) - ) + )).recordPlaintext?? '') : undefined; } catch (e: any) { logAndThrow( @@ -1314,14 +1310,12 @@ class ProgramManager { // Get the fee record from the account if it is not provided in the parameters try { feeRecord = privateFee - ? ( - await this.getCreditsRecord( + ? RecordPlaintext.fromString((await this.getCreditsRecord( priorityFee, [], feeRecord, recordSearchParams, - ) - ) + )).recordPlaintext?? '') : undefined; } catch (e: any) { logAndThrow( @@ -1587,27 +1581,23 @@ class ProgramManager { const nonces: string[] = []; if (requiresAmountRecord(transferType)) { // If the transfer type is private and requires an amount record, get it from the record provider - amountRecord = ( - await this.getCreditsRecord( + amountRecord = RecordPlaintext.fromString((await this.getCreditsRecord( priorityFee, [], amountRecord, recordSearchParams, - ) - ); + )).recordPlaintext?? ''); nonces.push(amountRecord.nonce()); } else { amountRecord = undefined; } feeRecord = privateFee - ? ( - await this.getCreditsRecord( + ? RecordPlaintext.fromString((await this.getCreditsRecord( priorityFee, nonces, feeRecord, recordSearchParams, - ) - ) + )).recordPlaintext?? '') : undefined; } catch (e: any) { logAndThrow( @@ -2574,21 +2564,25 @@ class ProgramManager { nonces: string[], record?: RecordPlaintext | string, params?: RecordSearchParams, - ): Promise { + ): Promise { try { - return record instanceof RecordPlaintext - ? record - : RecordPlaintext.fromString(record); + // return record instanceof RecordPlaintext + // ? record + // : RecordPlaintext.fromString(record); + if (record && record instanceof RecordPlaintext) { + record = record.toString(); + } + return ({ + recordPlaintext: record, + programName: 'credits.aleo', + recordName: 'credits', + }) } catch (e) { try { const recordProvider = this.recordProvider; - return ( - await recordProvider.findCreditsRecord( - amount, - true, - nonces, - params, - ) + return await recordProvider.findCreditsRecord( + amount, + { ...params, unspent: true, nonces } ); } catch (e: any) { logAndThrow( diff --git a/sdk/src/record-provider.ts b/sdk/src/record-provider.ts index 6833551f0..b1d959c14 100644 --- a/sdk/src/record-provider.ts +++ b/sdk/src/record-provider.ts @@ -261,12 +261,10 @@ class NetworkRecordProvider implements RecordProvider { const recordsPts = await this.networkClient.findRecords(startHeight, endHeight, searchParameters.unspent, ["credits.aleo"], microcredits, maxAmount, searchParameters.nonces, this.account.privateKey()); return recordsPts.map((record) => ({ - commitment: record.commitment().toString(), - owner: record.owner().toString(), - programName: 'credits.aleo', - recordName: 'credits', - recordPlaintext: record.to_string(), - tag: record.tag().toString(), + owner: record.owner().toString(), + programName: 'credits.aleo', + recordName: 'credits', + recordPlaintext: record.toString(), })); } @@ -381,10 +379,7 @@ class NetworkRecordProvider implements RecordProvider { const recordPts = await this.networkClient.findRecords(startHeight, endHeight, searchParameters.unspent, programs, amounts, maxAmount, searchParameters.nonces, this.account.privateKey()); return recordPts.map((record) => ({ - commitment: record.commitment().toString(), - owner: record.owner().toString(), - programName: record.program().toString(), - recordName: record.name().toString(), + recordPlaintext: record.toString(), })); } @@ -423,10 +418,10 @@ class BlockHeightSearch implements RecordSearchParams { startHeight: number; endHeight: number; unspent: boolean; - constructor(startHeight: number, endHeight: number, unspent: boolean) { + constructor(startHeight: number, endHeight: number, unspent?: boolean) { this.startHeight = startHeight; this.endHeight = endHeight; - this.unspent = unspent; + this.unspent = !!unspent; } } diff --git a/sdk/src/record-scanner.ts b/sdk/src/record-scanner.ts index e18dacc3c..60c9f5ebf 100644 --- a/sdk/src/record-scanner.ts +++ b/sdk/src/record-scanner.ts @@ -69,7 +69,7 @@ class RecordScanner implements RecordProvider { throw new Error("Account not set"); } else { request = { - viewKey: this.account.viewKey(), + viewKey: this.account.viewKey().toString(), start: startBlock, }; } @@ -228,7 +228,7 @@ class RecordScanner implements RecordProvider { }); const record = records.find(record => { - const plaintext = RecordPlaintext.fromString(record.recordPlaintext); + const plaintext = RecordPlaintext.fromString(record.recordPlaintext ?? ''); const amountStr = plaintext.getMember("microcredits").toString(); const amount = parseInt(amountStr.replace("u64", "")); return amount >= microcredits; @@ -261,7 +261,7 @@ class RecordScanner implements RecordProvider { decrypt: true, }); return records.filter(record => { - const plaintext = RecordPlaintext.fromString(record.recordPlaintext); + const plaintext = RecordPlaintext.fromString(record.recordPlaintext ?? ''); const amount = plaintext.getMember("microcredits").toString(); return microcreditAmounts.includes(parseInt(amount.replace("u64", ""))); }); diff --git a/sdk/tests/record-provider.integration.ts b/sdk/tests/record-provider.integration.ts index ef73616a3..a076282b4 100644 --- a/sdk/tests/record-provider.integration.ts +++ b/sdk/tests/record-provider.integration.ts @@ -18,23 +18,25 @@ describe('RecordProvider', () => { try { // Find two records with findCreditsRecords const nonces: string[] = []; - const records = await recordProvider.findCreditsRecords([100, 200], true, []); + const records = await recordProvider.findCreditsRecords([100, 200], { unspent: true, nonces }); if (Array.isArray(records)) { expect(records.length).equal(2); records.forEach((record) => { - nonces.push(record.nonce()); + let pt = new RecordPlaintext(record.recordPlaintext); + nonces.push(pt.nonce()); }); } else { expect(Array.isArray(records)).equal(true); } // Get another two records with findCreditsRecords and ensure they are unique - const records2 = await recordProvider.findCreditsRecords([100, 200], true, nonces); + const records2 = await recordProvider.findCreditsRecords([100, 200], { unspent: true, nonces }); if (Array.isArray(records2)) { expect(records2.length).equal(2); records2.forEach((record) => { - expect(nonces.includes(record.nonce())).equal(false); - nonces.push(record.nonce()); + let pt = new RecordPlaintext(record.recordPlaintext); + expect(nonces.includes(pt.nonce())).equal(false); + nonces.push(pt.nonce()); }); } else { expect(Array.isArray(records2)).equal(true); diff --git a/sdk/tests/record-provider.test.ts b/sdk/tests/record-provider.test.ts index a54675637..20247f2c8 100644 --- a/sdk/tests/record-provider.test.ts +++ b/sdk/tests/record-provider.test.ts @@ -16,7 +16,7 @@ describe.skip('RecordProvider', () => { describe('Record provider', () => { it('should not find records where there are none', async () => { const params = new BlockHeightSearch(0, 100); - const records = await recordProvider.findCreditsRecords([100, 200], true, [], params); + const records = await recordProvider.findCreditsRecords([100, 200], params); expect(records).equal([]); }); }); From 49577dd36b3b8ccfc43f067d966c0fcfa8bc6062 Mon Sep 17 00:00:00 2001 From: Alex Pitsikoulis Date: Wed, 20 Aug 2025 11:27:22 -0700 Subject: [PATCH 05/14] fixed casing of fields on OwnedRecord and EncryptedRecord types --- sdk/src/browser.ts | 16 +- .../models/record-provider/encryptedRecord.ts | 67 +++--- sdk/src/models/record-provider/ownedRecord.ts | 69 ++++--- .../record-provider/recordSearchParams.ts | 6 +- .../record-provider/recordsResponseFilter.ts | 8 +- sdk/src/models/record-scanner/ownedFilter.ts | 6 +- .../models/record-scanner/recordsFilter.ts | 4 +- .../record-scanner/registrationRequest.ts | 4 +- sdk/src/program-manager.ts | 12 +- sdk/src/record-provider.ts | 2 +- sdk/src/record-scanner.ts | 35 ++-- sdk/tests/data/records.ts | 192 ++++++++++++++++++ sdk/tests/record-provider.integration.ts | 4 +- 13 files changed, 333 insertions(+), 92 deletions(-) diff --git a/sdk/src/browser.ts b/sdk/src/browser.ts index f97f7cbf5..1c38004b6 100644 --- a/sdk/src/browser.ts +++ b/sdk/src/browser.ts @@ -6,6 +6,7 @@ import { BlockJSON, Header, Metadata } from "./models/blockJSON.js"; import { ConfirmedTransactionJSON } from "./models/confirmed_transaction.js"; import { DeploymentJSON, VerifyingKeys } from "./models/deployment/deploymentJSON.js"; import { DeploymentObject } from "./models/deployment/deploymentObject.js"; +import { EncryptedRecord } from "./models/record-provider/encryptedRecord.js"; import { ExecutionJSON, FeeExecutionJSON } from "./models/execution/executionJSON.js"; import { ExecutionObject, FeeExecutionObject } from "./models/execution/executionObject.js"; import { FinalizeJSON } from "./models/finalizeJSON.js"; @@ -15,6 +16,8 @@ import { InputJSON } from "./models/input/inputJSON.js"; import { InputObject } from "./models/input/inputObject.js"; import { OutputJSON } from "./models/output/outputJSON.js"; import { OutputObject } from "./models/output/outputObject.js"; +import { OwnedFilter } from "./models/record-scanner/ownedFilter.js"; +import { OwnedRecord } from "./models/record-provider/ownedRecord.js"; import { OwnerJSON } from "./models/owner/ownerJSON.js"; import { PlaintextArray} from "./models/plaintext/array.js"; import { PlaintextLiteral} from "./models/plaintext/literal.js"; @@ -23,6 +26,9 @@ import { PlaintextStruct} from "./models/plaintext/struct.js"; import { ProvingRequestJSON } from "./models/provingRequest.js"; import { ProvingResponse } from "./models/provingResponse.js"; import { RatificationJSON } from "./models/ratification.js"; +import { RecordsFilter } from "./models/record-scanner/recordsFilter.js"; +import { RecordsResponseFilter } from "./models/record-provider/recordsResponseFilter.js"; +import { RecordSearchParams } from "./models/record-provider/recordSearchParams.js"; import { SolutionsJSON, SolutionJSON, PartialSolutionJSON } from "./models/solution.js"; import { TransactionJSON } from "./models/transaction/transactionJSON.js"; import { TransactionObject } from "./models/transaction/transactionObject.js"; @@ -46,9 +52,7 @@ import { NetworkRecordProvider, RecordProvider, } from "./record-provider.js"; -import { - RecordSearchParams, -} from "./models/record-provider/recordSearchParams.js"; +import { RecordScanner } from "./record-scanner.js"; // @TODO: This function is no longer needed, remove it. async function initializeWasm() { @@ -138,6 +142,7 @@ export { ConfirmedTransactionJSON, DeploymentJSON, DeploymentObject, + EncryptedRecord, ExecutionJSON, ExecutionObject, FeeExecutionJSON, @@ -158,6 +163,8 @@ export { OfflineSearchParams, OutputJSON, OutputObject, + OwnedFilter, + OwnedRecord, OwnerJSON, PartialSolutionJSON, PlaintextArray, @@ -168,7 +175,10 @@ export { ProvingRequestJSON, ProvingResponse, RatificationJSON, + RecordsFilter, + RecordsResponseFilter, RecordProvider, + RecordScanner, RecordSearchParams, SolutionJSON, SolutionsJSON, diff --git a/sdk/src/models/record-provider/encryptedRecord.ts b/sdk/src/models/record-provider/encryptedRecord.ts index 9e34d16e9..0ce5daccf 100644 --- a/sdk/src/models/record-provider/encryptedRecord.ts +++ b/sdk/src/models/record-provider/encryptedRecord.ts @@ -1,37 +1,52 @@ /** - * EncryptedRecord is a type that represents information about an encrypted record. + * Encrypted Record found on chain. This type provides the record ciphertext and metadata from the ledger that such as the record's name, the program/function that produced it, etc. + * + * @property {string} commitment - The commitment of the record. + * @property {string | undefined} checksum - The checksum of the record. + * @property {number | undefined} block_height - The block height of the record. + * @property {string | undefined} program_name - The name of the program that produced the record. + * @property {string | undefined} function_name - The name of the function that produced the record. + * @property {number | undefined} output_index - The output index of the record. + * @property {string | undefined} owner - The owner of the record. + * @property {string | undefined} record_ciphertext - The ciphertext of the record. + * @property {string | undefined} record_name - The name of the record. + * @property {string | undefined} record_nonce - The nonce of the record. + * @property {string | undefined} transaction_id - The ID of the transaction that produced the record. + * @property {string | undefined} transition_id - The ID of the transition that produced the record. + * @property {number | undefined} transaction_index - The index of the transaction that produced the record. + * @property {number | undefined} transition_index - The index of the transition that produced the record. * * @example * const encryptedRecord: EncryptedRecord = { - * commitment: "...", - * checksum: "...", - * blockHeight: 123456, - * programName: "...", - * functionName: "...", - * outputIndex: 0, - * owner: "...", - * recordCiphertext: "...", - * recordName: "...", - * recordNonce: "...", - * transactionId: "...", - * transitionId: "...", - * transactionIndex: 0, - * transitionIndex: 0, + * commitment: "1754131901135854615627743152473414463769543922079966020586765988138574911385field", + * checksum: "731623304764338277682996290553427512270277231686866672455141481050283829616field", + * block_height: 123456, + * program_name: "credits.aleo", + * function_name: "transfer_private", + * output_index: 0, + * owner: "ciphertext1qgqdetlfzk98jkm4e7sgqml66e3x2gpg5d6udkpw0g67z0tplkpmzrm6q5dyfd7xhgmhedvptxzwfhrtxaqn7n0hs0esge3lwg9s2zukqgzxd0cr", + * recordCiphertext: "record1qyqsqt43u9kp97svljyyup3v4jmppd0vgght9edvvmtxx6mxycsej8cwqsrxzmt0w4h8ggcqqgqspf8zqut2ycnap7f0uzz5ktu0cxscca96urtkg2aweuzn70787dsrpp6x76m9de0kjezrqqpqyqp3mn3xeh53lukvcy406amjf5g0ksl3saauzjk0j4ljtjqq6kqlqhdz05sw92zye96qym7kp83ra0eesgtwhaw37c85r499456se8ts28m90p6x2unwv9k97ct4w35x7unf0fshg6t0de0hyet3w45hyetyyvqqyqgq4t2wr9tmcrfha5tfz5j585ptvvslqe0f6sf29vytshhdh7ym05rpqct4w35x7unf0fjkghm4de6xjmprqqpqzqru6p7fef29vuz6smyqwcn3z7jhxtdgjdw5xv23ppxhpgnvu72fp8hz6fjt6gsdn8yxhzq7gpsah0rscwqrzxwl5e8aemkj5gt09y7q5506yrf", + * record_name: "credits", + * record_nonce: "3077450429259593211617823051143573281856129402760267155982965992208217472983group", + * transaction_id: "at1f8ueqxu3x49sckpc6jlg676tmxumddzer3fwe2l0dxwj4dqxygyqua4u2q", + * transition_id: "au17mm5v7sfwus6y40xsyc99d5rtsr4vsajdec6twdjzv0m458q85zspqdnka", + * transaction_index: 0, + * transition_index: 0, * } */ export type EncryptedRecord = { commitment: string; checksum?: string; - blockHeight?: number; - programName?: string; - functionName?: string; - outputIndex?: number; + block_height?: number; + program_name?: string; + function_name?: string; + output_index?: number; owner?: string; - recordCiphertext?: string; - recordName?: string; - recordNonce?: string; - transactionId?: string; - transitionId?: string; - transactionIndex?: number; - transitionIndex?: number; + record_ciphertext?: string; + record_name?: string; + record_nonce?: string; + transaction_id?: string; + transition_id?: string; + transaction_index?: number; + transition_index?: number; } \ No newline at end of file diff --git a/sdk/src/models/record-provider/ownedRecord.ts b/sdk/src/models/record-provider/ownedRecord.ts index ae2ca1de5..659c04306 100644 --- a/sdk/src/models/record-provider/ownedRecord.ts +++ b/sdk/src/models/record-provider/ownedRecord.ts @@ -1,39 +1,54 @@ /** - * OwnedRecord is a type that represents information about an owned record that is found on chain. - * + * Record owned by a registered view key. This type provides the record ciphertext, record plaintext and metadata from the ledger that such as the record's name, the program/function that produced it, etc. + * + * @property {number | undefined} block_height - Block height where the record was created. + * @property {string | undefined} commitment - Commitment of the record. + * @property {string | undefined} function_name - Name of the function that created the record. + * @property {number | undefined} output_index - Index of the output in the function call that created the record. + * @property {string | undefined} owner - Address of the record owner. + * @property {string | undefined} program_name - Name of the program that created the record. + * @property {string | undefined} record_ciphertext - Encrypted ciphertext of the record. + * @property {string | undefined} record_name - Name of the record. + * @property {boolean | undefined} spent - Whether the record has been spent. + * @property {string | undefined} tag - Tag associated with the record. + * @property {string | undefined} transaction_id - ID of the transaction that created the record. + * @property {string | undefined} transition_id - ID of the transition that created the record. + * @property {string | undefined} transaction_index - Index of the transaction in the block. + * @property {string | undefined} transition_index - Index of the transition in the transaction. + * * @example * const ownedRecord: OwnedRecord = { - * blockHeight: 123456, - * commitment: "...", - * functionName: "...", - * outputIndex: 0, - * owner: "...", - * programName: "...", - * recordCiphertext: "...", - * recordPlaintext: "...", - * recordName: "...", + * block_height: 123456, + * commitment: "1754131901135854615627743152473414463769543922079966020586765988138574911385field", + * function_name: "transfer_public_to_private", + * output_index: 0, + * owner: "ciphertext1qgqdetlfzk98jkm4e7sgqml66e3x2gpg5d6udkpw0g67z0tplkpmzrm6q5dyfd7xhgmhedvptxzwfhrtxaqn7n0hs0esge3lwg9s2zukqgzxd0cr", + * program_name: "credits.aleo", + * record_ciphertext: "record1qyqsqt43u9kp97svljyyup3v4jmppd0vgght9edvvmtxx6mxycsej8cwqsrxzmt0w4h8ggcqqgqspf8zqut2ycnap7f0uzz5ktu0cxscca96urtkg2aweuzn70787dsrpp6x76m9de0kjezrqqpqyqp3mn3xeh53lukvcy406amjf5g0ksl3saauzjk0j4ljtjqq6kqlqhdz05sw92zye96qym7kp83ra0eesgtwhaw37c85r499456se8ts28m90p6x2unwv9k97ct4w35x7unf0fshg6t0de0hyet3w45hyetyyvqqyqgq4t2wr9tmcrfha5tfz5j585ptvvslqe0f6sf29vytshhdh7ym05rpqct4w35x7unf0fjkghm4de6xjmprqqpqzqru6p7fef29vuz6smyqwcn3z7jhxtdgjdw5xv23ppxhpgnvu72fp8hz6fjt6gsdn8yxhzq7gpsah0rscwqrzxwl5e8aemkj5gt09y7q5506yrf", + * record_plaintext: "{ owner: aleo1j7qxyunfldj2lp8hsvy7mw5k8zaqgjfyr72x2gh3x4ewgae8v5gscf5jh3.private, microcredits: 1500000000000000u64.private, _nonce: 3077450429259593211617823051143573281856129402760267155982965992208217472983group.public , _version: 1u8 }", + * record_name: "credits", * spent: true, - * tag: "...", - * transactionId: "...", - * transitionId: "...", - * transactionIndex: 0, - * transitionIndex: 0, + * tag: "6511661650536816422260305447175136877451468301541296257226129781611237851030field", + * transaction_id: "at1f8ueqxu3x49sckpc6jlg676tmxumddzer3fwe2l0dxwj4dqxygyqua4u2q", + * transition_id: "au17mm5v7sfwus6y40xsyc99d5rtsr4vsajdec6twdjzv0m458q85zspqdnka", + * transaction_index: 0, + * transition_index: 0, * } */ export type OwnedRecord = { - blockHeight?: number; + block_height?: number; commitment?: string; - functionName?: string; - outputIndex?: number; + function_name?: string; + output_index?: number; owner?: string; - programName?: string; - recordCiphertext?: string; - recordPlaintext?: string; - recordName?: string; + program_name?: string; + record_ciphertext?: string; + record_plaintext?: string; + record_name?: string; spent?: boolean; tag?: string; - transactionId?: string; - transitionId?: string; - transactionIndex?: number; - transitionIndex?: number; + transaction_id?: string; + transition_id?: string; + transaction_index?: number; + transition_index?: number; } \ No newline at end of file diff --git a/sdk/src/models/record-provider/recordSearchParams.ts b/sdk/src/models/record-provider/recordSearchParams.ts index 1b7dd62df..10820e65f 100644 --- a/sdk/src/models/record-provider/recordSearchParams.ts +++ b/sdk/src/models/record-provider/recordSearchParams.ts @@ -6,14 +6,14 @@ * const recordSearchParams: RecordSearchParams = { * // Declared fields * unspent: true, - * nonces: ["..."], + * nonces: ["3077450429259593211617823051143573281856129402760267155982965992208217472983group"], * // Arbitrary fields * startHeight: 123456, - * programName: "..." + * programName: "credits.aleo" * } */ export interface RecordSearchParams { - unspent: boolean; + unspent?: boolean; nonces?: string[]; [key: string]: any; // This allows for arbitrary keys with any type values } \ No newline at end of file diff --git a/sdk/src/models/record-provider/recordsResponseFilter.ts b/sdk/src/models/record-provider/recordsResponseFilter.ts index 105456efd..ab0b30efa 100644 --- a/sdk/src/models/record-provider/recordsResponseFilter.ts +++ b/sdk/src/models/record-provider/recordsResponseFilter.ts @@ -9,8 +9,8 @@ * function: true, * transition: true, * blockHeight: true, - * transactionId: true, - * transitionId: true, + * transaction_id: true, + * transition_id: true, * ioIndex: true, * } */ @@ -20,7 +20,7 @@ export type RecordsResponseFilter = { function: boolean; transition: boolean; blockHeight: boolean; - transactionId: boolean; - transitionId: boolean; + transaction_id: boolean; + transition_id: boolean; ioIndex: boolean; } \ No newline at end of file diff --git a/sdk/src/models/record-scanner/ownedFilter.ts b/sdk/src/models/record-scanner/ownedFilter.ts index 56a01f43c..450bb6721 100644 --- a/sdk/src/models/record-scanner/ownedFilter.ts +++ b/sdk/src/models/record-scanner/ownedFilter.ts @@ -8,11 +8,11 @@ import { RecordsResponseFilter } from "../record-provider/recordsResponseFilter" * @example * const ownedFilter: OwnedFilter = { * unspent: true, - * nonces: ["..."], + * nonces: ["3077450429259593211617823051143573281856129402760267155982965992208217472983group"], * decrypt: true, * filter: { - * program: "...", - * record: "...", + * program: "credits.aleo", + * record: "credits", * }, * } */ diff --git a/sdk/src/models/record-scanner/recordsFilter.ts b/sdk/src/models/record-scanner/recordsFilter.ts index d4fb93303..e72c98575 100644 --- a/sdk/src/models/record-scanner/recordsFilter.ts +++ b/sdk/src/models/record-scanner/recordsFilter.ts @@ -7,8 +7,8 @@ import { RecordSearchParams } from "../record-provider/recordSearchParams"; * const recordsFilter: RecordsFilter = { * start: 0, * end: 100, - * program: "...", - * record: "...", + * program: "credits.aleo", + * record: "credits", * } */ export interface RecordsFilter extends RecordSearchParams { diff --git a/sdk/src/models/record-scanner/registrationRequest.ts b/sdk/src/models/record-scanner/registrationRequest.ts index ebd4f54ea..53cb71dd0 100644 --- a/sdk/src/models/record-scanner/registrationRequest.ts +++ b/sdk/src/models/record-scanner/registrationRequest.ts @@ -3,11 +3,11 @@ * * @example * const registrationRequest: RegistrationRequest = { - * viewKey: "...", + * view_key: "AViewKey1ccEt8A2Ryva5rxnKcAbn7wgTaTsb79tzkKHFpeKsm9NX", * start: 123456, * } */ export type RegistrationRequest = { - viewKey: string; + view_key: string; start: number; } \ No newline at end of file diff --git a/sdk/src/program-manager.ts b/sdk/src/program-manager.ts index 3231c6fce..b45da6ee4 100644 --- a/sdk/src/program-manager.ts +++ b/sdk/src/program-manager.ts @@ -360,7 +360,7 @@ class ProgramManager { [], feeRecord, recordSearchParams, - )).recordPlaintext?? '') + )).record_plaintext?? '') : undefined; } catch (e: any) { logAndThrow( @@ -588,7 +588,7 @@ class ProgramManager { [], feeRecord, recordSearchParams, - )).recordPlaintext?? '') + )).record_plaintext?? '') : undefined; } catch (e: any) { logAndThrow( @@ -979,7 +979,7 @@ class ProgramManager { [], feeRecord, recordSearchParams, - )).recordPlaintext?? '') + )).record_plaintext?? '') : undefined; } catch (e: any) { logAndThrow( @@ -1315,7 +1315,7 @@ class ProgramManager { [], feeRecord, recordSearchParams, - )).recordPlaintext?? '') + )).record_plaintext?? '') : undefined; } catch (e: any) { logAndThrow( @@ -1586,7 +1586,7 @@ class ProgramManager { [], amountRecord, recordSearchParams, - )).recordPlaintext?? ''); + )).record_plaintext?? ''); nonces.push(amountRecord.nonce()); } else { amountRecord = undefined; @@ -1597,7 +1597,7 @@ class ProgramManager { nonces, feeRecord, recordSearchParams, - )).recordPlaintext?? '') + )).record_plaintext?? '') : undefined; } catch (e: any) { logAndThrow( diff --git a/sdk/src/record-provider.ts b/sdk/src/record-provider.ts index b1d959c14..e82c59d12 100644 --- a/sdk/src/record-provider.ts +++ b/sdk/src/record-provider.ts @@ -379,7 +379,7 @@ class NetworkRecordProvider implements RecordProvider { const recordPts = await this.networkClient.findRecords(startHeight, endHeight, searchParameters.unspent, programs, amounts, maxAmount, searchParameters.nonces, this.account.privateKey()); return recordPts.map((record) => ({ - recordPlaintext: record.toString(), + record_plaintext: record.toString(), })); } diff --git a/sdk/src/record-scanner.ts b/sdk/src/record-scanner.ts index 60c9f5ebf..8c2b358b6 100644 --- a/sdk/src/record-scanner.ts +++ b/sdk/src/record-scanner.ts @@ -69,7 +69,7 @@ class RecordScanner implements RecordProvider { throw new Error("Account not set"); } else { request = { - viewKey: this.account.viewKey().toString(), + view_key: this.account.viewKey().to_string(), start: startBlock, }; } @@ -107,8 +107,7 @@ class RecordScanner implements RecordProvider { }), ); - const data = await response.json(); - return data.records; + return await response.json(); } catch (error) { console.error(`Failed to get encrypted records: ${error}`); throw error; @@ -221,14 +220,18 @@ class RecordScanner implements RecordProvider { async findCreditsRecord(microcredits: number, searchParameters: OwnedFilter): Promise { try { const records = await this.findRecords({ - ...searchParameters, - program: "credits.aleo", - record: "credits", decrypt: true, + unspent: searchParameters.unspent ?? false, + filter: { + start: searchParameters.filter?.start ?? 0, + program: "credits.aleo", + record: "credits", + }, + uuid: this.uuid, }); const record = records.find(record => { - const plaintext = RecordPlaintext.fromString(record.recordPlaintext ?? ''); + const plaintext = RecordPlaintext.fromString(record.record_plaintext ?? ''); const amountStr = plaintext.getMember("microcredits").toString(); const amount = parseInt(amountStr.replace("u64", "")); return amount >= microcredits; @@ -255,13 +258,17 @@ class RecordScanner implements RecordProvider { async findCreditsRecords(microcreditAmounts: number[], searchParameters: OwnedFilter): Promise { try { const records = await this.findRecords({ - ...searchParameters, - program: "credits.aleo", - record: "credits", decrypt: true, + unspent: searchParameters.unspent ?? false, + filter: { + start: searchParameters.filter?.start ?? 0, + program: "credits.aleo", + record: "credits", + }, + uuid: this.uuid, }); return records.filter(record => { - const plaintext = RecordPlaintext.fromString(record.recordPlaintext ?? ''); + const plaintext = RecordPlaintext.fromString(record.record_plaintext ?? ''); const amount = plaintext.getMember("microcredits").toString(); return microcreditAmounts.includes(parseInt(amount.replace("u64", ""))); }); @@ -299,9 +306,11 @@ class RecordScanner implements RecordProvider { * @param {RecordsResponseFilter} responseFilter The filter to use to filter the response. * @returns {string} The query string. */ - private buildQueryString(recordsFilter: RecordSearchParams, responseFilter: RecordsResponseFilter): string { + private buildQueryString(recordsFilter: RecordsFilter, responseFilter: RecordsResponseFilter): string { return Object.entries({ ...recordsFilter, ...responseFilter }) .map(([key, value]) => `${key}=${value}`) .join("&"); } -} \ No newline at end of file +} + +export { RecordScanner }; \ No newline at end of file diff --git a/sdk/tests/data/records.ts b/sdk/tests/data/records.ts index f95572ac5..7d53ecf76 100644 --- a/sdk/tests/data/records.ts +++ b/sdk/tests/data/records.ts @@ -27,11 +27,203 @@ const RECORD_PLAINTEXT_STRING = `{ const VIEW_KEY_STRING = "AViewKey1ccEt8A2Ryva5rxnKcAbn7wgTaTsb79tzkKHFpeKsm9NX"; const RECORD_VIEW_KEY_STRING = "4445718830394614891114647247073357094867447866913203502139893824059966201724field"; +const ENCRYPTED_RECORDS = [ + { + "commitment": "6965909872581584914039191373421463306059339471297771776610572511362422620805field", + "checksum": "2207226491095267609257407999738609780640985730602113917526120008525943934340field", + "block_height": 10002012, + "program_name": "credits.aleo", + "function_name": "transfer_public_to_private", + "output_index": 0, + "owner": "ciphertext1qyqzlxhmhtgtjaee6new2hsfls2ygj68va5h009u3h5htdp59gqasqcwfsafx", + "record_ciphertext": "record1qvqsqtu6lwadpwth88209e27p87pg3ztgankjaauhjx7jad5xs4qrkqrqyxx66trwfhkxun9v35hguerqqpqzqxkezwzg7z8lvpj4t2as605kn9ge8k9cvfls3mg0sqult798mfwqcskzfqlyn83lu3e9kexqmklf5ad9d4shvz5ntuenzrdujqpq0esqdddm55", + "record_name": "credits", + "record_nonce": "429364576614161221583920327616773968748250238602322587322300571348491329825group", + "transaction_id": "at1rdlnll8kljpuy506f0tx0r0xd2fck60hw7phkmjnptke77xukg8sekd6c9 ", + "transition_id": "au1f505f9uvu39f2mys9g0gl2qe5t3pr8tsdutzj4pd2apd5l6hzuqqv0t2n3 ", + "transaction_index": 0, + "transition_index": 0 + }, + { + "commitment": "1188397772964800575271786433438577861359270789629265569589281492544120074436field", + "checksum": "5605422683473081183936132265672104254159529769293480273428181950973232566109field", + "block_height": 10003899, + "program_name": "token_registry.aleo", + "function_name": "transfer_public_to_private", + "output_index": 0, + "owner": "ciphertext1qyqvlfsfleandncmy35mrzr2fwt0epgmnmj34dr6nq7zlndg2v3murskqakke", + "record_ciphertext": "record1qvqspnaxp8l8kdk0rvjxnvvgdf9edly9rw0w2x4502vrct7d4pfj80swqsrxzmt0w4h8ggcqqgqsprxt748jj4tl4lm799ys2qjnzz5s6zjrrq6r3ftpn26x4nscncqwpp6x76m9de0kjezrqqpqyq9x2mvm0uzcmt8unclhhze2aa0w2exzx6dwaqrzm879ya65gdytp55qf2ysvks4435vnzkmgwszppeh5vyud97kurp50zn95h7pakj3y8m90p6x2unwv9k97ct4w35x7unf0fshg6t0de0hyet3w45hyetyyvqqyqgqp7tq23f3nz56q6ptwrjyr3xf657pshhrmhvm8yqpuhkfjxjp85gpqct4w35x7unf0fjkghm4de6xjmprqqpqzqyp9aa48avx62cfnce36trczs0fj88ydw8cxg38pv69t983an7gqgaqvv25079m7w7gayzq25ddxadmnnxh5jttm4ra6jrn00vqn7cq7a36w4h", + "record_name": "Token", + "record_nonce": "7096758660619171646018003922303056044411325151108024435925109326208495060538group", + "transaction_id": "at1khgxe7rt7tlcv6kyrytlt57rdu4t67euelyy4fzrysa5spplms8qmyzm0g ", + "transition_id": "au1jxjw3vxpnjl7vrs3ckhkta0yxwq87wxeh7pwcs9q4nfw3knzlgzs95m32s ", + "transaction_index": 0, + "transition_index": 0 + } +] + +const OWNED_RECORDS = [ + { + block_height: 6090931, + commitment: '3190431573263899914391947973857035974322781794111836590387626738139790056909field', + function_name: 'place_bid', + output_index: 0, + owner: '7447609208500997115362207913251419702258326233440670198345216549215225004914field', + program_name: 'private_auction.aleo', + record_ciphertext: 'record1qyqspn9v8rwmmtwfynxl7z98sr3hgevduqk7wl4ufmp8yxanp9x5lhgdqsrxy6tyv3jhyscqqgpqp2k5sw5f37f86hge97ls2avx94pvg7fnaxr7uqwfwkqaljq8jlgyg480mg8fy67fkhqe6ysjxdr47whlt09gwuwmlme57nxvz4kn5qyqy6tygvqqyqsqaljsdk8g0g7f9t4s8z2utywzg6qwpgrl57k8fzvcz5qrthzy5qrk422ak9e9pwylg9k7fh99qa05qu4qj9k5mn7hjf5586fmlrk6qpqxv9kk7atwws3sqqspqryrtmup2kn26693c4f7e79gc0yfdammkxexk84vt0z5wms8dgnq2ztfwd0hw6twdejhygcqqgqsppdjyztk35ekkzq60jx7gg498tccxc9l0ysmk6hal0yglk436ng8pq5wrq99rlhqqglf0gvv70lfavzfu6mgf4sgzmuwej2a2f4umgzssk0fhc', + record_plaintext: '{\n' + + ' owner: aleo1q8zc0asncaw9d83ft2dynyqz08fcpq3p40depmrj4wjda28rdvrsvegg45.private,\n' + + ' bidder: aleo1jqnajd8g6ezqjq0eefm4zeqynwx6vzed8flnkmejw0afy09dusrszzk46k.private,\n' + + ' id: 127430097643352927347108200field.private,\n' + + ' amount: 2u64.private,\n' + + ' is_winner: false.private,\n' + + ' _nonce: 2648035478322331583038604395085314793293985787056490619358776499723646871560group.public,\n' + + ' _version: 0u8.public\n' + + '}', + record_name: 'Bid', + spent: false, + tag: '5941252181432651644402279701137165256963073258332916685063623109173576520831field', + transaction_id: 'at1unmfl5gv6d28c9dt405kxxj8nl7ytl5eq0ue9ctpc7lpmds8qgqqqglh2z ', + transition_id: 'au1rc32lk3umuye2ccpqkx8gw5m2cf92a29wzyfj5r7tg9jtnd2duxq6gtuss ', + transaction_index: 2, + transition_index: 0 + }, + { + block_height: 10139458, + commitment: '1402624598475573407524150006714553637670194594508553840106512461758960165497field', + function_name: 'transfer_public_to_private', + output_index: 0, + owner: '7447609208500997115362207913251419702258326233440670198345216549215225004914field', + program_name: 'credits.aleo', + record_ciphertext: 'record1qvqsq9pcyh8s0zlqwydq0uef42kanl88hct6c7f0y7m3gawxv8er20g2qyxx66trwfhkxun9v35hguerqqpqzqzue2ynz2mfnucdf9gft6cuqussdtge8efqwnlkqvq5nr52nhq9pmv47u20cmsk6mnsl70kxhengndepngqja8s5myu0778nncrttls576mt26', + record_plaintext: '{\n' + + ' owner: aleo1q8zc0asncaw9d83ft2dynyqz08fcpq3p40depmrj4wjda28rdvrsvegg45.private,\n' + + ' microcredits: 1000000u64.private,\n' + + ' _nonce: 4974295747251926833599773262811444029929021845727007977270637854284605120473group.public,\n' + + ' _version: 1u8.public\n' + + '}', + record_name: 'credits', + spent: false, + tag: '2965517500209150226508265073635793457193572667031485750956287906078711930968field', + transaction_id: 'at10g988ruzr70rv0jrnx67ay0f62gzn0vsmlpgsk9seqzd4k406sys8q9xhk ', + transition_id: 'au1wr7q6y6fw2gyrwem2zjm8kp2lvl04eay8hqmaz6l2gd4p60ddy9s2lj2qr ', + transaction_index: 0, + transition_index: 0 + }, + { + block_height: 10140546, + commitment: '6211743159355787867034474916630197652498323872351981366700814088987411118433field', + function_name: 'transfer_public_to_private', + output_index: 0, + owner: '7447609208500997115362207913251419702258326233440670198345216549215225004914field', + program_name: 'credits.aleo', + record_ciphertext: 'record1qvqspk7dttx96hemjmnzkf2vf7exfxt6uu04xxxhr987nycq5xx4lkc3qyxx66trwfhkxun9v35hguerqqpqzqqjw24ydxw5t5jgjhszz6dmtgwcw2r0ckgkg3kkxkh8y8cy0gump9an579cxeg5rxkawwdvh2wmesusl8tpwldpe60wkmgzdgd97pns2akmwge', + record_plaintext: '{\n' + + ' owner: aleo1q8zc0asncaw9d83ft2dynyqz08fcpq3p40depmrj4wjda28rdvrsvegg45.private,\n' + + ' microcredits: 2000000u64.private,\n' + + ' _nonce: 2445210375074181986314301912374182923270809668425048232267618676093400660603group.public,\n' + + ' _version: 1u8.public\n' + + '}', + record_name: 'credits', + spent: false, + tag: '8421937347379608036510120951995833971195343843566214313082589116311107280540field', + transaction_id: 'at1hy45m9w56cr8f5yzqp675heh3rde9996exy9875ylzk2w2nmt5qsfudud5 ', + transition_id: 'au1yp2x7t378w3d74kkkz9vcq7ckeg4h4amc3ltysmylz7a85yuy5pqefcjmh ', + transaction_index: 0, + transition_index: 0 + } +] + +const OWNED_CREDITS_RECORDS = [ + { + block_height: 6090931, + commitment: '3190431573263899914391947973857035974322781794111836590387626738139790056909field', + function_name: 'place_bid', + output_index: 0, + owner: '7447609208500997115362207913251419702258326233440670198345216549215225004914field', + program_name: 'private_auction.aleo', + record_ciphertext: 'record1qyqspn9v8rwmmtwfynxl7z98sr3hgevduqk7wl4ufmp8yxanp9x5lhgdqsrxy6tyv3jhyscqqgpqp2k5sw5f37f86hge97ls2avx94pvg7fnaxr7uqwfwkqaljq8jlgyg480mg8fy67fkhqe6ysjxdr47whlt09gwuwmlme57nxvz4kn5qyqy6tygvqqyqsqaljsdk8g0g7f9t4s8z2utywzg6qwpgrl57k8fzvcz5qrthzy5qrk422ak9e9pwylg9k7fh99qa05qu4qj9k5mn7hjf5586fmlrk6qpqxv9kk7atwws3sqqspqryrtmup2kn26693c4f7e79gc0yfdammkxexk84vt0z5wms8dgnq2ztfwd0hw6twdejhygcqqgqsppdjyztk35ekkzq60jx7gg498tccxc9l0ysmk6hal0yglk436ng8pq5wrq99rlhqqglf0gvv70lfavzfu6mgf4sgzmuwej2a2f4umgzssk0fhc', + record_plaintext: '{\n' + + ' owner: aleo1q8zc0asncaw9d83ft2dynyqz08fcpq3p40depmrj4wjda28rdvrsvegg45.private,\n' + + ' bidder: aleo1jqnajd8g6ezqjq0eefm4zeqynwx6vzed8flnkmejw0afy09dusrszzk46k.private,\n' + + ' id: 127430097643352927347108200field.private,\n' + + ' amount: 2u64.private,\n' + + ' is_winner: false.private,\n' + + ' _nonce: 2648035478322331583038604395085314793293985787056490619358776499723646871560group.public,\n' + + ' _version: 0u8.public\n' + + '}', + record_name: 'Bid', + spent: false, + tag: '5941252181432651644402279701137165256963073258332916685063623109173576520831field', + transaction_id: 'at1unmfl5gv6d28c9dt405kxxj8nl7ytl5eq0ue9ctpc7lpmds8qgqqqglh2z ', + transition_id: 'au1rc32lk3umuye2ccpqkx8gw5m2cf92a29wzyfj5r7tg9jtnd2duxq6gtuss ', + transaction_index: 2, + transition_index: 0 + }, + { + block_height: 10139458, + commitment: '1402624598475573407524150006714553637670194594508553840106512461758960165497field', + function_name: 'transfer_public_to_private', + output_index: 0, + owner: '7447609208500997115362207913251419702258326233440670198345216549215225004914field', + program_name: 'credits.aleo', + record_ciphertext: 'record1qvqsq9pcyh8s0zlqwydq0uef42kanl88hct6c7f0y7m3gawxv8er20g2qyxx66trwfhkxun9v35hguerqqpqzqzue2ynz2mfnucdf9gft6cuqussdtge8efqwnlkqvq5nr52nhq9pmv47u20cmsk6mnsl70kxhengndepngqja8s5myu0778nncrttls576mt26', + record_plaintext: '{\n' + + ' owner: aleo1q8zc0asncaw9d83ft2dynyqz08fcpq3p40depmrj4wjda28rdvrsvegg45.private,\n' + + ' microcredits: 1000000u64.private,\n' + + ' _nonce: 4974295747251926833599773262811444029929021845727007977270637854284605120473group.public,\n' + + ' _version: 1u8.public\n' + + '}', + record_name: 'credits', + spent: false, + tag: '2965517500209150226508265073635793457193572667031485750956287906078711930968field', + transaction_id: 'at10g988ruzr70rv0jrnx67ay0f62gzn0vsmlpgsk9seqzd4k406sys8q9xhk ', + transition_id: 'au1wr7q6y6fw2gyrwem2zjm8kp2lvl04eay8hqmaz6l2gd4p60ddy9s2lj2qr ', + transaction_index: 0, + transition_index: 0 + }, + { + block_height: 10140546, + commitment: '6211743159355787867034474916630197652498323872351981366700814088987411118433field', + function_name: 'transfer_public_to_private', + output_index: 0, + owner: '7447609208500997115362207913251419702258326233440670198345216549215225004914field', + program_name: 'credits.aleo', + record_ciphertext: 'record1qvqspk7dttx96hemjmnzkf2vf7exfxt6uu04xxxhr987nycq5xx4lkc3qyxx66trwfhkxun9v35hguerqqpqzqqjw24ydxw5t5jgjhszz6dmtgwcw2r0ckgkg3kkxkh8y8cy0gump9an579cxeg5rxkawwdvh2wmesusl8tpwldpe60wkmgzdgd97pns2akmwge', + record_plaintext: '{\n' + + ' owner: aleo1q8zc0asncaw9d83ft2dynyqz08fcpq3p40depmrj4wjda28rdvrsvegg45.private,\n' + + ' microcredits: 2000000u64.private,\n' + + ' _nonce: 2445210375074181986314301912374182923270809668425048232267618676093400660603group.public,\n' + + ' _version: 1u8.public\n' + + '}', + record_name: 'credits', + spent: false, + tag: '8421937347379608036510120951995833971195343843566214313082589116311107280540field', + transaction_id: 'at1hy45m9w56cr8f5yzqp675heh3rde9996exy9875ylzk2w2nmt5qsfudud5 ', + transition_id: 'au1yp2x7t378w3d74kkkz9vcq7ckeg4h4amc3ltysmylz7a85yuy5pqefcjmh ', + transaction_index: 0, + transition_index: 0 + } +] + +const CHECK_SNS_RESPONSE = { '3673836024253895240205884165890003872225300531298514519146928213266356324646field': true } + +const CHECK_TAGS_RESPONSE = { + '2965517500209150226508265073635793457193572667031485750956287906078711930968field': false, + '8421937347379608036510120951995833971195343843566214313082589116311107280540field': false, + '5941252181432651644402279701137165256963073258332916685063623109173576520831field': false +} + export { CREDITS_RECORD_V1, CREDITS_RECORD_VIEW_KEY, CREDITS_SENDER_CIPHERTEXT, CREDITS_SENDER_PLAINTEXT, + CHECK_SNS_RESPONSE, + CHECK_TAGS_RESPONSE, + ENCRYPTED_RECORDS, + OWNED_RECORDS, + OWNED_CREDITS_RECORDS, RECORD_CIPHERTEXT_STRING, RECORD_CIPHERTEXT_STRING_COPY, RECORD_CIPHERTEXT_STRING_NOT_OWNED, diff --git a/sdk/tests/record-provider.integration.ts b/sdk/tests/record-provider.integration.ts index a076282b4..53c864e0f 100644 --- a/sdk/tests/record-provider.integration.ts +++ b/sdk/tests/record-provider.integration.ts @@ -22,7 +22,7 @@ describe('RecordProvider', () => { if (Array.isArray(records)) { expect(records.length).equal(2); records.forEach((record) => { - let pt = new RecordPlaintext(record.recordPlaintext); + let pt = new RecordPlaintext(record.record_plaintext); nonces.push(pt.nonce()); }); } else { @@ -34,7 +34,7 @@ describe('RecordProvider', () => { if (Array.isArray(records2)) { expect(records2.length).equal(2); records2.forEach((record) => { - let pt = new RecordPlaintext(record.recordPlaintext); + let pt = new RecordPlaintext(record.record_plaintext); expect(nonces.includes(pt.nonce())).equal(false); nonces.push(pt.nonce()); }); From 8e14c996c1109f214e006176f77a57481c2fe942 Mon Sep 17 00:00:00 2001 From: Alex Pitsikoulis Date: Wed, 20 Aug 2025 15:32:36 -0700 Subject: [PATCH 06/14] corrected fields in RecordsFilter interface --- .../models/record-scanner/recordsFilter.ts | 21 ++++++++++++++++--- sdk/src/record-scanner.ts | 10 +++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/sdk/src/models/record-scanner/recordsFilter.ts b/sdk/src/models/record-scanner/recordsFilter.ts index e72c98575..8cfd181fa 100644 --- a/sdk/src/models/record-scanner/recordsFilter.ts +++ b/sdk/src/models/record-scanner/recordsFilter.ts @@ -1,4 +1,5 @@ import { RecordSearchParams } from "../record-provider/recordSearchParams"; +import { RecordsResponseFilter } from "../record-provider/recordsResponseFilter"; /** * RecordsFilter is an extension of RecordSearchParams that represents a filter for scanning encrypted or owned records. @@ -8,13 +9,27 @@ import { RecordSearchParams } from "../record-provider/recordSearchParams"; * start: 0, * end: 100, * program: "credits.aleo", - * record: "credits", + * records: ["credits"], + * functions: ["transfer_public_to_private"], + * response: { + * program: true, + * record: true, + * function: true, + * transition: true, + * block_height: true, + * transaction_id: true, + * } + * results_per_page: 100, + * page: 0, * } */ export interface RecordsFilter extends RecordSearchParams { start: number; end?: number; program?: string; - record?: string; - function?: string; + records?: string[]; + functions?: string[]; + results_per_page?: number; + page?: number; + response?: RecordsResponseFilter; } \ No newline at end of file diff --git a/sdk/src/record-scanner.ts b/sdk/src/record-scanner.ts index 8c2b358b6..f64780992 100644 --- a/sdk/src/record-scanner.ts +++ b/sdk/src/record-scanner.ts @@ -23,7 +23,7 @@ import { RegistrationRequest } from "./models/record-scanner/registrationRequest * start: 0, * end: 100, * program: "credits.aleo", - * record: "credits", + * records: ["credits"], * }; * * const responseFilter = { @@ -31,8 +31,8 @@ import { RegistrationRequest } from "./models/record-scanner/registrationRequest * record: true, * function: true, * transition: true, - * blockHeight: true, - * transactionId: true, + * block_height: true, + * transaction_id: true, * }; * * const records = await recordScanner.findRecords({ filter, responseFilter }); @@ -308,7 +308,9 @@ class RecordScanner implements RecordProvider { */ private buildQueryString(recordsFilter: RecordsFilter, responseFilter: RecordsResponseFilter): string { return Object.entries({ ...recordsFilter, ...responseFilter }) - .map(([key, value]) => `${key}=${value}`) + .map(([key, value]) => { + return `${key}=${Array.isArray(value) ? value.join(",") : value}` + }) .join("&"); } } From 2572192576a32ae3e5cf3c338bcc2948f675e944 Mon Sep 17 00:00:00 2001 From: Alex Pitsikoulis Date: Wed, 17 Sep 2025 09:20:09 -0700 Subject: [PATCH 07/14] updated encryptedRecords function to mirror changes made to the endpoint in RSS --- .../record-provider/recordsResponseFilter.ts | 40 +++++++++++------- .../models/record-scanner/recordsFilter.ts | 5 ++- sdk/src/record-scanner.ts | 41 ++++++++++--------- 3 files changed, 50 insertions(+), 36 deletions(-) diff --git a/sdk/src/models/record-provider/recordsResponseFilter.ts b/sdk/src/models/record-provider/recordsResponseFilter.ts index ab0b30efa..a971a5b1f 100644 --- a/sdk/src/models/record-provider/recordsResponseFilter.ts +++ b/sdk/src/models/record-provider/recordsResponseFilter.ts @@ -4,23 +4,35 @@ * * @example * const recordsResponseFilter: RecordsResponseFilter = { - * program: true, - * record: true, - * function: true, - * transition: true, - * blockHeight: true, + * block_height: true, + * checksum: true, + * commitment: true, + * record_ciphertext: true, + * function_name: true, + * nonce: true, + * output_index: true, + * owner: true, + * program_name: true, + * record_name: true, * transaction_id: true, * transition_id: true, - * ioIndex: true, + * transaction_index: true, + * transition_index: true, * } */ export type RecordsResponseFilter = { - program: boolean; - record: boolean; - function: boolean; - transition: boolean; - blockHeight: boolean; - transaction_id: boolean; - transition_id: boolean; - ioIndex: boolean; + blockHeight?: boolean; + checksum?: boolean; + commitment?: boolean; + record_ciphertext?: boolean; + function_name?: boolean; + nonce?: boolean; + output_index?: boolean; + owner?: boolean; + program_name?: boolean; + record_name?: boolean; + transaction_id?: boolean; + transition_id?: boolean; + transaction_index?: boolean; + transition_index?: boolean; } \ No newline at end of file diff --git a/sdk/src/models/record-scanner/recordsFilter.ts b/sdk/src/models/record-scanner/recordsFilter.ts index 8cfd181fa..cd4a91184 100644 --- a/sdk/src/models/record-scanner/recordsFilter.ts +++ b/sdk/src/models/record-scanner/recordsFilter.ts @@ -24,12 +24,13 @@ import { RecordsResponseFilter } from "../record-provider/recordsResponseFilter" * } */ export interface RecordsFilter extends RecordSearchParams { - start: number; + commitments?: string[]; + response?: RecordsResponseFilter; + start?: number; end?: number; program?: string; records?: string[]; functions?: string[]; results_per_page?: number; page?: number; - response?: RecordsResponseFilter; } \ No newline at end of file diff --git a/sdk/src/record-scanner.ts b/sdk/src/record-scanner.ts index f64780992..5d9fc136e 100644 --- a/sdk/src/record-scanner.ts +++ b/sdk/src/record-scanner.ts @@ -17,25 +17,26 @@ import { RegistrationRequest } from "./models/record-scanner/registrationRequest * * const recordScanner = new RecordScanner("https://record-scanner.aleo.org"); * recordScanner.setAccount(account); - * await recordScanner.register(0); + * const uuid = await recordScanner.register(0); * * const filter = { - * start: 0, - * end: 100, - * program: "credits.aleo", - * records: ["credits"], + * uuid, + * filter: { + * program: "credits.aleo", + * records: ["credits"], + * }, + * responseFilter: { + * program: true, + * record: true, + * function: true, + * transition: true, + * block_height: true, + * transaction_id: true, + * }, + * unspent: true, * }; * - * const responseFilter = { - * program: true, - * record: true, - * function: true, - * transition: true, - * block_height: true, - * transaction_id: true, - * }; - * - * const records = await recordScanner.findRecords({ filter, responseFilter }); + * const records = await recordScanner.findRecords(filter); */ class RecordScanner implements RecordProvider { account?: Account; @@ -94,16 +95,16 @@ class RecordScanner implements RecordProvider { /** * Get encrypted records from the record scanner service. * - * @param {RecordsFilter} recordsFilter The filter to use to find the records. - * @param {RecordsResponseFilter} responseFilter The filter to use to filter the response. + * @param {RecordsFilter} recordsFilter The filter to use to find the records and filter the response. * @returns {Promise} The encrypted records. */ - async encryptedRecords(recordsFilter: RecordsFilter, responseFilter: RecordsResponseFilter): Promise { + async encryptedRecords(recordsFilter: RecordsFilter): Promise { try { const response = await this.request( - new Request(`${this.url}/records/encrypted?${this.buildQueryString(recordsFilter, responseFilter)}`, { - method: "GET", + new Request(`${this.url}/records/encrypted`, { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify(recordsFilter), }), ); From 691f71bec1cfd1e2754a1c832195e763b9f8c5d2 Mon Sep 17 00:00:00 2001 From: Alex Pitsikoulis Date: Wed, 17 Sep 2025 10:14:44 -0700 Subject: [PATCH 08/14] changed program to programs in RecordsFilter --- sdk/src/models/record-scanner/recordsFilter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/models/record-scanner/recordsFilter.ts b/sdk/src/models/record-scanner/recordsFilter.ts index cd4a91184..f0d3c314b 100644 --- a/sdk/src/models/record-scanner/recordsFilter.ts +++ b/sdk/src/models/record-scanner/recordsFilter.ts @@ -8,7 +8,7 @@ import { RecordsResponseFilter } from "../record-provider/recordsResponseFilter" * const recordsFilter: RecordsFilter = { * start: 0, * end: 100, - * program: "credits.aleo", + * programs: ["credits.aleo"], * records: ["credits"], * functions: ["transfer_public_to_private"], * response: { @@ -28,7 +28,7 @@ export interface RecordsFilter extends RecordSearchParams { response?: RecordsResponseFilter; start?: number; end?: number; - program?: string; + programs?: string[]; records?: string[]; functions?: string[]; results_per_page?: number; From f541996facfa6b23b27f76d42aeb377810f00ad8 Mon Sep 17 00:00:00 2001 From: Alex Pitsikoulis Date: Wed, 17 Sep 2025 10:48:09 -0700 Subject: [PATCH 09/14] fixed outdated test case for invalid transaction id API response --- sdk/tests/network-client.test.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/sdk/tests/network-client.test.ts b/sdk/tests/network-client.test.ts index 25ca9f762..b14c9abd5 100644 --- a/sdk/tests/network-client.test.ts +++ b/sdk/tests/network-client.test.ts @@ -370,17 +370,7 @@ describe("NodeConnection", () => { it("should throw for a malformed tx ID", async () => { const connection = new AleoNetworkClient(host); - try { - await connection.waitForTransactionConfirmation(invalidTx); - throw new Error( - "Expected waitForTransactionConfirmation to throw", - ); - } catch (err: any) { - console.log(err.message); - if (connection.network === "mainnet") { - expect(err.message).to.include("Malformed transaction ID"); - } - } + expectThrows(() => connection.waitForTransactionConfirmation(invalidTx)); }); }); From 39675bf801b7ea6fc95401624587c8d9c1a10036 Mon Sep 17 00:00:00 2001 From: Alex Pitsikoulis Date: Wed, 17 Sep 2025 11:18:48 -0700 Subject: [PATCH 10/14] added optional apiKey field to RecordScanner --- sdk/src/record-scanner.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sdk/src/record-scanner.ts b/sdk/src/record-scanner.ts index 5d9fc136e..355fa39d5 100644 --- a/sdk/src/record-scanner.ts +++ b/sdk/src/record-scanner.ts @@ -41,11 +41,13 @@ import { RegistrationRequest } from "./models/record-scanner/registrationRequest class RecordScanner implements RecordProvider { account?: Account; readonly url: string; + private apiKey?: string; private uuid?: string; - constructor(url: string, account?: Account) { + constructor(url: string, account?: Account, apiKey?: string) { this.account = account; this.url = url; + this.apiKey = apiKey; } /** @@ -58,6 +60,15 @@ class RecordScanner implements RecordProvider { this.account = account; } + /** + * Set the API key to use for the record scanner. + * + * @param {string} apiKey The API key to use for the record scanner. + */ + async setApiKey(apiKey: string): Promise { + this.apiKey = apiKey; + } + /** * Register the account with the record scanner service. * @@ -287,6 +298,9 @@ class RecordScanner implements RecordProvider { */ private async request(req: Request): Promise { try { + if (this.apiKey) { + req.headers.set("X-Provable-API-Key", this.apiKey); + } const response = await fetch(req); if (!response.ok) { From 2549a9d8f9cae8275ee22a471f676355c5b2001d Mon Sep 17 00:00:00 2001 From: Alex Pitsikoulis Date: Wed, 17 Sep 2025 15:36:45 -0700 Subject: [PATCH 11/14] changed api key field on RecordScanner to be able to accept either a plain key as a string or an object containing the key and the header name --- sdk/src/record-scanner.ts | 27 ++++++--------------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/sdk/src/record-scanner.ts b/sdk/src/record-scanner.ts index 355fa39d5..7816196e4 100644 --- a/sdk/src/record-scanner.ts +++ b/sdk/src/record-scanner.ts @@ -41,13 +41,13 @@ import { RegistrationRequest } from "./models/record-scanner/registrationRequest class RecordScanner implements RecordProvider { account?: Account; readonly url: string; - private apiKey?: string; + private apiKey?: { header: string, value: string }; private uuid?: string; - constructor(url: string, account?: Account, apiKey?: string) { + constructor(url: string, account?: Account, apiKey?: string | { header: string, value: string }) { this.account = account; this.url = url; - this.apiKey = apiKey; + this.apiKey = typeof apiKey === "string" ? { header: "X-Provable-API-Key", value: apiKey } : apiKey; } /** @@ -65,8 +65,8 @@ class RecordScanner implements RecordProvider { * * @param {string} apiKey The API key to use for the record scanner. */ - async setApiKey(apiKey: string): Promise { - this.apiKey = apiKey; + async setApiKey(apiKey: string | { header: string, value: string }): Promise { + this.apiKey = typeof apiKey === "string" ? { header: "X-Provable-API-Key", value: apiKey } : apiKey; } /** @@ -299,7 +299,7 @@ class RecordScanner implements RecordProvider { private async request(req: Request): Promise { try { if (this.apiKey) { - req.headers.set("X-Provable-API-Key", this.apiKey); + req.headers.set(this.apiKey.header, this.apiKey.value); } const response = await fetch(req); @@ -313,21 +313,6 @@ class RecordScanner implements RecordProvider { throw error; } } - - /** - * Helper function to build a query string from the records filter and response filter. - * - * @param {RecordSearchParams} recordsFilter The filter to use to find the records. - * @param {RecordsResponseFilter} responseFilter The filter to use to filter the response. - * @returns {string} The query string. - */ - private buildQueryString(recordsFilter: RecordsFilter, responseFilter: RecordsResponseFilter): string { - return Object.entries({ ...recordsFilter, ...responseFilter }) - .map(([key, value]) => { - return `${key}=${Array.isArray(value) ? value.join(",") : value}` - }) - .join("&"); - } } export { RecordScanner }; \ No newline at end of file From 3b345bab7735a6c9691888c26d44eac57cbf06ce Mon Sep 17 00:00:00 2001 From: Alex Pitsikoulis Date: Fri, 19 Sep 2025 10:28:53 -0700 Subject: [PATCH 12/14] removed concrete record scanner impl --- sdk/src/browser.ts | 4 +- .../models/record-provider/encryptedRecord.ts | 8 +- sdk/src/models/record-provider/ownedRecord.ts | 2 +- sdk/src/models/record-scanner/ownedFilter.ts | 4 +- .../ownedRecordsResponseFilter.ts | 42 +++ .../models/record-scanner/recordsFilter.ts | 2 +- .../recordsResponseFilter.ts | 0 .../record-scanner/registrationResponse.ts | 15 + sdk/src/record-provider.ts | 12 +- sdk/src/record-scanner.ts | 318 ------------------ 10 files changed, 74 insertions(+), 333 deletions(-) create mode 100644 sdk/src/models/record-scanner/ownedRecordsResponseFilter.ts rename sdk/src/models/{record-provider => record-scanner}/recordsResponseFilter.ts (100%) create mode 100644 sdk/src/models/record-scanner/registrationResponse.ts delete mode 100644 sdk/src/record-scanner.ts diff --git a/sdk/src/browser.ts b/sdk/src/browser.ts index 1c38004b6..47313d8a7 100644 --- a/sdk/src/browser.ts +++ b/sdk/src/browser.ts @@ -27,7 +27,7 @@ import { ProvingRequestJSON } from "./models/provingRequest.js"; import { ProvingResponse } from "./models/provingResponse.js"; import { RatificationJSON } from "./models/ratification.js"; import { RecordsFilter } from "./models/record-scanner/recordsFilter.js"; -import { RecordsResponseFilter } from "./models/record-provider/recordsResponseFilter.js"; +import { RecordsResponseFilter } from "./models/record-scanner/recordsResponseFilter.js"; import { RecordSearchParams } from "./models/record-provider/recordSearchParams.js"; import { SolutionsJSON, SolutionJSON, PartialSolutionJSON } from "./models/solution.js"; import { TransactionJSON } from "./models/transaction/transactionJSON.js"; @@ -52,7 +52,6 @@ import { NetworkRecordProvider, RecordProvider, } from "./record-provider.js"; -import { RecordScanner } from "./record-scanner.js"; // @TODO: This function is no longer needed, remove it. async function initializeWasm() { @@ -178,7 +177,6 @@ export { RecordsFilter, RecordsResponseFilter, RecordProvider, - RecordScanner, RecordSearchParams, SolutionJSON, SolutionsJSON, diff --git a/sdk/src/models/record-provider/encryptedRecord.ts b/sdk/src/models/record-provider/encryptedRecord.ts index 0ce5daccf..86f53cf02 100644 --- a/sdk/src/models/record-provider/encryptedRecord.ts +++ b/sdk/src/models/record-provider/encryptedRecord.ts @@ -1,9 +1,10 @@ /** - * Encrypted Record found on chain. This type provides the record ciphertext and metadata from the ledger that such as the record's name, the program/function that produced it, etc. + * Encrypted Record found on chain. This type provides the record ciphertext and metadata from the ledger such as the record's name, the program/function that produced it, etc. * * @property {string} commitment - The commitment of the record. * @property {string | undefined} checksum - The checksum of the record. * @property {number | undefined} block_height - The block height of the record. + * @property {number | undefined} block_timestamp - The block timestamp of the record. * @property {string | undefined} program_name - The name of the program that produced the record. * @property {string | undefined} function_name - The name of the function that produced the record. * @property {number | undefined} output_index - The output index of the record. @@ -11,6 +12,7 @@ * @property {string | undefined} record_ciphertext - The ciphertext of the record. * @property {string | undefined} record_name - The name of the record. * @property {string | undefined} record_nonce - The nonce of the record. + * @property {string | undefined} sender_ciphertext - The ciphertext of the sender. * @property {string | undefined} transaction_id - The ID of the transaction that produced the record. * @property {string | undefined} transition_id - The ID of the transition that produced the record. * @property {number | undefined} transaction_index - The index of the transaction that produced the record. @@ -25,9 +27,10 @@ * function_name: "transfer_private", * output_index: 0, * owner: "ciphertext1qgqdetlfzk98jkm4e7sgqml66e3x2gpg5d6udkpw0g67z0tplkpmzrm6q5dyfd7xhgmhedvptxzwfhrtxaqn7n0hs0esge3lwg9s2zukqgzxd0cr", - * recordCiphertext: "record1qyqsqt43u9kp97svljyyup3v4jmppd0vgght9edvvmtxx6mxycsej8cwqsrxzmt0w4h8ggcqqgqspf8zqut2ycnap7f0uzz5ktu0cxscca96urtkg2aweuzn70787dsrpp6x76m9de0kjezrqqpqyqp3mn3xeh53lukvcy406amjf5g0ksl3saauzjk0j4ljtjqq6kqlqhdz05sw92zye96qym7kp83ra0eesgtwhaw37c85r499456se8ts28m90p6x2unwv9k97ct4w35x7unf0fshg6t0de0hyet3w45hyetyyvqqyqgq4t2wr9tmcrfha5tfz5j585ptvvslqe0f6sf29vytshhdh7ym05rpqct4w35x7unf0fjkghm4de6xjmprqqpqzqru6p7fef29vuz6smyqwcn3z7jhxtdgjdw5xv23ppxhpgnvu72fp8hz6fjt6gsdn8yxhzq7gpsah0rscwqrzxwl5e8aemkj5gt09y7q5506yrf", + * record_ciphertext: "record1qyqsqt43u9kp97svljyyup3v4jmppd0vgght9edvvmtxx6mxycsej8cwqsrxzmt0w4h8ggcqqgqspf8zqut2ycnap7f0uzz5ktu0cxscca96urtkg2aweuzn70787dsrpp6x76m9de0kjezrqqpqyqp3mn3xeh53lukvcy406amjf5g0ksl3saauzjk0j4ljtjqq6kqlqhdz05sw92zye96qym7kp83ra0eesgtwhaw37c85r499456se8ts28m90p6x2unwv9k97ct4w35x7unf0fshg6t0de0hyet3w45hyetyyvqqyqgq4t2wr9tmcrfha5tfz5j585ptvvslqe0f6sf29vytshhdh7ym05rpqct4w35x7unf0fjkghm4de6xjmprqqpqzqru6p7fef29vuz6smyqwcn3z7jhxtdgjdw5xv23ppxhpgnvu72fp8hz6fjt6gsdn8yxhzq7gpsah0rscwqrzxwl5e8aemkj5gt09y7q5506yrf", * record_name: "credits", * record_nonce: "3077450429259593211617823051143573281856129402760267155982965992208217472983group", + * sender_ciphertext: "1754131901135854615627743152473414463769543922079966020586765988138574911385field", * transaction_id: "at1f8ueqxu3x49sckpc6jlg676tmxumddzer3fwe2l0dxwj4dqxygyqua4u2q", * transition_id: "au17mm5v7sfwus6y40xsyc99d5rtsr4vsajdec6twdjzv0m458q85zspqdnka", * transaction_index: 0, @@ -38,6 +41,7 @@ export type EncryptedRecord = { commitment: string; checksum?: string; block_height?: number; + block_timestamp?: number; program_name?: string; function_name?: string; output_index?: number; diff --git a/sdk/src/models/record-provider/ownedRecord.ts b/sdk/src/models/record-provider/ownedRecord.ts index 659c04306..2a1e83eda 100644 --- a/sdk/src/models/record-provider/ownedRecord.ts +++ b/sdk/src/models/record-provider/ownedRecord.ts @@ -1,5 +1,5 @@ /** - * Record owned by a registered view key. This type provides the record ciphertext, record plaintext and metadata from the ledger that such as the record's name, the program/function that produced it, etc. + * Record owned by a registered view key. This type provides the record ciphertext, record plaintext and metadata from the ledger such as the record's name, the program/function that produced it, etc. * * @property {number | undefined} block_height - Block height where the record was created. * @property {string | undefined} commitment - Commitment of the record. diff --git a/sdk/src/models/record-scanner/ownedFilter.ts b/sdk/src/models/record-scanner/ownedFilter.ts index 450bb6721..fddacbe9a 100644 --- a/sdk/src/models/record-scanner/ownedFilter.ts +++ b/sdk/src/models/record-scanner/ownedFilter.ts @@ -1,6 +1,6 @@ import { RecordSearchParams } from "../record-provider/recordSearchParams"; import { RecordsFilter } from "./recordsFilter"; -import { RecordsResponseFilter } from "../record-provider/recordsResponseFilter"; +import { OwnedRecordsResponseFilter } from "./ownedRecordsResponseFilter"; /** * OwnedFilter is an extension of RecordSearchParams that represents a filter for scanning owned records. @@ -19,6 +19,6 @@ import { RecordsResponseFilter } from "../record-provider/recordsResponseFilter" export interface OwnedFilter extends RecordSearchParams { decrypt?: boolean; filter?: RecordsFilter; - responseFilter?: RecordsResponseFilter; + responseFilter?: OwnedRecordsResponseFilter; uuid?: string; } \ No newline at end of file diff --git a/sdk/src/models/record-scanner/ownedRecordsResponseFilter.ts b/sdk/src/models/record-scanner/ownedRecordsResponseFilter.ts new file mode 100644 index 000000000..0dc72bf4e --- /dev/null +++ b/sdk/src/models/record-scanner/ownedRecordsResponseFilter.ts @@ -0,0 +1,42 @@ +/** + * OwnedRecordsResponseFilter is a type that represents a filter for the response from a record provider. + * A `true` value for a field in the filter will include that field in the response. + * + * @example + * const ownedRecordsResponseFilter: OwnedRecordsResponseFilter = { + * commitment: true, + * owner: true, + * tag: true, + * sender: true, + * spent: true, + * record_ciphertext: true, + * block_height: true, + * block_timestamp: true, + * output_index: true, + * record_name: true, + * function_name: true, + * program_name: true, + * transition_id: true, + * transaction_id: true, + * transaction_index: true, + * transition_index: true, + * } + */ +export interface OwnedRecordsResponseFilter { + commitment?: boolean; + owner?: boolean; + tag?: boolean; + sender?: boolean; + spent?: boolean; + record_ciphertext?: boolean; + block_height?: boolean; + block_timestamp?: boolean; + output_index?: boolean; + record_name?: boolean; + function_name?: boolean; + program_name?: boolean; + transition_id?: boolean; + transaction_id?: boolean; + transaction_index?: boolean; + transition_index?: boolean; +} \ No newline at end of file diff --git a/sdk/src/models/record-scanner/recordsFilter.ts b/sdk/src/models/record-scanner/recordsFilter.ts index f0d3c314b..bc65024f3 100644 --- a/sdk/src/models/record-scanner/recordsFilter.ts +++ b/sdk/src/models/record-scanner/recordsFilter.ts @@ -1,5 +1,5 @@ import { RecordSearchParams } from "../record-provider/recordSearchParams"; -import { RecordsResponseFilter } from "../record-provider/recordsResponseFilter"; +import { RecordsResponseFilter } from "./recordsResponseFilter"; /** * RecordsFilter is an extension of RecordSearchParams that represents a filter for scanning encrypted or owned records. diff --git a/sdk/src/models/record-provider/recordsResponseFilter.ts b/sdk/src/models/record-scanner/recordsResponseFilter.ts similarity index 100% rename from sdk/src/models/record-provider/recordsResponseFilter.ts rename to sdk/src/models/record-scanner/recordsResponseFilter.ts diff --git a/sdk/src/models/record-scanner/registrationResponse.ts b/sdk/src/models/record-scanner/registrationResponse.ts new file mode 100644 index 000000000..12177f2e9 --- /dev/null +++ b/sdk/src/models/record-scanner/registrationResponse.ts @@ -0,0 +1,15 @@ +/** + * RegistrationResponse is a type that represents a response from a record scanning service. + * + * @example + * const registrationResponse: RegistrationResponse = { + * uuid: "5291249998620209321712738612705518874926462927543783711572375085855029172391field", + * job_id: "3019177021147406178755252788128212930359855601860174268911518336835545087409field", + * status: "pending", + * } + */ +interface RegistrationResponse { + uuid: string, + job_id?: string, + status?: string + } \ No newline at end of file diff --git a/sdk/src/record-provider.ts b/sdk/src/record-provider.ts index e82c59d12..d95c772cc 100644 --- a/sdk/src/record-provider.ts +++ b/sdk/src/record-provider.ts @@ -4,7 +4,7 @@ import { EncryptedRecord } from "./models/record-provider/encryptedRecord.js"; import { logAndThrow } from "./utils.js"; import { OwnedRecord } from "./models/record-provider/ownedRecord.js"; import { RecordSearchParams } from "./models/record-provider/recordSearchParams.js"; -import { RecordsResponseFilter } from "./models/record-provider/recordsResponseFilter.js"; +import { RecordsResponseFilter } from "./models/record-scanner/recordsResponseFilter.js"; /** * Interface for a record provider. A record provider is used to find records for use in deployment and execution @@ -25,10 +25,10 @@ interface RecordProvider { * @param {RecordsResponseFilter} responseFilter The filter used to filter the response. * @returns {Promise} The encrypted records. */ - encryptedRecords(recordsFilter: RecordSearchParams, responseFilter: RecordsResponseFilter): Promise; + encryptedRecords(recordsFilter: RecordSearchParams, responseFilter?: RecordsResponseFilter): Promise; /** - * Check if a list of serial numbers exist in the chosen provider + * Check if a list of serial numbers exist in the chosen provider. * * @param {string[]} serialNumbers The serial numbers to check. * @returns {Promise>} Map of Aleo Record serial numbers and whether they appeared in any inputs on chain. If boolean corresponding to the Serial Number has a true value, that Record is considered spent by the Aleo Network. @@ -36,7 +36,7 @@ interface RecordProvider { checkSerialNumbers(serialNumbers: string[]): Promise>; /** - * Check if a list of tags exist in the chosen provider + * Check if a list of tags exist in the chosen provider. * * @param {string[]} tags The tags to check. * @returns {Promise>} Map of Aleo Record tags and whether they appeared in any inputs on chain. If boolean corresponding to the tag has a true value, that Record is considered spent by the Aleo Network. @@ -44,7 +44,7 @@ interface RecordProvider { checkTags(tags: string[]): Promise>; /** - * Find a credits.aleo record with a given number of microcredits from the chosen provider + * Find a credits.aleo record with a given number of microcredits from the chosen provider. * * @param {number} microcredits The number of microcredits to search for. * @param {RecordSearchParams} searchParameters Additional parameters to search for. @@ -383,7 +383,7 @@ class NetworkRecordProvider implements RecordProvider { })); } - async encryptedRecords(recordsFilter: RecordSearchParams, responseFilter: RecordsResponseFilter): Promise { + async encryptedRecords(recordsFilter: RecordSearchParams, responseFilter?: RecordsResponseFilter): Promise { throw new Error("Not implemented"); } diff --git a/sdk/src/record-scanner.ts b/sdk/src/record-scanner.ts deleted file mode 100644 index 7816196e4..000000000 --- a/sdk/src/record-scanner.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { Account } from "./account"; -import { EncryptedRecord } from "./models/record-provider/encryptedRecord"; -import { OwnedFilter } from "./models/record-scanner/ownedFilter"; -import { OwnedRecord } from "./models/record-provider/ownedRecord"; -import { RecordProvider } from "./record-provider"; -import { RecordPlaintext } from "./wasm"; -import { RecordSearchParams } from "./models/record-provider/recordSearchParams"; -import { RecordsFilter } from "./models/record-scanner/recordsFilter"; -import { RecordsResponseFilter } from "./models/record-provider/recordsResponseFilter"; -import { RegistrationRequest } from "./models/record-scanner/registrationRequest"; - -/** - * RecordScanner is a RecordProvider implementation that uses the record scanner service to find records. - * - * @example - * const account = new Account({ privateKey: 'APrivateKey1...' }); - * - * const recordScanner = new RecordScanner("https://record-scanner.aleo.org"); - * recordScanner.setAccount(account); - * const uuid = await recordScanner.register(0); - * - * const filter = { - * uuid, - * filter: { - * program: "credits.aleo", - * records: ["credits"], - * }, - * responseFilter: { - * program: true, - * record: true, - * function: true, - * transition: true, - * block_height: true, - * transaction_id: true, - * }, - * unspent: true, - * }; - * - * const records = await recordScanner.findRecords(filter); - */ -class RecordScanner implements RecordProvider { - account?: Account; - readonly url: string; - private apiKey?: { header: string, value: string }; - private uuid?: string; - - constructor(url: string, account?: Account, apiKey?: string | { header: string, value: string }) { - this.account = account; - this.url = url; - this.apiKey = typeof apiKey === "string" ? { header: "X-Provable-API-Key", value: apiKey } : apiKey; - } - - /** - * Set the account to use for the record scanner. - * - * @param {Account} account The account to use for the record scanner. - */ - async setAccount(account: Account): Promise { - this.uuid = undefined; - this.account = account; - } - - /** - * Set the API key to use for the record scanner. - * - * @param {string} apiKey The API key to use for the record scanner. - */ - async setApiKey(apiKey: string | { header: string, value: string }): Promise { - this.apiKey = typeof apiKey === "string" ? { header: "X-Provable-API-Key", value: apiKey } : apiKey; - } - - /** - * Register the account with the record scanner service. - * - * @param {number} startBlock The block height to start scanning from. - * @returns {Promise} A promise that resolves when the account is registered. - */ - async register(startBlock: number): Promise { - let request: RegistrationRequest; - if (!this.account) { - throw new Error("Account not set"); - } else { - request = { - view_key: this.account.viewKey().to_string(), - start: startBlock, - }; - } - - try { - const response = await this.request( - new Request(`${this.url}/register`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(request), - }) - ); - - const data = await response.json(); - this.uuid = data.uuid; - } catch (error) { - console.error(`Failed to register view key: ${error}`); - throw error; - } - } - - /** - * Get encrypted records from the record scanner service. - * - * @param {RecordsFilter} recordsFilter The filter to use to find the records and filter the response. - * @returns {Promise} The encrypted records. - */ - async encryptedRecords(recordsFilter: RecordsFilter): Promise { - try { - const response = await this.request( - new Request(`${this.url}/records/encrypted`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(recordsFilter), - }), - ); - - return await response.json(); - } catch (error) { - console.error(`Failed to get encrypted records: ${error}`); - throw error; - } - } - - /** - * Check if a list of serial numbers exist in the record scanner service. - * - * @param {string[]} serialNumbers The serial numbers to check. - * @returns {Promise>} Map of Aleo Record serial numbers and whether they appeared in any inputs on chain. If boolean corresponding to the Serial Number has a true value, that Record is considered spent by the Aleo Network. - */ - async checkSerialNumbers(serialNumbers: string[]): Promise> { - try { - const response = await this.request( - new Request(`${this.url}/records/sns`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(serialNumbers), - }), - ); - - return await response.json(); - } catch (error) { - console.error(`Failed to check if serial numbers exist: ${error}`); - throw error; - } - } - - /** - * Check if a list of tags exist in the record scanner service. - * - * @param {string[]} tags The tags to check. - * @returns {Promise>} Map of Aleo Record tags and whether they appeared in any inputs on chain. If boolean corresponding to the tag has a true value, that Record is considered spent by the Aleo Network. - */ - async checkTags(tags: string[]): Promise> { - try { - const response = await this.request( - new Request(`${this.url}/records/tags`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(tags), - }), - ); - - return await response.json(); - } catch (error) { - console.error(`Failed to check if tags exist: ${error}`); - throw error; - } - } - - /** - * Find a record in the record scanner service. - * - * @param {OwnedFilter} searchParameters The filter to use to find the record. - * @returns {Promise} The record. - */ - async findRecord(searchParameters: OwnedFilter): Promise { - try { - const records = await this.findRecords(searchParameters); - - if (records.length > 0) { - return records[0]; - } - - throw new Error("Record not found"); - } catch (error) { - console.error(`Failed to find record: ${error}`); - throw error; - } - } - - /** - * Find records in the record scanner service. - * - * @param {OwnedFilter} filter The filter to use to find the records. - * @returns {Promise} The records. - */ - async findRecords(filter: OwnedFilter): Promise { - if (!this.uuid) { - throw new Error("Not registered"); - } - - filter.uuid = this.uuid; - - try { - const response = await this.request( - new Request(`${this.url}/records/owned`, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify(filter), - }), - ); - - return await response.json(); - } catch (error) { - console.error(`Failed to get owned records: ${error}`); - throw error; - } - } - - /** - * Find a credits record in the record scanner service. - * - * @param {number} microcredits The amount of microcredits to find. - * @param {OwnedFilter} searchParameters The filter to use to find the record. - * @returns {Promise} The record. - */ - async findCreditsRecord(microcredits: number, searchParameters: OwnedFilter): Promise { - try { - const records = await this.findRecords({ - decrypt: true, - unspent: searchParameters.unspent ?? false, - filter: { - start: searchParameters.filter?.start ?? 0, - program: "credits.aleo", - record: "credits", - }, - uuid: this.uuid, - }); - - const record = records.find(record => { - const plaintext = RecordPlaintext.fromString(record.record_plaintext ?? ''); - const amountStr = plaintext.getMember("microcredits").toString(); - const amount = parseInt(amountStr.replace("u64", "")); - return amount >= microcredits; - }); - - if (!record) { - throw new Error("Record not found"); - } - - return record; - } catch (error) { - console.error(`Failed to find credits record: ${error}`); - throw error; - } - } - - /** - * Find credits records using a record scanning service. - * - * @param {number[]} microcreditAmounts The amounts of microcredits to find. - * @param {OwnedFilter} searchParameters The filter to use to find the records. - * @returns {Promise} The records - */ - async findCreditsRecords(microcreditAmounts: number[], searchParameters: OwnedFilter): Promise { - try { - const records = await this.findRecords({ - decrypt: true, - unspent: searchParameters.unspent ?? false, - filter: { - start: searchParameters.filter?.start ?? 0, - program: "credits.aleo", - record: "credits", - }, - uuid: this.uuid, - }); - return records.filter(record => { - const plaintext = RecordPlaintext.fromString(record.record_plaintext ?? ''); - const amount = plaintext.getMember("microcredits").toString(); - return microcreditAmounts.includes(parseInt(amount.replace("u64", ""))); - }); - } catch (error) { - console.error(`Failed to find credits records: ${error}`); - throw error; - } - } - - /** - * Wrapper function to make a request to the record scanner service and handle any errors. - * - * @param {Request} req The request to make. - * @returns {Promise} The response. - */ - private async request(req: Request): Promise { - try { - if (this.apiKey) { - req.headers.set(this.apiKey.header, this.apiKey.value); - } - const response = await fetch(req); - - if (!response.ok) { - throw new Error(await response.text() ?? `Request to ${req.url} failed with status ${response.status}`); - } - - return response; - } catch (error) { - console.error(`Failed to make request to ${req.url}: ${error}`); - throw error; - } - } -} - -export { RecordScanner }; \ No newline at end of file From 71654fb14155d6df8fe6ba036a9d71479324ba86 Mon Sep 17 00:00:00 2001 From: Mike Turner Date: Fri, 19 Sep 2025 19:22:09 -0400 Subject: [PATCH 13/14] Add missing fields to documentation and objects Signed-off-by: Mike Turner --- sdk/src/models/record-provider/encryptedRecord.ts | 2 ++ sdk/src/models/record-provider/ownedRecord.ts | 6 ++++++ sdk/src/models/record-scanner/ownedFilter.ts | 1 + 3 files changed, 9 insertions(+) diff --git a/sdk/src/models/record-provider/encryptedRecord.ts b/sdk/src/models/record-provider/encryptedRecord.ts index 86f53cf02..59e14fa42 100644 --- a/sdk/src/models/record-provider/encryptedRecord.ts +++ b/sdk/src/models/record-provider/encryptedRecord.ts @@ -23,6 +23,7 @@ * commitment: "1754131901135854615627743152473414463769543922079966020586765988138574911385field", * checksum: "731623304764338277682996290553427512270277231686866672455141481050283829616field", * block_height: 123456, + * block_timestamp: 1725845998, * program_name: "credits.aleo", * function_name: "transfer_private", * output_index: 0, @@ -49,6 +50,7 @@ export type EncryptedRecord = { record_ciphertext?: string; record_name?: string; record_nonce?: string; + sender_ciphertext?: string; transaction_id?: string; transition_id?: string; transaction_index?: number; diff --git a/sdk/src/models/record-provider/ownedRecord.ts b/sdk/src/models/record-provider/ownedRecord.ts index 2a1e83eda..d905558e2 100644 --- a/sdk/src/models/record-provider/ownedRecord.ts +++ b/sdk/src/models/record-provider/ownedRecord.ts @@ -2,6 +2,7 @@ * Record owned by a registered view key. This type provides the record ciphertext, record plaintext and metadata from the ledger such as the record's name, the program/function that produced it, etc. * * @property {number | undefined} block_height - Block height where the record was created. + * @property {number | undefined} block_timestamp - The timestamp of the block that the record was created in. * @property {string | undefined} commitment - Commitment of the record. * @property {string | undefined} function_name - Name of the function that created the record. * @property {number | undefined} output_index - Index of the output in the function call that created the record. @@ -9,6 +10,7 @@ * @property {string | undefined} program_name - Name of the program that created the record. * @property {string | undefined} record_ciphertext - Encrypted ciphertext of the record. * @property {string | undefined} record_name - Name of the record. + * @property {string | undefined} sender - Address of the sender. * @property {boolean | undefined} spent - Whether the record has been spent. * @property {string | undefined} tag - Tag associated with the record. * @property {string | undefined} transaction_id - ID of the transaction that created the record. @@ -19,6 +21,7 @@ * @example * const ownedRecord: OwnedRecord = { * block_height: 123456, + * block_timestamp: 1725845998, * commitment: "1754131901135854615627743152473414463769543922079966020586765988138574911385field", * function_name: "transfer_public_to_private", * output_index: 0, @@ -28,6 +31,7 @@ * record_plaintext: "{ owner: aleo1j7qxyunfldj2lp8hsvy7mw5k8zaqgjfyr72x2gh3x4ewgae8v5gscf5jh3.private, microcredits: 1500000000000000u64.private, _nonce: 3077450429259593211617823051143573281856129402760267155982965992208217472983group.public , _version: 1u8 }", * record_name: "credits", * spent: true, + * sender: "aleo1sf5kk4f8mcmgjasw9fannmm0h8z2nwqxu5e200cjneu28jxvtvpqulfxsa", * tag: "6511661650536816422260305447175136877451468301541296257226129781611237851030field", * transaction_id: "at1f8ueqxu3x49sckpc6jlg676tmxumddzer3fwe2l0dxwj4dqxygyqua4u2q", * transition_id: "au17mm5v7sfwus6y40xsyc99d5rtsr4vsajdec6twdjzv0m458q85zspqdnka", @@ -37,6 +41,7 @@ */ export type OwnedRecord = { block_height?: number; + block_timestamp?: number; commitment?: string; function_name?: string; output_index?: number; @@ -45,6 +50,7 @@ export type OwnedRecord = { record_ciphertext?: string; record_plaintext?: string; record_name?: string; + sender?: string; spent?: boolean; tag?: string; transaction_id?: string; diff --git a/sdk/src/models/record-scanner/ownedFilter.ts b/sdk/src/models/record-scanner/ownedFilter.ts index fddacbe9a..0f6f8b542 100644 --- a/sdk/src/models/record-scanner/ownedFilter.ts +++ b/sdk/src/models/record-scanner/ownedFilter.ts @@ -20,5 +20,6 @@ export interface OwnedFilter extends RecordSearchParams { decrypt?: boolean; filter?: RecordsFilter; responseFilter?: OwnedRecordsResponseFilter; + unspent?: bool; uuid?: string; } \ No newline at end of file From 957f8c8cdc08f789dc58b9e931f5593864a1fa4a Mon Sep 17 00:00:00 2001 From: Alex Pitsikoulis Date: Fri, 19 Sep 2025 16:34:57 -0700 Subject: [PATCH 14/14] corrected boolean type name in ownedFilter.ts --- sdk/src/models/record-scanner/ownedFilter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/models/record-scanner/ownedFilter.ts b/sdk/src/models/record-scanner/ownedFilter.ts index 0f6f8b542..dd91fb626 100644 --- a/sdk/src/models/record-scanner/ownedFilter.ts +++ b/sdk/src/models/record-scanner/ownedFilter.ts @@ -20,6 +20,6 @@ export interface OwnedFilter extends RecordSearchParams { decrypt?: boolean; filter?: RecordsFilter; responseFilter?: OwnedRecordsResponseFilter; - unspent?: bool; + unspent?: boolean; uuid?: string; } \ No newline at end of file