@did-btcr2/aggregation 0.1.0
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/LICENSE +373 -0
- package/README.md +19 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/browser.js +24017 -0
- package/dist/browser.mjs +24011 -0
- package/dist/cjs/index.js +5690 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/beacon-strategy.js +76 -0
- package/dist/esm/beacon-strategy.js.map +1 -0
- package/dist/esm/cohort.js +316 -0
- package/dist/esm/cohort.js.map +1 -0
- package/dist/esm/conditions.js +102 -0
- package/dist/esm/conditions.js.map +1 -0
- package/dist/esm/errors.js +22 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/fallback-spend.js +132 -0
- package/dist/esm/fallback-spend.js.map +1 -0
- package/dist/esm/index.js +21 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/logger.js +15 -0
- package/dist/esm/logger.js.map +1 -0
- package/dist/esm/messages/base.js +36 -0
- package/dist/esm/messages/base.js.map +1 -0
- package/dist/esm/messages/bodies.js +149 -0
- package/dist/esm/messages/bodies.js.map +1 -0
- package/dist/esm/messages/constants.js +37 -0
- package/dist/esm/messages/constants.js.map +1 -0
- package/dist/esm/messages/factories.js +139 -0
- package/dist/esm/messages/factories.js.map +1 -0
- package/dist/esm/messages/guards.js +40 -0
- package/dist/esm/messages/guards.js.map +1 -0
- package/dist/esm/messages/index.js +6 -0
- package/dist/esm/messages/index.js.map +1 -0
- package/dist/esm/participant.js +588 -0
- package/dist/esm/participant.js.map +1 -0
- package/dist/esm/phases.js +45 -0
- package/dist/esm/phases.js.map +1 -0
- package/dist/esm/recovery-policy.js +156 -0
- package/dist/esm/recovery-policy.js.map +1 -0
- package/dist/esm/recovery-spend.js +106 -0
- package/dist/esm/recovery-spend.js.map +1 -0
- package/dist/esm/runner/aggregation-runner.js +75 -0
- package/dist/esm/runner/aggregation-runner.js.map +1 -0
- package/dist/esm/runner/events.js +2 -0
- package/dist/esm/runner/events.js.map +1 -0
- package/dist/esm/runner/index.js +6 -0
- package/dist/esm/runner/index.js.map +1 -0
- package/dist/esm/runner/participant-runner.js +407 -0
- package/dist/esm/runner/participant-runner.js.map +1 -0
- package/dist/esm/runner/service-runner.js +701 -0
- package/dist/esm/runner/service-runner.js.map +1 -0
- package/dist/esm/runner/typed-emitter.js +80 -0
- package/dist/esm/runner/typed-emitter.js.map +1 -0
- package/dist/esm/service.js +774 -0
- package/dist/esm/service.js.map +1 -0
- package/dist/esm/signer.js +33 -0
- package/dist/esm/signer.js.map +1 -0
- package/dist/esm/signing-session.js +195 -0
- package/dist/esm/signing-session.js.map +1 -0
- package/dist/esm/transport/didcomm.js +41 -0
- package/dist/esm/transport/didcomm.js.map +1 -0
- package/dist/esm/transport/error.js +12 -0
- package/dist/esm/transport/error.js.map +1 -0
- package/dist/esm/transport/factory.js +29 -0
- package/dist/esm/transport/factory.js.map +1 -0
- package/dist/esm/transport/http/client.js +342 -0
- package/dist/esm/transport/http/client.js.map +1 -0
- package/dist/esm/transport/http/envelope.js +126 -0
- package/dist/esm/transport/http/envelope.js.map +1 -0
- package/dist/esm/transport/http/errors.js +11 -0
- package/dist/esm/transport/http/errors.js.map +1 -0
- package/dist/esm/transport/http/inbox-buffer.js +45 -0
- package/dist/esm/transport/http/inbox-buffer.js.map +1 -0
- package/dist/esm/transport/http/index.js +12 -0
- package/dist/esm/transport/http/index.js.map +1 -0
- package/dist/esm/transport/http/nonce-cache.js +38 -0
- package/dist/esm/transport/http/nonce-cache.js.map +1 -0
- package/dist/esm/transport/http/protocol.js +28 -0
- package/dist/esm/transport/http/protocol.js.map +1 -0
- package/dist/esm/transport/http/rate-limiter.js +45 -0
- package/dist/esm/transport/http/rate-limiter.js.map +1 -0
- package/dist/esm/transport/http/request-auth.js +100 -0
- package/dist/esm/transport/http/request-auth.js.map +1 -0
- package/dist/esm/transport/http/server.js +475 -0
- package/dist/esm/transport/http/server.js.map +1 -0
- package/dist/esm/transport/http/sse-stream.js +110 -0
- package/dist/esm/transport/http/sse-stream.js.map +1 -0
- package/dist/esm/transport/http/sse-writer.js +25 -0
- package/dist/esm/transport/http/sse-writer.js.map +1 -0
- package/dist/esm/transport/in-memory.js +149 -0
- package/dist/esm/transport/in-memory.js.map +1 -0
- package/dist/esm/transport/index.js +8 -0
- package/dist/esm/transport/index.js.map +1 -0
- package/dist/esm/transport/nostr.js +491 -0
- package/dist/esm/transport/nostr.js.map +1 -0
- package/dist/esm/transport/transport.js +2 -0
- package/dist/esm/transport/transport.js.map +1 -0
- package/dist/types/beacon-strategy.d.ts +56 -0
- package/dist/types/beacon-strategy.d.ts.map +1 -0
- package/dist/types/cohort.d.ts +188 -0
- package/dist/types/cohort.d.ts.map +1 -0
- package/dist/types/conditions.d.ts +86 -0
- package/dist/types/conditions.d.ts.map +1 -0
- package/dist/types/errors.d.ts +14 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/fallback-spend.d.ts +94 -0
- package/dist/types/fallback-spend.d.ts.map +1 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/logger.d.ts +22 -0
- package/dist/types/logger.d.ts.map +1 -0
- package/dist/types/messages/base.d.ts +58 -0
- package/dist/types/messages/base.d.ts.map +1 -0
- package/dist/types/messages/bodies.d.ts +177 -0
- package/dist/types/messages/bodies.d.ts.map +1 -0
- package/dist/types/messages/constants.d.ts +31 -0
- package/dist/types/messages/constants.d.ts.map +1 -0
- package/dist/types/messages/factories.d.ts +217 -0
- package/dist/types/messages/factories.d.ts.map +1 -0
- package/dist/types/messages/guards.d.ts +11 -0
- package/dist/types/messages/guards.d.ts.map +1 -0
- package/dist/types/messages/index.d.ts +6 -0
- package/dist/types/messages/index.d.ts.map +1 -0
- package/dist/types/participant.d.ts +162 -0
- package/dist/types/participant.d.ts.map +1 -0
- package/dist/types/phases.d.ts +55 -0
- package/dist/types/phases.d.ts.map +1 -0
- package/dist/types/recovery-policy.d.ts +132 -0
- package/dist/types/recovery-policy.d.ts.map +1 -0
- package/dist/types/recovery-spend.d.ts +94 -0
- package/dist/types/recovery-spend.d.ts.map +1 -0
- package/dist/types/runner/aggregation-runner.d.ts +72 -0
- package/dist/types/runner/aggregation-runner.d.ts.map +1 -0
- package/dist/types/runner/events.d.ts +147 -0
- package/dist/types/runner/events.d.ts.map +1 -0
- package/dist/types/runner/index.d.ts +6 -0
- package/dist/types/runner/index.d.ts.map +1 -0
- package/dist/types/runner/participant-runner.d.ts +122 -0
- package/dist/types/runner/participant-runner.d.ts.map +1 -0
- package/dist/types/runner/service-runner.d.ts +228 -0
- package/dist/types/runner/service-runner.d.ts.map +1 -0
- package/dist/types/runner/typed-emitter.d.ts +41 -0
- package/dist/types/runner/typed-emitter.d.ts.map +1 -0
- package/dist/types/service.d.ts +177 -0
- package/dist/types/service.d.ts.map +1 -0
- package/dist/types/signer.d.ts +50 -0
- package/dist/types/signer.d.ts.map +1 -0
- package/dist/types/signing-session.d.ts +81 -0
- package/dist/types/signing-session.d.ts.map +1 -0
- package/dist/types/transport/didcomm.d.ts +23 -0
- package/dist/types/transport/didcomm.d.ts.map +1 -0
- package/dist/types/transport/error.d.ts +8 -0
- package/dist/types/transport/error.d.ts.map +1 -0
- package/dist/types/transport/factory.d.ts +28 -0
- package/dist/types/transport/factory.d.ts.map +1 -0
- package/dist/types/transport/http/client.d.ts +56 -0
- package/dist/types/transport/http/client.d.ts.map +1 -0
- package/dist/types/transport/http/envelope.d.ts +64 -0
- package/dist/types/transport/http/envelope.d.ts.map +1 -0
- package/dist/types/transport/http/errors.d.ts +9 -0
- package/dist/types/transport/http/errors.d.ts.map +1 -0
- package/dist/types/transport/http/inbox-buffer.d.ts +32 -0
- package/dist/types/transport/http/inbox-buffer.d.ts.map +1 -0
- package/dist/types/transport/http/index.d.ts +12 -0
- package/dist/types/transport/http/index.d.ts.map +1 -0
- package/dist/types/transport/http/nonce-cache.d.ts +26 -0
- package/dist/types/transport/http/nonce-cache.d.ts.map +1 -0
- package/dist/types/transport/http/protocol.d.ts +53 -0
- package/dist/types/transport/http/protocol.d.ts.map +1 -0
- package/dist/types/transport/http/rate-limiter.d.ts +41 -0
- package/dist/types/transport/http/rate-limiter.d.ts.map +1 -0
- package/dist/types/transport/http/request-auth.d.ts +50 -0
- package/dist/types/transport/http/request-auth.d.ts.map +1 -0
- package/dist/types/transport/http/server.d.ts +118 -0
- package/dist/types/transport/http/server.d.ts.map +1 -0
- package/dist/types/transport/http/sse-stream.d.ts +34 -0
- package/dist/types/transport/http/sse-stream.d.ts.map +1 -0
- package/dist/types/transport/http/sse-writer.d.ts +12 -0
- package/dist/types/transport/http/sse-writer.d.ts.map +1 -0
- package/dist/types/transport/in-memory.d.ts +64 -0
- package/dist/types/transport/in-memory.d.ts.map +1 -0
- package/dist/types/transport/index.d.ts +8 -0
- package/dist/types/transport/index.d.ts.map +1 -0
- package/dist/types/transport/nostr.d.ts +153 -0
- package/dist/types/transport/nostr.d.ts.map +1 -0
- package/dist/types/transport/transport.d.ts +62 -0
- package/dist/types/transport/transport.d.ts.map +1 -0
- package/package.json +128 -0
- package/src/beacon-strategy.ts +142 -0
- package/src/cohort.ts +422 -0
- package/src/conditions.ts +172 -0
- package/src/errors.ts +25 -0
- package/src/fallback-spend.ts +223 -0
- package/src/index.ts +23 -0
- package/src/logger.ts +33 -0
- package/src/messages/base.ts +80 -0
- package/src/messages/bodies.ts +328 -0
- package/src/messages/constants.ts +40 -0
- package/src/messages/factories.ts +297 -0
- package/src/messages/guards.ts +61 -0
- package/src/messages/index.ts +5 -0
- package/src/participant.ts +779 -0
- package/src/phases.ts +91 -0
- package/src/recovery-policy.ts +237 -0
- package/src/recovery-spend.ts +192 -0
- package/src/runner/aggregation-runner.ts +125 -0
- package/src/runner/events.ts +129 -0
- package/src/runner/index.ts +5 -0
- package/src/runner/participant-runner.ts +488 -0
- package/src/runner/service-runner.ts +891 -0
- package/src/runner/typed-emitter.ts +87 -0
- package/src/service.ts +1000 -0
- package/src/signer.ts +67 -0
- package/src/signing-session.ts +290 -0
- package/src/transport/didcomm.ts +59 -0
- package/src/transport/error.ts +13 -0
- package/src/transport/factory.ts +65 -0
- package/src/transport/http/client.ts +409 -0
- package/src/transport/http/envelope.ts +204 -0
- package/src/transport/http/errors.ts +11 -0
- package/src/transport/http/inbox-buffer.ts +53 -0
- package/src/transport/http/index.ts +11 -0
- package/src/transport/http/nonce-cache.ts +43 -0
- package/src/transport/http/protocol.ts +57 -0
- package/src/transport/http/rate-limiter.ts +75 -0
- package/src/transport/http/request-auth.ts +164 -0
- package/src/transport/http/server.ts +617 -0
- package/src/transport/http/sse-stream.ts +121 -0
- package/src/transport/http/sse-writer.ts +23 -0
- package/src/transport/in-memory.ts +178 -0
- package/src/transport/index.ts +7 -0
- package/src/transport/nostr.ts +576 -0
- package/src/transport/transport.ts +79 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { SerializedSMTProof } from '@did-btcr2/smt';
|
|
2
|
+
import type { CohortAdvert, PendingFallbackRequest, PendingSigningRequest, PendingValidation } from '../participant.js';
|
|
3
|
+
import type { AggregationResult, PendingOptIn, Rejection } from '../service.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sidecar data a participant keeps when a cohort completes from its
|
|
7
|
+
* perspective: the cohort's beacon coordinates plus the off-chain artifact it
|
|
8
|
+
* needs for future DID resolution (the CAS Announcement map for CAS beacons, or
|
|
9
|
+
* the SMT inclusion proof for SMT beacons). Emitted via `cohort-complete` and
|
|
10
|
+
* returned by the participant runner's join helpers.
|
|
11
|
+
*/
|
|
12
|
+
export interface CohortCompleteInfo {
|
|
13
|
+
cohortId: string;
|
|
14
|
+
beaconAddress: string;
|
|
15
|
+
beaconType: string;
|
|
16
|
+
/** True if this participant submitted an update; false if it declined (non-inclusion). */
|
|
17
|
+
included: boolean;
|
|
18
|
+
/** DID to base64url update hash. Populated only for CAS beacons. */
|
|
19
|
+
casAnnouncement?: Record<string, string>;
|
|
20
|
+
/** Inclusion proof (submitter) or non-inclusion proof (decliner) for this slot. Populated only for SMT beacons. */
|
|
21
|
+
smtProof?: SerializedSMTProof;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* AggregationServiceRunner events are emitted by the AggregationServiceRunner to signal important
|
|
26
|
+
* milestones and actions during the aggregation process. They can be listened to by external code
|
|
27
|
+
* to react to these events, such as logging, updating a UI, or triggering additional actions.
|
|
28
|
+
*/
|
|
29
|
+
export type AggregationServiceEvents = {
|
|
30
|
+
/** A cohort has been created and the advert message broadcast. */
|
|
31
|
+
'cohort-advertised': [{ cohortId: string }];
|
|
32
|
+
|
|
33
|
+
/** A participant has opted in. Fires before the accept decision callback. */
|
|
34
|
+
'opt-in-received': [PendingOptIn];
|
|
35
|
+
|
|
36
|
+
/** A participant has been accepted into the cohort. */
|
|
37
|
+
'participant-accepted': [{ cohortId: string; participantDid: string }];
|
|
38
|
+
|
|
39
|
+
/** Keygen has been finalized: beacon address is now available. */
|
|
40
|
+
'keygen-complete': [{ cohortId: string; beaconAddress: string }];
|
|
41
|
+
|
|
42
|
+
/** A participant has submitted a signed update. */
|
|
43
|
+
'update-received': [{ cohortId: string; participantDid: string }];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* An inbound message was silently dropped by the state machine (bad proof,
|
|
47
|
+
* oversized payload, wrong wire version, etc.). Fires for *any* rejection,
|
|
48
|
+
* not just SUBMIT_UPDATE.
|
|
49
|
+
*/
|
|
50
|
+
'message-rejected': [Rejection & { cohortId: string }];
|
|
51
|
+
|
|
52
|
+
/** Aggregated data has been distributed to all participants for validation. */
|
|
53
|
+
'data-distributed': [{ cohortId: string }];
|
|
54
|
+
|
|
55
|
+
/** A participant has acknowledged validation (approved or rejected). */
|
|
56
|
+
'validation-received': [{ cohortId: string; participantDid: string; approved: boolean }];
|
|
57
|
+
|
|
58
|
+
/** Signing has started: auth requests sent to participants. */
|
|
59
|
+
'signing-started': [{ cohortId: string; sessionId: string }];
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The optimistic n-of-n key path was abandoned and the k-of-n fallback
|
|
63
|
+
* (script-path) signing round started (ADR 042). After this, the cohort
|
|
64
|
+
* completes via the fallback once k members sign.
|
|
65
|
+
*/
|
|
66
|
+
'fallback-started': [{ cohortId: string; sessionId: string }];
|
|
67
|
+
|
|
68
|
+
/** A participant has contributed their MuSig2 nonce. */
|
|
69
|
+
'nonce-received': [{ cohortId: string; participantDid: string }];
|
|
70
|
+
|
|
71
|
+
/** Signing complete: final aggregated signature is ready to broadcast. */
|
|
72
|
+
'signing-complete': [AggregationResult];
|
|
73
|
+
|
|
74
|
+
/** Cohort transitioned to Failed phase (e.g. a participant rejected validation). */
|
|
75
|
+
'cohort-failed': [{ cohortId: string; reason: string }];
|
|
76
|
+
|
|
77
|
+
/** A non-fatal error occurred. Fatal errors reject the run() promise. */
|
|
78
|
+
'error': [Error];
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* AggregationParticipantRunner events are emitted by the AggregationParticipantRunner to signal
|
|
83
|
+
* important milestones and actions during the participant's involvement in the aggregation process.
|
|
84
|
+
* They can be listened to by external code to react to these events, such as logging, updating a
|
|
85
|
+
* UI, or triggering additional actions.
|
|
86
|
+
*/
|
|
87
|
+
export type AggregationParticipantEvents = {
|
|
88
|
+
/** A new cohort advert was discovered. Fires before the shouldJoin filter. */
|
|
89
|
+
'cohort-discovered': [CohortAdvert];
|
|
90
|
+
|
|
91
|
+
/** Participant has opted in to a cohort. */
|
|
92
|
+
'cohort-joined': [{ cohortId: string }];
|
|
93
|
+
|
|
94
|
+
/** Cohort keygen is complete: beacon address is now available. */
|
|
95
|
+
'cohort-ready': [{ cohortId: string; beaconAddress: string }];
|
|
96
|
+
|
|
97
|
+
/** Participant has submitted their signed update. */
|
|
98
|
+
'update-submitted': [{ cohortId: string }];
|
|
99
|
+
|
|
100
|
+
/** Participant declined to submit an update this round (cooperative non-inclusion). */
|
|
101
|
+
'update-declined': [{ cohortId: string }];
|
|
102
|
+
|
|
103
|
+
/** Aggregated data has arrived for validation. Fires before the validate callback. */
|
|
104
|
+
'validation-requested': [PendingValidation];
|
|
105
|
+
|
|
106
|
+
/** Signing request has arrived. Fires before the sign approval callback. */
|
|
107
|
+
'signing-requested': [PendingSigningRequest];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* A fallback (k-of-n script-path) signing request has arrived because the
|
|
111
|
+
* service abandoned the optimistic key path (ADR 042). Fires before the sign
|
|
112
|
+
* approval callback; approving signs the fallback spend.
|
|
113
|
+
*/
|
|
114
|
+
'fallback-requested': [PendingFallbackRequest];
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Cohort signing is complete from this participant's perspective.
|
|
118
|
+
* Includes the aggregated sidecar data the participant needs to keep for
|
|
119
|
+
* future DID resolution: the CAS Announcement map (for CAS beacons) or the
|
|
120
|
+
* SMT inclusion proof (for SMT beacons).
|
|
121
|
+
*/
|
|
122
|
+
'cohort-complete': [CohortCompleteInfo];
|
|
123
|
+
|
|
124
|
+
/** Cohort failed (rejected validation, signing error, etc.). */
|
|
125
|
+
'cohort-failed': [{ cohortId: string; reason: string }];
|
|
126
|
+
|
|
127
|
+
/** A non-fatal error occurred. */
|
|
128
|
+
'error': [Error];
|
|
129
|
+
};
|
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import type { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
|
|
2
|
+
import type { SchnorrKeyPair } from '@did-btcr2/keypair';
|
|
3
|
+
import type { BaseMessage } from '../messages/base.js';
|
|
4
|
+
import {
|
|
5
|
+
AGGREGATED_NONCE,
|
|
6
|
+
AUTHORIZATION_REQUEST,
|
|
7
|
+
COHORT_ADVERT,
|
|
8
|
+
COHORT_OPT_IN_ACCEPT,
|
|
9
|
+
COHORT_READY,
|
|
10
|
+
DISTRIBUTE_AGGREGATED_DATA,
|
|
11
|
+
FALLBACK_AUTHORIZATION_REQUEST,
|
|
12
|
+
} from '../messages/constants.js';
|
|
13
|
+
import type {
|
|
14
|
+
CohortAdvert,
|
|
15
|
+
PendingSigningRequest,
|
|
16
|
+
PendingValidation} from '../participant.js';
|
|
17
|
+
import {
|
|
18
|
+
AggregationParticipant
|
|
19
|
+
} from '../participant.js';
|
|
20
|
+
import { ParticipantCohortPhase } from '../phases.js';
|
|
21
|
+
import { KeyPairAggregationSigner } from '../signer.js';
|
|
22
|
+
import type { Transport } from '../transport/transport.js';
|
|
23
|
+
import type { AggregationParticipantEvents, CohortCompleteInfo } from './events.js';
|
|
24
|
+
import { TypedEventEmitter } from './typed-emitter.js';
|
|
25
|
+
|
|
26
|
+
/** Decision callback: filter discovered cohorts. Default rejects all. */
|
|
27
|
+
export type ShouldJoin = (advert: CohortAdvert) => Promise<boolean>;
|
|
28
|
+
|
|
29
|
+
/** Data callback: provide a signed BTCR2 update for a joined cohort. */
|
|
30
|
+
export type OnProvideUpdate = (info: {
|
|
31
|
+
cohortId: string;
|
|
32
|
+
beaconAddress: string;
|
|
33
|
+
}) => Promise<SignedBTCR2Update | null>;
|
|
34
|
+
|
|
35
|
+
/** Decision callback: approve or reject aggregated data. */
|
|
36
|
+
export type OnValidateData = (info: PendingValidation) => Promise<{ approved: boolean }>;
|
|
37
|
+
|
|
38
|
+
/** Decision callback: approve or reject signing. */
|
|
39
|
+
export type OnApproveSigning = (info: PendingSigningRequest) => Promise<{ approved: boolean }>;
|
|
40
|
+
|
|
41
|
+
export interface AggregationParticipantRunnerOptions {
|
|
42
|
+
/** Underlying transport. */
|
|
43
|
+
transport: Transport;
|
|
44
|
+
|
|
45
|
+
/** Participant identity. */
|
|
46
|
+
did: string;
|
|
47
|
+
keys: SchnorrKeyPair;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Filter discovered cohorts. Returns true to join, false to skip.
|
|
51
|
+
* Default: rejects all cohorts (caller MUST override or no cohorts will be joined).
|
|
52
|
+
*/
|
|
53
|
+
shouldJoin?: ShouldJoin;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Provide a signed BTCR2 update for the cohort.
|
|
57
|
+
* REQUIRED - no sensible default.
|
|
58
|
+
*/
|
|
59
|
+
onProvideUpdate: OnProvideUpdate;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Approve or reject aggregated data.
|
|
63
|
+
* Default: approve if validation matches the submitted update's hash.
|
|
64
|
+
*/
|
|
65
|
+
onValidateData?: OnValidateData;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Approve or reject the signing request.
|
|
69
|
+
* Default: approve.
|
|
70
|
+
*/
|
|
71
|
+
onApproveSigning?: OnApproveSigning;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* High-level facade for an Aggregation Participant.
|
|
76
|
+
*
|
|
77
|
+
* Long-running listener: waits for cohort adverts, applies the `shouldJoin`
|
|
78
|
+
* filter, and drives each accepted cohort through the full protocol to
|
|
79
|
+
* completion in parallel.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* const transport = new NostrTransport({ relays: [RELAY] });
|
|
84
|
+
* transport.registerActor(myDid, myKeys);
|
|
85
|
+
*
|
|
86
|
+
* const runner = new AggregationParticipantRunner({
|
|
87
|
+
* transport,
|
|
88
|
+
* did: myDid,
|
|
89
|
+
* keys: myKeys,
|
|
90
|
+
* shouldJoin: async (advert) => advert.beaconType === 'CASBeacon',
|
|
91
|
+
* onProvideUpdate: async ({ beaconAddress }) => {
|
|
92
|
+
* return Updater.sign(myDid, unsigned, vm, secretKey);
|
|
93
|
+
* },
|
|
94
|
+
* });
|
|
95
|
+
*
|
|
96
|
+
* runner.on('cohort-complete', ({ beaconAddress }) => {
|
|
97
|
+
* console.log(`Add to DID document: bitcoin:${beaconAddress}`);
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* await runner.start();
|
|
101
|
+
* ```
|
|
102
|
+
*
|
|
103
|
+
* For full manual control, drop down to the underlying state machine via
|
|
104
|
+
* `runner.session`. The state machine has no transport coupling and exposes
|
|
105
|
+
* every protocol decision as an explicit method.
|
|
106
|
+
*
|
|
107
|
+
* @class AggregationParticipantRunner
|
|
108
|
+
* @extends TypedEventEmitter<AggregationParticipantEvents>
|
|
109
|
+
*/
|
|
110
|
+
export class AggregationParticipantRunner extends TypedEventEmitter<AggregationParticipantEvents> {
|
|
111
|
+
/** Direct access to the underlying state machine for advanced use. */
|
|
112
|
+
readonly session: AggregationParticipant;
|
|
113
|
+
|
|
114
|
+
readonly #transport: Transport;
|
|
115
|
+
readonly #did: string;
|
|
116
|
+
readonly #shouldJoin: ShouldJoin;
|
|
117
|
+
readonly #onProvideUpdate: OnProvideUpdate;
|
|
118
|
+
readonly #onValidateData: OnValidateData;
|
|
119
|
+
readonly #onApproveSigning: OnApproveSigning;
|
|
120
|
+
|
|
121
|
+
#handlersRegistered = false;
|
|
122
|
+
#stopped = false;
|
|
123
|
+
|
|
124
|
+
constructor(options: AggregationParticipantRunnerOptions) {
|
|
125
|
+
super();
|
|
126
|
+
this.#transport = options.transport;
|
|
127
|
+
this.#did = options.did;
|
|
128
|
+
this.#shouldJoin = options.shouldJoin ?? (async () => false);
|
|
129
|
+
this.#onProvideUpdate = options.onProvideUpdate;
|
|
130
|
+
this.#onValidateData = options.onValidateData ?? (async (info) => ({ approved: info.matches }));
|
|
131
|
+
this.#onApproveSigning = options.onApproveSigning ?? (async () => ({ approved: true }));
|
|
132
|
+
|
|
133
|
+
this.session = new AggregationParticipant({
|
|
134
|
+
did : options.did,
|
|
135
|
+
signer : new KeyPairAggregationSigner(options.keys),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Start listening for cohorts. The runner stays active until {@link stop}
|
|
141
|
+
* is called or the underlying transport disconnects.
|
|
142
|
+
*/
|
|
143
|
+
async start(): Promise<void> {
|
|
144
|
+
this.#registerHandlers();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Stop the runner and detach transport handlers. Safe to call repeatedly. */
|
|
148
|
+
stop(): void {
|
|
149
|
+
this.#stopped = true;
|
|
150
|
+
this.#unregisterHandlers();
|
|
151
|
+
// Deterministically wipe any secret nonce left on an abandoned signing
|
|
152
|
+
// session. The signing key itself lives behind the AggregationSigner and is
|
|
153
|
+
// never retained here.
|
|
154
|
+
this.session.clearSecrets();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Message types this runner listens for on the transport. */
|
|
158
|
+
static readonly #HANDLED_MESSAGE_TYPES: readonly string[] = [
|
|
159
|
+
COHORT_ADVERT,
|
|
160
|
+
COHORT_OPT_IN_ACCEPT,
|
|
161
|
+
COHORT_READY,
|
|
162
|
+
DISTRIBUTE_AGGREGATED_DATA,
|
|
163
|
+
AUTHORIZATION_REQUEST,
|
|
164
|
+
AGGREGATED_NONCE,
|
|
165
|
+
FALLBACK_AUTHORIZATION_REQUEST,
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
/** Internal: detach from the transport. Safe to call repeatedly. */
|
|
169
|
+
#unregisterHandlers(): void {
|
|
170
|
+
if(!this.#handlersRegistered) return;
|
|
171
|
+
this.#handlersRegistered = false;
|
|
172
|
+
for(const type of AggregationParticipantRunner.#HANDLED_MESSAGE_TYPES) {
|
|
173
|
+
this.#transport.unregisterMessageHandler(this.#did, type);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Single-shot helper: start, join the first cohort that passes `shouldJoin`,
|
|
179
|
+
* drive it to completion, and resolve. Convenient for tests and demos. The
|
|
180
|
+
* single-cohort special case of {@link joinMatching} (count = 1).
|
|
181
|
+
*/
|
|
182
|
+
static async joinFirst(
|
|
183
|
+
options: AggregationParticipantRunnerOptions
|
|
184
|
+
): Promise<CohortCompleteInfo> {
|
|
185
|
+
return new Promise((resolve, reject) => {
|
|
186
|
+
const runner = new AggregationParticipantRunner(options);
|
|
187
|
+
runner.once('cohort-complete', (info) => {
|
|
188
|
+
runner.stop();
|
|
189
|
+
resolve(info);
|
|
190
|
+
});
|
|
191
|
+
runner.on('error', reject);
|
|
192
|
+
runner.start().catch(reject);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Multi-cohort helper: start, join EVERY cohort whose advert passes
|
|
198
|
+
* `shouldJoin`, drive each to completion in parallel, and resolve once
|
|
199
|
+
* `count` cohorts have completed (the runner stops at that point). The
|
|
200
|
+
* N-cohort generalization of {@link joinFirst}, for a participant that joins
|
|
201
|
+
* several cohorts advertised by one service.
|
|
202
|
+
*
|
|
203
|
+
* For an open-ended, long-lived subscriber (no fixed count), construct an
|
|
204
|
+
* {@link AggregationParticipantRunner} directly, set `shouldJoin`, call
|
|
205
|
+
* `start()`, and listen for `cohort-complete` - the runner already drives
|
|
206
|
+
* any number of cohorts concurrently.
|
|
207
|
+
*
|
|
208
|
+
* @param options Participant runner options (set `shouldJoin` to select cohorts).
|
|
209
|
+
* @param count Number of completed cohorts to collect before resolving.
|
|
210
|
+
* @returns The {@link CohortCompleteInfo} for each completed cohort, in completion order.
|
|
211
|
+
*/
|
|
212
|
+
static async joinMatching(
|
|
213
|
+
options: AggregationParticipantRunnerOptions,
|
|
214
|
+
count: number,
|
|
215
|
+
): Promise<CohortCompleteInfo[]> {
|
|
216
|
+
return new Promise((resolve, reject) => {
|
|
217
|
+
const runner = new AggregationParticipantRunner(options);
|
|
218
|
+
const completed: CohortCompleteInfo[] = [];
|
|
219
|
+
runner.on('cohort-complete', (info) => {
|
|
220
|
+
completed.push(info);
|
|
221
|
+
if(completed.length >= count) {
|
|
222
|
+
runner.stop();
|
|
223
|
+
resolve(completed);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
runner.on('error', reject);
|
|
227
|
+
runner.start().catch(reject);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Internal: handler registration with the transport. Idempotent and safe to call multiple times,
|
|
233
|
+
* but only registers handlers once.
|
|
234
|
+
*/
|
|
235
|
+
#registerHandlers(): void {
|
|
236
|
+
if (this.#handlersRegistered) return;
|
|
237
|
+
this.#handlersRegistered = true;
|
|
238
|
+
|
|
239
|
+
this.#transport.registerMessageHandler(this.#did, COHORT_ADVERT, this.#handleAdvert.bind(this));
|
|
240
|
+
this.#transport.registerMessageHandler(this.#did, COHORT_OPT_IN_ACCEPT, this.#handleOptInAccept.bind(this));
|
|
241
|
+
this.#transport.registerMessageHandler(this.#did, COHORT_READY, this.#handleCohortReady.bind(this));
|
|
242
|
+
this.#transport.registerMessageHandler(this.#did, DISTRIBUTE_AGGREGATED_DATA, this.#handleDistributeData.bind(this));
|
|
243
|
+
this.#transport.registerMessageHandler(this.#did, AUTHORIZATION_REQUEST, this.#handleAuthorizationRequest.bind(this));
|
|
244
|
+
this.#transport.registerMessageHandler(this.#did, AGGREGATED_NONCE, this.#handleAggregatedNonce.bind(this));
|
|
245
|
+
this.#transport.registerMessageHandler(this.#did, FALLBACK_AUTHORIZATION_REQUEST, this.#handleFallbackAuthorizationRequest.bind(this));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Internal: handler for cohort adverts. Applies the `shouldJoin` filter and joins if approved.
|
|
250
|
+
* @param {BaseMessage} msg - The received cohort advert message.
|
|
251
|
+
* @returns {Promise<void>} Resolves when processing is complete.
|
|
252
|
+
*/
|
|
253
|
+
async #handleAdvert(msg: BaseMessage): Promise<void> {
|
|
254
|
+
if (this.#stopped) return;
|
|
255
|
+
try {
|
|
256
|
+
this.session.receive(msg);
|
|
257
|
+
|
|
258
|
+
const advert = this.session.discoveredCohorts.get(msg.body?.cohortId ?? '');
|
|
259
|
+
if (!advert) return;
|
|
260
|
+
this.emit('cohort-discovered', advert);
|
|
261
|
+
|
|
262
|
+
// Register the service's communication key for encrypted message routing
|
|
263
|
+
if (advert.serviceCommunicationPk) {
|
|
264
|
+
this.#transport.registerPeer(advert.serviceDid, advert.serviceCommunicationPk);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const join = await this.#shouldJoin(advert);
|
|
268
|
+
if (!join) return;
|
|
269
|
+
|
|
270
|
+
await this.#sendAll(this.session.joinCohort(advert.cohortId));
|
|
271
|
+
this.emit('cohort-joined', { cohortId: advert.cohortId });
|
|
272
|
+
} catch (err) {
|
|
273
|
+
this.emit('error', err as Error);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Internal: handler for opt-in accept messages. Updates the session state accordingly.
|
|
279
|
+
* @param {BaseMessage} msg - The received opt-in accept message.
|
|
280
|
+
*/
|
|
281
|
+
#handleOptInAccept(msg: BaseMessage): void {
|
|
282
|
+
if (this.#stopped) return;
|
|
283
|
+
try {
|
|
284
|
+
this.session.receive(msg);
|
|
285
|
+
} catch (err) {
|
|
286
|
+
this.emit('error', err as Error);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Internal: handler for cohort ready messages. Updates the session state, emits a 'cohort-ready'
|
|
292
|
+
* event, and triggers the update submission flow via the `onProvideUpdate` callback.
|
|
293
|
+
* @param {BaseMessage} msg - The received cohort ready message.
|
|
294
|
+
* @returns {Promise<void>} Resolves when processing is complete.
|
|
295
|
+
*/
|
|
296
|
+
async #handleCohortReady(msg: BaseMessage): Promise<void> {
|
|
297
|
+
if (this.#stopped) return;
|
|
298
|
+
try {
|
|
299
|
+
this.session.receive(msg);
|
|
300
|
+
|
|
301
|
+
const cohortId = msg.body?.cohortId;
|
|
302
|
+
if (!cohortId) return;
|
|
303
|
+
|
|
304
|
+
const info = this.session.joinedCohorts.get(cohortId);
|
|
305
|
+
if (!info) return;
|
|
306
|
+
this.emit('cohort-ready', { cohortId, beaconAddress: info.beaconAddress });
|
|
307
|
+
|
|
308
|
+
// Construct the signed update via caller callback and submit. A null return
|
|
309
|
+
// means the member has no update this round: it declines (cooperative
|
|
310
|
+
// non-inclusion) but stays in the cohort and still signs.
|
|
311
|
+
const signedUpdate = await this.#onProvideUpdate({
|
|
312
|
+
cohortId,
|
|
313
|
+
beaconAddress : info.beaconAddress,
|
|
314
|
+
});
|
|
315
|
+
if(signedUpdate === null) {
|
|
316
|
+
await this.#sendAll(this.session.declineUpdate(cohortId));
|
|
317
|
+
this.emit('update-declined', { cohortId });
|
|
318
|
+
} else {
|
|
319
|
+
await this.#sendAll(this.session.submitUpdate(cohortId, signedUpdate));
|
|
320
|
+
this.emit('update-submitted', { cohortId });
|
|
321
|
+
}
|
|
322
|
+
} catch (err) {
|
|
323
|
+
this.emit('error', err as Error);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Internal: handler for distribute aggregated data messages. Updates the session state, emits a
|
|
329
|
+
* 'validation-requested' event, and triggers the validation decision flow via the `onValidateData`
|
|
330
|
+
* callback. Depending on the decision, sends an approval or rejection message.
|
|
331
|
+
* @param {BaseMessage} msg - The received distribute aggregated data message.
|
|
332
|
+
* @returns {Promise<void>} Resolves when processing is complete.
|
|
333
|
+
* @throws {Error} If an error occurs during message processing or callback execution.
|
|
334
|
+
*/
|
|
335
|
+
async #handleDistributeData(msg: BaseMessage): Promise<void> {
|
|
336
|
+
if (this.#stopped) return;
|
|
337
|
+
try {
|
|
338
|
+
this.session.receive(msg);
|
|
339
|
+
|
|
340
|
+
const cohortId = msg.body?.cohortId;
|
|
341
|
+
if (!cohortId) return;
|
|
342
|
+
|
|
343
|
+
const validation = this.session.pendingValidations.get(cohortId);
|
|
344
|
+
if (!validation) return;
|
|
345
|
+
this.emit('validation-requested', validation);
|
|
346
|
+
|
|
347
|
+
const decision = await this.#onValidateData(validation);
|
|
348
|
+
if (decision.approved) {
|
|
349
|
+
await this.#sendAll(this.session.approveValidation(cohortId));
|
|
350
|
+
} else {
|
|
351
|
+
await this.#sendAll(this.session.rejectValidation(cohortId));
|
|
352
|
+
this.emit('cohort-failed', { cohortId, reason: 'Validation rejected by participant' });
|
|
353
|
+
}
|
|
354
|
+
} catch (err) {
|
|
355
|
+
this.emit('error', err as Error);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Internal: handler for authorization request messages. Updates the session state, emits a
|
|
361
|
+
* 'signing-requested' event, and triggers the signing approval flow via the `onApproveSigning`
|
|
362
|
+
* callback. Depending on the decision, sends a nonce approval message or emits a 'cohort-failed'
|
|
363
|
+
* event.
|
|
364
|
+
* @param {BaseMessage} msg - The received authorization request message.
|
|
365
|
+
* @returns {Promise<void>} Resolves when processing is complete.
|
|
366
|
+
* @throws {Error} If an error occurs during message processing or callback execution.
|
|
367
|
+
*/
|
|
368
|
+
async #handleAuthorizationRequest(msg: BaseMessage): Promise<void> {
|
|
369
|
+
if (this.#stopped) return;
|
|
370
|
+
try {
|
|
371
|
+
this.session.receive(msg);
|
|
372
|
+
|
|
373
|
+
const cohortId = msg.body?.cohortId;
|
|
374
|
+
if (!cohortId) return;
|
|
375
|
+
|
|
376
|
+
const req = this.session.pendingSigningRequests.get(cohortId);
|
|
377
|
+
if (!req) return;
|
|
378
|
+
this.emit('signing-requested', req);
|
|
379
|
+
|
|
380
|
+
const decision = await this.#onApproveSigning(req);
|
|
381
|
+
if (!decision.approved) {
|
|
382
|
+
this.emit('cohort-failed', { cohortId, reason: 'Signing rejected by participant' });
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
await this.#sendAll(this.session.approveNonce(cohortId));
|
|
387
|
+
} catch (err) {
|
|
388
|
+
this.emit('error', err as Error);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Internal: handler for aggregated nonce messages. Updates the session state, triggers partial
|
|
394
|
+
* signature generation, and sends the partial signature to the aggregator. If the cohort reaches
|
|
395
|
+
* completion after processing the nonce, emits a 'cohort-complete' event.
|
|
396
|
+
* @param {BaseMessage} msg - The received aggregated nonce message.
|
|
397
|
+
* @returns {Promise<void>} Resolves when processing is complete.
|
|
398
|
+
* @throws {Error} If an error occurs during message processing or partial signature generation.
|
|
399
|
+
*/
|
|
400
|
+
async #handleAggregatedNonce(msg: BaseMessage): Promise<void> {
|
|
401
|
+
if (this.#stopped) return;
|
|
402
|
+
try {
|
|
403
|
+
this.session.receive(msg);
|
|
404
|
+
|
|
405
|
+
const cohortId = msg.body?.cohortId;
|
|
406
|
+
if (!cohortId) return;
|
|
407
|
+
|
|
408
|
+
await this.#sendAll(this.session.generatePartialSignature(cohortId));
|
|
409
|
+
|
|
410
|
+
// Check if we've reached completion
|
|
411
|
+
this.#emitCohortCompleteIfDone(cohortId);
|
|
412
|
+
} catch (err) {
|
|
413
|
+
this.emit('error', err as Error);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Internal: handler for fallback authorization requests. The service abandoned
|
|
419
|
+
* the optimistic key path; the member authorizes the k-of-n script-path spend
|
|
420
|
+
* of the same beacon transaction (ADR 042). Reuses `onApproveSigning` to gate
|
|
421
|
+
* the decision, signs the fallback, and completes once it has contributed (the
|
|
422
|
+
* service needs only k members).
|
|
423
|
+
* @param {BaseMessage} msg - The received fallback authorization request message.
|
|
424
|
+
* @returns {Promise<void>} Resolves when processing is complete.
|
|
425
|
+
*/
|
|
426
|
+
async #handleFallbackAuthorizationRequest(msg: BaseMessage): Promise<void> {
|
|
427
|
+
if (this.#stopped) return;
|
|
428
|
+
try {
|
|
429
|
+
this.session.receive(msg);
|
|
430
|
+
|
|
431
|
+
const cohortId = msg.body?.cohortId;
|
|
432
|
+
if (!cohortId) return;
|
|
433
|
+
|
|
434
|
+
const req = this.session.pendingFallbackRequests.get(cohortId);
|
|
435
|
+
if (!req) return;
|
|
436
|
+
this.emit('fallback-requested', req);
|
|
437
|
+
|
|
438
|
+
const decision = await this.#onApproveSigning(req);
|
|
439
|
+
if (!decision.approved) {
|
|
440
|
+
this.emit('cohort-failed', { cohortId, reason: 'Fallback signing rejected by participant' });
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
await this.#sendAll(this.session.approveFallback(cohortId));
|
|
445
|
+
// The member has contributed its fallback signature and is done from its
|
|
446
|
+
// own perspective, regardless of whether the service has yet collected k.
|
|
447
|
+
this.#emitCohortCompleteIfDone(cohortId);
|
|
448
|
+
} catch (err) {
|
|
449
|
+
this.emit('error', err as Error);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Internal: emit `cohort-complete` with the participant's retained sidecar once
|
|
455
|
+
* the cohort has reached the Complete phase. Surfaces the CAS Announcement map
|
|
456
|
+
* (CAS beacons) or the SMT proof (SMT beacons) the participant keeps for future
|
|
457
|
+
* DID resolution. Read via getValidation (not pendingValidations, which lists
|
|
458
|
+
* only the AwaitingValidation phase) so the sidecar is still available at
|
|
459
|
+
* Complete. Shared by the optimistic and fallback completion paths.
|
|
460
|
+
*/
|
|
461
|
+
#emitCohortCompleteIfDone(cohortId: string): void {
|
|
462
|
+
if (this.session.getCohortPhase(cohortId) !== ParticipantCohortPhase.Complete) return;
|
|
463
|
+
const info = this.session.joinedCohorts.get(cohortId);
|
|
464
|
+
if (!info) return;
|
|
465
|
+
const validation = this.session.getValidation(cohortId);
|
|
466
|
+
this.emit('cohort-complete', {
|
|
467
|
+
cohortId,
|
|
468
|
+
beaconAddress : info.beaconAddress,
|
|
469
|
+
beaconType : validation?.beaconType ?? '',
|
|
470
|
+
included : validation?.included ?? true,
|
|
471
|
+
casAnnouncement : validation?.casAnnouncement,
|
|
472
|
+
smtProof : validation?.smtProof,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Internal: send helper to ensure messages are sent sequentially. This is important for protocol
|
|
478
|
+
* correctness, as some transports may not guarantee message order if sent in parallel.
|
|
479
|
+
* @param {BaseMessage[]} msgs - The messages to send.
|
|
480
|
+
* @returns {Promise<void>} Resolves when all messages have been sent.
|
|
481
|
+
* @throws {Error} If an error occurs during message sending.
|
|
482
|
+
*/
|
|
483
|
+
async #sendAll(msgs: BaseMessage[]): Promise<void> {
|
|
484
|
+
for (const m of msgs) {
|
|
485
|
+
await this.#transport.sendMessage(m, this.#did, m.to);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|