Skip to content

Conversation

@PeterChen13579
Copy link
Contributor

High Level Overview of Change

This PR adds the ability to filter account_tx results based on transaction delegation. This is useful for scenarios where one account (the delegatee) signs and submits a transaction on behalf of another account (the delegator).

Context of Change

New Request Parameter:

"delegate": {
"delegate_filter": "delegatee" or "delegator",
"counter_party": "account_address"
}

Example: If User A submits a transaction to C on behalf of B:
User B (The account holder) can query with delegate_filter: "delegatee" to find transactions signed by others (like User A).
User A (The signer) can query with delegate_filter: "delegator" to find transactions they signed for others (like User B).
The counter_party field can be used in either case to filter by a specific valid address

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking change that only restructures code)
  • Performance (increase or change in throughput and/or latency)
  • Tests (you added tests for code that already exists, or your new feature included in this PR)
  • Documentation update
  • Chore (no impact to binary, e.g. .gitignore, formatting, dropping support for older tooling)
  • Release

API Impact

  • Public API: New feature (new methods and/or new fields)
  • Public API: Breaking change (in general, breaking changes should only impact the next api_version)
  • libxrpl change (any change that may affect libxrpl or dependents of libxrpl)
  • Peer protocol change (must be backward compatible or bump the peer protocol version)

@PeterChen13579 PeterChen13579 requested a review from a team as a code owner December 8, 2025 21:11
@codecov
Copy link

codecov bot commented Dec 8, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.1%. Comparing base (c9f17dd) to head (eb55b3d).
⚠️ Report is 8 commits behind head on develop.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##           develop   #6126     +/-   ##
=========================================
+ Coverage     78.6%   79.1%   +0.5%     
=========================================
  Files          818     839     +21     
  Lines        68938   71425   +2487     
  Branches      8240    8346    +106     
=========================================
+ Hits         54177   56469   +2292     
- Misses       14761   14956    +195     
Files with missing lines Coverage Δ
src/xrpld/app/misc/NetworkOPs.cpp 69.9% <100.0%> (+0.1%) ⬆️
src/xrpld/app/rdb/RelationalDatabase.h 100.0% <ø> (ø)
src/xrpld/app/rdb/backend/detail/Node.cpp 60.5% <100.0%> (+1.7%) ⬆️
src/xrpld/rpc/handlers/AccountTx.cpp 88.8% <100.0%> (+1.2%) ⬆️

... and 71 files with indirect coverage changes

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@kuznetsss
Copy link
Contributor

A few suggestions to the new API:

  • don't duplicate delegate in filter:
"delegate": {
  "filter": "delegatee" or "delegator",
  "counter_party": "account_address"
}
  • maybe use role instead of filter?

  • I remember we had some other naming ideas for delegator and delegatee, did you decide that these one are the best?

auto const& filter = options.delegate.value();
auto const& contextAccount = options.account;

if (filter.type == DelegateType::Delegatee)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be better to use switch case for enum

Comment on lines +464 to +501
if (params.isMember("delegate"))
{
auto const& delegateNode = params["delegate"];

if (!delegateNode.isObject() ||
!delegateNode.isMember(jss::delegate_filter))
return RPC::invalid_field_error("delegate");

DelegateFilter filter;

if (!delegateNode[jss::delegate_filter].isString())
return RPC::invalid_field_error(jss::delegate_filter);

auto const& delegateFilterStr =
delegateNode[jss::delegate_filter].asString();

if (delegateFilterStr == "delegatee")
filter.type = DelegateType::Delegatee;
else if (delegateFilterStr == "delegator")
filter.type = DelegateType::Delegator;
else
return RPC::invalid_field_error(jss::delegate_filter);

if (delegateNode.isMember(jss::counterparty))
{
if (!delegateNode[jss::counterparty].isString())
return RPC::invalid_field_error(jss::counterparty);

auto const counterparty = parseBase58<AccountID>(
delegateNode[jss::counterparty].asString());
if (!counterparty)
return rpcError(rpcACT_MALFORMED);

filter.counterparty = *counterparty;
}

args.delegate = filter;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe all of this could be moved to a static method of DelegateFilter?

std::expected<std::optional<DelegateFilter>, RpcError> create(params);

The the usage will be much simpler:

if (auto const filter = DelegateFilter::create(params); filter.has_value()) {
  args.filter = *filter;
} else {
  return filter.error();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants