Table of Contents

Class ContractReconciliationService

Namespace
NArk.Core.Services
Assembly
NArk.Core.dll

Keeps every SingleKey wallet's advertised "Default" contract aligned with the CURRENT arkd signer, and flags any wallet whose Arkade sweep destination was orphaned by a signer rotation.

A SingleKey wallet's Default contract is derived from ArkServerInfo.SignerKey. When arkd rotates its signer, the old-signer Default becomes stale: the new-signer Default must be derived (and advertised), and the stale one superseded so only one row is the advertised default.

Triggers:

Destination safety: on the same triggers, for ANY wallet (SingleKey or HD) that has a sweep destination, if the destination's ArkAddress server key is now a deprecated signer the destination is flagged pending re-confirmation (a Metadata marker via DestinationSafety) and DestinationDisabled is raised once (on the set transition); a destination that is no longer stale clears the flag. WalletSaved therefore also enqueues HD wallets that carry a destination, so a re-save clears the flag.

Supersede semantics: funds safety does NOT depend on the deactivation — the sweeper gathers coins by VTXO script regardless of Active state — so deactivating stale Source="Default" rows is purely about which row is the advertised default.

Lifecycle mirrors SweeperService: event handlers only enqueue (non-blocking), a background worker drains the channel, subscribe in StartAsync(CancellationToken), unsubscribe + cancel in DisposeAsync().

public class ContractReconciliationService : IAsyncDisposable, IDestinationSafetyNotifier
Inheritance
ContractReconciliationService
Implements
Inherited Members

Constructors

ContractReconciliationService(IWalletStorage, IContractStorage, ISingleKeyDefaultEnsurer, IServerInfoCacheInvalidation, IClientTransport, ILogger<ContractReconciliationService>?, TimeSpan?)

Keeps every SingleKey wallet's advertised "Default" contract aligned with the CURRENT arkd signer, and flags any wallet whose Arkade sweep destination was orphaned by a signer rotation.

A SingleKey wallet's Default contract is derived from ArkServerInfo.SignerKey. When arkd rotates its signer, the old-signer Default becomes stale: the new-signer Default must be derived (and advertised), and the stale one superseded so only one row is the advertised default.

Triggers:

Destination safety: on the same triggers, for ANY wallet (SingleKey or HD) that has a sweep destination, if the destination's ArkAddress server key is now a deprecated signer the destination is flagged pending re-confirmation (a Metadata marker via DestinationSafety) and DestinationDisabled is raised once (on the set transition); a destination that is no longer stale clears the flag. WalletSaved therefore also enqueues HD wallets that carry a destination, so a re-save clears the flag.

Supersede semantics: funds safety does NOT depend on the deactivation — the sweeper gathers coins by VTXO script regardless of Active state — so deactivating stale Source="Default" rows is purely about which row is the advertised default.

Lifecycle mirrors SweeperService: event handlers only enqueue (non-blocking), a background worker drains the channel, subscribe in StartAsync(CancellationToken), unsubscribe + cancel in DisposeAsync().

public ContractReconciliationService(IWalletStorage walletStorage, IContractStorage contractStorage, ISingleKeyDefaultEnsurer defaultEnsurer, IServerInfoCacheInvalidation serverInfoCacheInvalidation, IClientTransport clientTransport, ILogger<ContractReconciliationService>? logger = null, TimeSpan? reconcileAllRetryDelay = null)

Parameters

walletStorage IWalletStorage
contractStorage IContractStorage
defaultEnsurer ISingleKeyDefaultEnsurer
serverInfoCacheInvalidation IServerInfoCacheInvalidation
clientTransport IClientTransport
logger ILogger<ContractReconciliationService>
reconcileAllRetryDelay TimeSpan?

Methods

DisposeAsync()

Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources asynchronously.

public ValueTask DisposeAsync()

Returns

ValueTask

A task that represents the asynchronous dispose operation.

ReconcileAllAsync(CancellationToken)

Reconciles every SingleKey wallet known to storage. Per-wallet failures during a good-backend pass are absorbed and logged so one bad wallet doesn't abort the whole pass.

First probes backend availability via GetServerInfoAsync(CancellationToken). If the backend is unreachable (e.g. arkd down at boot after a rotation-while-offline) the probe throws and the WHOLE pass fails — surfaced to the loop so the bounded retry requeues it. Without the probe, "arkd down" would only surface inside each wallet's EnsureDefaultAsync(string, CancellationToken) (its own GetServerInfo call), which the per-wallet catch below absorbs, so the pass would silently reconcile nothing and never retry. The probe is a cheap availability gate: a successful result is cached by the transport, so the per-wallet ensures reuse it rather than re-fetching.

public Task ReconcileAllAsync(CancellationToken cancellationToken = default)

Parameters

cancellationToken CancellationToken

Returns

Task

ReconcileWalletAsync(string, CancellationToken)

Ensures the wallet's current-signer Default exists, then deactivates any stale pre-rotation defaults (Active, Source="Default", script != current). No-op when the wallet is missing or not SingleKey.

public Task ReconcileWalletAsync(string walletId, CancellationToken cancellationToken = default)

Parameters

walletId string
cancellationToken CancellationToken

Returns

Task

StartAsync(CancellationToken)

public Task StartAsync(CancellationToken cancellationToken)

Parameters

cancellationToken CancellationToken

Returns

Task

Events

DestinationDisabled

Raised when a wallet's sweep destination is auto-disabled due to an Arkade signer rotation.

public event EventHandler<DestinationDisabledEventArgs>? DestinationDisabled

Event Type

EventHandler<DestinationDisabledEventArgs>