From 9ec54e9ca889726d6388af67eafe025633d145f7 Mon Sep 17 00:00:00 2001 From: feruzm Date: Sat, 25 Jul 2026 19:21:44 +0000 Subject: [PATCH 1/2] feat(wallet): expose set_withdraw_vesting_route in the HP action list Clients build the HP operation buttons from this list, so the withdraw routes UI they already implement has no entry point. Add the chain operation name alongside the existing two ids. Adds a parity divergence entry and pins the action ids with tests. --- dotnet/EcencyApi.Tests/WalletActionsTests.cs | 39 +++++++++++++++++++ dotnet/EcencyApi/Handlers/WalletApi.Market.cs | 5 ++- dotnet/parity/driver.py | 4 ++ 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 dotnet/EcencyApi.Tests/WalletActionsTests.cs diff --git a/dotnet/EcencyApi.Tests/WalletActionsTests.cs b/dotnet/EcencyApi.Tests/WalletActionsTests.cs new file mode 100644 index 00000000..dee23509 --- /dev/null +++ b/dotnet/EcencyApi.Tests/WalletActionsTests.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Nodes; +using EcencyApi.Handlers; +using Xunit; + +namespace EcencyApi.Tests; + +/// +/// The token action ids are a client-facing contract: web and mobile map them to +/// concrete wallet operations, and an id neither client recognizes either renders +/// nothing (web drops unknown ids) or renders an unlabeled control (mobile passes +/// them through). Pin the ids and their order. +/// +public class WalletActionsTests +{ + private static string[] Ids(JsonArray actions) => + actions.Select(a => a!["id"]!.GetValue()).ToArray(); + + [Fact] + public void HpActions_AreTheChainOperationNamesInRenderOrder() + { + Assert.Equal( + new[] { "delegate_vesting_shares", "withdraw_vesting", "set_withdraw_vesting_route" }, + Ids(WalletApi.HpActions())); + } + + [Fact] + public void HiveActions_OnlyOfferSavingsWithdrawalWhenSavingsExist() + { + Assert.DoesNotContain("transfer_from_savings", Ids(WalletApi.BuildHiveActions(0))); + Assert.Contains("transfer_from_savings", Ids(WalletApi.BuildHiveActions(1.5))); + } + + [Fact] + public void HbdActions_OnlyOfferSavingsWithdrawalWhenSavingsExist() + { + Assert.DoesNotContain("transfer_from_savings", Ids(WalletApi.BuildHbdActions(0))); + Assert.Contains("transfer_from_savings", Ids(WalletApi.BuildHbdActions(1.5))); + } +} diff --git a/dotnet/EcencyApi/Handlers/WalletApi.Market.cs b/dotnet/EcencyApi/Handlers/WalletApi.Market.cs index 06e8c58a..1cd00e52 100644 --- a/dotnet/EcencyApi/Handlers/WalletApi.Market.cs +++ b/dotnet/EcencyApi/Handlers/WalletApi.Market.cs @@ -32,7 +32,10 @@ internal static JsonArray BuildHiveActions(double savings) return Actions(ids.ToArray()); } - internal static JsonArray HpActions() => Actions("delegate_vesting_shares", "withdraw_vesting"); + // set_withdraw_vesting_route lets clients link straight to the withdraw routes UI. + // Deliberately added after the original two ids: clients render actions in order. + internal static JsonArray HpActions() => + Actions("delegate_vesting_shares", "withdraw_vesting", "set_withdraw_vesting_route"); internal static JsonArray BuildHbdActions(double savings) { diff --git a/dotnet/parity/driver.py b/dotnet/parity/driver.py index 606be668..950b56f2 100644 --- a/dotnet/parity/driver.py +++ b/dotnet/parity/driver.py @@ -228,6 +228,10 @@ def norm_body(text): "Same request-delete rerouting as ::min.", "/private-api/request-delete::badcode": "Same request-delete rerouting as ::min.", + "/wallet-api/portfolio-v2::pop": + "The HP action list gained set_withdraw_vesting_route so clients can link " + "straight to the withdraw routes UI; the reference build emits only " + "delegate_vesting_shares and withdraw_vesting.", } From 7633c76355cf09eaa601d0e5115c9104cbfc8cd4 Mon Sep 17 00:00:00 2001 From: feruzm Date: Sat, 25 Jul 2026 19:27:40 +0000 Subject: [PATCH 2/2] review: drop the portfolio-v2 parity exemption, keep the case checked A KNOWN_DIVERGENCES entry skips the case entirely, status and content-type included, which would hide unrelated regressions in the whole aggregation endpoint. The body is already exempt via loose comparison because balances and APR move every block, and that path still checks status and content-type. --- dotnet/parity/driver.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/dotnet/parity/driver.py b/dotnet/parity/driver.py index 950b56f2..ed367eac 100644 --- a/dotnet/parity/driver.py +++ b/dotnet/parity/driver.py @@ -228,12 +228,17 @@ def norm_body(text): "Same request-delete rerouting as ::min.", "/private-api/request-delete::badcode": "Same request-delete rerouting as ::min.", - "/wallet-api/portfolio-v2::pop": - "The HP action list gained set_withdraw_vesting_route so clients can link " - "straight to the withdraw routes UI; the reference build emits only " - "delegate_vesting_shares and withdraw_vesting.", } +# Deliberately NOT listed above: /wallet-api/portfolio-v2::pop, whose HP action list +# gained set_withdraw_vesting_route (the reference build emits only +# delegate_vesting_shares and withdraw_vesting). A KNOWN_DIVERGENCES entry skips the +# case entirely — status and content-type included — which would blind this large +# aggregation endpoint to unrelated regressions. Its body is already exempt for a +# better reason: balances and APR move every block, so the run-vs-run comparison puts +# it in `loose`, which still checks status and content-type. Add an entry here only +# for a divergence that is deterministic and not already loose. + def diff(a_name, b_name, loose_name=None): a = json.loads((HERE / f"run-{a_name}.json").read_text())