Skip to content

Commit aa152ec

Browse files
authored
chore: cleanup address providers logic (#153)
* fix(core): derive ArkPaymentContract for unknown purpose, like in HD wallet, rather than htlc with random preimage * refractor(core): refractor HDAddresProvider for better readiness. remove dead code * fix: rollback contract in SingleKeyAddressProvider.cs change, it wasn't a bug
1 parent 5171d69 commit aa152ec

2 files changed

Lines changed: 76 additions & 74 deletions

File tree

NArk.Core/Wallet/HierarchicalDeterministicAddressProvider.cs

Lines changed: 45 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,8 @@ public class HierarchicalDeterministicAddressProvider(
2020
ArkAddress? sweepDestination)
2121
: IArkadeAddressProvider
2222
{
23-
private int _lastUsedIndex = wallet.LastUsedIndex;
24-
25-
public async Task<bool> IsOurs(OutputDescriptor descriptor, CancellationToken cancellationToken = default)
23+
///<inheritdoc/>
24+
public Task<bool> IsOurs(OutputDescriptor descriptor, CancellationToken cancellationToken = default)
2625
{
2726
// Guard: the wallet must have an account descriptor. The pre-existing
2827
// `OutputDescriptor.Parse(wallet.AccountDescriptor, network)` line on
@@ -36,12 +35,13 @@ public async Task<bool> IsOurs(OutputDescriptor descriptor, CancellationToken ca
3635
var index = descriptor.Extract().DerivationPath?.Indexes.Last().ToString();
3736
if (index is null)
3837
{
39-
return false;
38+
return Task.FromResult(false);
4039
}
4140
var expected = GetDescriptorFromIndex(network, wallet.AccountDescriptor, Convert.ToInt32(index));
42-
return expected.Equals(descriptor);
41+
return Task.FromResult(expected.Equals(descriptor));
4342
}
44-
43+
44+
///<inheritdoc/>
4545
public async Task<OutputDescriptor> GetNextSigningDescriptor(CancellationToken cancellationToken = default)
4646
{
4747
await using var @lock = await safetyService.LockKeyAsync($"wallet::{wallet.Id}", cancellationToken);
@@ -58,8 +58,6 @@ public async Task<OutputDescriptor> GetNextSigningDescriptor(CancellationToken c
5858

5959
await walletStorage.UpdateLastUsedIndex(wallet.Id, nextIndex + 1, cancellationToken);
6060

61-
_lastUsedIndex = nextIndex + 1;
62-
6361
return descriptor;
6462
}
6563

@@ -74,58 +72,56 @@ internal static OutputDescriptor GetDescriptorFromIndex(Network network, string
7472
{
7573
// Route through the cached parser — `OutputDescriptor.Parse` is observed
7674
// at ~500-1000ms per call and this path runs on every IsOurs check.
77-
return NArk.Abstractions.Extensions.KeyExtensions.ParseOutputDescriptor(descriptor.Replace("/*", $"/{index}"), network);
75+
return KeyExtensions.ParseOutputDescriptor(descriptor.Replace("/*", $"/{index}"), network);
7876
}
7977

78+
///<inheritdoc/>
8079
public async Task<(ArkContract contract, ArkContractEntity entity)> GetNextContract(
8180
NextContractPurpose purpose,
8281
ContractActivityState activityState,
8382
ArkContract[]? inputContracts = null,
8483
CancellationToken cancellationToken = default)
8584
{
8685
var info = await transport.GetServerInfoAsync(cancellationToken);
87-
ArkContract? result = null;
88-
89-
if (purpose == NextContractPurpose.Boarding)
90-
{
91-
result = new ArkBoardingContract(
92-
info.SignerKey,
93-
info.BoardingExit,
94-
await GetNextSigningDescriptor(cancellationToken)
95-
);
96-
}
97-
else if (purpose == NextContractPurpose.SendToSelf && sweepDestination is not null)
98-
{
99-
result = new UnknownArkContract(sweepDestination, info.SignerKey, info.Network.ChainName == ChainName.Mainnet);
100-
activityState = ContractActivityState.Inactive;
101-
}
102-
else if (purpose == NextContractPurpose.SendToSelf)
86+
87+
(ArkContract contract, ContractActivityState state) = purpose switch
10388
{
104-
var recycledDescriptor = inputContracts is not null
105-
? await TryGetRecyclableDescriptor(inputContracts, info.SignerKey, cancellationToken)
106-
: null;
107-
108-
if (recycledDescriptor is not null)
109-
{
110-
result = new ArkPaymentContract(info.SignerKey, info.UnilateralExit, recycledDescriptor);
111-
activityState = ContractActivityState.Inactive;
112-
}
113-
else
114-
{
115-
activityState = ContractActivityState.AwaitingFundsBeforeDeactivate;
116-
}
117-
}
118-
119-
result ??= new ArkPaymentContract(
120-
info.SignerKey,
121-
info.UnilateralExit,
122-
await GetNextSigningDescriptor(cancellationToken)
123-
);
124-
125-
var entity = result.ToEntity(wallet.Id, info.SignerKey, null, activityState);
126-
if (result is UnknownArkContract)
89+
NextContractPurpose.Boarding => (
90+
new ArkBoardingContract(
91+
info.SignerKey, info.BoardingExit, await GetNextSigningDescriptor(cancellationToken)),
92+
activityState),
93+
94+
// Collaborative-exit sweep target: a fixed external address, never tracked.
95+
NextContractPurpose.SendToSelf when sweepDestination is not null => (
96+
new UnknownArkContract(sweepDestination, info.SignerKey, info.Network.ChainName == ChainName.Mainnet),
97+
ContractActivityState.Inactive),
98+
99+
NextContractPurpose.SendToSelf =>
100+
await SendToSelfContractAsync(info, inputContracts, cancellationToken),
101+
102+
_ => (
103+
new ArkPaymentContract(info.SignerKey, info.UnilateralExit, await GetNextSigningDescriptor(cancellationToken)),
104+
activityState),
105+
};
106+
107+
var entity = contract.ToEntity(wallet.Id, info.SignerKey, null, state);
108+
if (contract is UnknownArkContract)
127109
entity = entity with { Metadata = new Dictionary<string, string> { ["Source"] = "sweep-destination" } };
128-
return (result, entity);
110+
return (contract, entity);
111+
}
112+
113+
private async Task<(ArkContract, ContractActivityState)> SendToSelfContractAsync(
114+
ArkServerInfo info, ArkContract[]? inputContracts, CancellationToken cancellationToken)
115+
{
116+
var recycledDescriptor = inputContracts is not null
117+
? await TryGetRecyclableDescriptor(inputContracts, info.SignerKey, cancellationToken)
118+
: null;
119+
120+
return recycledDescriptor is not null
121+
? (new ArkPaymentContract(info.SignerKey, info.UnilateralExit, recycledDescriptor),
122+
ContractActivityState.Inactive)
123+
: (new ArkPaymentContract(info.SignerKey, info.UnilateralExit, await GetNextSigningDescriptor(cancellationToken)),
124+
ContractActivityState.AwaitingFundsBeforeDeactivate);
129125
}
130126

131127
private async Task<OutputDescriptor?> TryGetRecyclableDescriptor(

NArk.Core/Wallet/SingleKeyAddressProvider.cs

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public class SingleKeyAddressProvider(
2121
{
2222
public OutputDescriptor Descriptor { get; } = OutputDescriptor.Parse(wallet.AccountDescriptor!, network);
2323

24+
/// <inheritdoc/>>
2425
public Task<bool> IsOurs(OutputDescriptor descriptor, CancellationToken cancellationToken = default)
2526
{
2627
var theirs = descriptor.Extract().XOnlyPubKey.ToBytes();
@@ -40,11 +41,13 @@ public Task<bool> IsOurs(OutputDescriptor descriptor, CancellationToken cancella
4041
return Task.FromResult(match);
4142
}
4243

44+
/// <inheritdoc/>>
4345
public Task<OutputDescriptor> GetNextSigningDescriptor(CancellationToken cancellationToken = default)
4446
{
4547
return Task.FromResult(Descriptor);
4648
}
47-
49+
50+
/// <inheritdoc/>>
4851
public async Task<(ArkContract contract, ArkContractEntity entity)> GetNextContract(
4952
NextContractPurpose purpose,
5053
ContractActivityState activityState,
@@ -53,32 +56,35 @@ public Task<OutputDescriptor> GetNextSigningDescriptor(CancellationToken cancell
5356
{
5457
var info = await transport.GetServerInfoAsync(cancellationToken);
5558
var signingDescriptor = await GetNextSigningDescriptor(cancellationToken);
56-
ArkContract? result = null;
57-
if (purpose == NextContractPurpose.Boarding)
58-
{
59-
result = new ArkBoardingContract(info.SignerKey, info.BoardingExit, signingDescriptor);
60-
}
61-
else if (purpose == NextContractPurpose.SendToSelf && sweepingAddress is not null)
62-
{
63-
result = new UnknownArkContract(sweepingAddress, info.SignerKey, info.Network.ChainName == ChainName.Mainnet);
64-
activityState = ContractActivityState.Inactive;
65-
}
66-
else if (purpose == NextContractPurpose.SendToSelf)
59+
60+
(ArkContract contract, ContractActivityState state) = purpose switch
6761
{
68-
result = new ArkPaymentContract(info.SignerKey, info.UnilateralExit, signingDescriptor);
69-
activityState = ContractActivityState.Active;
70-
}
62+
NextContractPurpose.Boarding => (
63+
(ArkContract)new ArkBoardingContract(info.SignerKey, info.BoardingExit, signingDescriptor),
64+
activityState),
65+
66+
// Collaborative-exit sweep target: a fixed external address, never tracked.
67+
NextContractPurpose.SendToSelf when sweepingAddress is not null => (
68+
new UnknownArkContract(sweepingAddress, info.SignerKey, info.Network.ChainName == ChainName.Mainnet),
69+
ContractActivityState.Inactive),
70+
71+
NextContractPurpose.SendToSelf => (
72+
new ArkPaymentContract(info.SignerKey, info.UnilateralExit, signingDescriptor),
73+
ContractActivityState.Active),
74+
75+
_ => (
76+
new HashLockedArkPaymentContract(
77+
info.SignerKey,
78+
info.UnilateralExit,
79+
signingDescriptor,
80+
RandomUtils.GetBytes(32),
81+
HashLockTypeOption.Hash160
82+
), activityState)
83+
};
7184

72-
result ??= new HashLockedArkPaymentContract(
73-
info.SignerKey,
74-
info.UnilateralExit,
75-
signingDescriptor,
76-
RandomUtils.GetBytes(32),
77-
HashLockTypeOption.Hash160
78-
);
79-
var entity = result.ToEntity(wallet.Id, info.SignerKey, null, activityState);
80-
if (result is UnknownArkContract)
85+
var entity = contract.ToEntity(wallet.Id, info.SignerKey, null, state);
86+
if (contract is UnknownArkContract)
8187
entity = entity with { Metadata = new Dictionary<string, string> { ["Source"] = "sweep-destination" } };
82-
return (result, entity);
88+
return (contract, entity);
8389
}
8490
}

0 commit comments

Comments
 (0)