diff --git a/sdk/wallet-sdk/src/wallet/__test__/mocks.ts b/sdk/wallet-sdk/src/wallet/__test__/mocks.ts index 44d9a3e5a..4a6b66605 100644 --- a/sdk/wallet-sdk/src/wallet/__test__/mocks.ts +++ b/sdk/wallet-sdk/src/wallet/__test__/mocks.ts @@ -41,7 +41,6 @@ export const ctx: SDKContext = { userId: 'userId', logger: mockLogger, error: mockErrorHandler, - defaultSynchronizerId: '', } export const offlineCtx: OfflineSDKContext = { diff --git a/sdk/wallet-sdk/src/wallet/init/__test__/plugin.test.ts b/sdk/wallet-sdk/src/wallet/init/__test__/plugin.test.ts index a292a4736..dafdd231a 100644 --- a/sdk/wallet-sdk/src/wallet/init/__test__/plugin.test.ts +++ b/sdk/wallet-sdk/src/wallet/init/__test__/plugin.test.ts @@ -42,14 +42,9 @@ describe('plugin', () => { it('should call a plugin constructor when registering', async () => { // Mock the authenticated user response - mock.ledgerProvider.request - .mockResolvedValueOnce({ - user: { id: 'test-user-id' }, - }) - // Mock the connected synchronizers response - .mockResolvedValueOnce({ - connectedSynchronizers: [{ id: 'sync-1' }], - }) + mock.ledgerProvider.request.mockResolvedValueOnce({ + user: { id: 'test-user-id' }, + }) const sdk = await SDK.create({ ledgerProvider: mock.ledgerProvider as never, @@ -67,14 +62,9 @@ describe('plugin', () => { it('should successfully register a plugin under provided name', async () => { // Mock the authenticated user response - mock.ledgerProvider.request - .mockResolvedValueOnce({ - user: { id: 'test-user-id' }, - }) - // Mock the connected synchronizers response - .mockResolvedValueOnce({ - connectedSynchronizers: [{ id: 'sync-1' }], - }) + mock.ledgerProvider.request.mockResolvedValueOnce({ + user: { id: 'test-user-id' }, + }) const sdk = await SDK.create({ ledgerProvider: mock.ledgerProvider as never, diff --git a/sdk/wallet-sdk/src/wallet/init/types/context.ts b/sdk/wallet-sdk/src/wallet/init/types/context.ts index 44f64fad8..bab9d9596 100644 --- a/sdk/wallet-sdk/src/wallet/init/types/context.ts +++ b/sdk/wallet-sdk/src/wallet/init/types/context.ts @@ -10,7 +10,6 @@ export type SDKContext = { userId: string logger: SDKLogger error: SDKErrorHandler - defaultSynchronizerId: string } export type OfflineSDKContext = { diff --git a/sdk/wallet-sdk/src/wallet/namespace/amulet/amulet.test.ts b/sdk/wallet-sdk/src/wallet/namespace/amulet/amulet.test.ts index 56e6a71e0..5c5235351 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/amulet/amulet.test.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/amulet/amulet.test.ts @@ -46,7 +46,6 @@ const mockAmuletService = { const config: AmuletNamespaceConfig = { commonCtx: { ...ctx, - defaultSynchronizerId: 'mock-synchronizer-id', logger: mockLogger, } as any, registry: { @@ -64,7 +63,6 @@ const config: AmuletNamespaceConfig = { const configNoValidator: AmuletNamespaceConfig = { commonCtx: { ...ctx, - defaultSynchronizerId: 'mock-synchronizer-id', logger: mockLogger, } as any, registry: { @@ -133,7 +131,9 @@ describe('AmuletNamespace', () => { ['dc-1'] as any, ]) - const result = await amuletNamespace.tapInternal('10000') + const result = await amuletNamespace.tapInternal('10000', { + synchronizerId: 'mock-synchronizer-id', + }) expect(amuletNamespace.tap).toHaveBeenCalledWith( config.validatorParty, @@ -142,7 +142,7 @@ describe('AmuletNamespace', () => { expect(mockSubmit).toHaveBeenCalledWith({ commands: [{ ExerciseCommand: tapCommand }], disclosedContracts: ['dc-1'], - synchronizerId: config.commonCtx.defaultSynchronizerId, + synchronizerId: 'mock-synchronizer-id', actAs: [config.validatorParty], }) expect(result).toStrictEqual({ @@ -253,6 +253,7 @@ describe('AmuletNamespace with no validator party', () => { const result = await amuletNamespace.tapInternal('10000', { partyId: 'providerParty::123', + synchronizerId: 'mock-synchronizer-id', }) expect(amuletNamespace.tap).toHaveBeenCalledWith( @@ -262,7 +263,7 @@ describe('AmuletNamespace with no validator party', () => { expect(mockSubmit).toHaveBeenCalledWith({ commands: [{ ExerciseCommand: tapCommand }], disclosedContracts: ['dc-1'], - synchronizerId: config.commonCtx.defaultSynchronizerId, + synchronizerId: 'mock-synchronizer-id', actAs: ['providerParty::123'], }) expect(result).toStrictEqual({ diff --git a/sdk/wallet-sdk/src/wallet/namespace/amulet/namespace.ts b/sdk/wallet-sdk/src/wallet/namespace/amulet/namespace.ts index aa44eeb57..18dba3f6d 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/amulet/namespace.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/amulet/namespace.ts @@ -18,6 +18,7 @@ import { PreapprovalNamespace } from './preapproval.js' import { Decimal } from 'decimal.js' import { parseAssets, ParsedURL } from '../utils/url.js' import { resolveProviderParty } from './utils.js' +import { resolveSynchronizerId } from '../../synchronizer.js' const defaultMaxRetries = 10 const defaultDelayMs = 5000 @@ -94,15 +95,14 @@ export class AmuletNamespace { 'tapInternal', options?.partyId ) - const synchronizerId = - options?.synchronizerId ?? - this.sdkContext.commonCtx.defaultSynchronizerId const [tapCommand, disclosedContracts] = await this.tap(partyId, amount) return await this.ledger.internal.submit({ commands: [tapCommand], disclosedContracts, - synchronizerId, + ...(options?.synchronizerId !== undefined && { + synchronizerId: options.synchronizerId, + }), actAs: [partyId], }) } @@ -137,9 +137,11 @@ export class AmuletNamespace { if (featuredAppRights) { return featuredAppRights } - const synchronizerId = - options.synchronizerId ?? - this.sdkContext.commonCtx.defaultSynchronizerId + const synchronizerId = await resolveSynchronizerId( + this.sdkContext.commonCtx.ledgerProvider, + this.sdkContext.commonCtx.error, + options.synchronizerId + ) const [featuredAppCommand, dc] = await this.sdkContext.amuletService.selfGrantFeatureAppRight( diff --git a/sdk/wallet-sdk/src/wallet/namespace/amulet/preapproval.ts b/sdk/wallet-sdk/src/wallet/namespace/amulet/preapproval.ts index f79513d5c..41b56cd4d 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/amulet/preapproval.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/amulet/preapproval.ts @@ -9,6 +9,7 @@ import { LedgerNamespace } from '../ledger/namespace.js' import { fetchAmulet } from './namespace.js' import { SDKLogger } from '../../logger/logger.js' import { resolveProviderParty } from './utils.js' +import { resolveSynchronizerId } from '../../synchronizer.js' const EMPTY_COMMAND_RESULT = [null, []] as const @@ -127,13 +128,11 @@ export class PreapprovalNamespace { parties?.provider ) - const synchronizerId = - args.synchronizerId ?? this.ctx.commonCtx.defaultSynchronizerId - if (!synchronizerId) - this.ctx.commonCtx.error.throw({ - type: 'Unexpected', - message: 'Cannot obtain synchronizer id', - }) + const synchronizerId = await resolveSynchronizerId( + this.ctx.commonCtx.ledgerProvider, + this.ctx.commonCtx.error, + args.synchronizerId + ) if ( !preapprovalStatus || diff --git a/sdk/wallet-sdk/src/wallet/namespace/amulet/traffic.test.ts b/sdk/wallet-sdk/src/wallet/namespace/amulet/traffic.test.ts index 64a7ab58e..f922c44af 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/amulet/traffic.test.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/amulet/traffic.test.ts @@ -20,7 +20,6 @@ describe('TrafficNamespace', () => { config = { commonCtx: { ...mock.ctx, - defaultSynchronizerId: 'SYNCDEFAULT::123', } as any, amuletService: { getMemberTrafficStatus: vi.fn(), @@ -69,9 +68,18 @@ describe('TrafficNamespace', () => { }, }) - mock.ledgerProvider.request.mockResolvedValueOnce({ - participantId: 'PAR::234', - }) + mock.ledgerProvider.request + .mockResolvedValueOnce({ + connectedSynchronizers: [ + { + synchronizerId: 'SYNCDEFAULT::123', + synchronizerAlias: 'global', + }, + ], + }) + .mockResolvedValueOnce({ + participantId: 'PAR::234', + }) const result = await trafficNamespace.status() @@ -149,9 +157,18 @@ describe('TrafficNamespace', () => { [], ]) - mock.ledgerProvider.request.mockResolvedValueOnce({ - participantId: 'PAR::234', - }) + mock.ledgerProvider.request + .mockResolvedValueOnce({ + connectedSynchronizers: [ + { + synchronizerId: 'SYNCDEFAULT::123', + synchronizerAlias: 'global', + }, + ], + }) + .mockResolvedValueOnce({ + participantId: 'PAR::234', + }) await trafficNamespace.buy({ buyer: buyer, @@ -159,7 +176,7 @@ describe('TrafficNamespace', () => { inputUtxos: utxos, }) - expect(mock.ledgerProvider.request).toHaveBeenCalledTimes(1) + expect(mock.ledgerProvider.request).toHaveBeenCalledTimes(2) expect(config.amuletService.buyMemberTraffic).toHaveBeenCalledWith( 'DSO::123', buyer, diff --git a/sdk/wallet-sdk/src/wallet/namespace/amulet/traffic.ts b/sdk/wallet-sdk/src/wallet/namespace/amulet/traffic.ts index 4bcd2a651..1d624186e 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/amulet/traffic.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/amulet/traffic.ts @@ -5,6 +5,7 @@ import { PartyId } from '@canton-network/core-types' import { PreparedCommand } from '../transactions/types.js' import { Ops } from '@canton-network/core-provider-ledger' import { AmuletNamespaceConfig, fetchAmulet } from './namespace.js' +import { resolveSynchronizerId } from '../../synchronizer.js' export class TrafficNamespace { constructor(private readonly sdkContext: AmuletNamespaceConfig) {} @@ -12,9 +13,11 @@ export class TrafficNamespace { async status( params?: Partial<{ memberId?: string; synchronizerId?: string }> ) { - const synchronizerId = - params?.synchronizerId || - this.sdkContext.commonCtx.defaultSynchronizerId + const synchronizerId = await resolveSynchronizerId( + this.sdkContext.commonCtx.ledgerProvider, + this.sdkContext.commonCtx.error, + params?.synchronizerId + ) const memberId = params?.memberId ?? @@ -46,6 +49,11 @@ export class TrafficNamespace { }): Promise { const { buyer, ccAmount, inputUtxos } = params const migrationId = params.migrationId ?? 0 + const synchronizerId = await resolveSynchronizerId( + this.sdkContext.commonCtx.ledgerProvider, + this.sdkContext.commonCtx.error, + params.synchronizerId + ) const defaultAmulet = await fetchAmulet(this.sdkContext) const memberId = params.memberId ?? @@ -61,10 +69,6 @@ export class TrafficNamespace { ) ).participantId - const synchronizerId = - params.synchronizerId || - this.sdkContext.commonCtx.defaultSynchronizerId - const [command, dc] = await this.sdkContext.amuletService.buyMemberTraffic( defaultAmulet.admin, diff --git a/sdk/wallet-sdk/src/wallet/namespace/ledger/dar/dar.test.ts b/sdk/wallet-sdk/src/wallet/namespace/ledger/dar/dar.test.ts index ff6b2b12a..3c81b2c67 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/ledger/dar/dar.test.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/ledger/dar/dar.test.ts @@ -31,7 +31,7 @@ describe('Dar Namespace', () => { const uploadSpy = vi.spyOn(dar, 'upload') const darBytes = new Uint8Array() - await dar.upload(darBytes, 'packageId') + await dar.upload(darBytes, 'packageId', 'synchronizerId') expect(checkSpy).toHaveBeenCalledExactlyOnceWith('packageId') expect(checkSpy).toHaveResolvedWith(false) diff --git a/sdk/wallet-sdk/src/wallet/namespace/ledger/dar/index.ts b/sdk/wallet-sdk/src/wallet/namespace/ledger/dar/index.ts index c35d271ab..f3b4e8d2d 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/ledger/dar/index.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/ledger/dar/index.ts @@ -3,6 +3,7 @@ import { SDKContext } from '../../../sdk.js' import { Ops } from '@canton-network/core-provider-ledger' +import { resolveSynchronizerId } from '../../../synchronizer.js' export class DarNamespace { constructor(private readonly sdkContext: SDKContext) {} @@ -23,14 +24,19 @@ export class DarNamespace { return } + const resolvedSynchronizerId = await resolveSynchronizerId( + this.sdkContext.ledgerProvider, + this.sdkContext.error, + synchronizerId + ) + await this.sdkContext.ledgerProvider.request({ method: 'ledgerApi', params: { resource: '/v2/packages', requestMethod: 'post', query: { - synchronizerId: - synchronizerId ?? this.sdkContext.defaultSynchronizerId, + synchronizerId: resolvedSynchronizerId, vetAllPackages: vetAllPackages ?? true, }, body: darBytes as never, diff --git a/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/internal.test.ts b/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/internal.test.ts index d6d776d52..8337946ab 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/internal.test.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/internal.test.ts @@ -47,6 +47,7 @@ describe('Internal Leger Namespace', () => { }, ], actAs: ['partyId'], + synchronizerId: 'synchronizerId', } const result = await internal.submit(arg) @@ -79,6 +80,7 @@ describe('Internal Leger Namespace', () => { }, ], actAs: ['partyId'], + synchronizerId: 'synchronizerId', } const result = await internal.prepare(arg) diff --git a/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/namespace.ts b/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/namespace.ts index 813c38408..d88617715 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/namespace.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/ledger/internal/namespace.ts @@ -5,6 +5,7 @@ import { SDKContext } from '../../../sdk.js' import { v4 } from 'uuid' import { Ops } from '@canton-network/core-provider-ledger' import { InternalOperationParams } from './types.js' +import { resolveSynchronizerId } from '../../../synchronizer.js' export class InternalLedgerNamespace { constructor(private readonly ctx: SDKContext) {} @@ -14,13 +15,18 @@ export class InternalLedgerNamespace { ) { const { commands, - synchronizerId = this.ctx.defaultSynchronizerId, + synchronizerId: synchronizerIdArg, disclosedContracts = [], readAs = [], actAs, commandId = v4(), packageIdSelectionPreference = [], } = args + const synchronizerId = await resolveSynchronizerId( + this.ctx.ledgerProvider, + this.ctx.error, + synchronizerIdArg + ) const request = { commands, commandId, @@ -49,7 +55,7 @@ export class InternalLedgerNamespace { ) { const { commands, - synchronizerId = this.ctx.defaultSynchronizerId, + synchronizerId: synchronizerIdArg, disclosedContracts = [], readAs = [], actAs, @@ -57,6 +63,11 @@ export class InternalLedgerNamespace { packageIdSelectionPreference = [], verboseHashing = false, } = args + const synchronizerId = await resolveSynchronizerId( + this.ctx.ledgerProvider, + this.ctx.error, + synchronizerIdArg + ) const request = { commands, commandId, diff --git a/sdk/wallet-sdk/src/wallet/namespace/ledger/namespace.ts b/sdk/wallet-sdk/src/wallet/namespace/ledger/namespace.ts index e8230c696..bdf5a0e5f 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/ledger/namespace.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/ledger/namespace.ts @@ -42,14 +42,12 @@ export class LedgerNamespace { */ public prepare(options: PrepareOptions): PreparedTransaction { const preparePromise = async () => { - const synchronizerId = - options.synchronizerId || this.sdkContext.defaultSynchronizerId - const { partyId, commands, commandId = v4(), disclosedContracts = [], + synchronizerId, } = options const commandArray = Array.isArray(commands) ? commands : [commands] @@ -59,7 +57,7 @@ export class LedgerNamespace { commandId, actAs: [partyId], disclosedContracts, - synchronizerId, + ...(synchronizerId !== undefined && { synchronizerId }), }) } diff --git a/sdk/wallet-sdk/src/wallet/namespace/party/external/service.ts b/sdk/wallet-sdk/src/wallet/namespace/party/external/service.ts index 8475e2207..61007b86a 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/party/external/service.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/party/external/service.ts @@ -10,6 +10,7 @@ import { CreatePartyOptions } from './types.js' import { SDKLogger } from '../../../logger/index.js' import { LedgerProvider, Ops } from '@canton-network/core-provider-ledger' import { AuthTokenProvider } from '@canton-network/core-wallet-auth' +import { resolveSynchronizerId } from '../../../synchronizer.js' export class ExternalPartyNamespace { private readonly logger: SDKLogger @@ -32,7 +33,11 @@ export class ExternalPartyNamespace { this.resolveParticipantUids( options?.confirmingParticipantEndpoints ?? [] ), - options?.synchronizerId || this.resolveSynchronizerId(), + resolveSynchronizerId( + this.ctx.ledgerProvider, + this.ctx.error, + options?.synchronizerId + ), ]).then( ([ observingParticipantUids, @@ -79,34 +84,6 @@ export class ExternalPartyNamespace { ) } - private async resolveSynchronizerId() { - const connectedSynchronizers = - await this.ctx.ledgerProvider.request( - { - method: 'ledgerApi', - params: { - resource: '/v2/state/connected-synchronizers', - requestMethod: 'get', - query: {}, - }, - } - ) - - if (!connectedSynchronizers.connectedSynchronizers?.[0]) { - throw new Error('No connected synchronizers found') - } - - const synchronizerId = - connectedSynchronizers.connectedSynchronizers[0].synchronizerId - if (connectedSynchronizers.connectedSynchronizers.length > 1) { - this.logger.warn( - `Found ${connectedSynchronizers.connectedSynchronizers.length} synchronizers, defaulting to ${synchronizerId}` - ) - } - - return synchronizerId - } - /** * Retrieves participant IDs from the given endpoints by querying their ledger API. * @param hostingParticipantConfigs - Participant endpoint configurations to query diff --git a/sdk/wallet-sdk/src/wallet/namespace/party/external/signed.ts b/sdk/wallet-sdk/src/wallet/namespace/party/external/signed.ts index ab98168bc..e1ed1e9eb 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/party/external/signed.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/party/external/signed.ts @@ -17,6 +17,7 @@ import { Ops, } from '@canton-network/core-provider-ledger' import { AuthTokenProvider } from '@canton-network/core-wallet-auth' +import { resolveSynchronizerId } from '../../../synchronizer.js' /** * Represents a signed party creation, ready to be allocated on the ledger. @@ -144,7 +145,11 @@ export class SignedPartyCreationService { } = options const ledgerProvider = defaultLedgerProvider ?? this.ctx.ledgerProvider try { - const synchronizerId = this.ctx.defaultSynchronizerId + const synchronizerId = await resolveSynchronizerId( + this.ctx.ledgerProvider, + this.ctx.error, + this.createPartyOptions?.synchronizerId + ) await this.allocate( ledgerProvider, diff --git a/sdk/wallet-sdk/src/wallet/namespace/party/internal/index.ts b/sdk/wallet-sdk/src/wallet/namespace/party/internal/index.ts index 5e0f11b5e..814c7dc9c 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/party/internal/index.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/party/internal/index.ts @@ -6,6 +6,7 @@ import { SDKContext } from '../../../sdk.js' import { v4 } from 'uuid' import { PartyId } from '@canton-network/core-types' import { SDKLogger } from '../../../logger/logger.js' +import { resolveSynchronizerId } from '../../../synchronizer.js' export class InternalPartyNamespace { private readonly logger: SDKLogger @@ -50,9 +51,11 @@ export class InternalPartyNamespace { body: { partyIdHint: params.partyHint ?? v4(), identityProviderId: '', - synchronizerId: - params.synchronizerId ?? - this.ctx.defaultSynchronizerId, + synchronizerId: await resolveSynchronizerId( + this.ctx.ledgerProvider, + this.ctx.error, + params.synchronizerId + ), userId: params.userId ?? this.ctx.userId, }, }, diff --git a/sdk/wallet-sdk/src/wallet/namespace/party/party.test.ts b/sdk/wallet-sdk/src/wallet/namespace/party/party.test.ts index 4d51ab748..ba82d4635 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/party/party.test.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/party/party.test.ts @@ -288,14 +288,16 @@ describe('Party namespace', () => { preparedParty = new PreparedPartyCreationService( ctx, - Promise.resolve(partyTransaction) + Promise.resolve(partyTransaction), + { synchronizerId: 'syncId' } ) signedParty = new SignedPartyCreationService( ctx, Promise.resolve({ party: partyTransaction, signature: 'defaultSignature', - }) + }), + { synchronizerId: 'syncId' } ) }) @@ -513,7 +515,7 @@ describe('Party namespace', () => { resource: '/v2/parties/external/allocate', requestMethod: 'post', body: { - synchronizer: ctx.defaultSynchronizerId, + synchronizer: 'syncId', identityProviderId: '', onboardingTransactions: partyTransaction.topologyTransactions.map( @@ -560,7 +562,7 @@ describe('Party namespace', () => { resource: '/v2/parties/external/allocate', requestMethod: 'post', body: { - synchronizer: ctx.defaultSynchronizerId, + synchronizer: 'syncId', identityProviderId: '', onboardingTransactions: partyTransaction.topologyTransactions.map( diff --git a/sdk/wallet-sdk/src/wallet/namespace/token/allocation/allocation.test.ts b/sdk/wallet-sdk/src/wallet/namespace/token/allocation/allocation.test.ts index 4f42d85a8..0281da9b0 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/token/allocation/allocation.test.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/token/allocation/allocation.test.ts @@ -34,7 +34,6 @@ const mockTokenStandard = { const config: TokenNamespaceConfig = { commonCtx: { ...ctx, - defaultSynchronizerId: 'mock-synchronizer-id', logger: mockLogger, } as any, registryUrls: [new ParsedURL(ctx, 'http://registry.com')], diff --git a/sdk/wallet-sdk/src/wallet/namespace/token/token.test.ts b/sdk/wallet-sdk/src/wallet/namespace/token/token.test.ts index 6107e1577..0e7ae46dd 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/token/token.test.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/token/token.test.ts @@ -92,7 +92,6 @@ const mockTokenStandard = { const config: TokenNamespaceConfig = { commonCtx: { ...ctx, - defaultSynchronizerId: 'mock-synchronizer-id', logger: mockLogger, } as any, registryUrls: [new ParsedURL(ctx, 'http://registry.com')], diff --git a/sdk/wallet-sdk/src/wallet/namespace/token/transfer/proxyDelegation.ts b/sdk/wallet-sdk/src/wallet/namespace/token/transfer/proxyDelegation.ts index 13f0c0af3..1a2570bd1 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/token/transfer/proxyDelegation.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/token/transfer/proxyDelegation.ts @@ -13,6 +13,7 @@ import { FeaturedAppRight } from '../../amulet/types.js' import { TokenStandardService } from '@canton-network/core-token-standard-service' import { LedgerNamespace } from '../../ledger/index.js' import { resolveProviderParty } from '../utils.js' +import { resolveSynchronizerId } from '../../../synchronizer.js' export type ProxyDelegationCommandArgs = { proxyCid: string @@ -21,6 +22,7 @@ export type ProxyDelegationCommandArgs = { featuredAppRight: FeaturedAppRight beneficiaries?: Beneficiaries[] validatorParty?: PartyId + synchronizerId?: string } type ProxyDelegationCommand = 'accept' | 'reject' | 'withdraw' @@ -110,6 +112,7 @@ export class ProxyDelegationNamespace { beneficiaries = [], registryUrl = localNetStaticConfig.LOCALNET_REGISTRY_API_URL, validatorParty, + synchronizerId, } = args const providerParty = resolveProviderParty( @@ -117,6 +120,11 @@ export class ProxyDelegationNamespace { 'command', validatorParty ) + const resolvedSynchronizerId = await resolveSynchronizerId( + this.ctx.commonCtx.ledgerProvider, + this.ctx.commonCtx.error, + synchronizerId + ) const defaultBeneficiary: Beneficiaries = { beneficiary: providerParty, weight: beneficiaries.reduce( @@ -139,20 +147,24 @@ export class ProxyDelegationNamespace { }, [ ...disclosedContracts, - this.createFeaturedAppDisclosedContract(args), + this.createFeaturedAppDisclosedContract( + args, + resolvedSynchronizerId + ), ], ] } private createFeaturedAppDisclosedContract( - args: ProxyDelegationCommandArgs + args: ProxyDelegationCommandArgs, + synchronizerId: string ) { const { featuredAppRight } = args return { templateId: featuredAppRight.template_id, contractId: featuredAppRight.contract_id, createdEventBlob: featuredAppRight.created_event_blob, - synchronizerId: this.ctx.commonCtx.defaultSynchronizerId, + synchronizerId, } } } diff --git a/sdk/wallet-sdk/src/wallet/namespace/token/transfer/transfer.test.ts b/sdk/wallet-sdk/src/wallet/namespace/token/transfer/transfer.test.ts index 035777446..8e0f236db 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/token/transfer/transfer.test.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/token/transfer/transfer.test.ts @@ -29,7 +29,6 @@ const mockTokenStandard = { const config: TokenNamespaceConfig = { commonCtx: { ...ctx, - defaultSynchronizerId: 'mock-synchronizer-id', logger: mockLogger, } as any, registryUrls: [new ParsedURL(ctx, 'http://registry.com')], @@ -165,6 +164,7 @@ describe('token transfer namespace', () => { created_at: 'createdat', }, registryUrl: new URL('http://registry.com'), + synchronizerId: 'mock-synchronizer-id', } it('should create proxy transfer instruction accept', async () => { @@ -234,7 +234,6 @@ describe('token transfer namespace no validatorURL', () => { transfer = new TransferNamespace({ commonCtx: { ...ctx, - defaultSynchronizerId: 'mock-synchronizer-id', logger: mockLogger, } as any, registryUrls: [new ParsedURL(ctx, 'http://registry.com')], diff --git a/sdk/wallet-sdk/src/wallet/namespace/token/utxos/utxos.test.ts b/sdk/wallet-sdk/src/wallet/namespace/token/utxos/utxos.test.ts index 4a753c9ee..05b1183cc 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/token/utxos/utxos.test.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/token/utxos/utxos.test.ts @@ -31,7 +31,6 @@ const mockTokenStandard = { const config: TokenNamespaceConfig = { commonCtx: { ...ctx, - defaultSynchronizerId: 'mock-synchronizer-id', logger: mockLogger, } as any, registryUrls: [new ParsedURL(ctx, 'http://registry.com')], @@ -331,7 +330,6 @@ describe('delegated utxos merge namespace without validatorParty', () => { utxos = new TokenNamespace({ commonCtx: { ...ctx, - defaultSynchronizerId: 'mock-synchronizer-id', logger: mockLogger, } as any, registryUrls: [new ParsedURL(ctx, 'http://registry.com')], diff --git a/sdk/wallet-sdk/src/wallet/namespace/user/namespace.test.ts b/sdk/wallet-sdk/src/wallet/namespace/user/namespace.test.ts index cc152765c..d248a9724 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/user/namespace.test.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/user/namespace.test.ts @@ -19,7 +19,6 @@ const sdkContext = { userId: 'ledger-api-user', logger: new SDKLogger('console'), error: new SDKErrorHandler(logger), - defaultSynchronizerId: 'syncid', } describe('user namespace', () => { diff --git a/sdk/wallet-sdk/src/wallet/namespace/utils/utils.test.ts b/sdk/wallet-sdk/src/wallet/namespace/utils/utils.test.ts index a9efa3613..551360122 100644 --- a/sdk/wallet-sdk/src/wallet/namespace/utils/utils.test.ts +++ b/sdk/wallet-sdk/src/wallet/namespace/utils/utils.test.ts @@ -20,7 +20,6 @@ const ctx: SDKContext = { userId: 'ledger-api-user', logger: new SDKLogger('console'), error: new SDKErrorHandler(new SDKLogger('console')), - defaultSynchronizerId: 'synchronizerId', } const amuletAsset = { diff --git a/sdk/wallet-sdk/src/wallet/sdk.ts b/sdk/wallet-sdk/src/wallet/sdk.ts index b4f0c858f..0b75baf0a 100644 --- a/sdk/wallet-sdk/src/wallet/sdk.ts +++ b/sdk/wallet-sdk/src/wallet/sdk.ts @@ -121,17 +121,11 @@ export class SDK { }) } - const defaultSynchronizerId = await getDefaultSynchronizerId( - ledgerProvider, - logger - ) - const ctx: SDKContext = { ledgerProvider, userId: userId!, logger, error, - defaultSynchronizerId, } const config = {} as Pick< @@ -164,35 +158,6 @@ export class SDK { } } -async function getDefaultSynchronizerId( - provider: AbstractLedgerProvider, - logger: SDKLogger -) { - const connectedSynchronizers = - await provider.request({ - method: 'ledgerApi', - params: { - resource: '/v2/state/connected-synchronizers', - requestMethod: 'get', - query: {}, - }, - }) - - if (!connectedSynchronizers.connectedSynchronizers?.[0]) { - throw new Error('No connected synchronizers found') - } - - const defaultSynchronizerId = - connectedSynchronizers.connectedSynchronizers[0].synchronizerId - if (connectedSynchronizers.connectedSynchronizers.length > 1) { - logger.warn( - `Found ${connectedSynchronizers.connectedSynchronizers.length} synchronizers, defaulting to ${defaultSynchronizerId}` - ) - } - - return defaultSynchronizerId -} - export async function getValidatorParty( validatorUrl: URL, auth: AuthTokenProvider | TokenProviderConfig, diff --git a/sdk/wallet-sdk/src/wallet/synchronizer.ts b/sdk/wallet-sdk/src/wallet/synchronizer.ts new file mode 100644 index 000000000..7a964f4d2 --- /dev/null +++ b/sdk/wallet-sdk/src/wallet/synchronizer.ts @@ -0,0 +1,61 @@ +// 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 { SDKErrorHandler } from './error/handler.js' + +/** + * Resolves the synchronizer id to use for a ledger operation. + * + * The wallet SDK no longer guesses a synchronizer when several are connected — + * callers are responsible for selecting the appropriate one and passing it + * explicitly. As a convenience, when exactly one synchronizer is connected it + * is used implicitly. + * + * @param provider ledger provider used to query connected synchronizers + * @param error SDK error handler used to raise structured errors + * @param explicit synchronizer id supplied by the caller, returned as-is when present + * @returns the resolved synchronizer id + * @throws when no synchronizer is connected, or when several are connected and + * no explicit synchronizer id was provided + */ +export async function resolveSynchronizerId( + provider: AbstractLedgerProvider, + error: SDKErrorHandler, + explicit?: string +): Promise { + if (explicit) return explicit + + const connected = + await provider.request({ + method: 'ledgerApi', + params: { + resource: '/v2/state/connected-synchronizers', + requestMethod: 'get', + query: {}, + }, + }) + + const synchronizers = connected?.connectedSynchronizers ?? [] + + if (synchronizers.length === 0) { + return error.throw({ + message: 'No connected synchronizers found', + type: 'NotFound', + }) + } + + if (synchronizers.length > 1) { + return error.throw({ + message: + `Multiple synchronizers are connected (${synchronizers.length}). ` + + 'Pass synchronizerId explicitly to select which one to use.', + type: 'BadRequest', + }) + } + + return synchronizers[0].synchronizerId +}