diff --git a/core/acs-reader/package.json b/core/acs-reader/package.json index 54db11b83..338c6d7eb 100644 --- a/core/acs-reader/package.json +++ b/core/acs-reader/package.json @@ -38,6 +38,7 @@ "dependencies": { "@canton-network/core-ledger-client-types": "workspace:^", "@canton-network/core-provider-ledger": "workspace:^", + "@canton-network/core-token-standard": "workspace:^", "@canton-network/core-types": "workspace:^", "bignumber.js": "^9.3.1", "dayjs": "^1.11.19", diff --git a/core/acs-reader/src/cache/cache.ts b/core/acs-reader/src/cache/cache.ts new file mode 100644 index 000000000..d7d6fbe60 --- /dev/null +++ b/core/acs-reader/src/cache/cache.ts @@ -0,0 +1,312 @@ +// Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { ContractId } from '@canton-network/core-token-standard' +import { ACEvent, ACS_UPDATE_CONFIG, ACSState } from '../types' +import { + AbstractLedgerProvider, + Ops, +} from '@canton-network/core-provider-ledger' +import { LRUCacheOptions } from 'typescript-lru-cache' +import { LedgerCommonSchemas } from '@canton-network/core-ledger-client-types' +import pino from 'pino' +import { + ResolvedAcsOptions, + AcsService, + buildActiveContractFilter, +} from '../service' + +export type ACSCacheOptions = Pick< + LRUCacheOptions, + 'maxSize' | 'entryExpirationTimeInMS' +> + +const logger = pino({ name: 'acs-reader/cache' }) + +export class ACSCache { + private readonly state: ACSState = { + initial: { + offset: 0, + acs: [], + }, + updates: { + offset: 0, + acs: [], + }, + archivedACs: new Set(), + } + private readonly service: AcsService + + constructor(private readonly ledger: AbstractLedgerProvider) { + this.service = new AcsService(ledger) + } + + /** + * Returns the initial snapshot of the active contract set. + * Contains the base state captured at a specific ledger offset. + */ + private get initial() { + return this.state.initial + } + + /** + * Returns the incremental updates applied after the initial snapshot. + * Contains created and archived events that occurred since the initial state. + */ + private get updates() { + return this.state.updates + } + + /** + * Updates the cache to include ledger changes up to the specified offset. + * Fetches and applies incremental updates from the ledger, initializing the cache if needed. + * Automatically prunes old events when the update buffer exceeds configured thresholds. + */ + public async update(options: ResolvedAcsOptions) { + if (!this.initial.acs.length || this.initial.offset > options.offset) { + await this.initState(options) + } + + const builtFilter = buildActiveContractFilter(options) + + const updates = await this.fetchUpdates({ + beginExclusive: this.updates.offset, + endInclusive: options.offset, + eventFormat: { + verbose: Boolean(builtFilter.verbose), + ...builtFilter.filter, + }, + }) + + // in practise length should never be > maxUpdatesToFetch only equal (server should never return more than limit in query). This is just a safeguard. + if (updates.length >= ACS_UPDATE_CONFIG.maxUpdatesToFetch) + void this.update(options) + + const { newEvents, newOffset } = this.extractEvents({ + offset: this.updates.offset, + updates, + }) + + if (newOffset > this.updates.offset) { + this.updates.offset = newOffset + this.updates.acs = this.updates.acs.concat(newEvents) + } else this.updates.offset = options.offset + + if (this.updates.acs.length >= ACS_UPDATE_CONFIG.maxEventsBeforePrune) { + this.prune() + } + } + + /** + * Calculates the active contract set at a specific ledger offset. + * Applies cached updates to the initial snapshot and filters out archived contracts. + * Throws an error if the cache is not initialized or the requested offset is too old. + */ + public calculateAt(offset: number) { + if (!this.initial.acs) + throw Error('No ACS initialized. Call `.update()` first') + if (this.initial.offset > offset) + throw Error('Provided offset cannot be smaller than ACS offset') + + const newContracts: LedgerCommonSchemas['JsGetActiveContractsResponse'][] = + [] + const newArchivedContracts: Set> = new Set() + + this.updates.acs + .filter((ac) => ac.offset <= offset) + .map((ac) => { + if (isCreatedEvent(ac)) { + newContracts.push({ + workflowId: ac.workflowId ?? '', + contractEntry: { + JsActiveContract: { + createdEvent: ac.event, + synchronizerId: ac.synchronizerId ?? '', + reassignmentCounter: 0, + }, + }, + }) + } else { + newArchivedContracts.add( + ac.event.contractId as ContractId + ) + } + }) + + const allContracts = this.initial.acs.concat(newContracts) + this.state.archivedACs = + this.state.archivedACs.union(newArchivedContracts) + + return allContracts.filter(({ contractEntry }) => { + if (!contractEntry) return false + const id = ( + 'JsActiveContract' in contractEntry + ? contractEntry.JsActiveContract.createdEvent.contractId + : '' + ) as ContractId + + return !this.state.archivedACs.has(id) + }) + } + + /** + * Initializes the cache state by fetching the active contract set at the specified offset. + * Clears any existing updates and archived contract tracking. + */ + private async initState(options: ResolvedAcsOptions) { + const initialAcs = await this.service.getActiveContracts(options) + this.state.initial = { + offset: options.offset, + acs: initialAcs, + } + this.state.updates = { + offset: options.offset, + acs: [], + } + this.state.archivedACs = new Set() + } + + /** + * Compacts the cache by moving the initial snapshot forward to a more recent offset. + * Applies accumulated updates to create a new initial state and discards old events. + * Improves performance by reducing the number of updates to process on each query. + */ + private prune() { + const newOffset = Math.max( + this.initial.offset, + this.updates.offset - ACS_UPDATE_CONFIG.safeOffsetDeltaForPrune + ) + + if (newOffset > this.initial.offset) { + const responses = this.calculateAt(newOffset) + + this.state.initial = { + offset: newOffset, + acs: responses, + } + this.state.updates = { + offset: this.updates.offset, + acs: this.updates.acs.filter((ac) => ac.offset > newOffset), + } + } + } + + /** + * Fetches ledger updates between two offsets. + * Queries the ledger API for transactions containing contract create and archive events. + */ + private async fetchUpdates(args: { + beginExclusive: number + endInclusive: number + eventFormat: LedgerCommonSchemas['EventFormat'] + filter?: LedgerCommonSchemas['TransactionFilter'] + }) { + const { beginExclusive, endInclusive, eventFormat, filter } = args + const updateFormat: Ops.PostV2UpdatesFlats['ledgerApi']['params']['body']['updateFormat'] = + { + includeTransactions: { + eventFormat, + transactionShape: 'TRANSACTION_SHAPE_ACS_DELTA', + }, + } + + return await this.ledger.request({ + method: 'ledgerApi', + params: { + resource: '/v2/updates', + requestMethod: 'post', + body: { + beginExclusive, + endInclusive, + updateFormat, + verbose: false, + ...(filter ? { filter } : {}), + }, + query: { + limit: ACS_UPDATE_CONFIG.maxUpdatesToFetch, + stream_idle_timeout_ms: 1000, + }, + }, + }) + } + + /** + * Extracts contract creation and archival events from raw ledger updates. + * Processes transaction and checkpoint updates to build a list of relevant contract events. + * Tracks the highest offset seen across all updates. + */ + private extractEvents(args: { + updates: Awaited> + offset: number + }) { + const { updates, offset } = args + const newEvents: Array = [] + let newOffset = offset + updates.forEach((update) => { + if (!update || !update.update) { + return + } + if ('Transaction' in update.update) { + const transaction = update.update.Transaction + const trOffset = transaction?.value?.offset + if (trOffset && trOffset > newOffset) { + const events: Array = + transaction?.value?.events ?? [] + events.forEach((event) => { + if (!event) { + return + } + if ( + 'CreatedEvent' in event || + 'ArchivedEvent' in event + ) { + const eventData = + 'CreatedEvent' in event + ? event.CreatedEvent + : event.ArchivedEvent + + const acUpdate: ACEvent = { + event: eventData, + offset: trOffset, + workflowId: + transaction?.value?.workflowId ?? null, + synchronizerId: + transaction?.value?.synchronizerId ?? null, + ...('ArchivedEvent' in event && { + archived: true, + }), + } + newEvents.push(acUpdate) + newOffset = trOffset + } + }) + } + } else if ('OffsetCheckpoint' in update.update) { + const checkpoint = update.update.OffsetCheckpoint + const offset = checkpoint?.value?.offset + if (offset) { + newOffset = offset + } + } else { + logger.warn( + { + value: JSON.stringify(update.update), + }, + 'ACS update got unknown update type' + ) + } + }) + return { newEvents, newOffset } + } +} + +/** + * Checks if an event represents a contract creation. + * Used to distinguish between created and archived events when processing cache updates. + */ +function isCreatedEvent(event: ACEvent): event is ACEvent & { + archived: true + event: LedgerCommonSchemas['CreatedEvent'] +} { + return !event.archived +} diff --git a/core/acs-reader/src/cache/collection.ts b/core/acs-reader/src/cache/collection.ts new file mode 100644 index 000000000..e95d20e67 --- /dev/null +++ b/core/acs-reader/src/cache/collection.ts @@ -0,0 +1,97 @@ +// Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { LRUCache } from 'typescript-lru-cache' +import { ACSCache, ACSCacheOptions } from './cache' +import { ACSKey } from '../types' +import { ResolvedAcsOptions } from '../service' +import { LedgerCommonSchemas } from '@canton-network/core-ledger-client-types' +import { AbstractLedgerProvider } from '@canton-network/core-provider-ledger' + +export class ACSCacheCollection { + private readonly collection: LRUCache + + constructor( + private readonly ledger: AbstractLedgerProvider, + private readonly options: ACSCacheOptions = { + maxSize: 100, + entryExpirationTimeInMS: 10 * 60 * 1000, + } + ) { + this.collection = new LRUCache(options) + } + + /** + * Reads the active contract set from the ledger with caching. + * Resolves party references and constructs cache keys from the provided template and interface IDs. + * Queries are deduplicated and cached per party-template-interface combination. + * + * @override + * @see {@link ACSReader.readRaw} + */ + public async readFromCache( + options: ResolvedAcsOptions + ): Promise> { + const { parties, interfaceIds, templateIds } = options + const keys: ACSKey[] = + parties?.flatMap((party) => { + const withTemplateIds = + templateIds?.map((templateId) => ({ party, templateId })) ?? + [] + const withInterfaceIds = + interfaceIds?.map((interfaceId) => ({ + party, + interfaceId, + })) ?? [] + return [...withInterfaceIds, ...withTemplateIds] + }) ?? [] + + return await this.query({ options, keys }) + } + + private getCache(key: ACSKey) { + const serializedKey = this.serializeKey(key) + const existingCache = this.collection.get(serializedKey) + if (existingCache) return existingCache + + const newCache = new ACSCache(this.ledger) + this.collection.set(serializedKey, newCache) + + return newCache + } + + /** + * Updates the cached active contract set for a specific key and returns contracts at the requested offset. + * If the cache is outdated, fetches updates from the ledger and applies them incrementally. + */ + private async updateCache(args: { + options: ResolvedAcsOptions + key: ACSKey + }) { + const cache = this.getCache(args.key) + await cache.update(args.options) + return await cache.calculateAt(args.options.offset) + } + + /** + * Queries multiple cache keys in parallel and combines the results. + * Each key represents a unique party-template-interface combination to be queried independently. + */ + private async query(args: { + options: ResolvedAcsOptions + keys: ACSKey[] + }): Promise> { + const { options, keys } = args + return ( + await Promise.all( + keys.map( + async (key) => await this.updateCache({ options, key }) + ) + ) + ).flat() + } + + private serializeKey(key: ACSKey): string { + return `${key.party ?? 'ANY'}_T${key.templateId ?? '()'}_I${key.interfaceId ?? '()'}` + } +} diff --git a/core/acs-reader/src/index.ts b/core/acs-reader/src/index.ts index 13e346547..0950034ad 100644 --- a/core/acs-reader/src/index.ts +++ b/core/acs-reader/src/index.ts @@ -1,4 +1,5 @@ // Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -export { AcsReader, AcsOptions } from './acs-reader.js' +export * from './reader.js' +export * from './service.js' diff --git a/core/acs-reader/src/reader.ts b/core/acs-reader/src/reader.ts new file mode 100644 index 000000000..6f9e3618a --- /dev/null +++ b/core/acs-reader/src/reader.ts @@ -0,0 +1,104 @@ +// Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + AbstractLedgerProvider, + Ops, +} from '@canton-network/core-provider-ledger' +import { ACSCacheCollection } from './cache/collection' +import { AcsOptions, AcsService, ResolvedAcsOptions } from './service' +import { ACSCacheOptions } from './cache/cache' +import { LedgerCommonSchemas } from '@canton-network/core-ledger-client-types' + +type Reader = { + read: ( + options: AcsOptions + ) => Promise> + readJsContracts: (options: AcsOptions) => Promise< + Array< + LedgerCommonSchemas['JsActiveContract']['createdEvent'] & { + synchronizerId: LedgerCommonSchemas['JsActiveContract']['synchronizerId'] + } + > + > +} + +export class ACSReader implements Reader { + private cacheCollection: ACSCacheCollection + private service: AcsService + + constructor( + private readonly ledger: AbstractLedgerProvider, + private readonly cacheOptions?: ACSCacheOptions + ) { + this.cacheCollection = new ACSCacheCollection(ledger, cacheOptions) + this.service = new AcsService(ledger) + } + + /** + * Provides direct access to the ACS without using the cache. + */ + public raw: Reader = { + read: async (options: AcsOptions) => { + const resolvedOptions = await this.resolveAcsOptions(options) + return await this.service.getActiveContracts(resolvedOptions) + }, + readJsContracts: async (options: AcsOptions) => { + return this.readJsContractsWith(await this.raw.read(options)) + }, + } + + /** + * Reads active contracts from the cache. + */ + public async read(options: AcsOptions) { + const resolvedOptions = await this.resolveAcsOptions(options) + return await this.cacheCollection.readFromCache(resolvedOptions) + } + + /** + * Convenience method that returns active contracts as JS contract objects. + */ + public async readJsContracts(options: AcsOptions) { + return this.readJsContractsWith(await this.read(options)) + } + + private readJsContractsWith(output: Awaited>) { + return output + .filter( + (acs) => + acs.contractEntry != null && + 'JsActiveContract' in acs.contractEntry + ) + .map((acs) => { + const jsActiveContract = ( + acs.contractEntry as { + JsActiveContract: LedgerCommonSchemas['JsActiveContract'] + } + ).JsActiveContract + + return { + ...jsActiveContract.createdEvent, + synchronizerId: jsActiveContract.synchronizerId, + } + }) + } + + private async resolveAcsOptions( + options: AcsOptions + ): Promise { + const offset = + options.offset ?? + ( + await this.ledger.request({ + method: 'ledgerApi', + params: { + resource: '/v2/state/ledger-end', + requestMethod: 'get', + }, + }) + ).offset! + + return { ...options, offset } + } +} diff --git a/core/acs-reader/src/acs-reader.ts b/core/acs-reader/src/service.ts similarity index 98% rename from core/acs-reader/src/acs-reader.ts rename to core/acs-reader/src/service.ts index ce8d9d869..bd3abb466 100644 --- a/core/acs-reader/src/acs-reader.ts +++ b/core/acs-reader/src/service.ts @@ -19,7 +19,7 @@ const COMPLETIONS_LIMIT = '100' const COMPLETIONS_STREAM_IDLE_TIMEOUT_MS = '1000' export type AcsOptions = { - offset: number + offset?: number templateIds?: string[] parties?: string[] //TODO: Figure out if this should use this.partyId by default and not allow cross party filtering filterByParty?: boolean @@ -28,15 +28,13 @@ export type AcsOptions = { continueUntilCompletion?: boolean } -export class AcsReader { - private readonly ledgerProvider: AbstractLedgerProvider +export type ResolvedAcsOptions = Omit & { offset: number } - constructor(ledgerProvider: AbstractLedgerProvider) { - this.ledgerProvider = ledgerProvider - } +export class AcsService { + constructor(private readonly ledgerProvider: AbstractLedgerProvider) {} public async getActiveContracts( - options: AcsOptions + options: ResolvedAcsOptions ): Promise> { const { limit, continueUntilCompletion } = options @@ -50,8 +48,6 @@ export class AcsReader { ) } - //TODO: add back caching later - return await this.ledgerProvider.request( { method: 'ledgerApi', diff --git a/core/acs-reader/src/types.ts b/core/acs-reader/src/types.ts new file mode 100644 index 000000000..b4a5381ee --- /dev/null +++ b/core/acs-reader/src/types.ts @@ -0,0 +1,44 @@ +// Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { LedgerCommonSchemas } from '@canton-network/core-ledger-client-types' +import { ContractId } from '@canton-network/core-token-standard' +import { PartyId } from '@canton-network/core-types' + +export type ACSKey = Partial<{ + party: PartyId + templateId: string + interfaceId: string +}> + +export type ACEvent = { + offset: number + event: + | LedgerCommonSchemas['CreatedEvent'] + | LedgerCommonSchemas['ArchivedEvent'] + workflowId: string | null + synchronizerId: string | null + archived?: boolean +} + +export type ACSComponentState = { + offset: number + acs: Array +} + +export type ACSState = { + initial: ACSComponentState< + LedgerCommonSchemas['JsGetActiveContractsResponse'] + > + updates: ACSComponentState + archivedACs: Set> +} + +export const ACS_UPDATE_CONFIG = { + // How many events do we accumulate before we prune (compact) the ACS history - set to 0 to enable to compact all events, which is more efficient as long as application always ask for increasing (or equal) offsets + maxEventsBeforePrune: 150, + // When we compact the ACS history, we keep all events within this offset delta of the last seen update offset - set 0 to allow to compact everything + safeOffsetDeltaForPrune: 200, + // How many updates do we fetch at once when fetching updates - if there are more updates, we will fetch again until we have caught up (returned data is always complete to the requested endInclusive offset - even if that means multiple fetches) + maxUpdatesToFetch: 100, +} as const diff --git a/core/token-standard-service/src/token-standard-service.ts b/core/token-standard-service/src/token-standard-service.ts index 59b0920b6..67c13c78d 100644 --- a/core/token-standard-service/src/token-standard-service.ts +++ b/core/token-standard-service/src/token-standard-service.ts @@ -28,7 +28,7 @@ import { type LedgerCommonSchemas, } from '@canton-network/core-ledger-client-types' import { Logger, PartyId } from '@canton-network/core-types' -import { AcsReader, AcsOptions } from '@canton-network/core-acs-reader' +import { ACSReader, AcsOptions } from '@canton-network/core-acs-reader' import { TokenStandardTransactionInterfaces, ensureInterfaceViewIsPresent, @@ -246,10 +246,10 @@ export class CoreService { `continue to completion: ${Boolean(continueUntilCompletion)}` ) - const reader = new AcsReader(this.ledgerProvider) + const reader = new ACSReader(this.ledgerProvider) const acsResponses: JsGetActiveContractsResponse[] = - (await reader.getActiveContracts( + (await reader.raw.read( options ))! as JsGetActiveContractsResponse[] diff --git a/core/wallet-test-utils/src/otc-trade.ts b/core/wallet-test-utils/src/otc-trade.ts index 0f912b27b..d3c8ed989 100644 --- a/core/wallet-test-utils/src/otc-trade.ts +++ b/core/wallet-test-utils/src/otc-trade.ts @@ -151,13 +151,15 @@ export class OTCTrade { await this.acceptProposal(this.charlie, 'Charlie') // Venue initiates settlement of OTCTradeProposal - const activeTradeProposals2 = await this.sdk.ledger.acs.read({ - templateIds: [ - '#splice-token-test-trading-app:Splice.Testing.Apps.TradingApp:OTCTradeProposal', - ], - parties: [this.venue], - filterByParty: true, - }) + const activeTradeProposals2 = await this.sdk.ledger.acs.readJsContracts( + { + templateIds: [ + '#splice-token-test-trading-app:Splice.Testing.Apps.TradingApp:OTCTradeProposal', + ], + parties: [this.venue], + filterByParty: true, + } + ) const now = new Date() const prepareUntil = new Date( now.getTime() + 60 * 60 * 1000 @@ -187,7 +189,7 @@ export class OTCTrade { this.logger.info('Venue initated settlement of OTCTradeProposal') - const otcTrades = await this.sdk.ledger.acs.read({ + const otcTrades = await this.sdk.ledger.acs.readJsContracts({ templateIds: [ '#splice-token-test-trading-app:Splice.Testing.Apps.TradingApp:OTCTrade', ], @@ -212,7 +214,7 @@ export class OTCTrade { ): Promise { if (!this.sdk) throw new Error('SDK not initialized') - const activeTradeProposals = await this.sdk.ledger.acs.read({ + const activeTradeProposals = await this.sdk.ledger.acs.readJsContracts({ templateIds: [ '#splice-token-test-trading-app:Splice.Testing.Apps.TradingApp:OTCTradeProposal', ], diff --git a/docs/scripts/generateOutputDocs.js b/docs/scripts/generateOutputDocs.js index 742e543b8..146ebf20b 100644 --- a/docs/scripts/generateOutputDocs.js +++ b/docs/scripts/generateOutputDocs.js @@ -1,13 +1,16 @@ #!/usr/bin/env node +// Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + // generateOutputDocs.js // // - Reads a single export config: docs/config/exportConfig.json // - Writes extracted snippets into: docs-output/.mdx // - Resolves source files relative to the repo root -const fs = require('fs') -const path = require('path') +import fs from 'fs' +import path from 'path' const REPO_ROOT = path.join(__dirname, '..', '..') const EXPORT_CONFIG_PATH = path.join(REPO_ROOT, 'docs/config/exportConfig.json') @@ -17,7 +20,9 @@ function readFileContent(filePath) { try { return fs.readFileSync(filePath, 'utf8') } catch (error) { - throw new Error(`Failed to read file ${filePath}: ${error.message}`) + throw new Error(`Failed to read file ${filePath}: ${error.message}`, { + cause: error, + }) } } @@ -86,7 +91,9 @@ function extractByJsonIndex(fileContent, start, end) { try { arr = JSON.parse(fileContent) } catch (e) { - throw new Error(`File is not valid JSON: ${e.message}`) + throw new Error(`File is not valid JSON: ${e.message}`, { + cause: e, + }) } if (!Array.isArray(arr)) { throw new Error( @@ -278,7 +285,7 @@ function main() { try { processSnippet(snippet) successCount++ - } catch (error) { + } catch { errorCount++ } } diff --git a/docs/wallet-integration-guide/examples/package.json b/docs/wallet-integration-guide/examples/package.json index 7dd63abc3..faff497b3 100644 --- a/docs/wallet-integration-guide/examples/package.json +++ b/docs/wallet-integration-guide/examples/package.json @@ -27,7 +27,8 @@ "run-13": "tsx ./scripts/13-rewards-for-deposits/index.ts | pino-pretty", "run-14": "tsx ./scripts/14-offline-signing.ts | pino-pretty", "stress-run-01": "tsx ./scripts/stress/01-merge-utxos.ts | pino-pretty", - "stress-run-02": "tsx ./scripts/stress/02-merge-utxos-delegate.ts | pino-pretty" + "stress-run-02": "tsx ./scripts/stress/02-merge-utxos-delegate.ts | pino-pretty", + "stress-run-03": "tsx ./scripts/stress/03-acs-cache.ts | pino-pretty" }, "devDependencies": { "@canton-network/core-signing-lib": "workspace:^", diff --git a/docs/wallet-integration-guide/examples/scripts/04-token-standard-allocation.ts b/docs/wallet-integration-guide/examples/scripts/04-token-standard-allocation.ts index 4ea8a3847..d4f81b546 100644 --- a/docs/wallet-integration-guide/examples/scripts/04-token-standard-allocation.ts +++ b/docs/wallet-integration-guide/examples/scripts/04-token-standard-allocation.ts @@ -159,7 +159,7 @@ logger.info( // Bob accepts OTCTradeProposal -const activeTradeProposals = await sdk.ledger.acs.read({ +const activeTradeProposals = await sdk.ledger.acs.readJsContracts({ templateIds: [ '#splice-token-test-trading-app:Splice.Testing.Apps.TradingApp:OTCTradeProposal', ], @@ -197,7 +197,7 @@ logger.info('Bob accepted OTCTradeProposal') //Venue initiates settlement of OTCTradeProposal -const activeTradeProposals2 = await sdk.ledger.acs.read({ +const activeTradeProposals2 = await sdk.ledger.acs.readJsContracts({ templateIds: [ '#splice-token-test-trading-app:Splice.Testing.Apps.TradingApp:OTCTradeProposal', ], @@ -234,7 +234,7 @@ await sdk.ledger logger.info('Venue initated settlement of OTCTradeProposal') -const otcTrades = await sdk.ledger.acs.read({ +const otcTrades = await sdk.ledger.acs.readJsContracts({ templateIds: [ '#splice-token-test-trading-app:Splice.Testing.Apps.TradingApp:OTCTrade', ], diff --git a/docs/wallet-integration-guide/examples/scripts/05-preapproval.ts b/docs/wallet-integration-guide/examples/scripts/05-preapproval.ts index f072a2c1f..954997465 100644 --- a/docs/wallet-integration-guide/examples/scripts/05-preapproval.ts +++ b/docs/wallet-integration-guide/examples/scripts/05-preapproval.ts @@ -201,7 +201,7 @@ const cancelled = await sdk.amulet.preapproval.fetchStatus(bob.partyId, { cancelled: true, }) -const preapprovalACS = await sdk.ledger.acs.read({ +const preapprovalACS = await sdk.ledger.acs.readJsContracts({ parties: [bob.partyId], filterByParty: true, }) diff --git a/docs/wallet-integration-guide/examples/scripts/13-rewards-for-deposits/_withdraw.ts b/docs/wallet-integration-guide/examples/scripts/13-rewards-for-deposits/_withdraw.ts index fe639549b..0545a0d0f 100644 --- a/docs/wallet-integration-guide/examples/scripts/13-rewards-for-deposits/_withdraw.ts +++ b/docs/wallet-integration-guide/examples/scripts/13-rewards-for-deposits/_withdraw.ts @@ -52,13 +52,14 @@ export default async ( partyId: treasury.partyId, }) - const activeContractsForDelegateTreasuryProxy = sdk.ledger.acs.read({ - parties: [treasury.partyId], - templateIds: [ - '#splice-util-featured-app-proxies:Splice.Util.FeaturedApp.DelegateProxy:DelegateProxy', - ], - filterByParty: true, - }) + const activeContractsForDelegateTreasuryProxy = + sdk.ledger.acs.readJsContracts({ + parties: [treasury.partyId], + templateIds: [ + '#splice-util-featured-app-proxies:Splice.Util.FeaturedApp.DelegateProxy:DelegateProxy', + ], + filterByParty: true, + }) const proxyCid = await activeContractsForDelegateTreasuryProxy.then( (list) => list[0].contractId diff --git a/docs/wallet-integration-guide/examples/scripts/13-rewards-for-deposits/index.ts b/docs/wallet-integration-guide/examples/scripts/13-rewards-for-deposits/index.ts index 3882c4ae6..e821ef5f5 100644 --- a/docs/wallet-integration-guide/examples/scripts/13-rewards-for-deposits/index.ts +++ b/docs/wallet-integration-guide/examples/scripts/13-rewards-for-deposits/index.ts @@ -141,13 +141,14 @@ const setupIteration = partyId: alice.partyId, }) - const activeContractsForDelegateTreasuryProxy = sdk.ledger.acs.read({ - parties: [treasury.partyId], - templateIds: [ - '#splice-util-featured-app-proxies:Splice.Util.FeaturedApp.DelegateProxy:DelegateProxy', - ], - filterByParty: true, - }) + const activeContractsForDelegateTreasuryProxy = + sdk.ledger.acs.readJsContracts({ + parties: [treasury.partyId], + templateIds: [ + '#splice-util-featured-app-proxies:Splice.Util.FeaturedApp.DelegateProxy:DelegateProxy', + ], + filterByParty: true, + }) const proxyCid = await activeContractsForDelegateTreasuryProxy.then( (list) => list[0].contractId diff --git a/docs/wallet-integration-guide/examples/scripts/stress/03-acs-cache.ts b/docs/wallet-integration-guide/examples/scripts/stress/03-acs-cache.ts new file mode 100644 index 000000000..353b8f194 --- /dev/null +++ b/docs/wallet-integration-guide/examples/scripts/stress/03-acs-cache.ts @@ -0,0 +1,103 @@ +import { localNetStaticConfig, SDK } from '@canton-network/wallet-sdk' +import { TOKEN_PROVIDER_CONFIG_DEFAULT } from '../utils/index.js' +import pino from 'pino' + +const logger = pino({ name: 'stress-03-acs-cache', level: 'info' }) + +const partiesToCreate = parseInt(process.env.PARTIES_AMOUNT ?? '') || 25 + +const sdk = await SDK.create({ + auth: TOKEN_PROVIDER_CONFIG_DEFAULT, + ledgerClientUrl: localNetStaticConfig.LOCALNET_APP_USER_LEDGER_URL, +}) + +const setupParty = async () => { + const keys = sdk.keys.generate() + + const { partyId } = await sdk.party.external + .create(keys.publicKey) + .sign(keys.privateKey) + .execute() + + const [pingCommand] = sdk.utils.ping.create([ + { initiator: partyId, responder: partyId }, + ]) + + await sdk.ledger + .prepare({ + commands: pingCommand, + partyId, + }) + .sign(keys.privateKey) + .execute({ + partyId, + }) + + return { partyId, keys } +} + +// Create parties in batches to avoid overwhelming the server +const parties = [] +const batchSize = 5 + +logger.info(`Creating ${partiesToCreate} parties in batches of ${batchSize}...`) + +for (let i = 0; i < partiesToCreate; i += batchSize) { + const batch = Math.min(batchSize, partiesToCreate - i) + const batchNum = Math.floor(i / batchSize) + 1 + const totalBatches = Math.ceil(partiesToCreate / batchSize) + + logger.info( + `Creating batch ${batchNum}/${totalBatches} (${batch} parties)...` + ) + + const batchParties = await Promise.all( + [...Array(batch)].map(() => setupParty()) + ) + parties.push(...batchParties) + + logger.info( + `Batch ${batchNum}/${totalBatches} complete. Total parties: ${parties.length}` + ) +} + +logger.info(`All ${parties.length} parties created successfully!`) + +const timeResults: [number, number][] = [] + +for (const party of parties) { + const firstStartTime = performance.now() + await sdk.ledger.acs.readJsContracts({ + parties: [party.partyId], + filterByParty: true, + templateIds: [ + '#canton-builtin-admin-workflow-ping:Canton.Internal.Ping:Ping', + ], + }) + const secondStartTime = performance.now() + await sdk.ledger.acs.readJsContracts({ + parties: [party.partyId], + filterByParty: true, + templateIds: [ + '#canton-builtin-admin-workflow-ping:Canton.Internal.Ping:Ping', + ], + }) + const endTime = performance.now() + + timeResults.push([ + secondStartTime - firstStartTime, + endTime - secondStartTime, + ]) +} + +const isCacheFaster = timeResults.map( + ([beforeCacheTime, afterCacheTime]) => beforeCacheTime >= afterCacheTime +) + +const timesFaster = isCacheFaster.reduce((acc, value) => +value + acc, 0) +const timesSlower = isCacheFaster.length - timesFaster + +logger.info( + { timesFaster, timesSlower }, + 'Times when caching mechanism was faster' +) diff --git a/nx.json b/nx.json index c21316c00..84b4f21a4 100644 --- a/nx.json +++ b/nx.json @@ -104,5 +104,7 @@ "staticStorybookTargetName": "static-storybook" } } - ] + ], + "analytics": false, + "nxCloudId": "6a05b93522efcb8f608e1a3d" } diff --git a/scripts/src/clean-coding.ts b/scripts/src/clean-coding.ts index 5d6afdafc..b5cf5be86 100644 --- a/scripts/src/clean-coding.ts +++ b/scripts/src/clean-coding.ts @@ -147,9 +147,9 @@ function main(): void { let errorCount = 0 traverseDirectory(rootDir, (filePath) => { if ( - filePath.includes('.canton') || - filePath.includes('.yarn') || - filePath.includes('.splice') + ['.canton', '.yarn', '.splice', '.nx'].some((dirName) => + filePath.includes(dirName) + ) ) { return // Skip directories } diff --git a/sdk/wallet-sdk/package.json b/sdk/wallet-sdk/package.json index f7b191b88..2fe15a713 100644 --- a/sdk/wallet-sdk/package.json +++ b/sdk/wallet-sdk/package.json @@ -47,6 +47,7 @@ "jose": "^6.1.3", "pino": "^10.3.1", "pino-pretty": "^13.1.3", + "typescript-lru-cache": "^2.0.0", "uuid": "^14.0.0" }, "devDependencies": { diff --git a/sdk/wallet-sdk/src/wallet/logger/types.ts b/sdk/wallet-sdk/src/wallet/logger/types.ts index c9a3b6821..47dbb3989 100644 --- a/sdk/wallet-sdk/src/wallet/logger/types.ts +++ b/sdk/wallet-sdk/src/wallet/logger/types.ts @@ -13,10 +13,9 @@ import { SDKLogger } from './logger.js' // eslint-disable-line @typescript-eslin * from which the log originates. For example, in ConsoleLogAdapter and PinoLogAdapter, * the namespace is prepended to the log message, helping to distinguish logs from different * parts of the application and making log filtering and analysis easier. - * It is recommended to use {@link SDKLogger.child} to set the namespace for each logger instance. + * It is recommended to use {@link SDKLogger['child']} to set the namespace for each logger instance. * @property timestamp Optional timestamp for the log entry. This is provided by default by the logger implementation. * @property response Optional response data to include in the log. - * @property arguments Optional arguments or parameters related to the log event. * @property traceId Optional trace identifier for correlating logs across systems. * @property partyId Optional party identifier for domain-specific context. * @property [data: string] Any additional custom metadata fields. @@ -25,7 +24,6 @@ export type LogContext = Partial<{ namespace: string timestamp: string response: unknown - arguments: unknown traceId: string partyId: PartyId [data: string]: unknown diff --git a/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/index.ts b/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/index.ts index 851febd11..8dace158d 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/index.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/index.ts @@ -1,99 +1,5 @@ // Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { SDKContext } from '../../../sdk.js' -import { v4 } from 'uuid' -import { Ops } from '@canton-network/core-provider-ledger' - -type AllowedOperation = - | Ops.PostV2CommandsSubmitAndWait - | Ops.PostV2InteractiveSubmissionPrepare - -type OperationBodyRequest = - Operation['ledgerApi']['params']['body'] - -type RequiredParams = 'commands' | 'actAs' -type UnusedParams = 'userId' - -type InternalOperationParams = Required< - Pick, RequiredParams> -> & - Partial< - Omit, UnusedParams | RequiredParams> - > - -export class InternalLedgerNamespace { - constructor(private readonly ctx: SDKContext) {} - - async submit( - args: InternalOperationParams - ) { - const { - commands, - synchronizerId = this.ctx.defaultSynchronizerId, - disclosedContracts = [], - readAs = [], - actAs, - commandId = v4(), - packageIdSelectionPreference = [], - } = args - const request = { - commands, - commandId, - userId: this.ctx.userId, - actAs, - readAs, - disclosedContracts, - synchronizerId, - packageIdSelectionPreference, - } - - return await this.ctx.ledgerProvider.request( - { - method: 'ledgerApi', - params: { - resource: '/v2/commands/submit-and-wait', - requestMethod: 'post', - body: request, - }, - } - ) - } - - async prepare( - args: InternalOperationParams - ) { - const { - commands, - synchronizerId = this.ctx.defaultSynchronizerId, - disclosedContracts = [], - readAs = [], - actAs, - commandId = v4(), - packageIdSelectionPreference = [], - verboseHashing = false, - } = args - const request = { - commands, - commandId, - userId: this.ctx.userId, - actAs, - readAs, - disclosedContracts, - synchronizerId, - packageIdSelectionPreference, - verboseHashing, - } - - return await this.ctx.ledgerProvider.request( - { - method: 'ledgerApi', - params: { - resource: '/v2/interactive-submission/prepare', - requestMethod: 'post', - body: request, - }, - } - ) - } -} +export * from './namespace.js' +export * from './types.js' diff --git a/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/namespace.ts b/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/namespace.ts new file mode 100644 index 000000000..813c38408 --- /dev/null +++ b/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/namespace.ts @@ -0,0 +1,83 @@ +// Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { SDKContext } from '../../../sdk.js' +import { v4 } from 'uuid' +import { Ops } from '@canton-network/core-provider-ledger' +import { InternalOperationParams } from './types.js' + +export class InternalLedgerNamespace { + constructor(private readonly ctx: SDKContext) {} + + public async submit( + args: InternalOperationParams + ) { + const { + commands, + synchronizerId = this.ctx.defaultSynchronizerId, + disclosedContracts = [], + readAs = [], + actAs, + commandId = v4(), + packageIdSelectionPreference = [], + } = args + const request = { + commands, + commandId, + userId: this.ctx.userId, + actAs, + readAs, + disclosedContracts, + synchronizerId, + packageIdSelectionPreference, + } + + return await this.ctx.ledgerProvider.request( + { + method: 'ledgerApi', + params: { + resource: '/v2/commands/submit-and-wait', + requestMethod: 'post', + body: request, + }, + } + ) + } + + public async prepare( + args: InternalOperationParams + ) { + const { + commands, + synchronizerId = this.ctx.defaultSynchronizerId, + disclosedContracts = [], + readAs = [], + actAs, + commandId = v4(), + packageIdSelectionPreference = [], + verboseHashing = false, + } = args + const request = { + commands, + commandId, + userId: this.ctx.userId, + actAs, + readAs, + disclosedContracts, + synchronizerId, + packageIdSelectionPreference, + verboseHashing, + } + + return await this.ctx.ledgerProvider.request( + { + method: 'ledgerApi', + params: { + resource: '/v2/interactive-submission/prepare', + requestMethod: 'post', + body: request, + }, + } + ) + } +} diff --git a/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/types.ts b/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/types.ts new file mode 100644 index 000000000..6a85b6bc9 --- /dev/null +++ b/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/types.ts @@ -0,0 +1,39 @@ +// Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { Ops } from '@canton-network/core-provider-ledger' + +type AllowedOperation = + | Ops.PostV2CommandsSubmitAndWait + | Ops.PostV2InteractiveSubmissionPrepare + +type OperationBodyRequest = + Operation['ledgerApi']['params']['body'] + +type RequiredParamsFor = Extract< + keyof OperationBodyRequest, + Operation extends + | Ops.PostV2CommandsSubmitAndWait + | Ops.PostV2InteractiveSubmissionPrepare + ? 'commands' | 'actAs' + : never +> +type UnusedParams = Extract< + keyof OperationBodyRequest, + Operation extends + | Ops.PostV2CommandsSubmitAndWait + | Ops.PostV2InteractiveSubmissionPrepare + ? 'userId' + : never +> + +export type InternalOperationParams = + Required< + Pick, RequiredParamsFor> + > & + Partial< + Omit< + OperationBodyRequest, + UnusedParams & RequiredParamsFor + > + > diff --git a/sdk/wallet-sdk/src/wallet/namespace/ledger/namespace.ts b/sdk/wallet-sdk/src/wallet/namespace/ledger/namespace.ts index d6b7bd904..21e6a08d8 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/ledger/namespace.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/ledger/namespace.ts @@ -1,26 +1,28 @@ // Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { SDKContext, LedgerTypes } from '../../sdk.js' +import { SDKContext } from '../../sdk.js' import { v4 } from 'uuid' -import { PrepareOptions, ExecuteOptions, AcsRequestOptions } from './types.js' +import { PrepareOptions, ExecuteOptions } from './types.js' import { PreparedTransaction } from '../transactions/prepared.js' import { SignedTransaction } from '../transactions/signed.js' import { Ops } from '@canton-network/core-provider-ledger' import { DarNamespace } from './dar/client.js' -import { AcsOptions } from '@canton-network/core-acs-reader' import { InternalLedgerNamespace } from './internal/index.js' import { PreparedTransactionNamespace } from './hash/namespace.js' +import { ACSReader } from '@canton-network/core-acs-reader' export class LedgerNamespace { public readonly dar: DarNamespace public readonly internal: InternalLedgerNamespace public readonly preparedTransaction: PreparedTransactionNamespace + public readonly acs: ACSReader constructor(private readonly sdkContext: SDKContext) { this.dar = new DarNamespace(sdkContext) this.internal = new InternalLedgerNamespace(sdkContext) this.preparedTransaction = new PreparedTransactionNamespace(sdkContext) + this.acs = new ACSReader(sdkContext.ledgerProvider) } public async ledgerEnd() { @@ -40,7 +42,7 @@ export class LedgerNamespace { * Performs the prepare step of the interactive submission flow. * @returns PreparedTransaction which includes the response from the ledger and an execute function that can be called with a SignedTransaction to perform the execute step of the interactive submission flow. */ - prepare(options: PrepareOptions): PreparedTransaction { + public prepare(options: PrepareOptions): PreparedTransaction { const preparePromise = async () => { const synchronizerId = options.synchronizerId || this.sdkContext.defaultSynchronizerId @@ -76,7 +78,7 @@ export class LedgerNamespace { * @param options The options for executing the transaction, including userId, partyId, and an optional submissionId. * @returns The submissionId of the executed transaction. */ - async execute( + public async execute( signed: SignedTransaction, options: ExecuteOptions ): Promise< @@ -140,88 +142,13 @@ export class LedgerNamespace { ) } - acs = { - /** - * - * @param options AcsOptions for querying the Active Contract Set (ACS). - * offset: The ledger offset at which to query the ACS. If not provided, will fetch the ledgerEnd. - * templateIds: An optional array of template IDs to filter the ACS by. If not provided, no filtering by template ID will be applied. - * parties: An optional array of party IDs to filter the ACS by. If not provided, no filtering by party will be applied. - * filterByParty: A boolean flag indicating whether to apply party-based filtering. If true, the query will filter contracts based on the specified parties. If false or not provided, party-based filtering will not be applied. - * interfaceIds: An optional array of interface IDs to filter the ACS by. If not provided, no filtering by interface ID will be applied. - * limit: An optional number specifying the maximum number of active contracts to return in a single query. If not provided, the default limit will be determined by the ledger API. - * continueUntilCompletion: A boolean flag indicating whether to continue polling the ledger until the query is complete. If true, the method will repeatedly query the ledger until all matching active contracts have been retrieved. If false or not provided, the method will return after a single query, which may return a - * @returns Active contracts matching the provided query options. - */ - readRaw: async ( - options: AcsRequestOptions - ): Promise> => { - const resolvedOptions = await this.resolveAcsOptions(options) - - this.sdkContext.logger.debug( - resolvedOptions, - `Querying acs with options:` - ) - - return await this.sdkContext.acsReader.getActiveContracts( - resolvedOptions - ) - }, - /** - * Queries the ACS and filters for JsActiveContracts - * @param options AcsOptions for querying the Active Contract Set (ACS). - * returns the createdEvent and synchronizerId - */ - read: async (options: AcsRequestOptions) => { - return (await this.acs.readRaw(options)) - - .filter( - (acs) => - acs.contractEntry != null && - 'JsActiveContract' in acs.contractEntry - ) - .map((acs) => { - const jsActiveContract = ( - acs.contractEntry as { - JsActiveContract: LedgerTypes['JsActiveContract'] - } - ).JsActiveContract - - return { - ...jsActiveContract.createdEvent, - synchronizerId: jsActiveContract.synchronizerId, - } - }) - }, - } - - private async resolveAcsOptions( - options: AcsRequestOptions - ): Promise { - const offset = - options.offset ?? - ( - await this.sdkContext.ledgerProvider.request( - { - method: 'ledgerApi', - params: { - resource: '/v2/state/ledger-end', - requestMethod: 'get', - }, - } - ) - ).offset! - - return { ...options, offset } - } - /** * For offline signing workflows, construct a SignedTransaction from an externally produced signature. * @param response The prepare response from a previous prepare call * @param signature The externally produced signature * @returns A SignedTransaction that can be passed to execute() */ - fromSignature( + public fromSignature( response: Ops.PostV2InteractiveSubmissionPrepare['ledgerApi']['result'], signature: string ): SignedTransaction { diff --git a/sdk/wallet-sdk/src/wallet/namespace/token/utxos/mergeDelegation.ts b/sdk/wallet-sdk/src/wallet/namespace/token/utxos/mergeDelegation.ts index 795eb828d..d669107fb 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/token/utxos/mergeDelegation.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/token/utxos/mergeDelegation.ts @@ -45,7 +45,7 @@ export class MergeDelegationNamespace { async approve(args: { owner: PartyId; synchronizerId?: string }) { const { owner, synchronizerId = '' } = args - const mergeDelegationProposals = await this.ledger.acs.read({ + const mergeDelegationProposals = await this.ledger.acs.readJsContracts({ parties: [owner], templateIds: [ '#splice-util-token-standard-wallet:Splice.Util.Token.Wallet.MergeDelegation:MergeDelegationProposal', @@ -107,26 +107,28 @@ export class MergeDelegationNamespace { const allMergeDelegationChoices: WrappedCommand<'ExerciseCommand'>[] = [] - const mergeDelegationContractsForUser = await this.ledger.acs.read({ - parties: [party], - templateIds: [ - '#splice-util-token-standard-wallet:Splice.Util.Token.Wallet.MergeDelegation:MergeDelegation', - ], - filterByParty: true, - }) + const mergeDelegationContractsForUser = + await this.ledger.acs.readJsContracts({ + parties: [party], + templateIds: [ + '#splice-util-token-standard-wallet:Splice.Util.Token.Wallet.MergeDelegation:MergeDelegation', + ], + filterByParty: true, + }) const mergeDelegationDisclosedContract = this.activeContractToDisclosedContract( mergeDelegationContractsForUser[0] ) - const batchMergeUtilityContracts = await this.ledger.acs.read({ - parties: [this.ctx.validatorParty], - templateIds: [ - '#splice-util-token-standard-wallet:Splice.Util.Token.Wallet.MergeDelegation:BatchMergeUtility', - ], - filterByParty: true, - }) + const batchMergeUtilityContracts = + await this.ledger.acs.readJsContracts({ + parties: [this.ctx.validatorParty], + templateIds: [ + '#splice-util-token-standard-wallet:Splice.Util.Token.Wallet.MergeDelegation:BatchMergeUtility', + ], + filterByParty: true, + }) const batchMergeUtilityDisclosedContract = this.activeContractToDisclosedContract( @@ -226,7 +228,9 @@ export class MergeDelegationNamespace { } private activeContractToDisclosedContract( - data: Awaited>[number] + data: Awaited< + ReturnType + >[number] ) { return { templateId: data.templateId, diff --git a/sdk/wallet-sdk/src/wallet/namespace/transactions/signed.ts b/sdk/wallet-sdk/src/wallet/namespace/transactions/signed.ts index 5b22a53aa..5c09a3407 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/transactions/signed.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/transactions/signed.ts @@ -5,10 +5,6 @@ import { Ops } from '@canton-network/core-provider-ledger' import { SDKContext } from '../../sdk.js' import { ExecuteOptions } from '../ledger/types.js' import { LedgerNamespace } from '../ledger/index.js' -import { - PrivateKey, - signTransactionHash, -} from '@canton-network/core-signing-lib' export class SignedTransaction { constructor( @@ -38,15 +34,4 @@ export class SignedTransaction { } return this._execute(this, options) } - - sign(privateKey: PrivateKey): SignedTransaction { - const signedPromise = this.signedPromise.then(({ response }) => ({ - response, - signature: signTransactionHash( - response.preparedTransactionHash, - privateKey - ), - })) - return new SignedTransaction(this.ctx, signedPromise, this._execute) // pass execute function for online signing workflows - } } diff --git a/sdk/wallet-sdk/src/wallet/sdk.ts b/sdk/wallet-sdk/src/wallet/sdk.ts index 9138b4409..2e3db2e4e 100644 --- a/sdk/wallet-sdk/src/wallet/sdk.ts +++ b/sdk/wallet-sdk/src/wallet/sdk.ts @@ -8,7 +8,6 @@ import { LedgerProvider, Ops, } from '@canton-network/core-provider-ledger' -import { AcsReader } from '@canton-network/core-acs-reader' import { EXTENDED_SDK_OPTION_KEYS, ExtendedSDKOptions, @@ -43,7 +42,6 @@ export type LedgerTypes = LedgerCommonSchemas export type SDKContext = { ledgerProvider: AbstractLedgerProvider - acsReader: AcsReader userId: string logger: SDKLogger error: SDKErrorHandler @@ -127,11 +125,8 @@ export class SDK { logger ) - const acsReader = new AcsReader(ledgerProvider) - const ctx: SDKContext = { ledgerProvider, - acsReader, userId: userId!, logger, error, diff --git a/yarn.lock b/yarn.lock index e19cd3088..b85b0eb87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1464,6 +1464,7 @@ __metadata: dependencies: "@canton-network/core-ledger-client-types": "workspace:^" "@canton-network/core-provider-ledger": "workspace:^" + "@canton-network/core-token-standard": "workspace:^" "@canton-network/core-types": "workspace:^" "@types/node": "npm:^25.0.10" "@vitest/browser-playwright": "npm:^4.1.2" @@ -2503,6 +2504,7 @@ __metadata: playwright: "npm:^1.58.2" tsup: "npm:^8.5.1" typescript: "npm:^5.9.3" + typescript-lru-cache: "npm:^2.0.0" uuid: "npm:^14.0.0" vitest: "npm:^4.1.2" languageName: unknown