@layerzerolabs/common-canton 1.2.38 → 1.2.39
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/dist/{FFZGONTK.js → 2QBPIIAB.js} +4 -4
- package/dist/{FFZGONTK.js.map → 2QBPIIAB.js.map} +1 -1
- package/dist/{BFNNJFWC.js → 2UKHG3HM.js} +3 -3
- package/dist/{BFNNJFWC.js.map → 2UKHG3HM.js.map} +1 -1
- package/dist/{IOW2523G.js → 45YQNHS2.js} +3 -3
- package/dist/{IOW2523G.js.map → 45YQNHS2.js.map} +1 -1
- package/dist/{JJKMXRP7.js → 4AR5H4U6.js} +2 -2
- package/dist/4AR5H4U6.js.map +1 -0
- package/dist/{UV3W5HVQ.js → 4GYCD3G4.js} +3 -3
- package/dist/{UV3W5HVQ.js.map → 4GYCD3G4.js.map} +1 -1
- package/dist/{MNIG6M5P.js → 5HH7PZW3.js} +19 -19
- package/dist/{MNIG6M5P.js.map → 5HH7PZW3.js.map} +1 -1
- package/dist/{MCD2CFLV.js → 5OA7RJHH.js} +3 -3
- package/dist/{MCD2CFLV.js.map → 5OA7RJHH.js.map} +1 -1
- package/dist/{DJ67CXMJ.js → 73I7CBP3.js} +3 -3
- package/dist/{DJ67CXMJ.js.map → 73I7CBP3.js.map} +1 -1
- package/dist/{VAFLUPQJ.js → 75B7Y26B.js} +35 -8
- package/dist/75B7Y26B.js.map +1 -0
- package/dist/{25GBI5E7.js → 7GI6QGS6.js} +3 -3
- package/dist/{25GBI5E7.js.map → 7GI6QGS6.js.map} +1 -1
- package/dist/{T4AL4FJH.js → ALC4M7WK.js} +3 -3
- package/dist/{T4AL4FJH.js.map → ALC4M7WK.js.map} +1 -1
- package/dist/{3RGRZQQT.js → BL22CZFA.js} +3 -3
- package/dist/{3RGRZQQT.js.map → BL22CZFA.js.map} +1 -1
- package/dist/{5AD6IR4R.js → DJTKDAIT.js} +3 -3
- package/dist/{5AD6IR4R.js.map → DJTKDAIT.js.map} +1 -1
- package/dist/{TM5LJKTE.js → FADZDRWB.js} +5 -5
- package/dist/FADZDRWB.js.map +1 -0
- package/dist/{RJSKBZMO.js → HF5HP2EX.js} +24 -16
- package/dist/HF5HP2EX.js.map +1 -0
- package/dist/{WUXBJ4EJ.js → HLWLB4AY.js} +3 -3
- package/dist/{WUXBJ4EJ.js.map → HLWLB4AY.js.map} +1 -1
- package/dist/{RJHKVYCL.js → HRZTWWO4.js} +3 -3
- package/dist/{RJHKVYCL.js.map → HRZTWWO4.js.map} +1 -1
- package/dist/{EPHLTMJZ.js → JSG7MGHH.js} +3 -3
- package/dist/{EPHLTMJZ.js.map → JSG7MGHH.js.map} +1 -1
- package/dist/{JH2I4JGJ.js → JXKVPYCE.js} +3 -3
- package/dist/{JH2I4JGJ.js.map → JXKVPYCE.js.map} +1 -1
- package/dist/{Q4H5IB4B.js → K2SFNQJC.js} +3 -3
- package/dist/{Q4H5IB4B.js.map → K2SFNQJC.js.map} +1 -1
- package/dist/{OQVZLKQ5.js → KBZ6YXX4.js} +3 -3
- package/dist/{OQVZLKQ5.js.map → KBZ6YXX4.js.map} +1 -1
- package/dist/{W5H6OVFB.js → KH7RP6Q4.js} +3 -3
- package/dist/{W5H6OVFB.js.map → KH7RP6Q4.js.map} +1 -1
- package/dist/{WRKQRG6I.js → KTNEUXUI.js} +4 -4
- package/dist/{WRKQRG6I.js.map → KTNEUXUI.js.map} +1 -1
- package/dist/{D3AV6J7R.js → LL7Z56UN.js} +3 -3
- package/dist/{D3AV6J7R.js.map → LL7Z56UN.js.map} +1 -1
- package/dist/{BAIUJA2D.js → MFVQ6EHM.js} +3 -3
- package/dist/{BAIUJA2D.js.map → MFVQ6EHM.js.map} +1 -1
- package/dist/{4FW6H35F.js → NKXEPWZV.js} +3 -3
- package/dist/{4FW6H35F.js.map → NKXEPWZV.js.map} +1 -1
- package/dist/{VJP6DRFT.js → NS763D57.js} +3 -3
- package/dist/{VJP6DRFT.js.map → NS763D57.js.map} +1 -1
- package/dist/{2UWZKS5E.js → Q3RL6UXD.js} +4 -4
- package/dist/{2UWZKS5E.js.map → Q3RL6UXD.js.map} +1 -1
- package/dist/QSQZYJB6.js +93 -0
- package/dist/QSQZYJB6.js.map +1 -0
- package/dist/{5KFMHJZB.js → QYQWHMQM.js} +3 -3
- package/dist/{5KFMHJZB.js.map → QYQWHMQM.js.map} +1 -1
- package/dist/SWY4QT4I.js +31 -0
- package/dist/SWY4QT4I.js.map +1 -0
- package/dist/{4SOUDUP7.js → T4OC6JEE.js} +3 -3
- package/dist/T4OC6JEE.js.map +1 -0
- package/dist/{7ZK6FSHF.js → UBHUOSK3.js} +3 -3
- package/dist/{7ZK6FSHF.js.map → UBHUOSK3.js.map} +1 -1
- package/dist/{262S47C4.js → UL73PQD3.js} +4 -4
- package/dist/{262S47C4.js.map → UL73PQD3.js.map} +1 -1
- package/dist/{57KDDFEU.js → WLHCBBAJ.js} +5 -5
- package/dist/WLHCBBAJ.js.map +1 -0
- package/dist/{HU7X7G5L.js → WSLU3JGO.js} +3 -3
- package/dist/{HU7X7G5L.js.map → WSLU3JGO.js.map} +1 -1
- package/dist/{NVDYBSHG.js → ZPOY5WJ2.js} +3 -3
- package/dist/{NVDYBSHG.js.map → ZPOY5WJ2.js.map} +1 -1
- package/dist/{I6Z5XG3N.js → ZYPVFCJP.js} +7 -3
- package/dist/ZYPVFCJP.js.map +1 -0
- package/dist/address.d.ts.map +1 -1
- package/dist/address.js +2 -2
- package/dist/auth/index.js +1 -1
- package/dist/client/canton-client.js +79 -79
- package/dist/client/index.js +79 -79
- package/dist/deferredValue.d.ts +21 -3
- package/dist/deferredValue.d.ts.map +1 -1
- package/dist/deferredValue.js +1 -1
- package/dist/events.d.ts +2 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +91 -90
- package/dist/open-api/apis/DefaultApi.js +78 -78
- package/dist/open-api/apis/index.js +78 -78
- package/dist/open-api/index.js +78 -78
- package/dist/open-api/models/Command.js +3 -3
- package/dist/open-api/models/CreateUserRequest.js +6 -6
- package/dist/open-api/models/DeduplicationPeriod.js +1 -1
- package/dist/open-api/models/GrantUserRightsRequest.js +6 -6
- package/dist/open-api/models/GrantUserRightsResponse.js +6 -6
- package/dist/open-api/models/JsCommands.js +5 -5
- package/dist/open-api/models/JsGetUpdateResponse.js +10 -10
- package/dist/open-api/models/JsGetUpdateTreesResponse.js +3 -3
- package/dist/open-api/models/JsGetUpdatesResponse.js +10 -10
- package/dist/open-api/models/JsPrepareSubmissionRequest.js +5 -5
- package/dist/open-api/models/JsSubmitAndWaitForTransactionRequest.js +6 -6
- package/dist/open-api/models/JsTopologyTransaction.js +4 -4
- package/dist/open-api/models/Kind.js +4 -4
- package/dist/open-api/models/ListUserRightsResponse.js +6 -6
- package/dist/open-api/models/Operation.js +4 -4
- package/dist/open-api/models/RevokeUserRightsRequest.js +6 -6
- package/dist/open-api/models/RevokeUserRightsResponse.js +6 -6
- package/dist/open-api/models/Right.js +5 -5
- package/dist/open-api/models/TopologyEvent.js +3 -3
- package/dist/open-api/models/TopologyEventEvent.js +2 -2
- package/dist/open-api/models/TopologyTransaction.js +5 -5
- package/dist/open-api/models/Update.js +9 -9
- package/dist/open-api/models/Update1.js +2 -2
- package/dist/open-api/models/UpdateOneOf2.js +6 -6
- package/dist/open-api/models/UpdateVettedPackagesRequest.js +6 -6
- package/dist/open-api/models/VettedPackagesChange.js +5 -5
- package/dist/open-api/models/index.js +77 -77
- package/dist/provider.js +81 -81
- package/dist/refresh/address-resolver.d.ts +24 -4
- package/dist/refresh/address-resolver.d.ts.map +1 -1
- package/dist/refresh/address-resolver.js +3 -1
- package/dist/refresh/deployment-address.d.ts.map +1 -1
- package/dist/refresh/deployment-address.js +2 -2
- package/dist/refresh/index.js +7 -6
- package/dist/refresh/resolve.js +5 -4
- package/dist/refresh/reverse-substitute.d.ts.map +1 -1
- package/dist/refresh/reverse-substitute.js +2 -2
- package/dist/refresh/wrapped-lz-sdk.d.ts.map +1 -1
- package/dist/refresh/wrapped-lz-sdk.js +3 -3
- package/dist/transaction.d.ts +10 -1
- package/dist/transaction.d.ts.map +1 -1
- package/dist/transaction.js +1 -1
- package/dist/trusted-origin.d.ts +36 -0
- package/dist/trusted-origin.d.ts.map +1 -0
- package/dist/trusted-origin.js +4 -0
- package/dist/trusted-origin.js.map +1 -0
- package/package.json +10 -10
- package/src/address.ts +3 -6
- package/src/deferredValue.ts +57 -13
- package/src/events.ts +2 -1
- package/src/index.ts +1 -0
- package/src/refresh/address-resolver.ts +83 -13
- package/src/refresh/deployment-address.ts +2 -0
- package/src/refresh/reverse-substitute.ts +3 -3
- package/src/refresh/wrapped-lz-sdk.ts +20 -10
- package/src/transaction.ts +10 -1
- package/src/trusted-origin.ts +59 -0
- package/test/deferredValue.test.ts +40 -0
- package/test/refresh/deployment-artifacts.test.ts +53 -0
- package/test/refresh/observer-config-signing-dos.test.ts +336 -0
- package/test/refresh/reverse-substitute.test.ts +20 -0
- package/test/refresh/wrapped-lz-sdk.test.ts +50 -0
- package/dist/4SOUDUP7.js.map +0 -1
- package/dist/57KDDFEU.js.map +0 -1
- package/dist/I6Z5XG3N.js.map +0 -1
- package/dist/JJKMXRP7.js.map +0 -1
- package/dist/RJSKBZMO.js.map +0 -1
- package/dist/TM5LJKTE.js.map +0 -1
- package/dist/VAFLUPQJ.js.map +0 -1
- package/dist/Z35TKEFU.js +0 -68
- package/dist/Z35TKEFU.js.map +0 -1
package/src/address.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { trim0x } from '@layerzerolabs/common-encoding-utils';
|
|
2
2
|
import type { Branded } from '@layerzerolabs/typescript-utils';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { CANTON_DEFERRED_VALUE_SUFFIX, isCantonDeferredSentinel } from './deferredValue';
|
|
5
5
|
|
|
6
6
|
const CANTON_PARTY_ID_REGEX = /^(.+)::([0-9a-fA-F]+)$/;
|
|
7
7
|
const CANTON_CONTRACT_ID_HEX_LENGTH = 138; // 69 bytes
|
|
@@ -25,10 +25,7 @@ export const cantonDeferredAddress = (sentinel: string): CantonAddress => sentin
|
|
|
25
25
|
|
|
26
26
|
/** Wrap a raw Canton ledger value (party id, contract id, or deferred sentinel) as `CantonAddress`. */
|
|
27
27
|
export const toCantonAddress = (raw: string): CantonAddress => {
|
|
28
|
-
if (
|
|
29
|
-
raw.startsWith(CANTON_DEFERRED_VALUE_PREFIX) &&
|
|
30
|
-
raw.endsWith(CANTON_DEFERRED_VALUE_SUFFIX)
|
|
31
|
-
) {
|
|
28
|
+
if (isCantonDeferredSentinel(raw)) {
|
|
32
29
|
return cantonDeferredAddress(raw);
|
|
33
30
|
}
|
|
34
31
|
const partyMatch = CANTON_PARTY_ID_REGEX.exec(raw);
|
|
@@ -47,4 +44,4 @@ export const toCantonAddress = (raw: string): CantonAddress => {
|
|
|
47
44
|
export const isCantonAddress = (value: string): value is CantonAddress =>
|
|
48
45
|
value.startsWith(CANTON_PARTY_ADDRESS_PREFIX) ||
|
|
49
46
|
value.startsWith(CANTON_CID_ADDRESS_PREFIX) ||
|
|
50
|
-
value
|
|
47
|
+
isCantonDeferredSentinel(value);
|
package/src/deferredValue.ts
CHANGED
|
@@ -1,29 +1,55 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
|
|
3
|
+
import { NonRetryableError } from '@layerzerolabs/common-error-utils';
|
|
4
|
+
|
|
5
|
+
/** Filter / authority token resolved to the ACS query (funding) party at refresh. */
|
|
6
|
+
export const CANTON_DEFERRED_SENDER = 'sender' as const;
|
|
7
|
+
export type CantonDeferredSender = typeof CANTON_DEFERRED_SENDER;
|
|
8
|
+
|
|
9
|
+
export const cantonDeferredSenderSchema = z.literal(CANTON_DEFERRED_SENDER);
|
|
10
|
+
|
|
11
|
+
export const isCantonDeferredSender = (value: unknown): value is CantonDeferredSender =>
|
|
12
|
+
value === CANTON_DEFERRED_SENDER;
|
|
13
|
+
|
|
3
14
|
export const cantonRefreshPartyFilterSchema = z.union([
|
|
4
|
-
|
|
15
|
+
cantonDeferredSenderSchema,
|
|
5
16
|
z.object({ party: z.string() }),
|
|
6
17
|
]);
|
|
7
18
|
|
|
19
|
+
/** Non-empty list of parties that must all appear in created-event signatories. */
|
|
20
|
+
export const cantonExpectedSignatoriesSchema = z
|
|
21
|
+
.array(z.union([cantonDeferredSenderSchema, z.string().min(1)]))
|
|
22
|
+
.nonempty();
|
|
23
|
+
|
|
24
|
+
const cantonActiveContractRefFields = {
|
|
25
|
+
partyToFilter: cantonRefreshPartyFilterSchema,
|
|
26
|
+
/**
|
|
27
|
+
* Authority parties that must all be ledger signatories (authenticate-then-count).
|
|
28
|
+
* Use {@link CANTON_DEFERRED_SENDER} for the resolved funding/query party.
|
|
29
|
+
* Omitted on pre-existing persisted refs; refresh then defaults to the query
|
|
30
|
+
* party, except CIP-56 Amulet holdings (owner is not a signatory).
|
|
31
|
+
*/
|
|
32
|
+
expectedSignatories: cantonExpectedSignatoriesSchema.optional(),
|
|
33
|
+
templateId: z.string(),
|
|
34
|
+
contractStateFilter: z.partialRecord(z.string(), z.any()).optional(),
|
|
35
|
+
};
|
|
36
|
+
|
|
8
37
|
export const cantonDeferredValueSchema = z.discriminatedUnion('type', [
|
|
9
38
|
z.object({ type: z.literal('senderParty') }),
|
|
10
39
|
z.object({
|
|
11
40
|
type: z.literal('activeContractId'),
|
|
12
|
-
|
|
13
|
-
templateId: z.string(),
|
|
14
|
-
contractStateFilter: z.partialRecord(z.string(), z.any()).optional(),
|
|
41
|
+
...cantonActiveContractRefFields,
|
|
15
42
|
}),
|
|
16
43
|
z.object({
|
|
17
44
|
type: z.literal('activeContractIds'),
|
|
18
|
-
|
|
19
|
-
templateId: z.string(),
|
|
20
|
-
contractStateFilter: z.partialRecord(z.string(), z.any()).optional(),
|
|
45
|
+
...cantonActiveContractRefFields,
|
|
21
46
|
}),
|
|
22
47
|
z.object({ type: z.literal('amuletRulesCid') }),
|
|
23
48
|
z.object({ type: z.literal('amuletContext') }),
|
|
24
49
|
]);
|
|
25
50
|
|
|
26
51
|
export type CantonRefreshPartyFilter = z.infer<typeof cantonRefreshPartyFilterSchema>;
|
|
52
|
+
export type CantonExpectedSignatories = z.infer<typeof cantonExpectedSignatoriesSchema>;
|
|
27
53
|
export type CantonDeferredValue = z.infer<typeof cantonDeferredValueSchema>;
|
|
28
54
|
type CantonActiveContractIdRef = Omit<
|
|
29
55
|
Extract<CantonDeferredValue, { type: 'activeContractId' }>,
|
|
@@ -44,15 +70,17 @@ export type CantonDeferredRef = string & { readonly __cantonDeferredRef: unique
|
|
|
44
70
|
export const CANTON_DEFERRED_VALUE_PREFIX = '<<CANTON_DEFERRED:';
|
|
45
71
|
export const CANTON_DEFERRED_VALUE_SUFFIX = '>>';
|
|
46
72
|
|
|
73
|
+
/** True when `value` has deferred-ref framing, whether or not the payload parses. */
|
|
74
|
+
export const isCantonDeferredSentinel = (value: unknown): value is string =>
|
|
75
|
+
typeof value === 'string' &&
|
|
76
|
+
value.startsWith(CANTON_DEFERRED_VALUE_PREFIX) &&
|
|
77
|
+
value.endsWith(CANTON_DEFERRED_VALUE_SUFFIX);
|
|
78
|
+
|
|
47
79
|
export const encodeCantonDeferredValue = (value: CantonDeferredValue): CantonDeferredRef =>
|
|
48
80
|
`${CANTON_DEFERRED_VALUE_PREFIX}${JSON.stringify(value)}${CANTON_DEFERRED_VALUE_SUFFIX}` as CantonDeferredRef;
|
|
49
81
|
|
|
50
82
|
export const parseCantonDeferredValue = (value: unknown): CantonDeferredValue | undefined => {
|
|
51
|
-
if (
|
|
52
|
-
typeof value !== 'string' ||
|
|
53
|
-
!value.startsWith(CANTON_DEFERRED_VALUE_PREFIX) ||
|
|
54
|
-
!value.endsWith(CANTON_DEFERRED_VALUE_SUFFIX)
|
|
55
|
-
) {
|
|
83
|
+
if (!isCantonDeferredSentinel(value)) {
|
|
56
84
|
return undefined;
|
|
57
85
|
}
|
|
58
86
|
|
|
@@ -66,6 +94,22 @@ export const parseCantonDeferredValue = (value: unknown): CantonDeferredValue |
|
|
|
66
94
|
}
|
|
67
95
|
};
|
|
68
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Parse a deferred ref. Non-sentinels return `undefined`. A framed sentinel
|
|
99
|
+
* that fails schema validation throws — callers must not treat corrupt refs
|
|
100
|
+
* as plain strings.
|
|
101
|
+
*/
|
|
102
|
+
export const parseCantonDeferredValueOrThrow = (
|
|
103
|
+
value: unknown,
|
|
104
|
+
): CantonDeferredValue | undefined => {
|
|
105
|
+
const ref = parseCantonDeferredValue(value);
|
|
106
|
+
if (ref) return ref;
|
|
107
|
+
if (isCantonDeferredSentinel(value)) {
|
|
108
|
+
throw new NonRetryableError(`Invalid Canton deferred ref: ${value}`);
|
|
109
|
+
}
|
|
110
|
+
return undefined;
|
|
111
|
+
};
|
|
112
|
+
|
|
69
113
|
export const cantonSenderPartyRef = (): CantonDeferredRef =>
|
|
70
114
|
encodeCantonDeferredValue({ type: 'senderParty' });
|
|
71
115
|
|
|
@@ -81,6 +125,6 @@ export const cantonAmuletRulesCidRef = (): CantonDeferredRef =>
|
|
|
81
125
|
export const cantonAmuletContextRef = (): CantonDeferredRef =>
|
|
82
126
|
encodeCantonDeferredValue({ type: 'amuletContext' });
|
|
83
127
|
|
|
84
|
-
/** Returns true if `value` is a Canton deferred-value sentinel. */
|
|
128
|
+
/** Returns true if `value` is a valid Canton deferred-value sentinel. */
|
|
85
129
|
export const isCantonPlaceholder = (value: string): boolean =>
|
|
86
130
|
parseCantonDeferredValue(value) !== undefined;
|
package/src/events.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { cantonTransactionWithResultSchema } from './transaction';
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Finds a CreatedEvent matching the expected templateId whose signatories
|
|
9
|
-
* are a subset of the provided actAs parties.
|
|
9
|
+
* are a subset of the provided actAs parties (ledger signatories ⊆ expected).
|
|
10
|
+
* Opposite of `areExpectedSignatories` on the refresh path.
|
|
10
11
|
*/
|
|
11
12
|
export const findMatchingCreatedEvent = (
|
|
12
13
|
createdEvents: CreatedEvent[],
|
package/src/index.ts
CHANGED
|
@@ -3,16 +3,25 @@ import type { ContractId } from '@daml/types';
|
|
|
3
3
|
import { NonRetryableError } from '@layerzerolabs/common-error-utils';
|
|
4
4
|
|
|
5
5
|
import type { ICantonClient } from '../client/interface';
|
|
6
|
-
import
|
|
6
|
+
import {
|
|
7
|
+
CANTON_DEFERRED_SENDER,
|
|
8
|
+
type CantonDeferredValue,
|
|
9
|
+
isCantonDeferredSender,
|
|
10
|
+
} from '../deferredValue';
|
|
7
11
|
import type { JsActiveContract } from '../open-api/models';
|
|
8
12
|
import type { CantonDisclosedContracts } from '../transaction';
|
|
13
|
+
import { areExpectedSignatories, getCreateArgumentField } from '../trusted-origin';
|
|
14
|
+
|
|
15
|
+
/** CIP-56 Amulet holding — DSO is the sole signatory; owner is payload/observer. */
|
|
16
|
+
const isAmuletHoldingTemplateId = (templateId: string): boolean =>
|
|
17
|
+
templateId.endsWith(':Splice.Amulet:Amulet');
|
|
9
18
|
|
|
10
19
|
export type ActiveContractRef = Extract<
|
|
11
20
|
CantonDeferredValue,
|
|
12
21
|
{ type: 'activeContractId' | 'activeContractIds' }
|
|
13
22
|
>;
|
|
14
23
|
|
|
15
|
-
/** Active-contract ref whose handler party is concrete (not the
|
|
24
|
+
/** Active-contract ref whose handler party is concrete (not the sender sentinel). */
|
|
16
25
|
export type ConcreteActiveContractRef = ActiveContractRef & {
|
|
17
26
|
partyToFilter: { party: string };
|
|
18
27
|
};
|
|
@@ -21,7 +30,7 @@ export const isConcreteActiveContractRef = (
|
|
|
21
30
|
ref: CantonDeferredValue,
|
|
22
31
|
): ref is ConcreteActiveContractRef =>
|
|
23
32
|
(ref.type === 'activeContractId' || ref.type === 'activeContractIds') &&
|
|
24
|
-
ref.partyToFilter !==
|
|
33
|
+
ref.partyToFilter !== CANTON_DEFERRED_SENDER;
|
|
25
34
|
|
|
26
35
|
export interface ContractAddressResolverDeps {
|
|
27
36
|
client: ICantonClient;
|
|
@@ -31,14 +40,55 @@ export interface ContractAddressResolverDeps {
|
|
|
31
40
|
appendDisclosures: (disclosures: CantonDisclosedContracts) => void;
|
|
32
41
|
}
|
|
33
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Match create-argument fields, including dotted paths (`instrumentId.admin`).
|
|
45
|
+
*/
|
|
34
46
|
export const matchesContractStateFilter = (
|
|
35
47
|
contract: JsActiveContract,
|
|
36
48
|
contractStateFilter: ActiveContractRef['contractStateFilter'],
|
|
37
49
|
): boolean => {
|
|
38
50
|
if (!contractStateFilter) return true;
|
|
39
|
-
const createArgument = contract.createdEvent.createArgument
|
|
40
|
-
return Object.entries(contractStateFilter).every(
|
|
41
|
-
|
|
51
|
+
const createArgument = contract.createdEvent.createArgument;
|
|
52
|
+
return Object.entries(contractStateFilter).every(([key, value]) => {
|
|
53
|
+
const actual = getCreateArgumentField(createArgument, key);
|
|
54
|
+
// Missing / unresolvable paths must not match (fail closed).
|
|
55
|
+
if (actual === undefined) return false;
|
|
56
|
+
return JSON.stringify(actual) === JSON.stringify(value);
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve {@link CANTON_DEFERRED_SENDER} entries to the ACS query party.
|
|
62
|
+
* Missing lists default to `[queryParty]` except CIP-56 Amulet holdings, where
|
|
63
|
+
* the owner is not a signatory — omit the filter so pre-PR refs still resolve.
|
|
64
|
+
*/
|
|
65
|
+
export const resolveExpectedSignatories = (
|
|
66
|
+
expectedSignatories: readonly string[] | undefined,
|
|
67
|
+
queryParty: string,
|
|
68
|
+
templateId: string,
|
|
69
|
+
): string[] | undefined => {
|
|
70
|
+
if (expectedSignatories?.length) {
|
|
71
|
+
return expectedSignatories.map((party) =>
|
|
72
|
+
isCantonDeferredSender(party) ? queryParty : party,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
if (isAmuletHoldingTemplateId(templateId)) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return [queryParty];
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** Resolve {@link CANTON_DEFERRED_SENDER} values in `contractStateFilter` to the ACS query party. */
|
|
82
|
+
export const resolveContractStateFilter = (
|
|
83
|
+
contractStateFilter: ActiveContractRef['contractStateFilter'],
|
|
84
|
+
queryParty: string,
|
|
85
|
+
): ActiveContractRef['contractStateFilter'] => {
|
|
86
|
+
if (!contractStateFilter) return contractStateFilter;
|
|
87
|
+
return Object.fromEntries(
|
|
88
|
+
Object.entries(contractStateFilter).map(([key, value]) => [
|
|
89
|
+
key,
|
|
90
|
+
isCantonDeferredSender(value) ? queryParty : value,
|
|
91
|
+
]),
|
|
42
92
|
);
|
|
43
93
|
};
|
|
44
94
|
|
|
@@ -51,21 +101,40 @@ export const isRefAlive = async (
|
|
|
51
101
|
return ref.type === 'activeContractId' ? contracts.length === 1 : contracts.length > 0;
|
|
52
102
|
};
|
|
53
103
|
|
|
54
|
-
/**
|
|
104
|
+
/**
|
|
105
|
+
* Query ACS for `party` + template, apply state filter, then
|
|
106
|
+
* authenticate-then-count: keep only contracts where every resolved
|
|
107
|
+
* `expectedSignatories` entry is a ledger signatory.
|
|
108
|
+
*
|
|
109
|
+
* `partyToFilter` is visibility (may be an observer). `expectedSignatories` is
|
|
110
|
+
* authority (defaults to the query party when omitted, except Amulet holdings).
|
|
111
|
+
* Optional `contractStateFilter` disambiguates / binds payload roles (supports
|
|
112
|
+
* dotted paths; {@link CANTON_DEFERRED_SENDER} values resolve).
|
|
113
|
+
*/
|
|
55
114
|
export const queryLiveContractsForRef = async (
|
|
56
115
|
client: ICantonClient,
|
|
57
116
|
party: string,
|
|
58
117
|
ref: ActiveContractRef,
|
|
59
118
|
): Promise<JsActiveContract[]> => {
|
|
60
119
|
const offset = await client.getLedgerEndOffset();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
120
|
+
const expectedSignatories = resolveExpectedSignatories(
|
|
121
|
+
ref.expectedSignatories,
|
|
122
|
+
party,
|
|
123
|
+
ref.templateId,
|
|
124
|
+
);
|
|
125
|
+
const contractStateFilter = resolveContractStateFilter(ref.contractStateFilter, party);
|
|
126
|
+
return (await client.queryActiveContracts(party, offset, { templateIds: [ref.templateId] }))
|
|
127
|
+
.filter((contract) => matchesContractStateFilter(contract, contractStateFilter))
|
|
128
|
+
.filter(
|
|
129
|
+
(contract) =>
|
|
130
|
+
expectedSignatories === undefined ||
|
|
131
|
+
areExpectedSignatories(contract.createdEvent, expectedSignatories),
|
|
132
|
+
);
|
|
64
133
|
};
|
|
65
134
|
|
|
66
135
|
/**
|
|
67
136
|
* Returns the contractIds of all live contracts matching ref.
|
|
68
|
-
* Resolves
|
|
137
|
+
* Resolves {@link CANTON_DEFERRED_SENDER} to senderParty when provided; returns [] if unresolvable.
|
|
69
138
|
*/
|
|
70
139
|
export const queryLiveContractIdsForRef = async (
|
|
71
140
|
client: ICantonClient,
|
|
@@ -73,7 +142,8 @@ export const queryLiveContractIdsForRef = async (
|
|
|
73
142
|
senderParty?: string,
|
|
74
143
|
): Promise<string[]> => {
|
|
75
144
|
if (ref.type !== 'activeContractId' && ref.type !== 'activeContractIds') return [];
|
|
76
|
-
const party =
|
|
145
|
+
const party =
|
|
146
|
+
ref.partyToFilter === CANTON_DEFERRED_SENDER ? senderParty : ref.partyToFilter.party;
|
|
77
147
|
if (!party) return [];
|
|
78
148
|
const contracts = await queryLiveContractsForRef(client, party, ref);
|
|
79
149
|
return contracts.map((c) => c.createdEvent.contractId);
|
|
@@ -92,7 +162,7 @@ export const createContractAddressResolver = (deps: ContractAddressResolverDeps)
|
|
|
92
162
|
};
|
|
93
163
|
|
|
94
164
|
const resolveQueryParty = ({ partyToFilter, templateId }: ActiveContractRef): string =>
|
|
95
|
-
partyToFilter ===
|
|
165
|
+
partyToFilter === CANTON_DEFERRED_SENDER ? requireSender(templateId) : partyToFilter.party;
|
|
96
166
|
|
|
97
167
|
const queryCache = new Map<string, Promise<JsActiveContract[]>>();
|
|
98
168
|
const queryAsParty = async (ref: ActiveContractRef): Promise<JsActiveContract[]> => {
|
|
@@ -39,6 +39,8 @@ export const buildCantonDeployedContractAddressRef = (
|
|
|
39
39
|
|
|
40
40
|
return cantonActiveContractIdRef({
|
|
41
41
|
partyToFilter: { party: ownerParty },
|
|
42
|
+
// LZ deploy templates: ownerField is a create-argument signatory.
|
|
43
|
+
expectedSignatories: [ownerParty],
|
|
42
44
|
templateId: `#${spec.packageName}:${qualifiedName}`,
|
|
43
45
|
...(contractStateFilter ? { contractStateFilter } : {}),
|
|
44
46
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NonRetryableError } from '@layerzerolabs/common-error-utils';
|
|
2
2
|
|
|
3
|
-
import { type CantonDeferredValue,
|
|
3
|
+
import { type CantonDeferredValue, parseCantonDeferredValueOrThrow } from '../deferredValue';
|
|
4
4
|
|
|
5
5
|
// Opaque base64 — swapping a cid here would corrupt it.
|
|
6
6
|
const BLOB_KEY = 'createdEventBlob';
|
|
@@ -30,7 +30,7 @@ export const substituteRefsToCids = async <T>(value: T, resolve: ResolveRef): Pr
|
|
|
30
30
|
if (Array.isArray(value)) {
|
|
31
31
|
const items = await Promise.all(
|
|
32
32
|
value.map(async (item) => {
|
|
33
|
-
const ref =
|
|
33
|
+
const ref = parseCantonDeferredValueOrThrow(item);
|
|
34
34
|
return ref?.type === 'activeContractIds'
|
|
35
35
|
? ((await resolve(ref)) as unknown[])
|
|
36
36
|
: [await substituteRefsToCids(item, resolve)];
|
|
@@ -39,7 +39,7 @@ export const substituteRefsToCids = async <T>(value: T, resolve: ResolveRef): Pr
|
|
|
39
39
|
return items.flat() as T;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
const ref =
|
|
42
|
+
const ref = parseCantonDeferredValueOrThrow(value);
|
|
43
43
|
if (ref) {
|
|
44
44
|
if (ref.type === 'activeContractIds') {
|
|
45
45
|
throw new NonRetryableError(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NonRetryableError } from '@layerzerolabs/common-error-utils';
|
|
1
2
|
import { isPromise } from '@layerzerolabs/typescript-utils';
|
|
2
3
|
|
|
3
4
|
import { cantonActiveContractIdRef, type CantonDeferredRef } from '../deferredValue';
|
|
@@ -22,16 +23,25 @@ const applyRefs = <R>(result: R): WithRefsInjected<R> => {
|
|
|
22
23
|
if (!refs?.length) return result as WithRefsInjected<R>;
|
|
23
24
|
|
|
24
25
|
const contractIdToRef = new Map<string, CantonDeferredRef>(
|
|
25
|
-
refs.map((ref) =>
|
|
26
|
-
ref.
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
refs.map((ref) => {
|
|
27
|
+
const expectedSignatories = ref.expectedSignatories;
|
|
28
|
+
if (!expectedSignatories[0]) {
|
|
29
|
+
throw new NonRetryableError(
|
|
30
|
+
`ContractDataIdentifier for ${ref.cid} missing expectedSignatories`,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
return [
|
|
34
|
+
ref.cid,
|
|
35
|
+
cantonActiveContractIdRef({
|
|
36
|
+
partyToFilter: { party: ref.party },
|
|
37
|
+
expectedSignatories,
|
|
38
|
+
templateId: ref.templateId,
|
|
39
|
+
...(ref.contractStateFilter
|
|
40
|
+
? { contractStateFilter: ref.contractStateFilter }
|
|
41
|
+
: {}),
|
|
42
|
+
}),
|
|
43
|
+
];
|
|
44
|
+
}),
|
|
35
45
|
);
|
|
36
46
|
|
|
37
47
|
const { referencedContracts: _dropped, ...rest } = result as Record<string, unknown>;
|
package/src/transaction.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
} from '@layerzerolabs/common-chain-model';
|
|
10
10
|
import type { PartialBy } from '@layerzerolabs/typescript-utils';
|
|
11
11
|
|
|
12
|
-
import type { CantonDeferredRef } from './deferredValue';
|
|
12
|
+
import type { CantonDeferredRef, CantonExpectedSignatories } from './deferredValue';
|
|
13
13
|
|
|
14
14
|
// === Component schemas ===
|
|
15
15
|
|
|
@@ -167,7 +167,16 @@ export const cantonMinedTransactionSchema = minedTransactionSchema.extend({
|
|
|
167
167
|
export interface ContractDataIdentifier {
|
|
168
168
|
cid: string;
|
|
169
169
|
templateId: string;
|
|
170
|
+
/** ACS query / visibility party (`partyToFilter`). */
|
|
170
171
|
party: string;
|
|
172
|
+
/**
|
|
173
|
+
* Authority parties that must all be ledger signatories at refresh time.
|
|
174
|
+
* Typically `[party]`; use a different list when querying as an observer
|
|
175
|
+
* (e.g. gateway) while authenticating the admin signatory.
|
|
176
|
+
*
|
|
177
|
+
* Non-empty; same type as the deferred-ref wire field.
|
|
178
|
+
*/
|
|
179
|
+
expectedSignatories: CantonExpectedSignatories;
|
|
171
180
|
contractStateFilter?: Record<string, unknown>;
|
|
172
181
|
}
|
|
173
182
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a create-argument field, supporting dotted paths (`instrumentId.admin`).
|
|
3
|
+
*/
|
|
4
|
+
export const getCreateArgumentField = (createArgument: unknown, fieldPath: string): unknown => {
|
|
5
|
+
let current: unknown = createArgument;
|
|
6
|
+
for (const part of fieldPath.split('.')) {
|
|
7
|
+
if (current == null || typeof current !== 'object') {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
current = (current as Record<string, unknown>)[part];
|
|
11
|
+
}
|
|
12
|
+
return current;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* True when every party in `expectedSignatories` is among the created-event signatories
|
|
17
|
+
* (expected ⊆ ledger signatories). Opposite of `findMatchingCreatedEvent`, which
|
|
18
|
+
* requires every ledger signatory to be in the expected/actAs set.
|
|
19
|
+
*
|
|
20
|
+
* Drops observer-only foreign contracts that would otherwise poison ACS cardinality.
|
|
21
|
+
*/
|
|
22
|
+
export const areExpectedSignatories = (
|
|
23
|
+
created: { signatories?: readonly string[] },
|
|
24
|
+
expectedSignatories: readonly string[],
|
|
25
|
+
): boolean => {
|
|
26
|
+
if (expectedSignatories.length === 0) return false;
|
|
27
|
+
const signatories = created.signatories ?? [];
|
|
28
|
+
return expectedSignatories.every((party) => signatories.includes(party));
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* True when `expectedParty` is among the created-event signatories.
|
|
33
|
+
*/
|
|
34
|
+
export const isSignatoryOrigin = (
|
|
35
|
+
created: { signatories?: readonly string[] },
|
|
36
|
+
expectedParty: string,
|
|
37
|
+
): boolean => areExpectedSignatories(created, [expectedParty]);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* True when a created event originated under `expectedParty`: that party is among
|
|
41
|
+
* the created-event signatories, and `createArgument[createArgumentField]` equals it.
|
|
42
|
+
*
|
|
43
|
+
* Use when the template has multiple signatories and the party must bind a
|
|
44
|
+
* specific payload role (e.g. Request `handler` amid sender / oappAdmin).
|
|
45
|
+
* Prefer `contractStateFilter` + `expectedSignatories` on deferred refs instead.
|
|
46
|
+
*
|
|
47
|
+
* @param createArgumentField - Payload identity field (`owner`, `handler`,
|
|
48
|
+
* `admin`, or a dotted path such as `instrumentId.admin`).
|
|
49
|
+
*/
|
|
50
|
+
export const isTrustedOrigin = (
|
|
51
|
+
created: { createArgument?: unknown; signatories?: readonly string[] },
|
|
52
|
+
expectedParty: string,
|
|
53
|
+
createArgumentField: string,
|
|
54
|
+
): boolean => {
|
|
55
|
+
if (getCreateArgumentField(created.createArgument, createArgumentField) !== expectedParty) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
return isSignatoryOrigin(created, expectedParty);
|
|
59
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { NonRetryableError } from '@layerzerolabs/common-error-utils';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
CANTON_DEFERRED_SENDER,
|
|
7
|
+
isCantonDeferredSender,
|
|
8
|
+
isCantonDeferredSentinel,
|
|
9
|
+
isCantonPlaceholder,
|
|
10
|
+
parseCantonDeferredValue,
|
|
11
|
+
parseCantonDeferredValueOrThrow,
|
|
12
|
+
} from '../src/deferredValue';
|
|
13
|
+
|
|
14
|
+
describe('CANTON_DEFERRED_SENDER', () => {
|
|
15
|
+
it('is the typed sender token', () => {
|
|
16
|
+
expect(isCantonDeferredSender(CANTON_DEFERRED_SENDER)).toBe(true);
|
|
17
|
+
expect(isCantonDeferredSender('alice::1220ab')).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('parseCantonDeferredValueOrThrow', () => {
|
|
22
|
+
it('returns undefined for non-sentinels', () => {
|
|
23
|
+
expect(parseCantonDeferredValueOrThrow('00abc')).toBeUndefined();
|
|
24
|
+
expect(parseCantonDeferredValueOrThrow({ type: 'senderParty' })).toBeUndefined();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('parses a valid sentinel', () => {
|
|
28
|
+
expect(
|
|
29
|
+
parseCantonDeferredValueOrThrow('<<CANTON_DEFERRED:{"type":"senderParty"}>>'),
|
|
30
|
+
).toEqual({ type: 'senderParty' });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('throws NonRetryableError for a framed but invalid sentinel', () => {
|
|
34
|
+
const malformed = '<<CANTON_DEFERRED:{"type":"activeContractId","templateId":"#pkg:T"}>>';
|
|
35
|
+
expect(isCantonDeferredSentinel(malformed)).toBe(true);
|
|
36
|
+
expect(parseCantonDeferredValue(malformed)).toBeUndefined();
|
|
37
|
+
expect(isCantonPlaceholder(malformed)).toBe(false);
|
|
38
|
+
expect(() => parseCantonDeferredValueOrThrow(malformed)).toThrow(NonRetryableError);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
import { parseCantonDeferredValue } from '../../src/deferredValue';
|
|
7
|
+
|
|
8
|
+
const PREFIX = '<<CANTON_DEFERRED:';
|
|
9
|
+
const SUFFIX = '>>';
|
|
10
|
+
|
|
11
|
+
const encodeAddress = (nativeAddress: string): string => {
|
|
12
|
+
const inner = nativeAddress.slice(PREFIX.length, -SUFFIX.length);
|
|
13
|
+
return `0x${Buffer.from(inner, 'utf8').toString('hex')}02`.toLowerCase();
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const deploymentRoots = [
|
|
17
|
+
join(process.cwd(), '../../../../deployments/layerzero/mainnet/canton/deploymentInfo'),
|
|
18
|
+
join(process.cwd(), '../../../../deployments/layerzero/testnet/canton/deploymentInfo'),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
describe('Canton deploymentInfo deferred-ref artifacts', () => {
|
|
22
|
+
const artifacts = deploymentRoots.flatMap((root) =>
|
|
23
|
+
readdirSync(root)
|
|
24
|
+
.filter((file) => file.endsWith('.json'))
|
|
25
|
+
.map((file) => {
|
|
26
|
+
const path = join(root, file);
|
|
27
|
+
return {
|
|
28
|
+
path,
|
|
29
|
+
json: JSON.parse(readFileSync(path, 'utf8')) as Record<string, unknown>,
|
|
30
|
+
};
|
|
31
|
+
})
|
|
32
|
+
.filter(
|
|
33
|
+
({ json }) =>
|
|
34
|
+
typeof json.nativeAddress === 'string' &&
|
|
35
|
+
(json.nativeAddress as string).startsWith(PREFIX),
|
|
36
|
+
),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
it('has deferred-ref deploy artifacts to check', () => {
|
|
40
|
+
expect(artifacts.length).toBeGreaterThan(0);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('hex address matches nativeAddress and parses as a deferred ref', () => {
|
|
44
|
+
for (const { path, json } of artifacts) {
|
|
45
|
+
const nativeAddress = json.nativeAddress as string;
|
|
46
|
+
expect(json.address, path).toBe(encodeAddress(nativeAddress));
|
|
47
|
+
expect(parseCantonDeferredValue(nativeAddress), path).toMatchObject({
|
|
48
|
+
type: 'activeContractId',
|
|
49
|
+
templateId: expect.any(String),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|