feat(approval): migrate EVM and Tron approval to the prerequisites flow - #1442
Open
arlert-armin wants to merge 5 commits into
Open
arlert-armin wants to merge 5 commits into
arlert-armin wants to merge 5 commits into
Conversation
arlert-armin
requested review from
Ikari-Shinji-re,
RyukTheCoder,
llawliet-l-l,
nikaaru and
yeager-eren
as code owners
July 14, 2026 14:37
arlert-armin
force-pushed
the
feat/evm-approval-prerequisites
branch
2 times, most recently
from
July 14, 2026 15:10
f6c8f63 to
1df9219
Compare
RyukTheCoder
requested changes
Jul 19, 2026
RyukTheCoder
left a comment
Contributor
There was a problem hiding this comment.
Tnx, just added some comments.
arlert-armin
force-pushed
the
feat/evm-approval-prerequisites
branch
3 times, most recently
from
July 20, 2026 10:05
09612d9 to
62e9de0
Compare
RyukTheCoder
requested changes
Jul 21, 2026
arlert-armin
force-pushed
the
feat/evm-approval-prerequisites
branch
from
July 29, 2026 10:18
7e7a397 to
91dbb2e
Compare
arlert-armin
force-pushed
the
feat/evm-approval-prerequisites
branch
from
September 2, 2026 13:42
91dbb2e to
75ba226
Compare
arlert-armin
force-pushed
the
feat/evm-approval-prerequisites
branch
3 times, most recently
from
September 6, 2026 15:50
aead909 to
98cff5e
Compare
arlert-armin
force-pushed
the
feat/evm-approval-prerequisites
branch
from
September 8, 2026 12:57
5de39de to
57d147c
Compare
arlert-armin
force-pushed
the
feat/evm-approval-prerequisites
branch
from
September 8, 2026 13:16
57d147c to
dfb4bac
Compare
arlert-armin
force-pushed
the
feat/evm-approval-prerequisites
branch
from
September 8, 2026 14:07
dfb4bac to
539c844
Compare
arlert-armin
force-pushed
the
feat/evm-approval-prerequisites
branch
from
September 9, 2026 13:39
539c844 to
6c2441a
Compare
Approving makes the user wait twice and the two waits are not the same: a prompt is sitting in their wallet during the first, and nothing is asked of them during the second. `StepStatus` has no separate signing state, so both stay on `waitingForApproval` and are told apart by their message and event. The message has to carry it, because the swap-details alert renders `extraMessage` and falls back to `extraMessageDetail` only for a failed step. `isApprovalCurrentStepTx` reads the legacy `*ApprovalTransaction` step fields, which this flow never sets, so approval events rendered the generic swap copy. Rather than widen that predicate - it also selects state transitions in `produceNextStateForTransaction` - the notifier takes an optional `isApproval` flag and falls back to the existing check. The status poller's catch was bare, so any failure there became an indefinite silent retry indistinguishable from a transaction still being pending. It now reports what it caught before retrying.
Registers `getAllowance` and `getTransactionReceipt` on the WalletConnect EVM namespace so approvals resolve through the prerequisites flow like the other EVM providers. Both reads are implemented on the adapter, which already owns the universal provider, rather than reaching for an EVM instance from the namespace - that keeps the namespace actions as thin delegations and avoids casting the WalletConnect provider to `ProviderAPI`. They are served by the chain's node rather than the wallet. `UniversalProvider.request` throws unless `universalProvider.connect()` established the session, and this adapter drives the SignClient directly, so it has no session of its own and every call failed before reaching the network. Routing them to the wallet is not possible either: a session only authorizes the methods it negotiated, and those are signing-only - `eth_call` is not part of the eip155 RPC reference. WalletConnect's own provider makes the same split. Providers are memoized per chain, because the endpoint has to follow the session's chain: a token address is meaningless on the wrong chain, and an `eth_call` against it answers `0x`, which would read back as a zero allowance and re-approve on every attempt.
Tron approvals sat on "waiting for approval" indefinitely, for three reasons.
A wallet that lets the user choose the allowance does not sign the transaction
it was handed: it rebuilds the call with the amount the user entered, reading
it from `__payload__`. Server-built transactions carry that field,
`transactionBuilder` does not return it, and ours was hardcoded to `{}` - so
TronLink had nothing to rebuild from, reported "Modification failed", and the
approve could never be signed. Wallets that decode `raw_data` themselves were
unaffected, which is why it looked like an inconsistency between providers.
The payload is derived from the transaction the node returned, so it cannot
describe a call other than the one that was built.
A refused broadcast resolves rather than throwing, and still carries a `txid`,
since the id comes from the transaction's own bytes. Taking it at face value
left the queue polling a transaction no node ever had. Refusals arrive in more
than one shape and never set `result` to `false` - it is simply absent - so
both `code` with a hex `message` and a capitalised node-side `Error` are
treated as failures, and the reason is surfaced instead.
Status was read with `getTransactionInfo`, which queries the solidity node and
answers nothing until the transaction solidifies, roughly 19 blocks behind the
head. The full-node lookup answers as soon as it is in a block, about 3s. The
result is also confirmed on `blockNumber`, because Tron sets `receipt.result`
to a failure code only on failure and may omit it on success.
OKX was the only wallet with a Tron namespace that registered no approve
actions, so its Tron swaps stayed on the server-driven flow.
Both devices sign a raw transaction on-device and work nothing out for themselves, so they refused anything without a gas limit, and Trezor refused anything without pricing. Server-built transactions carry both, but the approve prerequisite is built on the client and leaves every gas field null, so approving through either wallet failed outright. The limit is estimated and the pricing quoted through the provider the signer already creates, and only where the transaction supplied neither. Anything it does supply is preferred and passed through untouched, so a server-built transaction is still signed with exactly the gas it was created with and costs no extra call. The node's quote settles on one scheme rather than both, since a signed transaction cannot carry them together: EIP-1559 wherever the chain quotes it, and a legacy price otherwise.
The status poller advanced on a single receipt plus a single allowance read, both answered by the wallet's RPC - which is a load balancer over several nodes. One read agreeing with the receipt is not evidence that the node the swap is submitted to has applied the approve block, so the swap went out against state where the allowance was still missing and reverted, while a manual retry moments later succeeded. The allowance now has to read as sufficient on consecutive reads a few seconds apart before the prerequisite is met, and a short read discards the reads before it rather than being believed. It is the allowance, not the receipt, that the swap depends on. The receipt status was also compared against the exact string `0x1`, so a wallet handing it over as `0x01` or as a number would have reported a successful approval as failed.
arlert-armin
force-pushed
the
feat/evm-approval-prerequisites
branch
from
September 16, 2026 08:42
6c2441a to
62094fd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Migrates EVM and Tron token approvals from the legacy flow (
isApprovalTxon the create-transaction response + polling the servercheck-approvalendpoint) to the unified prerequisites pattern already used for XRPL/Stellar trustlines. When the client opts in (validations.approve: false), the backend returns the approval requirement in the transaction'sprerequisiteslist, and the client checks the on-chain allowance, builds/signs the approve itself, and waits for confirmation through the wallet's namespace — no server round-trips for approval.Key decisions
checkApprove/checkApproveTransactionStatuspair; each chain supplies a small adapter (build-tx, status-check, namespace key). Avoids duplicating ~85% of the logic and lets other chains slot in later.Dependent PRs
This branch pins experimental builds of two dependencies while their PRs are in review. Both must merge and publish stable versions before this can:
rango-types: newEVM_APPROVE/TRON_APPROVEprerequisite types@hub3js/evm: newallowance/getTransactionReceiptactionsHow did you test this change?
build:widgetgreenChecklist: