@arcanetech/privacy-sdk-relay 0.2.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/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # @arcanetech/privacy-sdk-relay
2
+
3
+ Chain-agnostic protocol relay runtime for Arcane private payment flows.
4
+
5
+ ## Overview
6
+
7
+ This package owns admission, lifecycle polling, retry, and consented direct-submit fallback for prepared private operations. Callers supply five required ports; `relayApi` and `relayConfig` are optional and enable Protocol Relay. The runtime never inspects opaque package payloads and has no Stellar, Soroban, or zero-knowledge dependencies.
8
+
9
+ The ports are:
10
+
11
+ - `store` — pending-operation persistence (survives reload)
12
+ - `submitDirect` — caller-supplied wallet-signed submission (not serializable; after reload only polling and retry remain)
13
+ - `finalizeLocalState` — apply local record updates after success
14
+ - `drainDeliveries` — deliver output notes after success
15
+ - `persistUserTransaction` — record the user-visible transaction after success
16
+ - `relayConfig` / `relayApi` — optional origin and transport used to admit a request, read lifecycle status, and retry an attempt
17
+
18
+ ## Quick example
19
+
20
+ ```ts
21
+ import {
22
+ createRelayApi,
23
+ resolveRelayOrigin,
24
+ submitAndAwaitPrivateOperation,
25
+ throwIfRelayUnsuccessful,
26
+ type ProtocolRelayPorts,
27
+ } from '@arcanetech/privacy-sdk-relay';
28
+
29
+ const origin = resolveRelayOrigin(relayOriginFromEnv);
30
+ const ports: ProtocolRelayPorts = {
31
+ store,
32
+ submitDirect,
33
+ finalizeLocalState,
34
+ drainDeliveries,
35
+ persistUserTransaction,
36
+ ...(origin
37
+ ? { relayConfig: { origin }, relayApi: createRelayApi({ origin }) }
38
+ : {}),
39
+ };
40
+
41
+ const result = await submitAndAwaitPrivateOperation({ ports, operation });
42
+ const { txId } = throwIfRelayUnsuccessful(result);
43
+ ```
44
+
45
+ Relaying is off until `relayConfig.origin` is set. An unset origin Direct-submits even when the caller marks the operation as `relay`, and the relayer is never contacted.
46
+
47
+ Substitute `relayApi` (or the optional `fetch` on `createRelayApi`) in tests. Direct submission stays a caller-supplied port because it closes over a prepared operation that cannot be serialized.
48
+
49
+ ## Public API boundaries
50
+
51
+ - Bind ports in the application; keep chain-specific finalize, delivery, and persist adapters there.
52
+ - Pass the opaque relay package through unchanged. Routing follows the caller-supplied `submissionPath`; this package does not inspect deposit amounts or display kind.
53
+ - This package owns the wire vocabulary: lifecycle statuses (`RELAY_STATUS`), public/HTTP reason codes, and JSON serialization (`serializeRelayPackage` / `deserializeRelayPackage`). Serialization copies package fields only and never inspects proof or public-signal bytes.
54
+ - After a reload, resume with `resumePendingOperations` and `retryFailedRelayAttempt`. Direct fallback requires an in-memory submitter the caller registered before unload.
55
+
56
+ ## Related docs
57
+
58
+ - [Root README](../../README.md)
59
+ - [Packages](../../docs/overview/packages.mdx)
@@ -0,0 +1,297 @@
1
+ declare function jsonSafeClone(value: unknown): unknown;
2
+
3
+ declare function resolveRelayOrigin(origin?: string): string | undefined;
4
+ declare function isRelayConfigured(config?: {
5
+ origin?: string;
6
+ }): boolean;
7
+
8
+ declare const SUBMISSION_PATH: {
9
+ readonly direct: "direct";
10
+ readonly relay: "relay";
11
+ };
12
+ type SubmissionPath = (typeof SUBMISSION_PATH)[keyof typeof SUBMISSION_PATH];
13
+ declare const RELAY_STATUS: {
14
+ readonly accepted: "accepted";
15
+ readonly validating: "validating";
16
+ readonly queued: "queued";
17
+ readonly submitted: "submitted";
18
+ readonly reconciling: "reconciling";
19
+ readonly succeeded: "succeeded";
20
+ readonly rejected: "rejected";
21
+ readonly failed: "failed";
22
+ };
23
+ type RelayLifecycleStatus = (typeof RELAY_STATUS)[keyof typeof RELAY_STATUS];
24
+ declare const PENDING_OPERATION_PHASE: {
25
+ readonly prepared: "prepared";
26
+ readonly relayAccepted: "relay_accepted";
27
+ readonly admissionFailed: "admission_failed";
28
+ readonly succeeded: "succeeded";
29
+ readonly rejected: "rejected";
30
+ readonly failed: "failed";
31
+ readonly settlementTimedOut: "settlement_timed_out";
32
+ };
33
+ type PendingOperationPhase = (typeof PENDING_OPERATION_PHASE)[keyof typeof PENDING_OPERATION_PHASE];
34
+ type RelayPackageJson = {
35
+ version: number;
36
+ poolSelector: string;
37
+ zkConfigNonce: string;
38
+ proofBytes: string;
39
+ publicSignals: string;
40
+ applicationIdHints: string[];
41
+ ciphertextBytes?: string;
42
+ outputNoteEphemeralScalars?: string[];
43
+ escrowAuthorization?: string;
44
+ keyVersionHints?: Array<number | undefined>;
45
+ };
46
+ type SafeDisplayMetadata = {
47
+ kind: 'deposit' | 'transfer' | 'withdraw';
48
+ assetId: string;
49
+ amountDisplay: number;
50
+ counterparty: string;
51
+ senderPrivateAddress: string;
52
+ };
53
+ type DeliveryOutboxEntry = {
54
+ recipientPrivateAddress: string;
55
+ commitmentHex: string;
56
+ coinNote: Record<string, string>;
57
+ depositScalarHex: string;
58
+ precommitementHex?: string;
59
+ assetId: string;
60
+ amountDisplay: number;
61
+ };
62
+ type SdkFinalizationSnapshot = {
63
+ consumedRecordIds: string[];
64
+ outputRecords: DeliveryOutboxEntry[];
65
+ };
66
+ type PendingPrivateOperation = {
67
+ id: string;
68
+ walletPublicKey: string;
69
+ phase: PendingOperationPhase;
70
+ retryAllowed: boolean;
71
+ display: SafeDisplayMetadata;
72
+ snapshot: SdkFinalizationSnapshot;
73
+ deliveryOutbox: DeliveryOutboxEntry[];
74
+ relayPackage: RelayPackageJson;
75
+ finalized: boolean;
76
+ deliveriesDrained: boolean;
77
+ transactionPersisted: boolean;
78
+ escrowSend?: boolean;
79
+ relayRequestId?: string;
80
+ relayStatus?: RelayLifecycleStatus;
81
+ publicReason?: string;
82
+ transactionHash?: string;
83
+ };
84
+ type RelayRequestAccepted = {
85
+ relayRequestId: string;
86
+ status: string;
87
+ createdAt: string;
88
+ statusUrl: string;
89
+ };
90
+ type RelayRequestStatus = {
91
+ relayRequestId: string;
92
+ status: string;
93
+ createdAt: string;
94
+ updatedAt: string;
95
+ retryAllowed: boolean;
96
+ attemptNumber?: number;
97
+ transactionHash?: string;
98
+ publicReason?: string;
99
+ };
100
+ type RelayApi = {
101
+ createRequest: (body: RelayPackageJson) => Promise<RelayRequestAccepted>;
102
+ readStatus: (relayRequestId: string) => Promise<RelayRequestStatus>;
103
+ retryAttempt: (relayRequestId: string) => Promise<RelayRequestStatus>;
104
+ };
105
+ type PendingPrivateOperationStore = {
106
+ save: (operation: PendingPrivateOperation) => Promise<void>;
107
+ list: (walletPublicKey: string) => Promise<PendingPrivateOperation[]>;
108
+ read: (input: {
109
+ walletPublicKey: string;
110
+ operationId: string;
111
+ }) => Promise<PendingPrivateOperation | undefined>;
112
+ };
113
+ type RelayRuntimeConfig = {
114
+ origin: string;
115
+ };
116
+ type ProtocolRelayPorts = {
117
+ relayApi?: RelayApi;
118
+ relayConfig?: RelayRuntimeConfig;
119
+ store: PendingPrivateOperationStore;
120
+ submitDirect: (operation: PendingPrivateOperation) => Promise<{
121
+ txId: string;
122
+ }>;
123
+ finalizeLocalState: (input: {
124
+ operation: PendingPrivateOperation;
125
+ txId: string;
126
+ }) => Promise<void>;
127
+ drainDeliveries: (input: {
128
+ operation: PendingPrivateOperation;
129
+ txId: string;
130
+ }) => Promise<string | undefined>;
131
+ persistUserTransaction: (input: {
132
+ operation: PendingPrivateOperation;
133
+ txId: string;
134
+ }) => void;
135
+ };
136
+ type NewPrivateOperation = {
137
+ id: string;
138
+ walletPublicKey: string;
139
+ submissionPath: SubmissionPath;
140
+ display: SafeDisplayMetadata;
141
+ snapshot: SdkFinalizationSnapshot;
142
+ deliveryOutbox: DeliveryOutboxEntry[];
143
+ relayPackage: RelayPackageJson;
144
+ escrowSend?: boolean;
145
+ };
146
+ type SubmitPrivateOperationResult = {
147
+ outcome: PendingOperationPhase;
148
+ operation: PendingPrivateOperation;
149
+ txId?: string;
150
+ coinDeliveryWarning?: string;
151
+ fallbackAllowed: boolean;
152
+ retryAllowed: boolean;
153
+ };
154
+ type CreateRelayApiInput = {
155
+ origin: string;
156
+ fetch?: typeof globalThis.fetch;
157
+ };
158
+
159
+ declare function createRelayApi(input: CreateRelayApiInput): RelayApi;
160
+
161
+ declare class RelayApiError extends Error {
162
+ readonly reason: string;
163
+ readonly httpStatus: number;
164
+ constructor(input: {
165
+ reason: string;
166
+ httpStatus: number;
167
+ });
168
+ }
169
+ declare function isInfrastructureRelayFailure(error: RelayApiError): boolean;
170
+ declare function isRelayApiError(error: unknown): error is RelayApiError;
171
+
172
+ declare function canOfferDirectSubmission(operation: PendingPrivateOperation): boolean;
173
+ declare function canRetryRelayAttempt(operation: PendingPrivateOperation): boolean;
174
+
175
+ declare function isUnfinalizedRelayOperation(operation: {
176
+ relayRequestId?: string;
177
+ finalized: boolean;
178
+ }): boolean;
179
+
180
+ declare function submitPreparedPrivateOperation(input: {
181
+ ports: ProtocolRelayPorts;
182
+ operation: NewPrivateOperation;
183
+ }): Promise<SubmitPrivateOperationResult>;
184
+
185
+ declare function pollPendingOperation(input: {
186
+ ports: ProtocolRelayPorts;
187
+ walletPublicKey: string;
188
+ operationId: string;
189
+ }): Promise<SubmitPrivateOperationResult>;
190
+ declare function isTerminalRelayStatus(status: string | undefined): boolean;
191
+
192
+ declare function retryFailedRelayAttempt(input: {
193
+ ports: ProtocolRelayPorts;
194
+ walletPublicKey: string;
195
+ operationId: string;
196
+ }): Promise<SubmitPrivateOperationResult>;
197
+ declare function resumePendingOperations(input: {
198
+ ports: ProtocolRelayPorts;
199
+ walletPublicKey: string;
200
+ }): Promise<SubmitPrivateOperationResult[]>;
201
+
202
+ declare function submitDirectFallback(input: {
203
+ ports: ProtocolRelayPorts;
204
+ walletPublicKey: string;
205
+ operationId: string;
206
+ consent: boolean;
207
+ }): Promise<SubmitPrivateOperationResult>;
208
+
209
+ declare const DEFAULT_SETTLEMENT_POLL_INTERVAL_MS = 2000;
210
+ declare const DEFAULT_SETTLEMENT_MAX_ATTEMPTS = 15;
211
+ declare function awaitRelaySettlement(input: {
212
+ ports: ProtocolRelayPorts;
213
+ walletPublicKey: string;
214
+ operationId: string;
215
+ pollIntervalMs?: number;
216
+ maxAttempts?: number;
217
+ backoffMultiplier?: number;
218
+ maxPollIntervalMs?: number;
219
+ delay?: (ms: number) => Promise<void>;
220
+ }): Promise<SubmitPrivateOperationResult>;
221
+ declare function submitAndAwaitPrivateOperation(input: {
222
+ ports: ProtocolRelayPorts;
223
+ operation: NewPrivateOperation;
224
+ pollIntervalMs?: number;
225
+ maxAttempts?: number;
226
+ }): Promise<SubmitPrivateOperationResult>;
227
+
228
+ declare class ProtocolRelayClientError extends Error {
229
+ readonly outcome: SubmitPrivateOperationResult['outcome'];
230
+ readonly fallbackAllowed: boolean;
231
+ readonly retryAllowed: boolean;
232
+ readonly operationId: string;
233
+ readonly publicReason?: string;
234
+ constructor(result: SubmitPrivateOperationResult);
235
+ }
236
+ declare function throwIfRelayUnsuccessful(result: SubmitPrivateOperationResult): {
237
+ txId: string;
238
+ coinDeliveryWarning?: string;
239
+ };
240
+
241
+ declare const RELAY_PACKAGE_VERSION_V1 = 1;
242
+ type RelayPackageSerializable = {
243
+ version: number;
244
+ poolSelector: string;
245
+ zkConfigNonce: {
246
+ toString(): string;
247
+ } | string | number;
248
+ proofBytes: string;
249
+ publicSignals: string;
250
+ applicationIdHints: string[];
251
+ ciphertextBytes?: string;
252
+ outputNoteEphemeralScalars?: string[];
253
+ escrowAuthorization?: string;
254
+ keyVersionHints?: Array<number | undefined | null>;
255
+ };
256
+ declare function serializeRelayPackage(source: RelayPackageSerializable): RelayPackageJson;
257
+ declare function deserializeRelayPackage(payload: unknown): RelayPackageJson | undefined;
258
+
259
+ declare const RELAY_PUBLIC_REASON: {
260
+ readonly invalidPackage: "invalid_package";
261
+ readonly unsupportedSignalShape: "unsupported_signal_shape";
262
+ readonly positivePublicDeposit: "positive_public_deposit";
263
+ readonly zkConfigMissing: "zk_config_missing";
264
+ readonly zkConfigDeprecated: "zk_config_deprecated";
265
+ readonly zkConfigIncompatible: "zk_config_incompatible";
266
+ readonly invalidProof: "invalid_proof";
267
+ readonly kytRejected: "kyt_rejected";
268
+ readonly kytAuthorizationMismatch: "kyt_authorization_mismatch";
269
+ readonly escrowRelayUnconfigured: "escrow_relay_unconfigured";
270
+ readonly escrowAuthorizationMismatch: "escrow_authorization_mismatch";
271
+ readonly nullifiersSpent: "nullifiers_spent";
272
+ readonly simulationFailed: "simulation_failed";
273
+ readonly resourceFeeExceeded: "resource_fee_exceeded";
274
+ readonly resourceLimitExceeded: "resource_limit_exceeded";
275
+ readonly sendFailed: "send_failed";
276
+ readonly transactionFailed: "transaction_failed";
277
+ readonly infrastructureFailed: "infrastructure_failed";
278
+ };
279
+ type RelayPublicReason = (typeof RELAY_PUBLIC_REASON)[keyof typeof RELAY_PUBLIC_REASON];
280
+ declare const RELAY_HTTP_REASON: {
281
+ readonly payloadTooLarge: "payload_too_large";
282
+ readonly rateLimited: "rate_limited";
283
+ readonly unsupportedPool: "unsupported_pool";
284
+ readonly queueAtCapacity: "queue_at_capacity";
285
+ readonly lowBalance: "low_balance";
286
+ readonly conflictingPayload: "conflicting_payload";
287
+ readonly retryNotAllowed: "retry_not_allowed";
288
+ readonly notFound: "not_found";
289
+ readonly relayerUnavailable: "relayer_unavailable";
290
+ readonly invalidPackage: "invalid_package";
291
+ readonly unsupportedSignalShape: "unsupported_signal_shape";
292
+ };
293
+ type RelayHttpReason = (typeof RELAY_HTTP_REASON)[keyof typeof RELAY_HTTP_REASON];
294
+ declare function isRetryableFailureReason(reason: string | null | undefined): boolean;
295
+ declare function isRejectionReason(reason: string): boolean;
296
+
297
+ export { type CreateRelayApiInput, DEFAULT_SETTLEMENT_MAX_ATTEMPTS, DEFAULT_SETTLEMENT_POLL_INTERVAL_MS, type DeliveryOutboxEntry, type NewPrivateOperation, PENDING_OPERATION_PHASE, type PendingOperationPhase, type PendingPrivateOperation, type PendingPrivateOperationStore, ProtocolRelayClientError, type ProtocolRelayPorts, RELAY_HTTP_REASON, RELAY_PACKAGE_VERSION_V1, RELAY_PUBLIC_REASON, RELAY_STATUS, type RelayApi, RelayApiError, type RelayHttpReason, type RelayLifecycleStatus, type RelayPackageJson, type RelayPackageSerializable, type RelayPublicReason, type RelayRequestAccepted, type RelayRequestStatus, type RelayRuntimeConfig, SUBMISSION_PATH, type SafeDisplayMetadata, type SdkFinalizationSnapshot, type SubmissionPath, type SubmitPrivateOperationResult, awaitRelaySettlement, canOfferDirectSubmission, canRetryRelayAttempt, createRelayApi, deserializeRelayPackage, isInfrastructureRelayFailure, isRejectionReason, isRelayApiError, isRelayConfigured, isRetryableFailureReason, isTerminalRelayStatus, isUnfinalizedRelayOperation, jsonSafeClone, pollPendingOperation, resolveRelayOrigin, resumePendingOperations, retryFailedRelayAttempt, serializeRelayPackage, submitAndAwaitPrivateOperation, submitDirectFallback, submitPreparedPrivateOperation, throwIfRelayUnsuccessful };