Class ContractReconciliationService
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:
- WalletSaved (wallet created/updated) → reconcile that one wallet.
- ServerInfoChanged (signer rotated) → reconcile ALL SingleKey wallets.
- Startup pass on StartAsync(CancellationToken) → reconcile ALL SingleKey wallets (covers wallets rotated while offline).
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:
- WalletSaved (wallet created/updated) → reconcile that one wallet.
- ServerInfoChanged (signer rotated) → reconcile ALL SingleKey wallets.
- Startup pass on StartAsync(CancellationToken) → reconcile ALL SingleKey wallets (covers wallets rotated while offline).
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
walletStorageIWalletStoragecontractStorageIContractStoragedefaultEnsurerISingleKeyDefaultEnsurerserverInfoCacheInvalidationIServerInfoCacheInvalidationclientTransportIClientTransportloggerILogger<ContractReconciliationService>reconcileAllRetryDelayTimeSpan?
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
cancellationTokenCancellationToken
Returns
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
walletIdstringcancellationTokenCancellationToken
Returns
StartAsync(CancellationToken)
public Task StartAsync(CancellationToken cancellationToken)
Parameters
cancellationTokenCancellationToken
Returns
Events
DestinationDisabled
Raised when a wallet's sweep destination is auto-disabled due to an Arkade signer rotation.
public event EventHandler<DestinationDisabledEventArgs>? DestinationDisabled