Skip to content

design: evaluate sync/async maintenance strategy during D2 #492

Open

Description

@TimeToBuildBob

Background

PR #491 adds async clients (asyncuniswap.py, asyncuniswap4.py) alongside the existing sync clients. Handwritten parallel implementations can require fixes and protocol changes in two places. Its review raised nonce-handling and ETH-route concerns; those correctness/testing questions are distinct from whether generation is the best maintenance approach.

@ErikBjare raised the maintenance concern during grant planning and requested this issue to evaluate an async source of truth with generated synchronous clients. @liquid-8 points to web3.py's separate Web3/AsyncWeb3 classes and considers the current approach reasonable, with discussion welcome during D2.

Goal

Evaluate whether single-source generation would improve maintenance without adding disproportionate complexity or breaking either public API. Keep sync and async clients distinct; this is not a proposal to combine them into one runtime class.

Retaining separate implementations is a valid outcome. No framework or rewrite has been selected.

Options to evaluate during D2

  1. Current separate clients, shared pure helpers, and matched behavioral tests — retain provider-specific I/O and reduce duplication where it is straightforward.
  2. unasync or comparable async-to-sync generation — generate a separate synchronous implementation from canonical async code. Evaluate the required provider/type/decorator/locking mappings and reproducibility before adopting it.
  3. Thin sync wrapper around async code — evaluate event-loop ownership, provider/session lifetimes, and compatibility with callers already running an event loop. Per-call asyncio.run() is not a drop-in solution.

Using AnyIO or Trio alone does not generate a synchronous API; an explicit bridge or generation strategy would still be required.

Decision / acceptance criteria

  • Discuss during D2 and record whether to retain the current design or pursue a bounded prototype, with rationale.
  • If a prototype is agreed, verify compatibility and run the existing sync and async tests; do not infer correctness from signature parity alone.
  • Define appropriate parity coverage for the chosen design: signatures where applicable, plus matched behavioral tests for equivalent routes/calldata/value, approval, and nonce behavior, with intentional differences documented.
  • Document the chosen maintenance approach and, if generation is adopted, the reproducibility check and contributing workflow.

A per-instance nonce lock does not coordinate different client instances using the same wallet; that limitation and the documented native multihop testing gap remain separate correctness/testing concerns.

Relationship to #491 / UF grant

PR #491 is merged and ships the handwritten sync and async implementations for D1. This issue tracks a D2 architecture discussion, not a D1 merge blocker. The architectural refactor is not an agreed production-release prerequisite; any resulting implementation work requires an explicit scope decision.

Activity

  1. liquid-8 commented on Oct 14, 2026

    @liquid-8
    Member

    Let's take a look at web3.py. Open
    https://github.com/ApeWorX/web3.py/blob/main/web3/main.py
    What's there? Two classes: Web3 and AsyncWeb3. Historically, devs tried to keep it together, but dismissed that idea before 0.7, if I recall correctly. The reason is simple: it is easier to write ~ the same code twice than to combine both async and sync functionality in one class. Besides simplicity, there is also a quite logical reason: you shouldn't mix sync and async code without necessity (unless you know what you're doing very well).

    The aforementioned issues, like nonce handling and ETH route logic, are non-blocking. The former has always been a potential problem, just a non-obvious one. All you have to do to see that is to send transactions from the same wallet using different wrapper instances; here we go. Providing custom nonces lets users manage that. Regarding the ETH route logic, v2/v3 native multihops have been added in this delivery for a reason, but haven't been tested, which is stated clearly in the release notes.

    TL; DR: I think we should not be very concerned about this. However, I don't mind discussing this matter during D2.

  2. TimeToBuildBob commented on Oct 14, 2026

    @TimeToBuildBob
    ContributorAuthor

    Agreed on keeping this a D2 discussion rather than blocking D1. I checked the linked web3/main.py: it has separate Web3/AsyncWeb3 classes and provider-specific I/O, with common utilities in BaseWeb3. That is a useful precedent for keeping the public sync and async APIs separate. My concern is duplicated swap/transaction logic, not the existence of two classes; generation would still produce two separate classes, without running async code inside the sync client.

    The issue is too prescriptive as written. Erik asked us to evaluate single-source maintenance, not to commit to a rewrite, and my “before v4 is production-ready” sentence overstates that request. Keeping the current implementations is a valid outcome if generation adds more complexity than it removes. I would not choose per-call asyncio.run() wrappers as the default: event-loop ownership and provider/session lifetimes need explicit handling.

    For D2, the useful comparison is the current separate clients with shared pure helpers and matched behavioral tests versus a bounded generation prototype. Signatures alone cannot establish parity; equivalent route inputs should produce equivalent calldata/value, and tests should cover approval and nonce behavior while documenting intentional differences. I also agree that a per-instance nonce lock does not coordinate multiple clients using the same wallet, and that the documented native multihop testing gap is separate from this architecture decision. No refactor started here; the next step is agreement during D2 on whether a prototype is warranted and which compatibility requirements it must preserve.

  3. changed the title [-]refactor: single async source of truth with generated/wrapped sync client (eliminate dual-implementation drift)[/-] [+]design: evaluate sync/async maintenance strategy during D2[/+] on Oct 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions