feat(deposit-address-service): execute v3 refund withdrawals - #3709
Open
amateima wants to merge 1 commit into
Open
Conversation
amateima
requested review from
bmzig,
dijanin-brat,
mrice32,
nicholaspai and
pxrl
as code owners
August 11, 2026 20:30
amateima
force-pushed
the
feat/deposit-address-service-v3-withdrawal
branch
from
August 11, 2026 22:09
6427a95 to
7048066
Compare
This was referenced Aug 11, 2026
amateima
force-pushed
the
feat/deposit-address-service-v3-withdrawal
branch
from
August 24, 2026 11:32
7048066 to
427658f
Compare
The refund path for a mis_route or intent_refund, and for a correct_transfer the execute endpoint rejected as below the minimum — replacing the placeholder throws at the lines they sat on, so the deposit lock is held across both actions via processUnderLock's existing try/finally. The parent's exclusion routing is kept: anything that is not a correct_transfer diverts to the withdraw, so intent_refund takes the same second hop out to the committed refund address as a mis_route. Withdrawals are EVM-only, stricter than the deposit path, and need the message's withdraw leaf materials; canonicality-then-balance and the broadcast/reconciliation plumbing are reused unchanged, with operation: "withdraw" on the pending record. A sign-withdraw 422 is terminal on the HTTP status alone, recorded as withdraw_failed (code now optional — the client discards the API's discriminator) and ACKed; everything else NACKs. The refund deducts gas (deductGasFromRefund: true), deliberately unlike v1. withdraw_executed is recorded but not yet published; lifecycle publishing follows before execution is enabled. Gated by ENABLE_V3_WITHDRAWALS, the same variable the polling bot reads, defaulting off. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
amateima
force-pushed
the
feat/deposit-address-service-v3-withdrawal
branch
from
August 27, 2026 17:19
427658f to
fb4601d
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.
Part of #3663 — PR 5 of the standalone deposit-address service. Stacked on #3701.
Executes the v3 refund withdrawal: the path for a
mis_route, and for acorrect_transferthe execute endpoint rejected asAMOUNT_BELOW_MINIMUM. Replaces PR 4's two placeholder throws at the lines they sat on, so the deposit lock is held across both actions viaprocessUnderLock's existingtry/finally.WithdrawRouteNotImplementedErrordisappears with them, taking the last identifier carrying the removedroutevocabulary.Review focus: one lock held across both actions; terminal 422 handling.
What is genuinely new
executeWithdrawindepositHandler.ts, ported guard-for-guard frominitiateWithdrawV3per the issue's parity matrix: theENABLE_V3_WITHDRAWALSgate (same env var the polling bot reads, NACK while off), EVM-only namespaces (stricter than the deposit path —assertSupportedNamespaceallowstronon TVM chains, so it could not be reused), the withdraw leaf materials check, and a smaller response assertion (assertValidWithdrawResponse: signedchainIdagainst the refund chainerc20Transfer.chainId, and the now+60s signature deadline —assertValidExecuteResponsedeliberately does not carry over)._getSignedWithdrawV3does (isHttpError(err) && err.status === 422): persistwithdraw_failed, ACK. Everything else NACKs. No client change;_postOrThrowdiscards the API's error code, sowithdraw_failed.codebecomesoptional(...)and is unset — every existing PR 3 state test passes unchanged.deductGasFromRefund: true, deliberately unlike v1's full-amount refund — not to be unified in the v1 PR.Reused unchanged
The lock, both state reads,
assertSupportedOriginChain(already runs onerc20Transfer.chainIdbefore routing), canonicality-then-balance in that order and for the same reason,onBroadcast+maxTries, the pending-write retry, andresolvePendingTransaction(whoseoperation: "withdraw"mapping already existed).broadcast()is parameterised over{operation, to, data, value, message, mrkdwn}rather than duplicated. One adjustment there: a confirmed withdraw is not expected to carry theMetadataEmittedprovenance event, so the missing-metadata warning is now gated to deposits.Not in this PR
withdraw_executedis recorded but not published — lifecycle publishing and its recovery are PR 6, which lands before PR 7 enables execution anywhere.Verification
yarn tsc --build --forceclean;yarn lintclean.TransactionClient(163 on the base branch; +27 here).deductGasFromRefund, the signed-chainId check, theoperationlabel, 422-only terminal classification, the code-gated below-minimum fallthrough, the lock held across both actions (the fake API records the lock token it observed inside each call), the withdraw gate, the leafkindfilter, EVM-only strictness, and canonicality-before-balance ordering — each failed exactly the expected tests, then passed on revert.🤖 Generated with Claude Code