Skip to content

ZeroX DX zeroX.tradeAsset high-level swap method - #665

Open
AhmedRagabIssa wants to merge 5 commits into
release-candidatefrom
ragab/sdk-70-ios-sdk-implement-zeroxtradeasset-high-level-swap-method
Open

ZeroX DX zeroX.tradeAsset high-level swap method#665
AhmedRagabIssa wants to merge 5 commits into
release-candidatefrom
ragab/sdk-70-ios-sdk-implement-zeroxtradeasset-high-level-swap-method

Conversation

@AhmedRagabIssa

Copy link
Copy Markdown
Contributor

Summary

ZeroX DX zeroX.tradeAsset high-level swap method

Copilot Review: GitHub Copilot will automatically post a review below. Check its comments for additional context and suggestions.

@AhmedRagabIssa AhmedRagabIssa self-assigned this Jun 29, 2026
Copilot AI review requested due to automatic review settings June 29, 2026 12:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds a new high-level 0x swap execution API (zeroX.tradeAsset) to PortalSwift that fetches a quote, submits the returned transaction via portal.request, and polls for an on-chain receipt before returning.

Changes:

  • Introduces ZeroX.tradeAsset(params:onProgress:) with progress reporting and receipt-confirmation polling.
  • Wires Portal into Trading.zeroX via a minimal ZeroXPortalDependency protocol.
  • Adds a new test suite and extends test mocks, plus an example-app UI action and storyboard button for the new swap flow.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Tests/PortalSwiftTests/Trading/ZeroXTradeAssetTests.swift Adds async unit tests covering success/progress and key failure paths for tradeAsset.
Tests/PortalSwiftTests/Trading/ZeroXMock.swift Extends ZeroXProtocol test mock to support tradeAsset calls.
SPM Example/PortalSwift/MainViewController/ViewController+ZeroXTrading.swift Adds an example UI handler that calls portal.trading.zeroX.tradeAsset and logs progress.
SPM Example/PortalSwift/Base.lproj/Main.storyboard Adds a new button wired to the example handleZeroXTradeAsset action.
Sources/PortalSwift/Trading/ZeroX.swift Adds ZeroXPortalDependency, new tradeAsset API, and confirmation polling implementation.
Sources/PortalSwift/Trading/Trading.swift Passes the portal dependency through to ZeroX construction.
Sources/PortalSwift/Portal.swift Injects Portal into Trading and declares conformance to ZeroXPortalDependency.
Sources/PortalSwift/Core/Api/zeroX/ZeroXTradeAsset.swift Introduces ZeroXTradeAssetParams, progress types, result type, and error enum.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Sources/PortalSwift/Trading/ZeroX.swift
Comment thread Sources/PortalSwift/Trading/ZeroX.swift
Comment thread Tests/PortalSwiftTests/Trading/ZeroXMock.swift
…alSwift into ragab/sdk-70-ios-sdk-implement-zeroxtradeasset-high-level-swap-method

* 'release-candidate' of https://github.com/portal-hq/PortalSwift:
  update code owners
@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.58233% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.14%. Comparing base (a8e2343) to head (1ab55fd).

Files with missing lines Patch % Lines
Tests/PortalSwiftTests/Trading/ZeroXMock.swift 33.33% 10 Missing ⚠️
Sources/PortalSwift/Trading/ZeroX.swift 94.39% 6 Missing ⚠️
...ortalSwiftTests/Trading/ZeroXTradeAssetTests.swift 98.09% 5 Missing ⚠️
...s/PortalSwift/Core/Api/zeroX/ZeroXTradeAsset.swift 98.11% 1 Missing ⚠️
Additional details and impacted files
@@                  Coverage Diff                  @@
##           release-candidate     #665      +/-   ##
=====================================================
+ Coverage              87.02%   87.14%   +0.11%     
=====================================================
  Files                    277      279       +2     
  Lines                  35534    36030     +496     
=====================================================
+ Hits                   30923    31397     +474     
- Misses                  4611     4633      +22     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

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

Comment thread Sources/PortalSwift/Core/Api/zeroX/ZeroXTradeAsset.swift

@Rshahatit Rshahatit left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Summary: Adds a high-level portal.trading.zeroX.tradeAsset() that fetches a 0x quote, signs/broadcasts it via eth_sendTransaction, and polls for on-chain confirmation in one call, with onProgress callbacks through the flow (fetching_quotesigningsubmittedconfirmingconfirmed/failed).

How it works: ZeroX.tradeAsset fetches a quote via the existing getQuote API, validates it (non-empty error string, data.rawResponse, non-empty transaction.to), then sends the returned transaction object through the ZeroXPortalDependency.request bridge (implemented by Portal) and polls eth_getTransactionReceipt on a fixed interval (4s × up to 225 attempts ≈ 15 min) until status == "0x1", throwing ZeroXTradeAssetError.confirmationFailed on revert or timeout. Trading/Portal now thread a portal (or nil, for tests) into ZeroX via the new ZeroXPortalDependency protocol, keeping the mock-ability the test suite relies on.

