feat: add Arbitrum USDT wallet with USDT fees - #157
ben-kaufman wants to merge 11 commits into
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1f805f1091
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if let Ok(call) = Erc20::transferCall::abi_decode(&data) { | ||
| if payment.is_some() { | ||
| return None; | ||
| } | ||
| payment = Some((call.recipient, token_amount(call.amount).ok()?)); |
There was a problem hiding this comment.
Reject zero-value and self-transfer restored payments
When restoring a supported operation created by another wallet, decode_payment accepts any decoded transfer, including a zero amount or a recipient equal to the wallet address. The raw-log path explicitly discards those transfers, and locally quoted payments prohibit them, but this calldata enrichment path reintroduces them as confirmed outgoing activity, so restoring the same seed can display a spurious payment (and collapse its other raw debits into that entry). Apply the same zero/self-transfer checks before returning the decoded payment.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in ccdd478 and propagated to #158 and #159. Restore now excludes zero-value and self-directed ERC-20 transfers from decoded payments, preserving the raw fee debits and refunds.
I extended the existing raw-movement restoration test with both cases. It failed before the fix and now passes. All three USDT suites pass locally (48/54/60 tests), along with formatting, clippy and the iOS artifact checks. No signing, storage-schema or public API changes.
There was a problem hiding this comment.
Advice: ✅ Approve
Review: diff 33 files.
Findings:
1 inline (non-blocking)
Audit:
Audited - no findings.
Coverage:
QA: the tests under "QA Notes" need a person; no approval follows from this review
Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner) · wrong <why> (owner)
| .await?; | ||
| self.paymaster.validate_gas(&data.plan.operation).await?; | ||
| if self | ||
| .block_timestamp(self.block_number().await?) |
There was a problem hiding this comment.
The recovery floor still comes from quote time. If the L2 head moves backward before send, this operation can execute below created_block. Recovery then misses the event, marks the delivered payment Replaced, clears the pending plan, and can allow a duplicate payment. Could we lower the stored recovery floor using the send-time head with a reorg margin, or expire the quote when the head moves backward? A regression for quote at N and execution below N would cover this.
There was a problem hiding this comment.
Fixed in d946fc2. Before signing and persisting, send() lowers the recovery floor using the current head and the existing 4096-block revisit margin. The test quotes at a higher head, sends after a retreat, then restarts and recovers the executed payment through both indexed logs and the nonce/receipt fallback. Reusing the quote ID returns the confirmed payment.
| if address == TOKEN { | ||
| if let Ok(payment) = Erc20::Transfer::decode_log_data(&data) { | ||
| transfer_proven |= payment.from == self.address | ||
| && payment.to == parse_address(&transfer.recipient)? |
There was a problem hiding this comment.
A restored failed operation can contain a zero-address attempted recipient. Processing its paymaster fee transfer calls parse_address on that recipient and returns InvalidAddress, so history progress is never saved. Could we treat chain-decoded recipients as raw addresses here, or skip recipient proof when the operation failed, and add a regression?
There was a problem hiding this comment.
Fixed in d946fc2. Recipient transfer proof is now checked only for successful execution. Failed operations still retain their attempted recipient, amount and actual paymaster fee. The restore test covers a failed zero-address payment with a token fee debit and verifies that repeated history scans complete without duplicates.
| width = MAX_LOG_RANGE; | ||
| } | ||
| Err(UsdtError::NetworkUnavailable) => { | ||
| self.history_range_limit |
There was a problem hiding this comment.
A transient network error permanently lowers the range ceiling for the initial history scan. The next call uses the reduced value as both width and ceiling, so successful requests cannot recover until the full scan finishes. Repeated failures can reduce this to one block and make restoration impractical. Could we keep temporary network backoff separate from the learned provider ceiling, or grow it again after successful requests?
There was a problem hiding this comment.
Fixed in d946fc2. Each scan resumes with the last request width but allows successful pages to grow again. Explicit range-limit responses still narrow the ceiling for that scan. The test verifies recovery to full query width while the initial history scan is still incomplete. No extra stored state was needed.
| .unwrap_or(target) | ||
| .split_once('@') | ||
| .ok_or(UsdtError::WrongNetwork)?; | ||
| let Some(chain) = chain.strip_suffix("/transfer") else { |
There was a problem hiding this comment.
Under ERC-681, an ethereum:@42161 URI without a function name requests native ETH, but this branch accepts it as USDT. Could we require scheme-qualified requests to target the pinned token transfer function and keep plain-address support separate?
There was a problem hiding this comment.
Fixed in d946fc2. Scheme-qualified requests must target the pinned USDT token and its transfer function. Native-asset URIs, including the pay- form, are rejected. Bare receiving addresses remain supported, and the tests cover both behaviors.
Description
Adds USDT0 support on Arbitrum One to Bitkit Core, allowing users to receive and send USDT and pay transaction fees in USDT without holding ETH.
This PR covers direct Arbitrum payments. Native UI and the backend that protects provider credentials are maintained separately. Outbound bridging is added in #158; inbound deposits from other networks are added in #159.
QA Notes
cargo test --locked --lib modules::usdt,cargo fmt --checkandcargo clippy --locked --lib --tests.v0.6.0. Before remote package consumption, build the selected merged source, record the final SwiftPM checksum in a release-preparation commit, and tag/publish that exact iOS archive and matching Android package. Use matching local artifacts for branch testing; later stack layers must use a new version if an earlier layer has already been released.