@layerzerolabs/common-canton 0.2.116 → 0.2.118
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +649 -614
- package/.turbo/turbo-test.log +18 -21
- package/dist/57KDDFEU.js +52 -0
- package/dist/57KDDFEU.js.map +1 -0
- package/dist/{SMLSAWCA.js → 7IWE7Y2N.js} +30 -3
- package/dist/7IWE7Y2N.js.map +1 -0
- package/dist/AQJMQQ4W.js +3 -0
- package/dist/AQJMQQ4W.js.map +1 -0
- package/dist/AYF4OAKH.js +86 -0
- package/dist/AYF4OAKH.js.map +1 -0
- package/dist/CAJCIAM3.js +71 -0
- package/dist/CAJCIAM3.js.map +1 -0
- package/dist/I6Z5XG3N.js +33 -0
- package/dist/I6Z5XG3N.js.map +1 -0
- package/dist/{4CJTJBWF.js → JW6XB3KU.js} +9 -6
- package/dist/JW6XB3KU.js.map +1 -0
- package/dist/{FPRRDKT5.js → M5WKPPPH.js} +4 -4
- package/dist/M5WKPPPH.js.map +1 -0
- package/dist/QRFMJ32P.js +103 -0
- package/dist/QRFMJ32P.js.map +1 -0
- package/dist/RJSKBZMO.js +41 -0
- package/dist/RJSKBZMO.js.map +1 -0
- package/dist/TM5LJKTE.js +31 -0
- package/dist/TM5LJKTE.js.map +1 -0
- package/dist/{EYLCZMM4.js → WHTAP4A3.js} +9 -3
- package/dist/WHTAP4A3.js.map +1 -0
- package/dist/XE6H2SH4.js +3 -0
- package/dist/XE6H2SH4.js.map +1 -0
- package/dist/{5PRNJEPB.js → Z35TKEFU.js} +5 -7
- package/dist/Z35TKEFU.js.map +1 -0
- package/dist/address.d.ts +16 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +5 -0
- package/dist/address.js.map +1 -0
- package/dist/auth/index.js +1 -1
- package/dist/client/amulet.d.ts +38 -0
- package/dist/client/amulet.d.ts.map +1 -0
- package/dist/client/amulet.js +5 -0
- package/dist/client/amulet.js.map +1 -0
- package/dist/client/canton-client.d.ts +5 -1
- package/dist/client/canton-client.d.ts.map +1 -1
- package/dist/client/canton-client.js +2 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +5 -4
- package/dist/client/interface.d.ts +4 -0
- package/dist/client/interface.d.ts.map +1 -1
- package/dist/deferredValue.d.ts +16 -8
- package/dist/deferredValue.d.ts.map +1 -1
- package/dist/deferredValue.js +1 -1
- package/dist/events.js +2 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -17
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +6 -5
- package/dist/refresh/address-resolver.d.ts +29 -0
- package/dist/refresh/address-resolver.d.ts.map +1 -0
- package/dist/refresh/address-resolver.js +4 -0
- package/dist/refresh/address-resolver.js.map +1 -0
- package/dist/refresh/deployment-address.d.ts +14 -0
- package/dist/refresh/deployment-address.d.ts.map +1 -0
- package/dist/refresh/deployment-address.js +5 -0
- package/dist/refresh/deployment-address.js.map +1 -0
- package/dist/refresh/index.d.ts +6 -0
- package/dist/refresh/index.d.ts.map +1 -0
- package/dist/refresh/index.js +12 -0
- package/dist/refresh/index.js.map +1 -0
- package/dist/refresh/resolve.d.ts +15 -0
- package/dist/refresh/resolve.d.ts.map +1 -0
- package/dist/refresh/resolve.js +9 -0
- package/dist/refresh/resolve.js.map +1 -0
- package/dist/refresh/reverse-substitute.d.ts +6 -0
- package/dist/refresh/reverse-substitute.d.ts.map +1 -0
- package/dist/refresh/reverse-substitute.js +5 -0
- package/dist/refresh/reverse-substitute.js.map +1 -0
- package/dist/refresh/wrapped-lz-sdk.d.ts +10 -0
- package/dist/refresh/wrapped-lz-sdk.d.ts.map +1 -0
- package/dist/refresh/wrapped-lz-sdk.js +6 -0
- package/dist/refresh/wrapped-lz-sdk.js.map +1 -0
- package/dist/transaction.d.ts +37 -2
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +1 -1
- package/package.json +10 -10
- package/src/address.ts +50 -0
- package/src/client/amulet.ts +126 -0
- package/src/client/canton-client.ts +48 -2
- package/src/client/index.ts +1 -0
- package/src/client/interface.ts +6 -0
- package/src/deferredValue.ts +16 -11
- package/src/events.ts +1 -1
- package/src/index.ts +3 -13
- package/src/provider.ts +4 -1
- package/src/refresh/address-resolver.ts +161 -0
- package/src/refresh/deployment-address.ts +45 -0
- package/src/refresh/index.ts +5 -0
- package/src/refresh/resolve.ts +101 -0
- package/src/refresh/reverse-substitute.ts +63 -0
- package/src/refresh/wrapped-lz-sdk.ts +59 -0
- package/src/transaction.ts +18 -1
- package/dist/4CJTJBWF.js.map +0 -1
- package/dist/5PRNJEPB.js.map +0 -1
- package/dist/DYPWWRBK.js +0 -3
- package/dist/DYPWWRBK.js.map +0 -1
- package/dist/EYLCZMM4.js.map +0 -1
- package/dist/FPRRDKT5.js.map +0 -1
- package/dist/SMLSAWCA.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iCAAiC,CAAC;AAIjE,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;mBA+B9B,CAAC;AAEH,eAAO,MAAM,6BAA6B;;;;;iBAKxC,CAAC;AAIH;;GAEG;AACH,eAAO,MAAM,uBAAuB;gBA5ChC,EAAA,WAAS,CACL,EAAA,SAAE,CAAC,EAAE,SAAS,EAAC,EAAG,MAAM;eAChB,EAAG,SAAS;gBAChB,EAAA,SAAS;iBACb,EAAA,SACN;uBAEM,EAAA,WAAS,CAAC,EAAE,SAAS;gBACjB,EAAE,WAAW,CAAC,EAAE,SAC3B;cAAgB,EAAG,WAAW,CAAC,EAAE,SAC3B;mBAAqB,EAChB,WAAF,CAAE,EAAC,SAAS;OAAS,EAAG,IACjC,CAAO,MAAA;UAEJ,EAAA,WAAU,CACN,EAAA,SAAG;sBAAwB,EAAG,WAClC,CAAQ,EAAA,SAAI
|
|
1
|
+
{"version":3,"file":"transaction.d.ts","sourceRoot":"","sources":["../src/transaction.ts"],"names":[],"mappings":"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"}
|
package/dist/transaction.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { buildCantonTransaction, cantonAssignedTransactionSchema, cantonCommandSchema, cantonDisclosedContractSchema, cantonMinedTransactionSchema, cantonSignedTransactionSchema, cantonTransactionSchema, cantonTransactionWithResultSchema } from './
|
|
1
|
+
export { buildCantonTransaction, cantonAssignedTransactionSchema, cantonCommandSchema, cantonDisclosedContractSchema, cantonMinedTransactionSchema, cantonSignedTransactionSchema, cantonTransactionSchema, cantonTransactionWithResultSchema } from './WHTAP4A3.js';
|
|
2
2
|
import './VUOMXK5T.js';
|
|
3
3
|
//# sourceMappingURL=transaction.js.map
|
|
4
4
|
//# sourceMappingURL=transaction.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@layerzerolabs/common-canton",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.118",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Canton VM client and utilities",
|
|
6
6
|
"license": "MIT",
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
"@protobuf-ts/grpc-transport": "^2.9.4",
|
|
31
31
|
"es-toolkit": "^1.39.10",
|
|
32
32
|
"zod": "4.3.5",
|
|
33
|
-
"@layerzerolabs/common-encoding-utils": "0.2.
|
|
34
|
-
"@layerzerolabs/common-chain-model": "0.2.
|
|
35
|
-
"@layerzerolabs/common-error-utils": "0.2.
|
|
36
|
-
"@layerzerolabs/typescript-utils": "0.2.
|
|
33
|
+
"@layerzerolabs/common-encoding-utils": "0.2.118",
|
|
34
|
+
"@layerzerolabs/common-chain-model": "0.2.118",
|
|
35
|
+
"@layerzerolabs/common-error-utils": "0.2.118",
|
|
36
|
+
"@layerzerolabs/typescript-utils": "0.2.118"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.18.6",
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"tsup": "^8.4.0",
|
|
43
43
|
"typescript": "^5.8.2",
|
|
44
44
|
"vitest": "^3.2.6",
|
|
45
|
-
"@layerzerolabs/eslint-configuration": "^0.2.
|
|
46
|
-
"@layerzerolabs/tsup-configuration": "0.2.
|
|
47
|
-
"@layerzerolabs/
|
|
48
|
-
"@layerzerolabs/
|
|
49
|
-
"@layerzerolabs/
|
|
45
|
+
"@layerzerolabs/eslint-configuration": "^0.2.118",
|
|
46
|
+
"@layerzerolabs/tsup-configuration": "0.2.118",
|
|
47
|
+
"@layerzerolabs/vitest-configuration": "0.2.118",
|
|
48
|
+
"@layerzerolabs/typescript-configuration": "0.2.118",
|
|
49
|
+
"@layerzerolabs/vm-tooling-canton": "0.2.118"
|
|
50
50
|
},
|
|
51
51
|
"publishConfig": {
|
|
52
52
|
"access": "public",
|
package/src/address.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { trim0x } from '@layerzerolabs/common-encoding-utils';
|
|
2
|
+
import type { Branded } from '@layerzerolabs/typescript-utils';
|
|
3
|
+
|
|
4
|
+
import { CANTON_DEFERRED_VALUE_PREFIX, CANTON_DEFERRED_VALUE_SUFFIX } from './deferredValue';
|
|
5
|
+
|
|
6
|
+
const CANTON_PARTY_ID_REGEX = /^(.+)::([0-9a-fA-F]+)$/;
|
|
7
|
+
const CANTON_CONTRACT_ID_HEX_LENGTH = 138; // 69 bytes
|
|
8
|
+
|
|
9
|
+
export const CANTON_PARTY_ADDRESS_PREFIX = '<<CANTON_PARTY::';
|
|
10
|
+
export const CANTON_CID_ADDRESS_PREFIX = '<<CANTON_CID::';
|
|
11
|
+
|
|
12
|
+
/** A Canton native address — a branded string with one of three sentinel-prefix forms. */
|
|
13
|
+
export type CantonAddress = Branded<string, 'CantonAddress'>;
|
|
14
|
+
|
|
15
|
+
/** Construct a party `CantonAddress` from hint and fingerprint. */
|
|
16
|
+
export const cantonPartyAddress = (hint: string, fingerprint: string): CantonAddress =>
|
|
17
|
+
`${CANTON_PARTY_ADDRESS_PREFIX}${hint}::${fingerprint}${CANTON_DEFERRED_VALUE_SUFFIX}` as CantonAddress;
|
|
18
|
+
|
|
19
|
+
/** Construct a contract-ID `CantonAddress` from the raw 69-byte hex contract id. */
|
|
20
|
+
export const cantonCidAddress = (contractId: string): CantonAddress =>
|
|
21
|
+
`${CANTON_CID_ADDRESS_PREFIX}${contractId}${CANTON_DEFERRED_VALUE_SUFFIX}` as CantonAddress;
|
|
22
|
+
|
|
23
|
+
/** Wrap a deferred-ref sentinel (`<<CANTON_DEFERRED:{json}>>`) as a `CantonAddress`. */
|
|
24
|
+
export const cantonDeferredAddress = (sentinel: string): CantonAddress => sentinel as CantonAddress;
|
|
25
|
+
|
|
26
|
+
/** Wrap a raw Canton ledger value (party id, contract id, or deferred sentinel) as `CantonAddress`. */
|
|
27
|
+
export const toCantonAddress = (raw: string): CantonAddress => {
|
|
28
|
+
if (
|
|
29
|
+
raw.startsWith(CANTON_DEFERRED_VALUE_PREFIX) &&
|
|
30
|
+
raw.endsWith(CANTON_DEFERRED_VALUE_SUFFIX)
|
|
31
|
+
) {
|
|
32
|
+
return cantonDeferredAddress(raw);
|
|
33
|
+
}
|
|
34
|
+
const partyMatch = CANTON_PARTY_ID_REGEX.exec(raw);
|
|
35
|
+
if (partyMatch) {
|
|
36
|
+
const [, hint, fingerprint] = partyMatch;
|
|
37
|
+
return cantonPartyAddress(hint!, fingerprint!);
|
|
38
|
+
}
|
|
39
|
+
const bare = trim0x(raw);
|
|
40
|
+
if (bare.length === CANTON_CONTRACT_ID_HEX_LENGTH && bare.startsWith('00')) {
|
|
41
|
+
return cantonCidAddress(bare);
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`Not a Canton-native address: ${raw}`);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Returns true if `value` is a valid `CantonAddress` sentinel string. */
|
|
47
|
+
export const isCantonAddress = (value: string): value is CantonAddress =>
|
|
48
|
+
value.startsWith(CANTON_PARTY_ADDRESS_PREFIX) ||
|
|
49
|
+
value.startsWith(CANTON_CID_ADDRESS_PREFIX) ||
|
|
50
|
+
value.startsWith(CANTON_DEFERRED_VALUE_PREFIX);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { NonRetryableError } from '@layerzerolabs/common-error-utils';
|
|
2
|
+
|
|
3
|
+
import type { TokenProvider } from '../auth/token-provider';
|
|
4
|
+
import { CantonProtocolError } from '../errors';
|
|
5
|
+
import type { DisclosedContract } from '../open-api/models';
|
|
6
|
+
|
|
7
|
+
/** Validator-wallet access a {@link CantonClient} needs to serve amulet endpoints. */
|
|
8
|
+
export type CantonClientWalletConfig = {
|
|
9
|
+
/** Validator wallet URL for scan-proxy CIP-56 endpoints. */
|
|
10
|
+
walletUrl: string;
|
|
11
|
+
/** Bearer-token source for wallet requests. */
|
|
12
|
+
tokenProvider: TokenProvider;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** The single Amulet instrument id used across Canton Network fee flows. */
|
|
16
|
+
export const AMULET_INSTRUMENT_ID = 'Amulet';
|
|
17
|
+
|
|
18
|
+
/** Instrument identifier for an amulet context probe. */
|
|
19
|
+
export type InstrumentId = { admin: string; id: string };
|
|
20
|
+
|
|
21
|
+
/** DAML ChoiceContext — a bag of named AnyValues passed to fee-bearing choices. */
|
|
22
|
+
export type ChoiceContext = { values: Record<string, unknown> };
|
|
23
|
+
|
|
24
|
+
export type AmuletContextResult = {
|
|
25
|
+
amuletContext: ChoiceContext;
|
|
26
|
+
disclosedContracts: DisclosedContract[];
|
|
27
|
+
externalPartyAmuletRulesCid: string;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// The probe (amount 0, sender = receiver = admin) never executes — it only fetches context; 1-hour window is ample.
|
|
31
|
+
const CONTEXT_PROBE_EXECUTE_BEFORE_WINDOW_MS = 60 * 60 * 1000;
|
|
32
|
+
|
|
33
|
+
type AmuletContextResponse = {
|
|
34
|
+
choiceContext: {
|
|
35
|
+
disclosedContracts: DisclosedContract[];
|
|
36
|
+
choiceContextData: ChoiceContext;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/** Amulet ChoiceContext from the validator scan-proxy — same call for all fee-bearing choices. */
|
|
41
|
+
export const fetchAmuletContext = async (
|
|
42
|
+
walletUrl: string,
|
|
43
|
+
token: string,
|
|
44
|
+
instrumentId: InstrumentId,
|
|
45
|
+
): Promise<{ amuletContext: ChoiceContext; disclosedContracts: DisclosedContract[] }> => {
|
|
46
|
+
const now = new Date();
|
|
47
|
+
const later = new Date(now.getTime() + CONTEXT_PROBE_EXECUTE_BEFORE_WINDOW_MS);
|
|
48
|
+
|
|
49
|
+
const response = await fetch(
|
|
50
|
+
`${walletUrl}/api/validator/v0/scan-proxy/registry/transfer-instruction/v1/transfer-factory`,
|
|
51
|
+
{
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: {
|
|
54
|
+
Authorization: `Bearer ${token}`,
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
choiceArguments: {
|
|
59
|
+
expectedAdmin: instrumentId.admin,
|
|
60
|
+
transfer: {
|
|
61
|
+
sender: instrumentId.admin,
|
|
62
|
+
receiver: instrumentId.admin,
|
|
63
|
+
amount: '0',
|
|
64
|
+
instrumentId,
|
|
65
|
+
requestedAt: now.toISOString(),
|
|
66
|
+
executeBefore: later.toISOString(),
|
|
67
|
+
inputHoldingCids: [],
|
|
68
|
+
meta: { values: {} },
|
|
69
|
+
},
|
|
70
|
+
extraArgs: { context: { values: {} }, meta: { values: {} } },
|
|
71
|
+
},
|
|
72
|
+
}),
|
|
73
|
+
},
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const text = await response.text();
|
|
78
|
+
throw new CantonProtocolError(`Failed to get amulet context: ${response.status} ${text}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const data = (await response.json()) as AmuletContextResponse;
|
|
82
|
+
const { disclosedContracts, choiceContextData: amuletContext } = data.choiceContext;
|
|
83
|
+
|
|
84
|
+
return { amuletContext, disclosedContracts };
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/** Injects ExternalPartyAmuletRules cid into the ChoiceContext — required by every flow hitting FeeUtils.validateContext. */
|
|
88
|
+
export const injectExternalPartyAmuletRules = (
|
|
89
|
+
amuletContext: ChoiceContext,
|
|
90
|
+
disclosedContracts: DisclosedContract[],
|
|
91
|
+
): { amuletContext: ChoiceContext; externalPartyAmuletRulesCid: string } => {
|
|
92
|
+
const externalRulesContract = disclosedContracts.find((contract) =>
|
|
93
|
+
contract.templateId?.includes('ExternalPartyAmuletRules'),
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (!externalRulesContract) {
|
|
97
|
+
throw new NonRetryableError(
|
|
98
|
+
'ExternalPartyAmuletRules not found in disclosed contracts from amulet context',
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
amuletContext: {
|
|
104
|
+
values: {
|
|
105
|
+
...amuletContext.values,
|
|
106
|
+
'external-party-amulet-rules': {
|
|
107
|
+
tag: 'AV_ContractId',
|
|
108
|
+
value: externalRulesContract.contractId,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
externalPartyAmuletRulesCid: externalRulesContract.contractId,
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/** Fetch the DSO party id from the validator scan-proxy API. */
|
|
117
|
+
export const fetchDsoParty = async (walletUrl: string, token: string): Promise<string> => {
|
|
118
|
+
const response = await fetch(`${walletUrl}/api/validator/v0/scan-proxy/dso-party-id`, {
|
|
119
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
120
|
+
});
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
throw new CantonProtocolError(`Failed to get DSO party: ${response.status}`);
|
|
123
|
+
}
|
|
124
|
+
const data = (await response.json()) as { dso_party_id: string };
|
|
125
|
+
return data.dso_party_id;
|
|
126
|
+
};
|
|
@@ -33,6 +33,14 @@ import type {
|
|
|
33
33
|
WaitOptions,
|
|
34
34
|
WaitResult,
|
|
35
35
|
} from '../types';
|
|
36
|
+
import {
|
|
37
|
+
type AmuletContextResult,
|
|
38
|
+
type CantonClientWalletConfig,
|
|
39
|
+
fetchAmuletContext,
|
|
40
|
+
fetchDsoParty,
|
|
41
|
+
injectExternalPartyAmuletRules,
|
|
42
|
+
type InstrumentId,
|
|
43
|
+
} from './amulet';
|
|
36
44
|
import type {
|
|
37
45
|
CantonTransaction,
|
|
38
46
|
CantonUpdate,
|
|
@@ -55,10 +63,48 @@ import type {
|
|
|
55
63
|
export class CantonClient implements ICantonClient {
|
|
56
64
|
readonly #api: DefaultApi;
|
|
57
65
|
readonly #userId: string;
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
readonly #wallet?: CantonClientWalletConfig;
|
|
67
|
+
#dsoParty?: string;
|
|
68
|
+
|
|
69
|
+
constructor(
|
|
70
|
+
config: Configuration,
|
|
71
|
+
userId: string = 'participant_admin',
|
|
72
|
+
wallet?: CantonClientWalletConfig,
|
|
73
|
+
) {
|
|
60
74
|
this.#api = new DefaultApi(config);
|
|
61
75
|
this.#userId = userId;
|
|
76
|
+
this.#wallet = wallet;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#requireWallet(): CantonClientWalletConfig {
|
|
80
|
+
if (!this.#wallet) {
|
|
81
|
+
throw new CantonProtocolError(
|
|
82
|
+
'CantonClient has no wallet config (walletUrl + tokenProvider); amulet endpoints are unavailable',
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return this.#wallet;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async getDsoParty(): Promise<string> {
|
|
89
|
+
const { walletUrl, tokenProvider } = this.#requireWallet();
|
|
90
|
+
if (this.#dsoParty) return this.#dsoParty;
|
|
91
|
+
this.#dsoParty = await fetchDsoParty(walletUrl, await tokenProvider.getToken());
|
|
92
|
+
return this.#dsoParty;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Amulet ChoiceContext with ExternalPartyAmuletRules injected — required by every flow hitting FeeUtils.validateContext. */
|
|
96
|
+
async getAmuletContextWithExternalRules(
|
|
97
|
+
instrumentId: InstrumentId,
|
|
98
|
+
): Promise<AmuletContextResult> {
|
|
99
|
+
const { walletUrl, tokenProvider } = this.#requireWallet();
|
|
100
|
+
const { amuletContext, disclosedContracts } = await fetchAmuletContext(
|
|
101
|
+
walletUrl,
|
|
102
|
+
await tokenProvider.getToken(),
|
|
103
|
+
instrumentId,
|
|
104
|
+
);
|
|
105
|
+
const { amuletContext: contextWithRules, externalPartyAmuletRulesCid } =
|
|
106
|
+
injectExternalPartyAmuletRules(amuletContext, disclosedContracts);
|
|
107
|
+
return { amuletContext: contextWithRules, disclosedContracts, externalPartyAmuletRulesCid };
|
|
62
108
|
}
|
|
63
109
|
|
|
64
110
|
/**
|
package/src/client/index.ts
CHANGED
package/src/client/interface.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
WaitOptions,
|
|
22
22
|
WaitResult,
|
|
23
23
|
} from '../types';
|
|
24
|
+
import type { AmuletContextResult, InstrumentId } from './amulet';
|
|
24
25
|
|
|
25
26
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
26
27
|
// Corrected Update Types
|
|
@@ -71,6 +72,11 @@ export interface ICantonClient {
|
|
|
71
72
|
*/
|
|
72
73
|
connectedSynchronizers(): Promise<string[]>;
|
|
73
74
|
|
|
75
|
+
getDsoParty(): Promise<string>;
|
|
76
|
+
|
|
77
|
+
/** Amulet ChoiceContext with ExternalPartyAmuletRules injected — requires wallet config. */
|
|
78
|
+
getAmuletContextWithExternalRules(instrumentId: InstrumentId): Promise<AmuletContextResult>;
|
|
79
|
+
|
|
74
80
|
/**
|
|
75
81
|
* Submit a batch of commands atomically.
|
|
76
82
|
*/
|
package/src/deferredValue.ts
CHANGED
|
@@ -12,14 +12,12 @@ export const cantonDeferredValueSchema = z.discriminatedUnion('type', [
|
|
|
12
12
|
partyToFilter: cantonRefreshPartyFilterSchema,
|
|
13
13
|
templateId: z.string(),
|
|
14
14
|
contractStateFilter: z.partialRecord(z.string(), z.any()).optional(),
|
|
15
|
-
disclose: z.boolean().optional(),
|
|
16
15
|
}),
|
|
17
16
|
z.object({
|
|
18
17
|
type: z.literal('activeContractIds'),
|
|
19
18
|
partyToFilter: cantonRefreshPartyFilterSchema,
|
|
20
19
|
templateId: z.string(),
|
|
21
20
|
contractStateFilter: z.partialRecord(z.string(), z.any()).optional(),
|
|
22
|
-
disclose: z.boolean().optional(),
|
|
23
21
|
}),
|
|
24
22
|
z.object({ type: z.literal('amuletRulesCid') }),
|
|
25
23
|
z.object({ type: z.literal('amuletContext') }),
|
|
@@ -36,11 +34,18 @@ type CantonActiveContractIdsRef = Omit<
|
|
|
36
34
|
'type'
|
|
37
35
|
>;
|
|
38
36
|
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Branded encoded form of a {@link CantonDeferredValue}: a sign-time reference
|
|
39
|
+
* string that a resolver turns into a live contract id / party. Branding keeps it
|
|
40
|
+
* distinct from a plain `string` so the ref-substitution stays type-checked.
|
|
41
|
+
*/
|
|
42
|
+
export type CantonDeferredRef = string & { readonly __cantonDeferredRef: unique symbol };
|
|
41
43
|
|
|
42
|
-
export const
|
|
43
|
-
|
|
44
|
+
export const CANTON_DEFERRED_VALUE_PREFIX = '<<CANTON_DEFERRED:';
|
|
45
|
+
export const CANTON_DEFERRED_VALUE_SUFFIX = '>>';
|
|
46
|
+
|
|
47
|
+
export const encodeCantonDeferredValue = (value: CantonDeferredValue): CantonDeferredRef =>
|
|
48
|
+
`${CANTON_DEFERRED_VALUE_PREFIX}${JSON.stringify(value)}${CANTON_DEFERRED_VALUE_SUFFIX}` as CantonDeferredRef;
|
|
44
49
|
|
|
45
50
|
export const parseCantonDeferredValue = (value: unknown): CantonDeferredValue | undefined => {
|
|
46
51
|
if (
|
|
@@ -61,19 +66,19 @@ export const parseCantonDeferredValue = (value: unknown): CantonDeferredValue |
|
|
|
61
66
|
}
|
|
62
67
|
};
|
|
63
68
|
|
|
64
|
-
export const cantonSenderPartyRef = ():
|
|
69
|
+
export const cantonSenderPartyRef = (): CantonDeferredRef =>
|
|
65
70
|
encodeCantonDeferredValue({ type: 'senderParty' });
|
|
66
71
|
|
|
67
|
-
export const cantonActiveContractIdRef = (value: CantonActiveContractIdRef):
|
|
72
|
+
export const cantonActiveContractIdRef = (value: CantonActiveContractIdRef): CantonDeferredRef =>
|
|
68
73
|
encodeCantonDeferredValue({ type: 'activeContractId', ...value });
|
|
69
74
|
|
|
70
|
-
export const cantonActiveContractIdsRef = (value: CantonActiveContractIdsRef):
|
|
75
|
+
export const cantonActiveContractIdsRef = (value: CantonActiveContractIdsRef): CantonDeferredRef =>
|
|
71
76
|
encodeCantonDeferredValue({ type: 'activeContractIds', ...value });
|
|
72
77
|
|
|
73
|
-
export const cantonAmuletRulesCidRef = ():
|
|
78
|
+
export const cantonAmuletRulesCidRef = (): CantonDeferredRef =>
|
|
74
79
|
encodeCantonDeferredValue({ type: 'amuletRulesCid' });
|
|
75
80
|
|
|
76
|
-
export const cantonAmuletContextRef = ():
|
|
81
|
+
export const cantonAmuletContextRef = (): CantonDeferredRef =>
|
|
77
82
|
encodeCantonDeferredValue({ type: 'amuletContext' });
|
|
78
83
|
|
|
79
84
|
/** Returns true if `value` is a Canton deferred-value sentinel. */
|
package/src/events.ts
CHANGED
|
@@ -58,7 +58,7 @@ export const findMatchingCreatedEventFromTx = async (
|
|
|
58
58
|
|
|
59
59
|
if (!updateId || !actAs.length) return null;
|
|
60
60
|
|
|
61
|
-
const createCommand = unresolvedCommands
|
|
61
|
+
const createCommand = unresolvedCommands.find((cmd) => 'createCommand' in cmd);
|
|
62
62
|
if (!createCommand || !('createCommand' in createCommand)) return null;
|
|
63
63
|
|
|
64
64
|
const createdEvents = await fetchCreatedEvents(client, updateId, actAs);
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export * from './address';
|
|
1
2
|
export * from './amulet';
|
|
2
3
|
export * from './auth';
|
|
3
4
|
export * from './crypto';
|
|
@@ -5,6 +6,7 @@ export * from './daml-serde';
|
|
|
5
6
|
export * from './deferredValue';
|
|
6
7
|
export * from './events';
|
|
7
8
|
export * from './provider';
|
|
9
|
+
export * from './refresh';
|
|
8
10
|
export * from './transaction';
|
|
9
11
|
|
|
10
12
|
// Core types
|
|
@@ -31,19 +33,7 @@ export {
|
|
|
31
33
|
export { CantonError, CantonProtocolError, CantonTransportError } from './errors';
|
|
32
34
|
|
|
33
35
|
// Canton Client (low-level transport)
|
|
34
|
-
export
|
|
35
|
-
CantonClient,
|
|
36
|
-
type CantonEvent,
|
|
37
|
-
CantonGrpcClient,
|
|
38
|
-
type CantonGrpcClientOptions,
|
|
39
|
-
type CantonTransaction,
|
|
40
|
-
type CantonUpdate,
|
|
41
|
-
type CantonUpdateResponse,
|
|
42
|
-
computeCantonFingerprint,
|
|
43
|
-
extractTransactions,
|
|
44
|
-
type ICantonClient,
|
|
45
|
-
type TlsOptions,
|
|
46
|
-
} from './client';
|
|
36
|
+
export * from './client';
|
|
47
37
|
|
|
48
38
|
// Canton SDK (high-level API)
|
|
49
39
|
export { CantonSdk, type NormalizedActiveContract } from './sdk/index';
|
package/src/provider.ts
CHANGED
|
@@ -146,7 +146,10 @@ export function createCantonChainProvider({
|
|
|
146
146
|
basePath: params.jsonApiUrl,
|
|
147
147
|
accessToken: () => tokenProvider.getToken(),
|
|
148
148
|
});
|
|
149
|
-
const client = new CantonClient(configuration, params.userId
|
|
149
|
+
const client = new CantonClient(configuration, params.userId, {
|
|
150
|
+
walletUrl: params.walletUrl,
|
|
151
|
+
tokenProvider,
|
|
152
|
+
});
|
|
150
153
|
|
|
151
154
|
// ── gRPC Admin API Client ───────────────────────────────────────────
|
|
152
155
|
const grpcClient = new CantonGrpcClient({
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { ContractId } from '@daml/types';
|
|
2
|
+
|
|
3
|
+
import { NonRetryableError } from '@layerzerolabs/common-error-utils';
|
|
4
|
+
|
|
5
|
+
import type { ICantonClient } from '../client/interface';
|
|
6
|
+
import type { CantonDeferredValue } from '../deferredValue';
|
|
7
|
+
import type { JsActiveContract } from '../open-api/models';
|
|
8
|
+
import type { CantonDisclosedContracts } from '../transaction';
|
|
9
|
+
|
|
10
|
+
export type ActiveContractRef = Extract<
|
|
11
|
+
CantonDeferredValue,
|
|
12
|
+
{ type: 'activeContractId' | 'activeContractIds' }
|
|
13
|
+
>;
|
|
14
|
+
|
|
15
|
+
export interface ContractAddressResolverDeps {
|
|
16
|
+
client: ICantonClient;
|
|
17
|
+
senderParty?: string;
|
|
18
|
+
/** A contract is only disclosed when none of these parties is already a stakeholder on it. */
|
|
19
|
+
signingParties: string[];
|
|
20
|
+
appendDisclosures: (disclosures: CantonDisclosedContracts) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const matchesContractStateFilter = (
|
|
24
|
+
contract: JsActiveContract,
|
|
25
|
+
contractStateFilter: ActiveContractRef['contractStateFilter'],
|
|
26
|
+
): boolean => {
|
|
27
|
+
if (!contractStateFilter) return true;
|
|
28
|
+
const createArgument = contract.createdEvent.createArgument as Record<string, unknown>;
|
|
29
|
+
return Object.entries(contractStateFilter).every(
|
|
30
|
+
([key, value]) => JSON.stringify(createArgument[key]) === JSON.stringify(value),
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const isRefAlive = async (
|
|
35
|
+
client: ICantonClient,
|
|
36
|
+
ref: CantonDeferredValue,
|
|
37
|
+
): Promise<boolean> => {
|
|
38
|
+
if (ref.type !== 'activeContractId' && ref.type !== 'activeContractIds') return false;
|
|
39
|
+
if (ref.partyToFilter === 'sender') return false;
|
|
40
|
+
const contracts = await queryLiveContractsForRef(client, ref.partyToFilter.party, ref);
|
|
41
|
+
return ref.type === 'activeContractId' ? contracts.length === 1 : contracts.length > 0;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** partyToFilter: 'sender' is resolved per-caller — this function takes an already-resolved party string. */
|
|
45
|
+
export const queryLiveContractsForRef = async (
|
|
46
|
+
client: ICantonClient,
|
|
47
|
+
party: string,
|
|
48
|
+
ref: ActiveContractRef,
|
|
49
|
+
): Promise<JsActiveContract[]> => {
|
|
50
|
+
const offset = await client.getLedgerEndOffset();
|
|
51
|
+
return (
|
|
52
|
+
await client.queryActiveContracts(party, offset, { templateIds: [ref.templateId] })
|
|
53
|
+
).filter((contract) => matchesContractStateFilter(contract, ref.contractStateFilter));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Returns the contractIds of all live contracts matching ref.
|
|
58
|
+
* Resolves 'sender' to senderParty when provided; returns [] if unresolvable.
|
|
59
|
+
*/
|
|
60
|
+
export const queryLiveContractIdsForRef = async (
|
|
61
|
+
client: ICantonClient,
|
|
62
|
+
ref: CantonDeferredValue,
|
|
63
|
+
senderParty?: string,
|
|
64
|
+
): Promise<string[]> => {
|
|
65
|
+
if (ref.type !== 'activeContractId' && ref.type !== 'activeContractIds') return [];
|
|
66
|
+
const party = ref.partyToFilter === 'sender' ? senderParty : ref.partyToFilter.party;
|
|
67
|
+
if (!party) return [];
|
|
68
|
+
const contracts = await queryLiveContractsForRef(client, party, ref);
|
|
69
|
+
return contracts.map((c) => c.createdEvent.contractId);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const createContractAddressResolver = (deps: ContractAddressResolverDeps) => {
|
|
73
|
+
const { client, senderParty, signingParties, appendDisclosures } = deps;
|
|
74
|
+
|
|
75
|
+
const requireSender = (templateId: string): string => {
|
|
76
|
+
if (!senderParty) {
|
|
77
|
+
throw new NonRetryableError(
|
|
78
|
+
`Refresh entry for ${templateId} needs a resolved sender party, but none was provided`,
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
return senderParty;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const resolveQueryParty = ({ partyToFilter, templateId }: ActiveContractRef): string =>
|
|
85
|
+
partyToFilter === 'sender' ? requireSender(templateId) : partyToFilter.party;
|
|
86
|
+
|
|
87
|
+
const queryCache = new Map<string, Promise<JsActiveContract[]>>();
|
|
88
|
+
const queryAsParty = async (ref: ActiveContractRef): Promise<JsActiveContract[]> => {
|
|
89
|
+
const party = resolveQueryParty(ref);
|
|
90
|
+
const cacheKey = JSON.stringify({ ...ref, party });
|
|
91
|
+
const cached = queryCache.get(cacheKey);
|
|
92
|
+
if (cached) return cached;
|
|
93
|
+
|
|
94
|
+
const query = (async () => {
|
|
95
|
+
const contracts = await queryLiveContractsForRef(client, party, ref);
|
|
96
|
+
if (contracts.length === 0) {
|
|
97
|
+
throw new NonRetryableError(
|
|
98
|
+
`Party ${party} has no matching active contracts of ${ref.templateId}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return contracts;
|
|
102
|
+
})();
|
|
103
|
+
queryCache.set(cacheKey, query);
|
|
104
|
+
return query;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// A signing party that is already a stakeholder can see the contract on its
|
|
108
|
+
// own stream, so only disclose to parties that aren't signatories/observers.
|
|
109
|
+
const needsDisclosure = (contract: JsActiveContract): boolean => {
|
|
110
|
+
const stakeholders = new Set([
|
|
111
|
+
...(contract.createdEvent.signatories ?? []),
|
|
112
|
+
...(contract.createdEvent.observers ?? []),
|
|
113
|
+
]);
|
|
114
|
+
return !signingParties.some((party) => stakeholders.has(party));
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const discloseContracts = async (
|
|
118
|
+
ref: ActiveContractRef,
|
|
119
|
+
contracts: JsActiveContract[],
|
|
120
|
+
): Promise<void> => {
|
|
121
|
+
const toDisclose = contracts.filter(needsDisclosure);
|
|
122
|
+
if (toDisclose.length === 0) return;
|
|
123
|
+
|
|
124
|
+
const party = resolveQueryParty(ref);
|
|
125
|
+
const disclosures = await Promise.all(
|
|
126
|
+
toDisclose.map(async (contract) => {
|
|
127
|
+
const contractId = contract.createdEvent.contractId;
|
|
128
|
+
const disclosure = await client.createDisclosure(
|
|
129
|
+
contractId as ContractId<unknown>,
|
|
130
|
+
[party],
|
|
131
|
+
);
|
|
132
|
+
if (!disclosure) {
|
|
133
|
+
throw new NonRetryableError(
|
|
134
|
+
`Could not disclose ${contractId} (template ${ref.templateId}) — no longer active`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return disclosure;
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
140
|
+
appendDisclosures(disclosures);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const resolveActiveContractId = async (ref: ActiveContractRef): Promise<string> => {
|
|
144
|
+
const contracts = await queryAsParty(ref);
|
|
145
|
+
if (contracts.length !== 1) {
|
|
146
|
+
throw new NonRetryableError(
|
|
147
|
+
`Refresh for ${ref.templateId} matched ${contracts.length} contracts, but a scalar ref expects exactly one`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
await discloseContracts(ref, contracts);
|
|
151
|
+
return contracts[0]!.createdEvent.contractId;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const resolveActiveContractIds = async (ref: ActiveContractRef): Promise<string[]> => {
|
|
155
|
+
const contracts = await queryAsParty(ref);
|
|
156
|
+
await discloseContracts(ref, contracts);
|
|
157
|
+
return contracts.map((contract) => contract.createdEvent.contractId);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
return { resolveActiveContractId, resolveActiveContractIds, requireSenderParty: requireSender };
|
|
161
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NonRetryableError } from '@layerzerolabs/common-error-utils';
|
|
2
|
+
|
|
3
|
+
import { cantonActiveContractIdRef, type CantonDeferredRef } from '../deferredValue';
|
|
4
|
+
|
|
5
|
+
export interface CantonDeployedTemplateSpec {
|
|
6
|
+
packageName: string;
|
|
7
|
+
ownerField: string;
|
|
8
|
+
// Immutable disambiguating fields only — mutable fields break recreate-survival.
|
|
9
|
+
identityKeys: string[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Canton returns `<packageId>:Module:Entity`; the package id has no colon.
|
|
13
|
+
export const qualifiedTemplateName = (templateId: string): string => {
|
|
14
|
+
const firstColon = templateId.indexOf(':');
|
|
15
|
+
return firstColon === -1 ? templateId : templateId.slice(firstColon + 1);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
interface DeployedCreatedEvent {
|
|
19
|
+
templateId: string;
|
|
20
|
+
createArgument?: unknown;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const buildCantonDeployedContractAddressRef = (
|
|
24
|
+
createdEvent: DeployedCreatedEvent,
|
|
25
|
+
spec: CantonDeployedTemplateSpec,
|
|
26
|
+
): CantonDeferredRef => {
|
|
27
|
+
const qualifiedName = qualifiedTemplateName(createdEvent.templateId);
|
|
28
|
+
const createArgument = (createdEvent.createArgument ?? {}) as Record<string, unknown>;
|
|
29
|
+
const ownerParty = createArgument[spec.ownerField];
|
|
30
|
+
if (typeof ownerParty !== 'string') {
|
|
31
|
+
throw new NonRetryableError(
|
|
32
|
+
`Template '${qualifiedName}' create argument is missing string owner field '${spec.ownerField}'`,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const contractStateFilter = spec.identityKeys.length
|
|
37
|
+
? Object.fromEntries(spec.identityKeys.map((key) => [key, createArgument[key]]))
|
|
38
|
+
: undefined;
|
|
39
|
+
|
|
40
|
+
return cantonActiveContractIdRef({
|
|
41
|
+
partyToFilter: { party: ownerParty },
|
|
42
|
+
templateId: `#${spec.packageName}:${qualifiedName}`,
|
|
43
|
+
...(contractStateFilter ? { contractStateFilter } : {}),
|
|
44
|
+
});
|
|
45
|
+
};
|