Left two comments inline — one likely bug (an unused/dead parameter that silently drops caller-supplied data) and one design question about parity with the documented cross-SDK options pattern. Nice test coverage on the happy/failure/timeout paths otherwise.


Generated by Claude Code

Comment thread Sources/PortalSwift/Core/Api/zeroX/ZeroXTradeAsset.swift Outdated
onProgress: ((ZeroXTradeAssetProgressStatus, ZeroXTradeAssetProgressData) -> Void)? = nil
) async throws -> ZeroXTradeAssetResult {
func report(_ status: ZeroXTradeAssetProgressStatus, _ data: ZeroXTradeAssetProgressData = ZeroXTradeAssetProgressData()) {
onProgress?(status, data)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit/question: tradeAsset here has no options parameter — signing always goes through eth_sendTransaction and confirmation always goes through the private waitForConfirmation poller below, with no per-call or instance-level override hook. The cross-SDK high-level-methods design doc (the one this PR's type names — ZeroXTradeAssetParams/ProgressStatus/ProgressData/Result — line up with) specifies a ZeroXTradeAssetOptions with signAndSendTransaction and waitForConfirmation overrides, same priority pattern as LiFi/Yield ("no fallback poller" — should throw if neither per-call nor instance-level confirmation is configured). Was that scoped out intentionally for this first pass, or should it be added before this ships so iOS stays consistent with the other SDKs?


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This was not part of the requirements on the STD document

@Rshahatit Rshahatit left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

address these comments before merging #665 (review)

…k-70-ios-sdk-implement-zeroxtradeasset-high-level-swap-method

# Conflicts:
#	SPM Example/PortalSwift/Base.lproj/Main.storyboard
#	Sources/PortalSwift/Portal.swift
#	Sources/PortalSwift/Trading/Trading.swift
Copilot AI review requested due to automatic review settings July 31, 2026 22:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

There are confirmed issues in the new receipt polling loop (avoidable initial delay/no retry on transient RPC errors) and in the example action’s UI updates potentially running off the main thread.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (3)

SPM Example/PortalSwift/MainViewController/ViewController+ZeroXTrading.swift:365

  • Error-path UI updates (showStatusView/stopLoading) are executed inside an unscoped Task { ... } and may run off the main thread. Dispatch these calls to the main actor to avoid UIKit threading issues.
          self.logger.error("ViewController.handleZeroXTradeAsset() - ❌ Unable to trade asset with error: \(error)")
          self.showStatusView(message: "\(self.failureStatus) Unable to trade asset with error: \(error)")
          self.stopLoading()

SPM Example/PortalSwift/MainViewController/ViewController+ZeroXTrading.swift:361

  • showStatusView/stopLoading are UIKit updates but they’re executed inside an unscoped Task { ... }, which may run off the main thread. This can lead to UIKit threading violations; wrap UI updates in MainActor.run (or otherwise ensure they run on the main actor).

This issue also appears on line 363 of the same file.

          self.logger.info("ViewController.handleZeroXTradeAsset() - ✅ Swap confirmed. Hashes: \(result.hashes)")
          self.showStatusView(message: "\(self.successStatus) Swap confirmed: \(result.hashes.joined(separator: ", "))")
          self.stopLoading()

Sources/PortalSwift/Trading/ZeroX.swift:394

  • waitForConfirmation sleeps before the very first receipt poll and also fails immediately on transient RPC errors. This adds an avoidable delay (default 4s) even when the receipt is already available, and makes swaps flaky if eth_getTransactionReceipt temporarily fails. Align this polling loop with the existing Portal/Yield confirmation loops by polling immediately on the first attempt, checking cancellation, and retrying on non-cancellation errors.
  private func waitForConfirmation(txHash: String, chainId: String, portal: ZeroXPortalDependency) async throws -> Bool {
    for _ in 0 ..< confirmationMaxAttempts {
      try await Task.sleep(nanoseconds: confirmationPollIntervalNanoseconds)

      let response = try await portal.request(
  • Files reviewed: 8/8 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings July 31, 2026 22:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

The new receipt-confirmation polling currently adds avoidable latency and is brittle to transient RPC errors, which can degrade swap reliability in production.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (1)

Sources/PortalSwift/Trading/ZeroX.swift:394

  • waitForConfirmation sleeps before the first eth_getTransactionReceipt poll and also fails the whole swap on any transient receipt-RPC error. This adds an unnecessary ~4s delay on already-mined txs (default interval) and makes confirmation less reliable. Align with existing polling patterns in the codebase (e.g., Portal.waitForTransactionConfirmation polls immediately and retries on transient errors) by polling immediately, sleeping only between attempts, and swallowing non-cancellation errors while continuing to poll.
  private func waitForConfirmation(txHash: String, chainId: String, portal: ZeroXPortalDependency) async throws -> Bool {
    for _ in 0 ..< confirmationMaxAttempts {
      try await Task.sleep(nanoseconds: confirmationPollIntervalNanoseconds)

      let response = try await portal.request(
  • Files reviewed: 11/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

The new receipt-polling implementation in ZeroX.waitForConfirmation has reliability/UX regressions (initial sleep before first poll and no transient-error retry behavior) compared to established patterns elsewhere in the codebase.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (2)

Sources/PortalSwift/Trading/ZeroX.swift:394

  • waitForConfirmation sleeps before the first receipt check and fails immediately on any transient RPC error. Elsewhere in the codebase (e.g. Portal.waitForTransactionConfirmation and YieldXyz.waitForConfirmation) the first poll happens immediately, cancellation is checked, and transient errors are retried—this avoids an unnecessary initial 4s delay and improves reliability when nodes intermittently fail.
    for _ in 0 ..< confirmationMaxAttempts {
      try await Task.sleep(nanoseconds: confirmationPollIntervalNanoseconds)

      let response = try await portal.request(
        chainId: chainId,

Sources/PortalSwift/Trading/ZeroX.swift:307

  • For eth_sendTransaction the code builds a raw [String: Any] dictionary. In this repo, other call sites use the strongly-typed ETHTransactionParam (e.g. Portal.signAndSendTransaction and YieldXyz), which reduces encoding/key mismatches and keeps the request payload consistent across features.
    let txParams: [String: Any] = [
      "from": transaction.from,
      "to": transaction.to,
      "data": transaction.data,
      "value": transaction.value,
  • Files reviewed: 11/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings August 4, 2026 00:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

Confirmation polling and example-app UI updates include reliability/threading issues that should be addressed before merging.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (3)

Sources/PortalSwift/Trading/ZeroX.swift:387

  • waitForConfirmation sleeps before the first receipt poll and fails the entire swap on any transient eth_getTransactionReceipt RPC error. This adds unnecessary latency (even if the receipt is already available) and reduces reliability compared to existing confirmation polling helpers that poll immediately and retry on transient errors/cancellation.
  private func waitForConfirmation(txHash: String, chainId: String, portal: ZeroXPortalDependency) async throws {
    for _ in 0 ..< confirmationMaxAttempts {
      try await Task.sleep(nanoseconds: confirmationPollIntervalNanoseconds)

      let response = try await portal.request(

Sources/PortalSwift/Core/Api/zeroX/ZeroXPriceRequest.swift:16

  • The doc comment for chainId still says it is "used in URL path", which contradicts the updated note above (and the actual request body includes chainId). This can confuse SDK consumers about how the API is called.
/// Request model for getting a price quote from 0x (without transaction data).
/// Note: `chainId` is included in the request body, not the URL path.
public struct ZeroXPriceRequest: Codable {
  /// The chain ID for the price check (used in URL path, e.g., "eip155:1")
  public let chainId: String

SPM Example/PortalSwift/MainViewController/ViewController+ZeroXTrading.swift:367

  • showStatusView and stopLoading update UIKit state, but they’re called from inside a Task without hopping back to the main thread. This can cause intermittent UI issues/crashes; wrap UI updates in await MainActor.run { ... }.
          self.showStatusView(message: "\(self.successStatus) Swap confirmed: \(result.hashes.joined(separator: ", "))")
          self.stopLoading()
        } catch {
          self.logger.error("ViewController.handleZeroXTradeAsset() - ❌ Unable to trade asset with error: \(error)")
          self.showStatusView(message: "\(self.failureStatus) Unable to trade asset with error: \(error)")
  • Files reviewed: 12/12 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Not ready to approve

The new confirmation polling helper has avoidable initial delay and brittle error handling on transient receipt-RPC failures, and there’s also a misleading ZeroXPriceRequest.chainId doc comment to fix.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (2)

Sources/PortalSwift/Core/Api/zeroX/ZeroXPriceRequest.swift:15

  • The doc comment for chainId still says it’s used in the URL path, but this PR updates the API to send chainId in the request body (and the 0x endpoints use fixed URLs). This is misleading for SDK consumers.
/// Note: `chainId` is included in the request body, not the URL path.
public struct ZeroXPriceRequest: Codable {
  /// The chain ID for the price check (used in URL path, e.g., "eip155:1")
  public let chainId: String

Sources/PortalSwift/Trading/ZeroX.swift:387

  • waitForConfirmation sleeps before the first receipt check and fails immediately on any transient eth_getTransactionReceipt RPC error. This adds an unconditional initial delay (4s by default) and can make swaps flaky during intermittent RPC issues; Portal’s own confirmation helper polls immediately and retries on transient failures.
  private func waitForConfirmation(txHash: String, chainId: String, portal: ZeroXPortalDependency) async throws {
    for _ in 0 ..< confirmationMaxAttempts {
      try await Task.sleep(nanoseconds: confirmationPollIntervalNanoseconds)

      let response = try await portal.request(
  • Files reviewed: 12/12 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

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.

4 participants