@layerzerolabs/common-canton 0.2.105 → 0.2.107
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 +1248 -1250
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +620 -586
- package/dist/{MDLCDBAQ.js → HNZRSDTR.js} +41 -3
- package/dist/HNZRSDTR.js.map +1 -0
- package/dist/NO5PBPQH.js +97 -0
- package/dist/NO5PBPQH.js.map +1 -0
- package/dist/events.d.ts +28 -0
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +1 -1
- package/dist/index.js +2 -2
- package/dist/provider.d.ts +19 -3
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +1 -1
- package/package.json +10 -9
- package/src/events.ts +135 -0
- package/src/provider.ts +82 -11
- package/test/provider.test.ts +67 -0
- package/dist/623CCK2L.js +0 -37
- package/dist/623CCK2L.js.map +0 -1
- package/dist/MDLCDBAQ.js.map +0 -1
|
@@ -17,6 +17,12 @@ function parseCantonUri(uri) {
|
|
|
17
17
|
if (!walletUrl) {
|
|
18
18
|
throw new Error('Canton provider URI missing required "wallet-url" query parameter');
|
|
19
19
|
}
|
|
20
|
+
const sequencerUrl = url.searchParams.get("sequencer-url");
|
|
21
|
+
if (!sequencerUrl) {
|
|
22
|
+
throw new Error('Canton provider URI missing required "sequencer-url" query parameter');
|
|
23
|
+
}
|
|
24
|
+
const sequencerValidatorsParam = url.searchParams.get("sequencer-validators");
|
|
25
|
+
const sequencerQuorumParam = url.searchParams.get("sequencer-quorum");
|
|
20
26
|
const tokenUrl = url.searchParams.get("token-url") ?? void 0;
|
|
21
27
|
const clientId = url.searchParams.get("client-id") ?? void 0;
|
|
22
28
|
const clientSecret = url.searchParams.get("client-secret") ?? void 0;
|
|
@@ -26,6 +32,9 @@ function parseCantonUri(uri) {
|
|
|
26
32
|
"admin-api",
|
|
27
33
|
"user-id",
|
|
28
34
|
"wallet-url",
|
|
35
|
+
"sequencer-url",
|
|
36
|
+
"sequencer-validators",
|
|
37
|
+
"sequencer-quorum",
|
|
29
38
|
"token-url",
|
|
30
39
|
"client-id",
|
|
31
40
|
"client-secret",
|
|
@@ -34,6 +43,31 @@ function parseCantonUri(uri) {
|
|
|
34
43
|
]) {
|
|
35
44
|
url.searchParams.delete(key);
|
|
36
45
|
}
|
|
46
|
+
let sequencerCommittee;
|
|
47
|
+
if (sequencerValidatorsParam !== null && sequencerQuorumParam !== null) {
|
|
48
|
+
const publicKeys = sequencerValidatorsParam.split(",").map((s) => s.trim().toLowerCase()).filter((s) => s.length > 0);
|
|
49
|
+
if (publicKeys.length === 0) {
|
|
50
|
+
throw new Error("Canton provider URI sequencer-validators must contain at least one pubkey");
|
|
51
|
+
}
|
|
52
|
+
for (const publicKey of publicKeys) {
|
|
53
|
+
if (!publicKey.startsWith("0x")) {
|
|
54
|
+
throw new Error(`Canton provider URI sequencer-validators pubkey must start with 0x, got: ${publicKey.slice(0, 10)}...`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const quorum = Number(sequencerQuorumParam);
|
|
58
|
+
if (!Number.isInteger(quorum) || quorum < 1) {
|
|
59
|
+
throw new Error(`Canton provider URI sequencer-quorum must be a positive integer, got: ${sequencerQuorumParam}`);
|
|
60
|
+
}
|
|
61
|
+
if (quorum > publicKeys.length) {
|
|
62
|
+
throw new Error(`Canton provider URI sequencer-quorum=${quorum} exceeds committee size ${publicKeys.length}`);
|
|
63
|
+
}
|
|
64
|
+
sequencerCommittee = {
|
|
65
|
+
publicKeys,
|
|
66
|
+
quorum
|
|
67
|
+
};
|
|
68
|
+
} else if (sequencerValidatorsParam !== null || sequencerQuorumParam !== null) {
|
|
69
|
+
throw new Error("Canton provider URI must set both sequencer-validators and sequencer-quorum together");
|
|
70
|
+
}
|
|
37
71
|
const jsonApiUrl = url.toString().replace(/\/$/, "");
|
|
38
72
|
const oauth2 = tokenUrl && clientId && clientSecret ? {
|
|
39
73
|
tokenUrl,
|
|
@@ -47,6 +81,8 @@ function parseCantonUri(uri) {
|
|
|
47
81
|
adminApiHost,
|
|
48
82
|
userId,
|
|
49
83
|
walletUrl,
|
|
84
|
+
sequencerUrl,
|
|
85
|
+
sequencerCommittee,
|
|
50
86
|
oauth2
|
|
51
87
|
};
|
|
52
88
|
}
|
|
@@ -67,7 +103,9 @@ function createCantonProvider({ uri, isLocal }) {
|
|
|
67
103
|
client,
|
|
68
104
|
grpcClient,
|
|
69
105
|
tokenProvider,
|
|
70
|
-
walletUrl: params.walletUrl
|
|
106
|
+
walletUrl: params.walletUrl,
|
|
107
|
+
sequencerUrl: params.sequencerUrl,
|
|
108
|
+
sequencerCommittee: params.sequencerCommittee
|
|
71
109
|
};
|
|
72
110
|
}
|
|
73
111
|
__name(createCantonProvider, "createCantonProvider");
|
|
@@ -87,5 +125,5 @@ function createTokenProvider(params, isLocal) {
|
|
|
87
125
|
__name(createTokenProvider, "createTokenProvider");
|
|
88
126
|
|
|
89
127
|
export { createCantonProvider, parseCantonUri };
|
|
90
|
-
//# sourceMappingURL=
|
|
91
|
-
//# sourceMappingURL=
|
|
128
|
+
//# sourceMappingURL=HNZRSDTR.js.map
|
|
129
|
+
//# sourceMappingURL=HNZRSDTR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider.ts"],"names":["parseCantonUri","uri","url","URL","adminApiHost","searchParams","get","Error","userId","walletUrl","sequencerUrl","sequencerValidatorsParam","sequencerQuorumParam","tokenUrl","undefined","clientId","clientSecret","scope","audience","key","delete","sequencerCommittee","publicKeys","split","map","s","trim","toLowerCase","filter","length","publicKey","startsWith","slice","quorum","Number","isInteger","jsonApiUrl","toString","replace","oauth2","createCantonProvider","isLocal","params","tokenProvider","createTokenProvider","configuration","Configuration","basePath","accessToken","getToken","client","CantonClient","grpcClient","CantonGrpcClient","host","LocalTokenProvider","actAs","admin","ClientCredentialsTokenProvider"],"mappings":"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","file":"HNZRSDTR.js","sourcesContent":["import {\n ClientCredentialsTokenProvider,\n type ClientCredentialsTokenProviderConfig,\n} from './auth/client-credentials-token-provider';\nimport { LocalTokenProvider } from './auth/local-token-provider';\nimport type { TokenProvider } from './auth/token-provider';\nimport { CantonClient } from './client/canton-client';\nimport { CantonGrpcClient } from './client/grpc';\nimport type { ICantonClient } from './client/interface';\nimport { Configuration } from './open-api/runtime';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Provider Type\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Composite provider for Canton — bundles both transport layers and shared auth.\n *\n * - {@link client} — JSON Ledger API (commands, queries, updates)\n * - {@link grpcClient} — gRPC Admin API (topology transactions, namespace queries)\n * - {@link tokenProvider} — Shared Bearer-token source used by both transports\n */\nexport type CantonProvider = {\n client: ICantonClient;\n grpcClient: CantonGrpcClient;\n tokenProvider: TokenProvider;\n /** Validator wallet URL for scan-proxy CIP-56 endpoints. Required. */\n walletUrl: string;\n /** LayerZero sequencer base URL (e.g. \"http://localhost:8080\"); vApp JSON-RPC at `/vapp`. Required. */\n sequencerUrl: string;\n /** Sequencer validator pubkeys plus quorum for verifying signed read responses. */\n sequencerCommittee?: SequencerCommittee;\n};\n\n/**\n * Client-side committee config for secp256k1 sequencer read verification.\n * Pubkeys must be hex-encoded with `0x` prefix in the same format returned by\n * `recoverSecp256k1PublicKey`.\n */\nexport type SequencerCommittee = {\n publicKeys: `0x${string}`[];\n quorum: number;\n};\n\n// ─────────────────────────────────────────────────────────────────────────────\n// URI Parsing\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type CantonUriParams = {\n /** Base URL of the JSON Ledger API (e.g. \"http://localhost:5975\") */\n jsonApiUrl: string;\n /** Host:port of the gRPC Admin API (e.g. \"localhost:5076\") */\n adminApiHost: string;\n /** Ledger API user ID (default: \"participant_admin\") */\n userId: string;\n /** Validator wallet URL for scan-proxy CIP-56 endpoints (e.g. \"http://localhost:7201\"). Required. */\n walletUrl: string;\n /** LayerZero sequencer base URL (e.g. \"http://localhost:8080\"); vApp JSON-RPC at `/vapp`. Required. */\n sequencerUrl: string;\n /** Sequencer committee config parsed from the provider URI. */\n sequencerCommittee?: SequencerCommittee;\n /** OAuth2 client credentials config — present only for non-local environments */\n oauth2?: ClientCredentialsTokenProviderConfig;\n};\n\n/**\n * Parse a Canton provider URI into its constituent parts.\n *\n * URI format:\n * `<json-api-base>?admin-api=<host:port>&user-id=<id>[&token-url=...&client-id=...&client-secret=...&scope=...&audience=...]`\n *\n * The base URL (everything before the query string) is the JSON Ledger API endpoint.\n * Canton-specific params are extracted and removed; any remaining query params are\n * preserved on the JSON API URL.\n */\nexport function parseCantonUri(uri: string): CantonUriParams {\n const url = new URL(uri);\n\n const adminApiHost = url.searchParams.get('admin-api');\n if (!adminApiHost) {\n throw new Error('Canton provider URI missing required \"admin-api\" query parameter');\n }\n\n const userId = url.searchParams.get('user-id') ?? 'participant_admin';\n const walletUrl = url.searchParams.get('wallet-url');\n if (!walletUrl) {\n throw new Error('Canton provider URI missing required \"wallet-url\" query parameter');\n }\n const sequencerUrl = url.searchParams.get('sequencer-url');\n if (!sequencerUrl) {\n throw new Error('Canton provider URI missing required \"sequencer-url\" query parameter');\n }\n const sequencerValidatorsParam = url.searchParams.get('sequencer-validators');\n const sequencerQuorumParam = url.searchParams.get('sequencer-quorum');\n const tokenUrl = url.searchParams.get('token-url') ?? undefined;\n const clientId = url.searchParams.get('client-id') ?? undefined;\n const clientSecret = url.searchParams.get('client-secret') ?? undefined;\n const scope = url.searchParams.get('scope') ?? undefined;\n const audience = url.searchParams.get('audience') ?? undefined;\n\n // Remove Canton params to get clean JSON API URL\n for (const key of [\n 'admin-api',\n 'user-id',\n 'wallet-url',\n 'sequencer-url',\n 'sequencer-validators',\n 'sequencer-quorum',\n 'token-url',\n 'client-id',\n 'client-secret',\n 'scope',\n 'audience',\n ]) {\n url.searchParams.delete(key);\n }\n\n let sequencerCommittee: SequencerCommittee | undefined;\n if (sequencerValidatorsParam !== null && sequencerQuorumParam !== null) {\n const publicKeys = sequencerValidatorsParam\n .split(',')\n .map((s) => s.trim().toLowerCase())\n .filter((s) => s.length > 0);\n if (publicKeys.length === 0) {\n throw new Error(\n 'Canton provider URI sequencer-validators must contain at least one pubkey',\n );\n }\n for (const publicKey of publicKeys) {\n if (!publicKey.startsWith('0x')) {\n throw new Error(\n `Canton provider URI sequencer-validators pubkey must start with 0x, got: ${publicKey.slice(0, 10)}...`,\n );\n }\n }\n\n const quorum = Number(sequencerQuorumParam);\n if (!Number.isInteger(quorum) || quorum < 1) {\n throw new Error(\n `Canton provider URI sequencer-quorum must be a positive integer, got: ${sequencerQuorumParam}`,\n );\n }\n if (quorum > publicKeys.length) {\n throw new Error(\n `Canton provider URI sequencer-quorum=${quorum} exceeds committee size ${publicKeys.length}`,\n );\n }\n sequencerCommittee = {\n publicKeys: publicKeys as `0x${string}`[],\n quorum,\n };\n } else if (sequencerValidatorsParam !== null || sequencerQuorumParam !== null) {\n throw new Error(\n 'Canton provider URI must set both sequencer-validators and sequencer-quorum together',\n );\n }\n\n const jsonApiUrl = url.toString().replace(/\\/$/, '');\n\n const oauth2 =\n tokenUrl && clientId && clientSecret\n ? { tokenUrl, clientId, clientSecret, scope, audience }\n : undefined;\n\n return {\n jsonApiUrl,\n adminApiHost,\n userId,\n walletUrl,\n sequencerUrl,\n sequencerCommittee,\n oauth2,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Provider Factory\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport type CreateCantonProviderOptions = {\n /** Raw provider URI (from provider config) */\n uri: string;\n /** When true, uses LocalTokenProvider (self-signed JWTs) instead of OAuth2 */\n isLocal: boolean;\n};\n\n/**\n * Create a {@link CantonProvider} from a provider URI.\n *\n * - **isLocal=true**: Uses {@link LocalTokenProvider} with admin-level access (self-signed JWTs).\n * - **isLocal=false**: Uses {@link ClientCredentialsTokenProvider} with OAuth2 client\n * credentials extracted from the URI query parameters.\n */\nexport function createCantonProvider({\n uri,\n isLocal,\n}: CreateCantonProviderOptions): CantonProvider {\n const params = parseCantonUri(uri);\n\n const tokenProvider = createTokenProvider(params, isLocal);\n\n // ── JSON Ledger API Client ──────────────────────────────────────────\n const configuration = new Configuration({\n basePath: params.jsonApiUrl,\n accessToken: () => tokenProvider.getToken(),\n });\n const client = new CantonClient(configuration, params.userId);\n\n // ── gRPC Admin API Client ───────────────────────────────────────────\n const grpcClient = new CantonGrpcClient({\n host: params.adminApiHost,\n tokenProvider,\n });\n\n return {\n client,\n grpcClient,\n tokenProvider,\n walletUrl: params.walletUrl,\n sequencerUrl: params.sequencerUrl,\n sequencerCommittee: params.sequencerCommittee,\n };\n}\n\nfunction createTokenProvider(params: CantonUriParams, isLocal: boolean): TokenProvider {\n if (isLocal) {\n return new LocalTokenProvider({\n userId: params.userId,\n actAs: [],\n admin: true,\n });\n }\n\n // TODO(canton): add other prod auth methods (direct JWT passthrough, etc.).\n if (!params.oauth2) {\n throw new Error(\n 'Canton provider requires OAuth2 credentials (token-url, client-id, client-secret) in the provider URI',\n );\n }\n\n return new ClientCredentialsTokenProvider(params.oauth2);\n}\n"]}
|
package/dist/NO5PBPQH.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { cantonTransactionSchema } from './3YCNHEVD.js';
|
|
2
|
+
import { __name } from './VUOMXK5T.js';
|
|
3
|
+
import { buildTransactionWithResultSchema } from '@layerzerolabs/common-chain-model';
|
|
4
|
+
import { NonRetryableError } from '@layerzerolabs/common-error-utils';
|
|
5
|
+
|
|
6
|
+
var cantonTxWithResultSchema = buildTransactionWithResultSchema(cantonTransactionSchema);
|
|
7
|
+
var REQUEST_ARCHIVAL_POLL_INTERVAL_MS = 2e3;
|
|
8
|
+
var REQUEST_ARCHIVAL_MAX_ATTEMPTS = 60;
|
|
9
|
+
var IREQUEST_ACCEPT_CHOICE = "IRequest_Accept";
|
|
10
|
+
var IREQUEST_REJECT_CHOICE = "IRequest_Reject";
|
|
11
|
+
var REQUEST_TEMPLATE_QUALIFIED_NAME = "Request:Request";
|
|
12
|
+
var fetchCreatedEvents = /* @__PURE__ */ __name(async (client, updateId, parties) => {
|
|
13
|
+
const cantonTx = await client.fetchTransactionByUpdateId(updateId, parties);
|
|
14
|
+
return cantonTx.events?.filter((event) => "CreatedEvent" in event).map((event) => event.CreatedEvent) ?? [];
|
|
15
|
+
}, "fetchCreatedEvents");
|
|
16
|
+
var findMatchingCreatedEvent = /* @__PURE__ */ __name((createdEvents, expectedTemplateId, expectedSignatories) => {
|
|
17
|
+
const expectedSet = new Set(expectedSignatories);
|
|
18
|
+
return createdEvents.find((event) => {
|
|
19
|
+
if (event.templateId !== expectedTemplateId) return false;
|
|
20
|
+
const signatories = event.signatories ?? [];
|
|
21
|
+
return signatories.length > 0 && signatories.every((s) => expectedSet.has(s));
|
|
22
|
+
});
|
|
23
|
+
}, "findMatchingCreatedEvent");
|
|
24
|
+
var findMatchingCreatedEventFromTx = /* @__PURE__ */ __name(async (client, tx) => {
|
|
25
|
+
if (!tx) return null;
|
|
26
|
+
const parsed = cantonTxWithResultSchema.safeParse(tx);
|
|
27
|
+
if (!parsed.success) return null;
|
|
28
|
+
if (parsed.data.result.type !== "success") return null;
|
|
29
|
+
const { commands, actAs } = parsed.data.populatedTransaction;
|
|
30
|
+
const updateId = parsed.data.result.minedTxHash;
|
|
31
|
+
if (!updateId || !actAs.length) return null;
|
|
32
|
+
const createCommand = commands[0];
|
|
33
|
+
if (!createCommand || !("createCommand" in createCommand)) return null;
|
|
34
|
+
const createdEvents = await fetchCreatedEvents(client, updateId, actAs);
|
|
35
|
+
return findMatchingCreatedEvent(createdEvents, createCommand.createCommand.templateId, actAs) ?? null;
|
|
36
|
+
}, "findMatchingCreatedEventFromTx");
|
|
37
|
+
var cantonDeploymentSucceeded = /* @__PURE__ */ __name(async (client, txWithResult) => {
|
|
38
|
+
return await findMatchingCreatedEventFromTx(client, txWithResult) != null;
|
|
39
|
+
}, "cantonDeploymentSucceeded");
|
|
40
|
+
var cantonRegisterSucceeded = /* @__PURE__ */ __name(async (client, txWithResult) => {
|
|
41
|
+
const parsed = cantonTxWithResultSchema.safeParse(txWithResult);
|
|
42
|
+
if (!parsed.success || parsed.data.result.type !== "success") return false;
|
|
43
|
+
const updateId = parsed.data.result.minedTxHash;
|
|
44
|
+
if (!updateId) return false;
|
|
45
|
+
const { actAs } = parsed.data.populatedTransaction;
|
|
46
|
+
const parties = actAs.length ? actAs : await client.listUserParties();
|
|
47
|
+
if (!parties.length) return false;
|
|
48
|
+
const createdEvents = await fetchCreatedEvents(client, updateId, parties);
|
|
49
|
+
const requestEvent = createdEvents.find((event) => event.templateId?.includes(REQUEST_TEMPLATE_QUALIFIED_NAME));
|
|
50
|
+
if (!requestEvent) return false;
|
|
51
|
+
try {
|
|
52
|
+
await waitForRequestArchival(client, requestEvent.contractId, parties);
|
|
53
|
+
return true;
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}, "cantonRegisterSucceeded");
|
|
58
|
+
var waitForRequestArchival = /* @__PURE__ */ __name(async (client, requestContractId, parties) => {
|
|
59
|
+
for (let attempt = 1; attempt <= REQUEST_ARCHIVAL_MAX_ATTEMPTS; attempt++) {
|
|
60
|
+
const { archived } = await client.fetchEventsByContractId(requestContractId, parties);
|
|
61
|
+
if (archived) {
|
|
62
|
+
const transaction = await client.fetchTransactionByOffset(archived.offset, parties);
|
|
63
|
+
let interfaceChoice;
|
|
64
|
+
let interfaceArg;
|
|
65
|
+
let consumingChoice;
|
|
66
|
+
for (const event of transaction.events ?? []) {
|
|
67
|
+
if (!("ExercisedEvent" in event)) continue;
|
|
68
|
+
const ex = event.ExercisedEvent;
|
|
69
|
+
if (ex.contractId !== requestContractId) continue;
|
|
70
|
+
if (ex.choice === IREQUEST_ACCEPT_CHOICE || ex.choice === IREQUEST_REJECT_CHOICE) {
|
|
71
|
+
interfaceChoice = ex.choice;
|
|
72
|
+
interfaceArg = ex.choiceArgument;
|
|
73
|
+
}
|
|
74
|
+
if (ex.consuming) {
|
|
75
|
+
consumingChoice = ex.choice;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (interfaceChoice === IREQUEST_ACCEPT_CHOICE) {
|
|
79
|
+
return interfaceArg;
|
|
80
|
+
}
|
|
81
|
+
if (interfaceChoice === IREQUEST_REJECT_CHOICE) {
|
|
82
|
+
const rejectContext = typeof interfaceArg === "object" && interfaceArg !== null && "rejectContext" in interfaceArg ? interfaceArg.rejectContext : void 0;
|
|
83
|
+
const reason = rejectContext ? JSON.stringify(rejectContext).slice(0, 500) : "unknown";
|
|
84
|
+
throw new NonRetryableError(`Request rejected \u2014 rejectContext: ${reason}`);
|
|
85
|
+
}
|
|
86
|
+
throw new NonRetryableError(`Request ${requestContractId} archived via unexpected choice ${consumingChoice ?? "<none>"}`);
|
|
87
|
+
}
|
|
88
|
+
if (attempt < REQUEST_ARCHIVAL_MAX_ATTEMPTS) {
|
|
89
|
+
await new Promise((resolve) => setTimeout(resolve, REQUEST_ARCHIVAL_POLL_INTERVAL_MS));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw new NonRetryableError(`Request archival timeout (${REQUEST_ARCHIVAL_MAX_ATTEMPTS} attempts) for ${requestContractId}`);
|
|
93
|
+
}, "waitForRequestArchival");
|
|
94
|
+
|
|
95
|
+
export { cantonDeploymentSucceeded, cantonRegisterSucceeded, fetchCreatedEvents, findMatchingCreatedEvent, findMatchingCreatedEventFromTx, waitForRequestArchival };
|
|
96
|
+
//# sourceMappingURL=NO5PBPQH.js.map
|
|
97
|
+
//# sourceMappingURL=NO5PBPQH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/events.ts"],"names":["cantonTxWithResultSchema","buildTransactionWithResultSchema","cantonTransactionSchema","REQUEST_ARCHIVAL_POLL_INTERVAL_MS","REQUEST_ARCHIVAL_MAX_ATTEMPTS","IREQUEST_ACCEPT_CHOICE","IREQUEST_REJECT_CHOICE","REQUEST_TEMPLATE_QUALIFIED_NAME","fetchCreatedEvents","client","updateId","parties","cantonTx","fetchTransactionByUpdateId","events","filter","event","map","CreatedEvent","findMatchingCreatedEvent","createdEvents","expectedTemplateId","expectedSignatories","expectedSet","Set","find","templateId","signatories","length","every","s","has","findMatchingCreatedEventFromTx","tx","parsed","safeParse","success","data","result","type","commands","actAs","populatedTransaction","minedTxHash","createCommand","cantonDeploymentSucceeded","txWithResult","cantonRegisterSucceeded","listUserParties","requestEvent","includes","waitForRequestArchival","contractId","requestContractId","attempt","archived","fetchEventsByContractId","transaction","fetchTransactionByOffset","offset","interfaceChoice","interfaceArg","consumingChoice","ex","ExercisedEvent","choice","choiceArgument","consuming","rejectContext","undefined","reason","JSON","stringify","slice","NonRetryableError","Promise","resolve","setTimeout"],"mappings":"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","file":"NO5PBPQH.js","sourcesContent":["import { buildTransactionWithResultSchema } from '@layerzerolabs/common-chain-model';\nimport { NonRetryableError } from '@layerzerolabs/common-error-utils';\n\nimport type { ICantonClient } from './client';\nimport type { CreatedEvent } from './open-api/models';\nimport { cantonTransactionSchema } from './transaction';\n\nconst cantonTxWithResultSchema = buildTransactionWithResultSchema(cantonTransactionSchema);\n\n// TODO(canton): confirm these poll values are enough to wait for archival across environments.\nconst REQUEST_ARCHIVAL_POLL_INTERVAL_MS = 2000;\nconst REQUEST_ARCHIVAL_MAX_ATTEMPTS = 60;\n\n// IRequest interface choices that consume the Request contract via `archive reqCid` in their impls.\nconst IREQUEST_ACCEPT_CHOICE = 'IRequest_Accept';\nconst IREQUEST_REJECT_CHOICE = 'IRequest_Reject';\n\nconst REQUEST_TEMPLATE_QUALIFIED_NAME = 'Request:Request';\n\n/**\n * Fetches a transaction by updateId and returns all CreatedEvents.\n */\nexport const fetchCreatedEvents = async (\n client: ICantonClient,\n updateId: string,\n parties: string[],\n): Promise<CreatedEvent[]> => {\n const cantonTx = await client.fetchTransactionByUpdateId(updateId, parties);\n return (\n cantonTx.events\n ?.filter((event): event is { CreatedEvent: CreatedEvent } => 'CreatedEvent' in event)\n .map((event) => event.CreatedEvent) ?? []\n );\n};\n\n/**\n * Finds a CreatedEvent matching the expected templateId whose signatories\n * are a subset of the provided actAs parties.\n */\nexport const findMatchingCreatedEvent = (\n createdEvents: CreatedEvent[],\n expectedTemplateId: string,\n expectedSignatories: string[],\n): CreatedEvent | undefined => {\n const expectedSet = new Set(expectedSignatories);\n return createdEvents.find((event) => {\n if (event.templateId !== expectedTemplateId) return false;\n const signatories = event.signatories ?? [];\n return signatories.length > 0 && signatories.every((s) => expectedSet.has(s));\n });\n};\n\n/**\n * Parses a Canton transaction-with-result, fetches the created events from the ledger,\n * and returns the first CreatedEvent matching the submitted command's templateId and actAs\n * signatories. Returns null if the transaction is falsy, not successful, or has no match.\n */\nexport const findMatchingCreatedEventFromTx = async (\n client: ICantonClient,\n tx: unknown,\n): Promise<CreatedEvent | null> => {\n if (!tx) return null;\n\n const parsed = cantonTxWithResultSchema.safeParse(tx);\n if (!parsed.success) return null;\n if (parsed.data.result.type !== 'success') return null;\n\n const { commands, actAs } = parsed.data.populatedTransaction;\n const updateId = parsed.data.result.minedTxHash;\n\n if (!updateId || !actAs.length) return null;\n\n const createCommand = commands[0];\n if (!createCommand || !('createCommand' in createCommand)) return null;\n\n const createdEvents = await fetchCreatedEvents(client, updateId, actAs);\n return (\n findMatchingCreatedEvent(createdEvents, createCommand.createCommand.templateId, actAs) ??\n null\n );\n};\n\n/**\n * Checks that a Canton deploy transaction resulted in a CreatedEvent\n * matching the submitted command's templateId and actAs signatories.\n */\nexport const cantonDeploymentSucceeded = async (\n client: ICantonClient,\n txWithResult: unknown,\n): Promise<boolean> => {\n return (await findMatchingCreatedEventFromTx(client, txWithResult)) != null;\n};\n\n/**\n * Success check for register-style deploys (an exercise on Uln302 that creates a\n * Request, which the validator then accepts or rejects in a later transaction).\n *\n * Positively confirms acceptance: locate the Request created by this submission,\n * then delegate to waitForRequestArchival, which waits for the Request to be\n * archived and verifies the IRequest_Accept choice ran (throwing on reject,\n * unexpected archival, or timeout). This is a positive accept check rather than\n * inferring success from the absence of a rejection event.\n */\nexport const cantonRegisterSucceeded = async (\n client: ICantonClient,\n txWithResult: unknown,\n): Promise<boolean> => {\n const parsed = cantonTxWithResultSchema.safeParse(txWithResult);\n if (!parsed.success || parsed.data.result.type !== 'success') return false;\n\n const updateId = parsed.data.result.minedTxHash;\n if (!updateId) return false;\n\n // Deferred-sender register payloads build with empty actAs (the sender is\n // resolved at sign time), so fall back to the parties this user can read as.\n const { actAs } = parsed.data.populatedTransaction;\n const parties = actAs.length ? actAs : await client.listUserParties();\n if (!parties.length) return false;\n\n const createdEvents = await fetchCreatedEvents(client, updateId, parties);\n const requestEvent = createdEvents.find((event) =>\n event.templateId?.includes(REQUEST_TEMPLATE_QUALIFIED_NAME),\n );\n if (!requestEvent) return false;\n\n try {\n await waitForRequestArchival(client, requestEvent.contractId, parties);\n return true;\n } catch {\n return false;\n }\n};\n\n/**\n * Wait for a Request contract to be archived AND determine accept vs reject\n * by inspecting which IRequest choice ran on it.\n *\n * IRequest_Accept and IRequest_Reject are nonconsuming choices that both end\n * with `archive reqCid` in their implementations. We can't tell accept from\n * reject from the archive alone — must look at the ExercisedEvent in the\n * archive transaction tree.\n *\n * On accept, returns the IRequest_Accept choice argument (the accept call\n * context, including `acceptContext`) so callers can extract the validator's\n * internal address from it.\n *\n * Throws NonRetryableError on rejection, on archival via a non-Accept/Reject\n * path (unexpected), or on timeout.\n */\nexport const waitForRequestArchival = async (\n client: ICantonClient,\n requestContractId: string,\n parties: string[],\n): Promise<unknown> => {\n for (let attempt = 1; attempt <= REQUEST_ARCHIVAL_MAX_ATTEMPTS; attempt++) {\n const { archived } = await client.fetchEventsByContractId(requestContractId, parties);\n if (archived) {\n const transaction = await client.fetchTransactionByOffset(archived.offset, parties);\n // IRequest_Accept and IRequest_Reject are *nonconsuming* interface\n // choices on IRequest. Their template impls (Request.daml's\n // request_AcceptImpl / request_RejectImpl) end with `archive reqCid`,\n // which Canton records as a consuming exercise of the implicit\n // `Archive` choice. So in the transaction tree we see TWO exercises\n // on the Request contract:\n // - a non-consuming `IRequest_Accept` (or `IRequest_Reject`)\n // - a consuming `Archive` (the actual archival)\n // Scan for any IRequest_Accept/Reject exercise on it in the\n // tree to determine the outcome, regardless of which one consumed.\n let interfaceChoice: string | undefined;\n let interfaceArg: unknown;\n let consumingChoice: string | undefined;\n for (const event of transaction.events ?? []) {\n if (!('ExercisedEvent' in event)) continue;\n const ex = event.ExercisedEvent;\n if (ex.contractId !== requestContractId) continue;\n if (ex.choice === IREQUEST_ACCEPT_CHOICE || ex.choice === IREQUEST_REJECT_CHOICE) {\n interfaceChoice = ex.choice;\n interfaceArg = ex.choiceArgument;\n }\n if (ex.consuming) {\n consumingChoice = ex.choice;\n }\n }\n if (interfaceChoice === IREQUEST_ACCEPT_CHOICE) {\n return interfaceArg;\n }\n if (interfaceChoice === IREQUEST_REJECT_CHOICE) {\n const rejectContext =\n typeof interfaceArg === 'object' &&\n interfaceArg !== null &&\n 'rejectContext' in interfaceArg\n ? interfaceArg.rejectContext\n : undefined;\n const reason = rejectContext\n ? JSON.stringify(rejectContext).slice(0, 500)\n : 'unknown';\n throw new NonRetryableError(`Request rejected — rejectContext: ${reason}`);\n }\n // No IRequest_Accept/Reject ran on this Request — it was archived\n // via some other path (default `Archive`, or another consuming\n // choice). That's unexpected for register-style flows — treat as\n // failure so we don't silently call this a success.\n throw new NonRetryableError(\n `Request ${requestContractId} archived via unexpected choice ${consumingChoice ?? '<none>'}`,\n );\n }\n // Skip the sleep on the final attempt — there's no next iteration to\n // wait for, so it would only delay the timeout error pointlessly.\n if (attempt < REQUEST_ARCHIVAL_MAX_ATTEMPTS) {\n await new Promise((resolve) => setTimeout(resolve, REQUEST_ARCHIVAL_POLL_INTERVAL_MS));\n }\n }\n throw new NonRetryableError(\n `Request archival timeout (${REQUEST_ARCHIVAL_MAX_ATTEMPTS} attempts) for ${requestContractId}`,\n );\n};\n"]}
|
package/dist/events.d.ts
CHANGED
|
@@ -20,4 +20,32 @@ export declare const findMatchingCreatedEventFromTx: (client: ICantonClient, tx:
|
|
|
20
20
|
* matching the submitted command's templateId and actAs signatories.
|
|
21
21
|
*/
|
|
22
22
|
export declare const cantonDeploymentSucceeded: (client: ICantonClient, txWithResult: unknown) => Promise<boolean>;
|
|
23
|
+
/**
|
|
24
|
+
* Success check for register-style deploys (an exercise on Uln302 that creates a
|
|
25
|
+
* Request, which the validator then accepts or rejects in a later transaction).
|
|
26
|
+
*
|
|
27
|
+
* Positively confirms acceptance: locate the Request created by this submission,
|
|
28
|
+
* then delegate to waitForRequestArchival, which waits for the Request to be
|
|
29
|
+
* archived and verifies the IRequest_Accept choice ran (throwing on reject,
|
|
30
|
+
* unexpected archival, or timeout). This is a positive accept check rather than
|
|
31
|
+
* inferring success from the absence of a rejection event.
|
|
32
|
+
*/
|
|
33
|
+
export declare const cantonRegisterSucceeded: (client: ICantonClient, txWithResult: unknown) => Promise<boolean>;
|
|
34
|
+
/**
|
|
35
|
+
* Wait for a Request contract to be archived AND determine accept vs reject
|
|
36
|
+
* by inspecting which IRequest choice ran on it.
|
|
37
|
+
*
|
|
38
|
+
* IRequest_Accept and IRequest_Reject are nonconsuming choices that both end
|
|
39
|
+
* with `archive reqCid` in their implementations. We can't tell accept from
|
|
40
|
+
* reject from the archive alone — must look at the ExercisedEvent in the
|
|
41
|
+
* archive transaction tree.
|
|
42
|
+
*
|
|
43
|
+
* On accept, returns the IRequest_Accept choice argument (the accept call
|
|
44
|
+
* context, including `acceptContext`) so callers can extract the validator's
|
|
45
|
+
* internal address from it.
|
|
46
|
+
*
|
|
47
|
+
* Throws NonRetryableError on rejection, on archival via a non-Accept/Reject
|
|
48
|
+
* path (unexpected), or on timeout.
|
|
49
|
+
*/
|
|
50
|
+
export declare const waitForRequestArchival: (client: ICantonClient, requestContractId: string, parties: string[]) => Promise<unknown>;
|
|
23
51
|
//# sourceMappingURL=events.d.ts.map
|
package/dist/events.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAetD;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAC3B,QAAQ,aAAa,EACrB,UAAU,MAAM,EAChB,SAAS,MAAM,EAAE,KAClB,OAAO,CAAC,YAAY,EAAE,CAOxB,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GACjC,eAAe,YAAY,EAAE,EAC7B,oBAAoB,MAAM,EAC1B,qBAAqB,MAAM,EAAE,KAC9B,YAAY,GAAG,SAOjB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,GACvC,QAAQ,aAAa,EACrB,IAAI,OAAO,KACZ,OAAO,CAAC,YAAY,GAAG,IAAI,CAoB7B,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,yBAAyB,GAClC,QAAQ,aAAa,EACrB,cAAc,OAAO,KACtB,OAAO,CAAC,OAAO,CAEjB,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,GAChC,QAAQ,aAAa,EACrB,cAAc,OAAO,KACtB,OAAO,CAAC,OAAO,CAyBjB,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAC/B,QAAQ,aAAa,EACrB,mBAAmB,MAAM,EACzB,SAAS,MAAM,EAAE,KAClB,OAAO,CAAC,OAAO,CA8DjB,CAAC"}
|
package/dist/events.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { cantonDeploymentSucceeded, fetchCreatedEvents, findMatchingCreatedEvent, findMatchingCreatedEventFromTx } from './
|
|
1
|
+
export { cantonDeploymentSucceeded, cantonRegisterSucceeded, fetchCreatedEvents, findMatchingCreatedEvent, findMatchingCreatedEventFromTx, waitForRequestArchival } from './NO5PBPQH.js';
|
|
2
2
|
import './3YCNHEVD.js';
|
|
3
3
|
import './VUOMXK5T.js';
|
|
4
4
|
//# sourceMappingURL=events.js.map
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,8 @@ import './RSV7WS7X.js';
|
|
|
5
5
|
import './DYPWWRBK.js';
|
|
6
6
|
import './IIWYAFUO.js';
|
|
7
7
|
export { computeCantonFingerprint, extractTransactions } from './QHEXOE3Y.js';
|
|
8
|
-
export { cantonDeploymentSucceeded, fetchCreatedEvents, findMatchingCreatedEvent, findMatchingCreatedEventFromTx } from './
|
|
9
|
-
export { createCantonProvider, parseCantonUri } from './
|
|
8
|
+
export { cantonDeploymentSucceeded, cantonRegisterSucceeded, fetchCreatedEvents, findMatchingCreatedEvent, findMatchingCreatedEventFromTx, waitForRequestArchival } from './NO5PBPQH.js';
|
|
9
|
+
export { createCantonProvider, parseCantonUri } from './HNZRSDTR.js';
|
|
10
10
|
export { LocalTokenProvider } from './SCEF7TLZ.js';
|
|
11
11
|
export { CantonClient } from './JV2DWUBW.js';
|
|
12
12
|
import './EIXIZULE.js';
|
package/dist/provider.d.ts
CHANGED
|
@@ -13,9 +13,21 @@ export type CantonProvider = {
|
|
|
13
13
|
client: ICantonClient;
|
|
14
14
|
grpcClient: CantonGrpcClient;
|
|
15
15
|
tokenProvider: TokenProvider;
|
|
16
|
-
/** Validator wallet URL for scan-proxy CIP-56 endpoints. Required
|
|
17
|
-
* payment needs it on every Canton transaction; enforced in {@link parseCantonUri}. */
|
|
16
|
+
/** Validator wallet URL for scan-proxy CIP-56 endpoints. Required. */
|
|
18
17
|
walletUrl: string;
|
|
18
|
+
/** LayerZero sequencer base URL (e.g. "http://localhost:8080"); vApp JSON-RPC at `/vapp`. Required. */
|
|
19
|
+
sequencerUrl: string;
|
|
20
|
+
/** Sequencer validator pubkeys plus quorum for verifying signed read responses. */
|
|
21
|
+
sequencerCommittee?: SequencerCommittee;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Client-side committee config for secp256k1 sequencer read verification.
|
|
25
|
+
* Pubkeys must be hex-encoded with `0x` prefix in the same format returned by
|
|
26
|
+
* `recoverSecp256k1PublicKey`.
|
|
27
|
+
*/
|
|
28
|
+
export type SequencerCommittee = {
|
|
29
|
+
publicKeys: `0x${string}`[];
|
|
30
|
+
quorum: number;
|
|
19
31
|
};
|
|
20
32
|
export type CantonUriParams = {
|
|
21
33
|
/** Base URL of the JSON Ledger API (e.g. "http://localhost:5975") */
|
|
@@ -24,8 +36,12 @@ export type CantonUriParams = {
|
|
|
24
36
|
adminApiHost: string;
|
|
25
37
|
/** Ledger API user ID (default: "participant_admin") */
|
|
26
38
|
userId: string;
|
|
27
|
-
/** Validator wallet URL for scan-proxy CIP-56 endpoints (e.g. "http://localhost:7201") */
|
|
39
|
+
/** Validator wallet URL for scan-proxy CIP-56 endpoints (e.g. "http://localhost:7201"). Required. */
|
|
28
40
|
walletUrl: string;
|
|
41
|
+
/** LayerZero sequencer base URL (e.g. "http://localhost:8080"); vApp JSON-RPC at `/vapp`. Required. */
|
|
42
|
+
sequencerUrl: string;
|
|
43
|
+
/** Sequencer committee config parsed from the provider URI. */
|
|
44
|
+
sequencerCommittee?: SequencerCommittee;
|
|
29
45
|
/** OAuth2 client credentials config — present only for non-local environments */
|
|
30
46
|
oauth2?: ClientCredentialsTokenProviderConfig;
|
|
31
47
|
};
|
package/dist/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,oCAAoC,EAC5C,MAAM,0CAA0C,CAAC;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAOxD;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG;IACzB,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,aAAa,EAAE,aAAa,CAAC;IAC7B;
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,oCAAoC,EAC5C,MAAM,0CAA0C,CAAC;AAElD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAOxD;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG;IACzB,MAAM,EAAE,aAAa,CAAC;IACtB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,aAAa,EAAE,aAAa,CAAC;IAC7B,sEAAsE;IACtE,SAAS,EAAE,MAAM,CAAC;IAClB,uGAAuG;IACvG,YAAY,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;CAC3C,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC7B,UAAU,EAAE,KAAK,MAAM,EAAE,EAAE,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;CAClB,CAAC;AAMF,MAAM,MAAM,eAAe,GAAG;IAC1B,qEAAqE;IACrE,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,YAAY,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,qGAAqG;IACrG,SAAS,EAAE,MAAM,CAAC;IAClB,uGAAuG;IACvG,YAAY,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,iFAAiF;IACjF,MAAM,CAAC,EAAE,oCAAoC,CAAC;CACjD,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,CAkG3D;AAMD,MAAM,MAAM,2BAA2B,GAAG;IACtC,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,OAAO,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,EACjC,GAAG,EACH,OAAO,GACV,EAAE,2BAA2B,GAAG,cAAc,CA0B9C"}
|
package/dist/provider.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@layerzerolabs/common-canton",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.107",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Canton VM client and utilities",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
"@protobuf-ts/grpc-transport": "^2.9.4",
|
|
22
22
|
"es-toolkit": "^1.39.10",
|
|
23
23
|
"zod": "4.3.5",
|
|
24
|
-
"@layerzerolabs/
|
|
25
|
-
"@layerzerolabs/
|
|
24
|
+
"@layerzerolabs/common-error-utils": "0.2.107",
|
|
25
|
+
"@layerzerolabs/typescript-utils": "0.2.107",
|
|
26
|
+
"@layerzerolabs/common-chain-model": "0.2.107"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"@types/node": "^22.18.6",
|
|
@@ -31,12 +32,12 @@
|
|
|
31
32
|
"tsup": "^8.4.0",
|
|
32
33
|
"typescript": "^5.8.2",
|
|
33
34
|
"vitest": "^3.2.3",
|
|
34
|
-
"@layerzerolabs/
|
|
35
|
-
"@layerzerolabs/tsup-configuration": "0.2.
|
|
36
|
-
"@layerzerolabs/
|
|
37
|
-
"@layerzerolabs/ver-signer-adapter": "0.2.
|
|
38
|
-
"@layerzerolabs/vitest-configuration": "0.2.
|
|
39
|
-
"@layerzerolabs/vm-tooling-canton": "0.2.
|
|
35
|
+
"@layerzerolabs/eslint-configuration": "^0.2.107",
|
|
36
|
+
"@layerzerolabs/tsup-configuration": "0.2.107",
|
|
37
|
+
"@layerzerolabs/typescript-configuration": "0.2.107",
|
|
38
|
+
"@layerzerolabs/ver-signer-adapter": "0.2.107",
|
|
39
|
+
"@layerzerolabs/vitest-configuration": "0.2.107",
|
|
40
|
+
"@layerzerolabs/vm-tooling-canton": "0.2.107"
|
|
40
41
|
},
|
|
41
42
|
"publishConfig": {
|
|
42
43
|
"access": "restricted",
|
package/src/events.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildTransactionWithResultSchema } from '@layerzerolabs/common-chain-model';
|
|
2
|
+
import { NonRetryableError } from '@layerzerolabs/common-error-utils';
|
|
2
3
|
|
|
3
4
|
import type { ICantonClient } from './client';
|
|
4
5
|
import type { CreatedEvent } from './open-api/models';
|
|
@@ -6,6 +7,16 @@ import { cantonTransactionSchema } from './transaction';
|
|
|
6
7
|
|
|
7
8
|
const cantonTxWithResultSchema = buildTransactionWithResultSchema(cantonTransactionSchema);
|
|
8
9
|
|
|
10
|
+
// TODO(canton): confirm these poll values are enough to wait for archival across environments.
|
|
11
|
+
const REQUEST_ARCHIVAL_POLL_INTERVAL_MS = 2000;
|
|
12
|
+
const REQUEST_ARCHIVAL_MAX_ATTEMPTS = 60;
|
|
13
|
+
|
|
14
|
+
// IRequest interface choices that consume the Request contract via `archive reqCid` in their impls.
|
|
15
|
+
const IREQUEST_ACCEPT_CHOICE = 'IRequest_Accept';
|
|
16
|
+
const IREQUEST_REJECT_CHOICE = 'IRequest_Reject';
|
|
17
|
+
|
|
18
|
+
const REQUEST_TEMPLATE_QUALIFIED_NAME = 'Request:Request';
|
|
19
|
+
|
|
9
20
|
/**
|
|
10
21
|
* Fetches a transaction by updateId and returns all CreatedEvents.
|
|
11
22
|
*/
|
|
@@ -79,3 +90,127 @@ export const cantonDeploymentSucceeded = async (
|
|
|
79
90
|
): Promise<boolean> => {
|
|
80
91
|
return (await findMatchingCreatedEventFromTx(client, txWithResult)) != null;
|
|
81
92
|
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Success check for register-style deploys (an exercise on Uln302 that creates a
|
|
96
|
+
* Request, which the validator then accepts or rejects in a later transaction).
|
|
97
|
+
*
|
|
98
|
+
* Positively confirms acceptance: locate the Request created by this submission,
|
|
99
|
+
* then delegate to waitForRequestArchival, which waits for the Request to be
|
|
100
|
+
* archived and verifies the IRequest_Accept choice ran (throwing on reject,
|
|
101
|
+
* unexpected archival, or timeout). This is a positive accept check rather than
|
|
102
|
+
* inferring success from the absence of a rejection event.
|
|
103
|
+
*/
|
|
104
|
+
export const cantonRegisterSucceeded = async (
|
|
105
|
+
client: ICantonClient,
|
|
106
|
+
txWithResult: unknown,
|
|
107
|
+
): Promise<boolean> => {
|
|
108
|
+
const parsed = cantonTxWithResultSchema.safeParse(txWithResult);
|
|
109
|
+
if (!parsed.success || parsed.data.result.type !== 'success') return false;
|
|
110
|
+
|
|
111
|
+
const updateId = parsed.data.result.minedTxHash;
|
|
112
|
+
if (!updateId) return false;
|
|
113
|
+
|
|
114
|
+
// Deferred-sender register payloads build with empty actAs (the sender is
|
|
115
|
+
// resolved at sign time), so fall back to the parties this user can read as.
|
|
116
|
+
const { actAs } = parsed.data.populatedTransaction;
|
|
117
|
+
const parties = actAs.length ? actAs : await client.listUserParties();
|
|
118
|
+
if (!parties.length) return false;
|
|
119
|
+
|
|
120
|
+
const createdEvents = await fetchCreatedEvents(client, updateId, parties);
|
|
121
|
+
const requestEvent = createdEvents.find((event) =>
|
|
122
|
+
event.templateId?.includes(REQUEST_TEMPLATE_QUALIFIED_NAME),
|
|
123
|
+
);
|
|
124
|
+
if (!requestEvent) return false;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
await waitForRequestArchival(client, requestEvent.contractId, parties);
|
|
128
|
+
return true;
|
|
129
|
+
} catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Wait for a Request contract to be archived AND determine accept vs reject
|
|
136
|
+
* by inspecting which IRequest choice ran on it.
|
|
137
|
+
*
|
|
138
|
+
* IRequest_Accept and IRequest_Reject are nonconsuming choices that both end
|
|
139
|
+
* with `archive reqCid` in their implementations. We can't tell accept from
|
|
140
|
+
* reject from the archive alone — must look at the ExercisedEvent in the
|
|
141
|
+
* archive transaction tree.
|
|
142
|
+
*
|
|
143
|
+
* On accept, returns the IRequest_Accept choice argument (the accept call
|
|
144
|
+
* context, including `acceptContext`) so callers can extract the validator's
|
|
145
|
+
* internal address from it.
|
|
146
|
+
*
|
|
147
|
+
* Throws NonRetryableError on rejection, on archival via a non-Accept/Reject
|
|
148
|
+
* path (unexpected), or on timeout.
|
|
149
|
+
*/
|
|
150
|
+
export const waitForRequestArchival = async (
|
|
151
|
+
client: ICantonClient,
|
|
152
|
+
requestContractId: string,
|
|
153
|
+
parties: string[],
|
|
154
|
+
): Promise<unknown> => {
|
|
155
|
+
for (let attempt = 1; attempt <= REQUEST_ARCHIVAL_MAX_ATTEMPTS; attempt++) {
|
|
156
|
+
const { archived } = await client.fetchEventsByContractId(requestContractId, parties);
|
|
157
|
+
if (archived) {
|
|
158
|
+
const transaction = await client.fetchTransactionByOffset(archived.offset, parties);
|
|
159
|
+
// IRequest_Accept and IRequest_Reject are *nonconsuming* interface
|
|
160
|
+
// choices on IRequest. Their template impls (Request.daml's
|
|
161
|
+
// request_AcceptImpl / request_RejectImpl) end with `archive reqCid`,
|
|
162
|
+
// which Canton records as a consuming exercise of the implicit
|
|
163
|
+
// `Archive` choice. So in the transaction tree we see TWO exercises
|
|
164
|
+
// on the Request contract:
|
|
165
|
+
// - a non-consuming `IRequest_Accept` (or `IRequest_Reject`)
|
|
166
|
+
// - a consuming `Archive` (the actual archival)
|
|
167
|
+
// Scan for any IRequest_Accept/Reject exercise on it in the
|
|
168
|
+
// tree to determine the outcome, regardless of which one consumed.
|
|
169
|
+
let interfaceChoice: string | undefined;
|
|
170
|
+
let interfaceArg: unknown;
|
|
171
|
+
let consumingChoice: string | undefined;
|
|
172
|
+
for (const event of transaction.events ?? []) {
|
|
173
|
+
if (!('ExercisedEvent' in event)) continue;
|
|
174
|
+
const ex = event.ExercisedEvent;
|
|
175
|
+
if (ex.contractId !== requestContractId) continue;
|
|
176
|
+
if (ex.choice === IREQUEST_ACCEPT_CHOICE || ex.choice === IREQUEST_REJECT_CHOICE) {
|
|
177
|
+
interfaceChoice = ex.choice;
|
|
178
|
+
interfaceArg = ex.choiceArgument;
|
|
179
|
+
}
|
|
180
|
+
if (ex.consuming) {
|
|
181
|
+
consumingChoice = ex.choice;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
if (interfaceChoice === IREQUEST_ACCEPT_CHOICE) {
|
|
185
|
+
return interfaceArg;
|
|
186
|
+
}
|
|
187
|
+
if (interfaceChoice === IREQUEST_REJECT_CHOICE) {
|
|
188
|
+
const rejectContext =
|
|
189
|
+
typeof interfaceArg === 'object' &&
|
|
190
|
+
interfaceArg !== null &&
|
|
191
|
+
'rejectContext' in interfaceArg
|
|
192
|
+
? interfaceArg.rejectContext
|
|
193
|
+
: undefined;
|
|
194
|
+
const reason = rejectContext
|
|
195
|
+
? JSON.stringify(rejectContext).slice(0, 500)
|
|
196
|
+
: 'unknown';
|
|
197
|
+
throw new NonRetryableError(`Request rejected — rejectContext: ${reason}`);
|
|
198
|
+
}
|
|
199
|
+
// No IRequest_Accept/Reject ran on this Request — it was archived
|
|
200
|
+
// via some other path (default `Archive`, or another consuming
|
|
201
|
+
// choice). That's unexpected for register-style flows — treat as
|
|
202
|
+
// failure so we don't silently call this a success.
|
|
203
|
+
throw new NonRetryableError(
|
|
204
|
+
`Request ${requestContractId} archived via unexpected choice ${consumingChoice ?? '<none>'}`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
// Skip the sleep on the final attempt — there's no next iteration to
|
|
208
|
+
// wait for, so it would only delay the timeout error pointlessly.
|
|
209
|
+
if (attempt < REQUEST_ARCHIVAL_MAX_ATTEMPTS) {
|
|
210
|
+
await new Promise((resolve) => setTimeout(resolve, REQUEST_ARCHIVAL_POLL_INTERVAL_MS));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
throw new NonRetryableError(
|
|
214
|
+
`Request archival timeout (${REQUEST_ARCHIVAL_MAX_ATTEMPTS} attempts) for ${requestContractId}`,
|
|
215
|
+
);
|
|
216
|
+
};
|