Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
81f734a
feat: multi-sync example (example 15)
Viktor-Kalashnykov-da May 11, 2026
dae0c05
Code Review: removed dependency on @canton-network/core-splice-client…
Viktor-Kalashnykov-da May 11, 2026
4862bad
Code Review: refactored names of parties in logs
Viktor-Kalashnykov-da May 12, 2026
b143830
Code Review: added TokenAdmin party and replaced reassignment of Toke…
Viktor-Kalashnykov-da May 12, 2026
9028bfa
Merge branch 'main' into wiktor/multisync-example
Viktor-Kalashnykov-da May 12, 2026
3967eb4
Merge branch 'main' into wiktor/multisync-example
Viktor-Kalashnykov-da May 13, 2026
320cad2
Code Review: removed usage of hardcoded values for adding for request…
Viktor-Kalashnykov-da May 13, 2026
5c92e06
Code Review: replaced the reassignment of Bob's Token to self-transfe…
Viktor-Kalashnykov-da May 13, 2026
53d1592
Code Review: removed Token interface disovery description from docume…
Viktor-Kalashnykov-da May 13, 2026
13e99aa
Code Review: removed reference to token dars Troubleshooting section
Viktor-Kalashnykov-da May 13, 2026
c76aa8b
Code Review: moved globalSynchronizerId() method from common.ts to n…
Viktor-Kalashnykov-da May 13, 2026
7deb339
docs(example-15): simplify README, run all commands from repo root
jarekr-da May 20, 2026
0ba4a98
chore: reworking scenario - interna.reassign used
jarekr-da May 20, 2026
9571324
feat: working version - explicit reassign
jarekr-da May 20, 2026
a194868
chore(example-15): revert self-transfer token variant and remove gene…
jarekr-da May 20, 2026
14d8745
chore: fixed dar use
jarekr-da May 20, 2026
f9db78f
chore: removed stale changes
jarekr-da May 20, 2026
d386c91
chore: fixed wrong logger message
jarekr-da May 20, 2026
7226eac
refactor: move globalSynchronizerId logic out of wallet-sdk into exam…
jarekr-da May 20, 2026
563df2d
revert: restore party/external/service.ts to pre-multisync state
jarekr-da May 20, 2026
2cd0030
fix: remove automatic synchronizer selection — callers provide it exp…
jarekr-da May 13, 2026
db34ec1
fix: remove LedgerClient.getSynchronizerId() and update all callers
jarekr-da May 13, 2026
ec9099b
fix: update example scripts to pass synchronizerId explicitly to part…
jarekr-da May 13, 2026
1740558
chore: allow git commit in vscode terminal settings
jarekr-da May 13, 2026
0c9fb0b
fix: update example scripts to pass synchronizerId to ledger.prepare(…
jarekr-da May 13, 2026
465f1e3
tech: after rebase
jarekr-da May 19, 2026
6ceaef7
sdk: remove globalSynchronizerId from ledger state; SDK auto-resolves…
jarekr-da May 19, 2026
0a5c12c
chore: remove chat.tools.terminal.autoApprove from vscode settings
jarekr-da May 19, 2026
caf0f72
chore: remove review7.md
jarekr-da May 19, 2026
3ae7ca0
chore: remove leftover empty daml package from multi-sync example
jarekr-da May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/actions/setup_canton/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ inputs:
canton_version:
description: 'Canton version (required)'
required: true
multi-sync:
description: 'Start localnet with --profile multi-sync'
start_services:
description: 'Whether to start canton services after setup'
required: false
Expand Down Expand Up @@ -60,6 +62,28 @@ runs:
cat "$LOGFILE"
exit 1
'
# TODO (#1721): make multi-sync the default and remove the flag once multi-sync is fully supported and tested in the main scripts e2e tests, but for now we want to keep it as an option to avoid accidentally running multi-sync e2e tests without updating the main scripts e2e tests to cover multi-sync as well
- name: Start Localnet
if: inputs.instance == 'localnet'
shell: bash
run: |
MULTI_SYNC_FLAG=""
if [ "${{ inputs.multi-sync }}" = "true" ]; then
MULTI_SYNC_FLAG="--multi-sync"
fi
yarn start:localnet -- --network=${{ inputs.network }} $MULTI_SYNC_FLAG

- name: Save Docker images to cache
if: ${{ inputs.instance == 'localnet' && steps.localnet-cache.outputs.cache-hit != 'true' }}
shell: bash
run: |
mkdir -p /tmp/docker-images
images=$(docker images --format "{{.Repository}}:{{.Tag}}" | grep -v "<none>" || true)
if [ -n "$images" ]; then
echo "$images" | xargs -r docker save -o /tmp/docker-images/images.tar
else
echo "No Docker images found to save."
fi

- name: Save Canton cache
uses: ./.github/actions/save_cache_if_absent
Expand Down
70 changes: 67 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ jobs:
run: yarn nx snippets docs-wallet-integration-guide-examples

- uses: ./.github/actions/check_resources

# TODO (#1721): make multi-sync the default and remove the flag once multi-sync is fully supported and tested in the main scripts e2e tests, but for now we want to keep it as an option to avoid accidentally running multi-sync e2e tests without updating the main scripts e2e tests to cover multi-sync as well
- name: Stop Localnet (${{ matrix.network }})
if: always()
run: yarn stop:localnet -- --network=${{ matrix.network }}
Expand Down Expand Up @@ -431,7 +431,7 @@ jobs:
run: yarn script:test:examples

- uses: ./.github/actions/check_resources

# TODO (#1721): make multi-sync the default and remove the flag once multi-sync is fully supported and tested in the main scripts e2e tests, but for now we want to keep it as an option to avoid accidentally running multi-sync e2e tests without updating the main scripts e2e tests to cover multi-sync as well
- name: Stop Localnet (${{ matrix.network }})
if: always()
run: yarn stop:localnet -- --network=${{ matrix.network }}
Expand All @@ -453,10 +453,70 @@ jobs:
name: docker-logs-scripts-${{ matrix.network }}
path: logs/

# TODO (#1721): remove multi-sync scripts e2e tests once multi-sync is fully supported and tested in the main scripts e2e tests
wallet-sdk-scripts-e2e-multi-sync:
name: wallet-sdk-scripts-e2e-multi-sync (${{ matrix.network }})
runs-on: ubuntu-latest
needs: build
strategy:
fail-fast: false
matrix:
network: [devnet, mainnet]

steps:
- name: Checkout
uses: actions/checkout@v6

- uses: ./.github/actions/setup_yarn

- uses: ./.github/actions/setup_canton
with:
network: ${{ matrix.network }}
instance: localnet
multi-sync: 'true'

- uses: ./.github/actions/check_resources

- name: Build project
run: yarn build:all

- name: Test multi-sync example script (${{ matrix.network }})
env:
MAX_IO_LISTENERS: '50'
run: yarn script:test:examples:multi-sync

- uses: ./.github/actions/check_resources
# TODO (#1721): make multi-sync the default and remove the flag once multi-sync is fully supported and tested in the main scripts e2e tests, but for now we want to keep it as an option to avoid accidentally running multi-sync e2e tests without updating the main scripts e2e tests to cover multi-sync as well
- name: Stop Localnet (${{ matrix.network }})
if: always()
run: yarn stop:localnet -- --network=${{ matrix.network }} --multi-sync

- name: Save container logs
if: failure()
run: |
#!/usr/bin/env bash
set -euo pipefail
mkdir -p logs
for c in $(docker ps -a --format '{{.Names}}'); do
docker logs "$c" &> "logs/$c.log" || true
done

- name: Upload logs as artifacts
if: failure()
uses: actions/upload-artifact@v7
with:
name: docker-logs-scripts-multi-sync-${{ matrix.network }}
path: logs/

test-wallet-sdk-e2e:
name: test-wallet-sdk-e2e
runs-on: ubuntu-latest
needs: [wallet-sdk-snippets-e2e, wallet-sdk-scripts-e2e, wallet-sdk-pkg]
needs: [
wallet-sdk-snippets-e2e,
wallet-sdk-scripts-e2e,
wallet-sdk-scripts-e2e-multi-sync, # TODO (#1721): remove multi-sync scripts e2e tests once multi-sync is fully supported and tested in the main scripts e2e tests, but for now we want to keep it as a gate to ensure multi-sync e2e tests are not accidentally skipped without updating the main scripts e2e tests to cover multi-sync as well
wallet-sdk-pkg,
]
if: always()
steps:
- name: Report wallet-sdk e2e execution
Expand All @@ -469,6 +529,10 @@ jobs:
echo "wallet-sdk scripts e2e did not succeed"
exit 1
fi
if [ "${{ needs.wallet-sdk-scripts-e2e-multi-sync.result }}" != "success" ]; then
echo "wallet-sdk scripts e2e (multi-sync) did not succeed"
exit 1
fi
if [ "${{ needs.wallet-sdk-pkg.result }}" != "success" ]; then
echo "wallet-sdk package validation did not succeed"
exit 1
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/examples-under-stress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ jobs:
run: yarn script:test:examples-stress

- uses: ./.github/actions/check_resources

# TODO (#1721): make multi-sync the default and remove the flag once multi-sync is fully supported and tested in the main scripts e2e tests, but for now we want to keep it as an option to avoid accidentally running multi-sync e2e tests without updating the main scripts e2e tests to cover multi-sync as well
- name: Stop localnet (${{ github.event.inputs.network || 'devnet' }})
if: always()
run: yarn stop:localnet -- --network=${{ github.event.inputs.network || 'devnet' }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/stress-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ jobs:
run: yarn script:test:stress-scripts

- uses: ./.github/actions/check_resources

# TODO (#1721): make multi-sync the default and remove the flag once multi-sync is fully supported and tested in the main scripts e2e tests, but for now we want to keep it as an option to avoid accidentally running multi-sync e2e tests without updating the main scripts e2e tests to cover multi-sync as well
- name: Stop localnet (${{ github.event.inputs.network || 'devnet' }})
if: always()
run: yarn stop:localnet -- --network=${{ github.event.inputs.network || 'devnet' }}
Expand Down
76 changes: 38 additions & 38 deletions canton/multi-sync/app-synchronizer.sc
Original file line number Diff line number Diff line change
Expand Up @@ -10,60 +10,60 @@ bootstrap.synchronizer(
staticSynchronizerParameters = StaticSynchronizerParameters.defaultsWithoutKMS(ProtocolVersion.latest),
)

// Connect app-provider to the new synchronizer.
// TODO: app-user is intentionally NOT connected to app-synchronizer so that
// the SDK (which picks connectedSynchronizers[0]) always selects the global synchronizer.
// This is a temporary workaround until we have a better way to select synchronizers in the SDK.
// Connect app-user and app-provider to the new synchronizer.
// app-user — global + app-synchronizer
// app-provider — global + app-synchronizer
// sv — global only (TradingApp is only an observer of Token Allocations;
// it learns about them when they are reassigned to global before settlement)
//
// The global domain is connected first (before this bootstrap script runs),
// so connectedSynchronizers[0] remains global for all participants — the
// default synchronizer selection is unaffected.
`app-provider`.synchronizers.connect_local(`app-sequencer`, "app-synchronizer")
`app-user`.synchronizers.connect_local(`app-sequencer`, "app-synchronizer")

// Wait for app-provider to be active on app-synchronizer
// Wait for both participants to be active on app-synchronizer
utils.retry_until_true {
`app-provider`.synchronizers.active("app-synchronizer")
}
utils.retry_until_true {
`app-user`.synchronizers.active("app-synchronizer")
}

// Replicate package vetting from the global synchronizer to app-synchronizer so that
// the new synchronizer is fully functional for app-provider.
//
// Splice connects app-provider to the global synchronizer under the alias "global".
// We read vetting from its per-synchronizer store rather than the authorized store
// because we want to replicate exactly what is active on the global synchronizer.
// We wait until the global-synchronizer view is non-empty to avoid a topology-
// propagation race (which caused `multi-sync-startup` to fail in CI).
val connectedSynchronizers = `app-provider`.synchronizers.list_connected()
val appSyncId = connectedSynchronizers
// Vet packages on app-synchronizer for all three participants.
// The Splice app already uploaded DARs and vetted them on global-domain.
// We replicate the vetting from the authorized store to app-synchronizer
// so that the synchronizer is fully functional.
val appSyncId = `app-provider`.synchronizers.list_connected()
.find(_.synchronizerAlias.unwrap == "app-synchronizer")
.getOrElse(throw new RuntimeException("app-synchronizer not found in connected synchronizers"))
.synchronizerId
val globalSyncId = connectedSynchronizers
.find(_.synchronizerAlias.unwrap == "global")
.getOrElse(throw new RuntimeException(
s"'global' synchronizer not found. Connected: ${connectedSynchronizers.map(_.synchronizerAlias.unwrap).mkString(", ")}"
))
.synchronizerId

utils.retry_until_true {
`app-provider`.topology.vetted_packages
.list(store = Some(TopologyStoreId.Synchronizer(globalSyncId)), filterParticipant = `app-provider`.id.filterString)
for (participant <- Seq(`app-provider`, `app-user`)) {
val vettedFromAuthorized = participant.topology.vetted_packages
.list(store = Some(TopologyStoreId.Authorized), filterParticipant = participant.id.filterString)
.flatMap(_.item.packages)
.nonEmpty
}

val vettedPackages = `app-provider`.topology.vetted_packages
.list(store = Some(TopologyStoreId.Synchronizer(globalSyncId)), filterParticipant = `app-provider`.id.filterString)
.flatMap(_.item.packages)

logger.info(s"Vetting ${vettedPackages.size} packages on app-synchronizer for app-provider")
`app-provider`.topology.vetted_packages.propose_delta(
participant = `app-provider`.id,
store = appSyncId,
adds = vettedPackages.toSeq,
)
if (vettedFromAuthorized.nonEmpty) {
logger.info(s"Vetting ${vettedFromAuthorized.size} packages on app-synchronizer for ${participant.name}")
participant.topology.vetted_packages.propose_delta(
participant = participant.id,
store = appSyncId,
adds = vettedFromAuthorized.toSeq,
)
}
}

// Wait for vetting to propagate on app-synchronizer
// Wait for vetting topology to propagate for app-provider and app-user
utils.retry_until_true {
val providerVetted = `app-provider`.topology.vetted_packages
.list(store = Some(appSyncId), filterParticipant = `app-provider`.id.filterString)
providerVetted.nonEmpty && providerVetted.head.item.packages.nonEmpty
}
utils.retry_until_true {
val userVetted = `app-user`.topology.vetted_packages
.list(store = Some(appSyncId), filterParticipant = `app-user`.id.filterString)
userVetted.nonEmpty && userVetted.head.item.packages.nonEmpty
}

logger.info("app-synchronizer bootstrap with package vetting completed successfully")
logger.info("app-synchronizer bootstrap with package vetting completed successfully for app-provider and app-user")
20 changes: 0 additions & 20 deletions core/ledger-client/src/ledger-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -795,26 +795,6 @@ export class LedgerClient {
return filter
}

// Retrieve an (arbitrary) synchronizer id from the validator.
// This synchronizer id is cached for the remainder of this object's life.
public async getSynchronizerId(): Promise<string> {
if (this.synchronizerId) return this.synchronizerId
const response = await this.getWithRetry(
'/v2/state/connected-synchronizers'
)
if (!response.connectedSynchronizers?.[0]) {
throw new Error('No connected synchronizers found')
}
const synchronizerId = response.connectedSynchronizers[0].synchronizerId
if (response.connectedSynchronizers.length > 1) {
this.logger.warn(
`Found ${response.connectedSynchronizers.length} synchronizers, defaulting to ${synchronizerId}`
)
}
this.synchronizerId = synchronizerId
return synchronizerId
}

public async postWithRetry<Path extends PostEndpoint>(
path: Path,
body: PostRequest<Path>,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) 2025-2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { defineConfig } from 'file:///mnt/extra-ssd/jarek/dev/splice-wallet-kernel/.yarn/__virtual__/vite-virtual-28a4e6acbc/6/home/jarek/.yarn/berry/cache/vite-npm-7.3.2-20decd81df-10c0.zip/node_modules/vite/dist/node/index.js'
import dts from 'file:///mnt/extra-ssd/jarek/dev/splice-wallet-kernel/.yarn/__virtual__/vite-plugin-dts-virtual-dcaad0a523/6/home/jarek/.yarn/berry/cache/vite-plugin-dts-npm-4.5.4-2445647687-10c0.zip/node_modules/vite-plugin-dts/dist/index.mjs'
var vite_config_default = defineConfig({
build: {
emptyOutDir: false,
lib: {
entry: 'src/index.ts',
formats: ['es', 'cjs'],
fileName: (format) => (format === 'cjs' ? 'index.cjs' : 'index.js'),
cssFileName: 'index',
},
rollupOptions: {
external: ['lit', 'bootstrap', '@popperjs/core'],
output: {
exports: 'auto',
},
},
sourcemap: true,
},
plugins: [dts()],
})
export { vite_config_default as default }
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlUm9vdCI6ICJmaWxlOi8vL21udC9leHRyYS1zc2QvamFyZWsvZGV2L3NwbGljZS13YWxsZXQta2VybmVsL2NvcmUvd2FsbGV0LXVpLWNvbXBvbmVudHMvIiwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvbW50L2V4dHJhLXNzZC9qYXJlay9kZXYvc3BsaWNlLXdhbGxldC1rZXJuZWwvY29yZS93YWxsZXQtdWktY29tcG9uZW50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL21udC9leHRyYS1zc2QvamFyZWsvZGV2L3NwbGljZS13YWxsZXQta2VybmVsL2NvcmUvd2FsbGV0LXVpLWNvbXBvbmVudHMvdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL21udC9leHRyYS1zc2QvamFyZWsvZGV2L3NwbGljZS13YWxsZXQta2VybmVsL2NvcmUvd2FsbGV0LXVpLWNvbXBvbmVudHMvdml0ZS5jb25maWcudHNcIjsvLyBDb3B5cmlnaHQgKGMpIDIwMjUtMjAyNiBEaWdpdGFsIEFzc2V0IChTd2l0emVybGFuZCkgR21iSCBhbmQvb3IgaXRzIGFmZmlsaWF0ZXMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJ1xuaW1wb3J0IGR0cyBmcm9tICd2aXRlLXBsdWdpbi1kdHMnXG5cbmV4cG9ydCBkZWZhdWx0IGRlZmluZUNvbmZpZyh7XG4gICAgYnVpbGQ6IHtcbiAgICAgICAgZW1wdHlPdXREaXI6IGZhbHNlLFxuICAgICAgICBsaWI6IHtcbiAgICAgICAgICAgIGVudHJ5OiAnc3JjL2luZGV4LnRzJyxcbiAgICAgICAgICAgIGZvcm1hdHM6IFsnZXMnLCAnY2pzJ10sXG4gICAgICAgICAgICBmaWxlTmFtZTogKGZvcm1hdCkgPT4gKGZvcm1hdCA9PT0gJ2NqcycgPyAnaW5kZXguY2pzJyA6ICdpbmRleC5qcycpLFxuICAgICAgICAgICAgY3NzRmlsZU5hbWU6ICdpbmRleCcsXG4gICAgICAgIH0sXG4gICAgICAgIHJvbGx1cE9wdGlvbnM6IHtcbiAgICAgICAgICAgIGV4dGVybmFsOiBbJ2xpdCcsICdib290c3RyYXAnLCAnQHBvcHBlcmpzL2NvcmUnXSxcbiAgICAgICAgICAgIG91dHB1dDoge1xuICAgICAgICAgICAgICAgIGV4cG9ydHM6ICdhdXRvJyxcbiAgICAgICAgICAgIH0sXG4gICAgICAgIH0sXG4gICAgICAgIHNvdXJjZW1hcDogdHJ1ZSxcbiAgICB9LFxuICAgIHBsdWdpbnM6IFtkdHMoKV0sXG59KVxuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUdBLFNBQVMsb0JBQW9CO0FBQzdCLE9BQU8sU0FBUztBQUVoQixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUN4QixPQUFPO0FBQUEsSUFDSCxhQUFhO0FBQUEsSUFDYixLQUFLO0FBQUEsTUFDRCxPQUFPO0FBQUEsTUFDUCxTQUFTLENBQUMsTUFBTSxLQUFLO0FBQUEsTUFDckIsVUFBVSxDQUFDLFdBQVksV0FBVyxRQUFRLGNBQWM7QUFBQSxNQUN4RCxhQUFhO0FBQUEsSUFDakI7QUFBQSxJQUNBLGVBQWU7QUFBQSxNQUNYLFVBQVUsQ0FBQyxPQUFPLGFBQWEsZ0JBQWdCO0FBQUEsTUFDL0MsUUFBUTtBQUFBLFFBQ0osU0FBUztBQUFBLE1BQ2I7QUFBQSxJQUNKO0FBQUEsSUFDQSxXQUFXO0FBQUEsRUFDZjtBQUFBLEVBQ0EsU0FBUyxDQUFDLElBQUksQ0FBQztBQUNuQixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=
1 change: 1 addition & 0 deletions docs/wallet-integration-guide/examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"run-12": "tsx ./scripts/12-subscribe-to-events.ts | pino-pretty",
"run-13": "tsx ./scripts/13-rewards-for-deposits/index.ts | pino-pretty",
"run-14": "tsx ./scripts/14-offline-signing.ts | pino-pretty",
"run-15": "tsx ./scripts/15-multi-sync/index.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"
},
Expand Down
4 changes: 1 addition & 3 deletions docs/wallet-integration-guide/examples/scripts/03-parties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ const allocatedParties = await Promise.all(
['v1-03-alice', 'v1-03-bob'].map((partyHint) => {
const partyKeys = sdk.keys.generate()
return sdk.party.external
.create(partyKeys.publicKey, {
partyHint,
})
.create(partyKeys.publicKey, { partyHint })
.sign(partyKeys.privateKey)
.execute()
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const sdk = await SDK.create({
})

const allocatedParties = await Promise.all(
['v1-12-alice', 'v1-12-bob'].map((partyHint) => {
['v1-12-alice', 'v1-12-bob'].map(async (partyHint) => {
const partyKeys = sdk.keys.generate()
return sdk.party.external
.create(partyKeys.publicKey, {
Expand Down Expand Up @@ -56,6 +56,7 @@ const charlieKeys = sdk.keys.generate()
const charlie = await sdk.party.external
.create(charlieKeys.publicKey, {
partyHint: 'v1-12-charlie',

confirmingParticipantEndpoints: participantEndpoints,
})
.sign(charlieKeys.privateKey)
Expand Down Expand Up @@ -113,6 +114,7 @@ const observingCharlieKeys = sdk.keys.generate()
const observingCharlie = await sdk.party.external
.create(observingCharlieKeys.publicKey, {
partyHint: 'v1-12-observingCharlie',

observingParticipantEndpoints: participantEndpoints,
})
.sign(observingCharlieKeys.privateKey)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Example 15: Multi-Synchronizer DvP Trade

This example implements a Delivery vs Payment (DvP) flow across two synchronizers: Amulet on the global synchronizer and a Token instrument on a private app-synchronizer, settled via the OTC Trading App using only single-party submissions.

## Running Locally

All commands are run from the **repository root** unless noted otherwise.

```bash
# Step 1: Fetch localnet bundle (first time or after a Splice version update)
yarn script:fetch:localnet

# Step 2: Start localnet in multi-sync mode
yarn start:localnet -- --multi-sync

# Step 3: Run the example
yarn workspace docs-wallet-integration-guide-examples run-15

# Step 4: Stop when done (from the repository root)
yarn stop:localnet -- --multi-sync
```
Loading
Loading