Decryption Lifecycle
This page explains the shared production-side lifecycle behind both Decrypting to View and Decrypting to Transact.
It is not about permits, return types, or builder methods. It is about what happens after you already have a ctHash and ask CoFHE to decrypt it.
Why decryption is not always immediate
In production, decryption depends on the CoFHE coprocessor observing on-chain activity and preparing work for the Threshold Network.
That means there are three distinct states your request can pass through:
- The coprocessor has not seen the
ctHashon-chain yet. - The coprocessor knows the
ctHash, but has not produced a decryptionrequest_idyet. - A
request_idexists, and the Threshold Network is processing the request.
The SDK behavior differs in each state.
State 1: ctHash not known yet
If the coprocessor has not observed the ctHash on-chain yet, the submit request can fail with 404.
In practice, this usually means one of the following:
- the encrypting transaction was sent very recently,
- the relevant event has not been indexed or processed yet,
- you are trying to decrypt a handle from the wrong chain or environment,
- the
ctHashis invalid for that network.
At this stage there is no decryption request yet, so there is no request_id to poll.
The SDK does not automatically retry this 404 case for you. It surfaces the error, and the caller should retry later if the handle is expected to become available soon.
State 2: ctHash known, but no request_id yet
Once the coprocessor is aware of the ctHash, it still may not be ready to start the Threshold Network request immediately.
In that case the submit endpoint can return 204 No Content without a request_id.
This means:
- the handle is known,
- the request is not ready yet,
- retrying the submit call makes sense.
This is the case the SDK now handles automatically.
The SDK also treats short-lived submit-time 404 Not Found responses as retryable during an initial indexing window. By default, that 404 retry window is 10 seconds and can be changed via .set404RetryTimeout(timeoutMs) on either decrypt builder.
For both decryptForTx and decryptForView, the SDK will:
- retry the submit call,
- wait between attempts,
- keep using the same overall timeout budget,
- emit
.onPoll(...)callbacks during these retries.
During this stage, the .onPoll(...) callback receives an empty requestId because no request has been created yet.
State 3: request_id assigned
Once the submit endpoint returns a request_id, the decryption request has been accepted and the SDK switches to status polling.
From this point on, the SDK polls the request status endpoint until one of these happens:
- the request completes successfully,
- the request completes with an error,
- the request times out,
- the request is no longer found.
This is the normal long-polling phase most users think of as decryption polling.
Shared timeout budget
Submit retries and status polling do not have separate timers.
The SDK uses one shared timeout budget across the full production lifecycle:
- initial submit attempts,
- repeated
204submit retries, - transient
404submit retries within the configured 404 retry window, - status polling after
request_idcreation.
This matters because a request can spend most of its time budget before the request_id even exists.
How .onPoll(...) fits in
Both decryption builders support .onPoll(...):
The callback context is:
{
operation: 'decrypt' | 'sealoutput';
requestId: string;
attemptIndex: number;
elapsedMs: number;
intervalMs: number;
timeoutMs: number;
}Interpretation:
requestId === '': the SDK is still retrying submit because no request has been created yet.requestId !== '': the SDK is polling an existing request.operation === 'decrypt':decryptForTxflow.operation === 'sealoutput':decryptForViewflow.
End-to-end summary
decrypt call starts
-> submit request
-> 404 within retry window: SDK retries submit automatically
-> 404 after retry window: fail and surface submit-time error
-> 204 without request_id: SDK retries submit automatically
-> 200 with request_id: SDK starts status polling
-> PROCESSING: keep polling
-> COMPLETED: return decrypt resultPractical guidance
- If you just submitted the encrypting transaction, expect decryption to be eventually consistent rather than immediate.
- If you get a submit-time
404, the SDK now retries automatically for a short window; increase.set404RetryTimeout(...)if your backend indexes handles more slowly. - If you want progress UI, use
.onPoll(...)and treat an emptyrequestIdaswaiting for request creation. - If you are debugging slow decrypts, separate
coprocessor has not created a request yetfromThreshold Network is processing an existing request.