Skip to content

docs(deposit-addresses): require a signing wallet for refundTo and recoveryAddress - #514

Open
scout-relayprotocol[bot] wants to merge 4 commits into
mainfrom
docs/da-recovery-address-must-sign
Open

scout-relayprotocol[bot] wants to merge 4 commits into
mainfrom
docs/da-recovery-address-must-sign

Conversation

@scout-relayprotocol

@scout-relayprotocol scout-relayprotocol Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What changed

  • refundTo and recoveryAddress must both be origin-chain wallets the integrator controls and can authorize a withdrawal with: an EOA signature, or an ERC-1271 contract signature such as a Safe. Relay records one of them as the protocol depositor, and the depositor owns any funds that Relay cannot fill or refund. A new section lists the address types that do not qualify: exchange deposit addresses, deposit addresses issued by another protocol (for example NEAR Intents), custodial addresses whose custodian cannot sign, and smart contract wallets that cannot produce a valid ERC-1271 signature.
  • The blocked-depositor path for a zero-address refundTo is now documented: no fill, no refund, the deposit is credited on the Hub to recoveryAddress when set. The integrator withdraws it with that wallet's signature, or Relay pays it to recoveryAddress after a manual review.
  • The old statement that recoveryAddress never receives refunds is replaced. Since unevenlabs/solver#14140 the protocol refund pays the protocol depositor, which is recoveryAddress when set. The new note says automatic refunds still follow refundTo, and recoveryAddress receives funds only when Relay cannot fill and cannot refund the depositor.
  • Shows how to read the credit with GET /withdrawals/balances?recoveryAddress=<addr>&ownerChainId=<chainId>.
  • The withdrawals guide says the owner is the recorded depositor (protocol.deposit.origin.depositor), which is the configured refundTo or recoveryAddress, and carries the same signing requirement.

Why

Integrators have set recoveryAddress to an address they cannot sign with. When the depositor fails wallet screening, the recovered funds are credited to that address on the Hub. The integrator cannot withdraw them, and the payout then depends on a manual review by Relay. The current text says "integrator-controlled EOA" but does not say the wallet must be able to authorize a withdrawal, and it says recoveryAddress never receives refunds, which is no longer true.

Compliance confirmed on 2026-10-23 that the same requirement applies to refundTo, because both fields can become the protocol depositor.

Supersedes #512, which was opened from a fork.

Review notes

  • Docs-only clarification. No API behavior change, so no changelog entry.
  • Internal links and anchors were checked.

Requested by max.

--

View Junior Session [Sentry]

Integrators have set recoveryAddress to addresses they cannot sign with,
such as a deposit address issued by another protocol. When the depositor
is blocked, the recovered funds are credited on the Hub to recoveryAddress
and only its signature can withdraw them, so those funds are stranded.

- State that recoveryAddress must be a wallet the integrator controls and
  can sign with, and list the address types that do not qualify.
- Document the blocked-depositor path: no fill, no refund, Hub credit to
  recoveryAddress when set, withdrawal by signature only.
- Show how to read the credit with GET /withdrawals/balances using the
  recoveryAddress and ownerChainId parameters.
- Add the same signing requirement to the withdrawals guide.
The withdrawals guide already supports Safe wallets through ERC-1271
contract signatures, so requiring recoveryAddress to sign with its own
key was too strict, and the custodial exclusion conflicted with the
custodian exception. Describe the requirement as a supported signature,
EOA or ERC-1271, and keep the exceptions consistent on both pages.
@mintlify

mintlify Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
unevenlabs 🟢 Ready View Preview Oct 23, 2026, 8:14 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

@greptile-apps

greptile-apps Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Low impact] The PR appears safe to merge, with non-blocking corrections needed in the refund and balance guidance.

Findings

  1. P2 Refund instructions conflict ▶
  2. P2 Empty results can mislead ▶

Summary

This docs-only PR explains which wallets can recover failed deposits.

  • A deposit's recovery owner must be able to sign.
  • Deposit guides explain how blocked funds reach a recovery wallet.

Reviews (3) · Last reviewed commit: "docs(deposit-addresses): the signing req..." · Reviewed by Greptile

- **`recoveryAddress`**: the `recoveryAddress` you set on the quote.
- **`ownerChainId`**: the numeric Relay chain id of the origin chain, from `chains[].id` in the [chains API](/references/api/get-chains).

The response lists each credited balance with the raw `amount` and its `currency`. An empty `balances` array means that nothing is credited to that owner on that chain. To withdraw, follow the steps in [Programmatic Withdrawals](/references/protocol/guides/withdrawals#discover-credited-balances-optional) with `recoveryAddress` as the `owner`, or connect the wallet at [relay.link/withdraw](https://relay.link/withdraw).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Empty results can mislead

An empty balances array does not prove that no funds are credited. The endpoint's docs say results are eventually consistent, so a newly credited deposit may not appear yet. This could lead integrators to tell users their recovery funds are missing. Qualify the statement and tell readers to retry before concluding that no credit exists.

Suggested change
The response lists each credited balance with the raw `amount` and its `currency`. An empty `balances` array means that nothing is credited to that owner on that chain. To withdraw, follow the steps in [Programmatic Withdrawals](/references/protocol/guides/withdrawals#discover-credited-balances-optional) with `recoveryAddress` as the `owner`, or connect the wallet at [relay.link/withdraw](https://relay.link/withdraw).
The response lists each credited balance with the raw `amount` and its `currency`. Balance discovery is eventually consistent, so an empty `balances` array may not yet reflect a newly credited deposit. Retry before concluding that no credit exists for that owner on that chain. To withdraw, follow the steps in [Programmatic Withdrawals](/references/protocol/guides/withdrawals#discover-credited-balances-optional) with `recoveryAddress` as the `owner`, or connect the wallet at [relay.link/withdraw](https://relay.link/withdraw).

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

…und after manual review

Solver PR 14140 lets the recovery worker pay a recovered deposit to the
protocol depositor, which is recoveryAddress when set. Replace the
statements that said recoveryAddress never receives refunds and that
only its signature can move the funds.
@github-actions

github-actions Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @maxbayuk's task in 1m 14s —— View job


Code review: PR #514

  • Gather PR diff and context (all 3 commits, including 6b2fc41, which came after Greptile's review)
  • Check claims against the API reference and protocol docs
  • Check against the AGENTS.md style rules
  • Post review

The direction is right, and the links and anchors resolve (#the-wallet-must-be-able-to-sign, #when-the-depositor-cannot-be-refunded, #supported-chains-and-signers, #discover-credited-balances-optional, /references/api/attest-deposit, /resources/support). Here are the issues I found, most important first.


1. An empty balance does not mean nothing is credited — features/deposit-addresses.mdx:1000

An empty balances array means that nothing is credited to that owner on that chain.

Three sources contradict this:

  • references/api/get-withdrawal-balances.mdx:10 says discovery is eventually consistent, and that "Deposits awaiting attestation or recovery are excluded." That is exactly the state of a BLOCKED_WALLET deposit before the recovery job runs or before you call attest-deposit, which this same section describes at line 989.
  • The endpoint is paginated (limit defaults to 20, plus a continuation cursor; see withdrawals.mdx:78-79). The first page alone may not include the asset you are looking for.

A reader who gets [] may conclude the funds are gone. Greptile flagged the eventual-consistency part. The exclusion of deposits awaiting attestation is the bigger gap.
Fix this →

2. "Must be able to sign" now conflicts with the manual-review path — features/deposit-addresses.mdx:967, :970, :982

After 6b2fc41, a wallet that cannot sign can still be paid out after a manual review. The Constraints list still states "Must be able to authorize a withdrawal", next to bullets that the API actually enforces (useDepositAddress, native-currency address). That makes it read like a validation the API performs, which it does not. Two possible fixes:

  • Move it out of Constraints into a recommendation ("Use a wallet you can sign with…").
  • Retitle the section, for example "Use a wallet that can sign", so the heading matches the fallback described in the <Warning> at :982.

3. The ERC-1271 wording promises more than the withdrawals guide supports — features/deposit-addresses.mdx:972, :978

withdrawals.mdx:34 says the API flow covers EOA signatures only. A Safe works through the UI, and "Other ERC-1271 wallets don't have a documented flow yet; contact support." The new text says "your server can sign with its key through the withdrawals API" and implies any wallet that produces a valid ERC-1271 signature qualifies. Two changes would fix this:

  • Say the server path is EOA-only.
  • Say non-Safe ERC-1271 wallets currently need support, so integrators don't pick a contract wallet expecting a self-serve path.

4. Check that the manual-review payout is deployed — commit 6b2fc41

The commit message cites solver PR 14140. AGENTS.md §1.2 asks to "distinguish deployed platform routes from … future rollouts." Please confirm 14140 is merged and live before this merges. If it is not, the <Warning> at :982 and the new <Note> describe behavior integrators can't use yet.

5. Related pages left out of date

  • references/api/api_core_concepts/execution-errors.mdx:25 (BLOCKED_WALLET) still says "Funds are not automatically refunded; contact Support for review." It does not mention the Hub credit to recoveryAddress. A one-line link to #when-the-depositor-cannot-be-refunded would keep the two pages consistent (AGENTS.md §4.2, "Update inbound links").
  • The PR description is out of date. It still says "no support process can pay them out", which 6b2fc41 reverses. Please update it so reviewers aren't misled.

6. Style (AGENTS.md §2.10, 2–3 sentences per paragraph)

  • references/protocol/guides/withdrawals.mdx:21 is now 7 sentences. Consider moving the "cannot produce a signature" tail to a link to the new deposit-addresses section rather than restating it.
  • features/deposit-addresses.mdx:972 is 5 sentences, and the <Warning> at :982 is 4.
  • Minor: "Relay's recovery job" (:989) is internal jargon. Something like "automatically by Relay" reads better for integrators.

Not checked: mintlify dev / a broken-link run (I relied on the Mintlify preview deploy, which is green) and the solver-side behavior behind items 2 and 4.
· Branch: docs/da-recovery-address-must-sign

…ndTo

Relay records refundTo or recoveryAddress as the protocol depositor, so
both must be wallets the integrator can sign with. Per Compliance.
@maxbayuk maxbayuk changed the title docs(deposit-addresses): require a signing wallet for recoveryAddress docs(deposit-addresses): require a signing wallet for refundTo and recoveryAddress Oct 23, 2026
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @maxbayuk's task in 1m 15s —— View job


Code review: PR #514

  • Gather PR diff (all 4 commits, including 77862fb, which extends the signing requirement to refundTo)
  • Check claims against the related docs (withdrawals.mdx, get-withdrawal-balances.mdx, execution-errors.mdx, migrating-to-requests-v3.mdx)
  • Check against the AGENTS.md style rules
  • Post review

The new commit 77862fb adds three problems of its own (items 1–3). Most of the earlier review is still unaddressed (item 4).


1. "refundTo must be a wallet you can sign with" contradicts the zero-address opt-in — features/deposit-addresses.mdx:26, :976, :1010

The page tells readers to set refundTo to the native-currency address (0x000…000, bc1qqq…, 111…1) for auto-refund. No one can sign with that address. Two places put the two instructions side by side:

  • :1010, in the Auto-refund to depositor bullet, where refundTo is the zero address: "Both refundTo and recoveryAddress must be wallets you can sign with."
  • :26, the refundTo table cell, which describes the zero-address opt-in and then says "it must be a wallet you can sign with."

:976 ("Before you set refundTo or recoveryAddress, confirm that you can connect that wallet…") has the same problem. Limit the rule to the case where refundTo is an explicit address.
Fix this →

2. The requirement says "you", but the recommended setup uses the user's address — :933, :1008 vs :976

Recommended Setup (:1008) says "Set refundTo to the user's address", and the Refund Behavior table (:933) lists User's address as a normal option. The integrator cannot sign for that address. The real requirement is that whoever owns the depositor address can sign. As written ("a wallet you can sign with", "Relay support cannot sign for you"), the rule tells integrators not to follow the page's own recommendation. Change it to "a wallet the depositor's owner can sign with", or say that a user-controlled EOA qualifies.

3. Which address is the protocol depositor is now inconsistent — :951 vs :27, :957, :993

  • :951 says recoveryAddress is the protocol depositor only "when refundTo is not set or is the native-currency address". That means an explicit refundTo wins.
  • :957 (the "When recoveryAddress is supplied" bullet) and :27 say that when Relay cannot auto-refund, the deposit is credited to recoveryAddress, with no condition.
  • Strict addresses require refundTo (:973). If that refundTo is explicit, recoveryAddress would never be the depositor on a strict address, yet the page implies it still does something there.
  • The page never says who the depositor is when refundTo is the zero address and no recoveryAddress is set. Is it the detected sender? That gap matters for the BLOCKED_WALLET case at :993.

Please confirm the solver precedence and make :27, :951, :957 and the strict constraint agree. "Depositor" is also used for two things: the detected sender (:935, :991) and the protocol depositor (:976, :986). That makes the <Warning> at :986 ("If the depositor cannot sign") ambiguous. Use "protocol depositor" consistently, or name the field.

4. Still open from the previous review

  • An empty balances array does not mean nothing is credited (:1004). get-withdrawal-balances.mdx:10 says deposits awaiting attestation or recovery are excluded and that discovery is eventually consistent. The endpoint is also paginated (withdrawals.mdx:78-79). This is exactly the BLOCKED_WALLET state the section describes.
  • The ERC-1271 wording promises more than the withdrawal flow supports (:976). The text says "your server can sign with its key through the withdrawals API" for EOA or ERC-1271 signers. withdrawals.mdx:34 says the API flow covers EOA signatures only, a Safe works through the UI, and other ERC-1271 wallets need support.
  • The Constraints bullet "Must be able to authorize a withdrawal" (:971) sits next to rules the API actually enforces, but the API does not validate it. Since the manual-review fallback exists, word it as a requirement for self-serve withdrawal, not as a validation.
  • execution-errors.mdx:25 (BLOCKED_WALLET) still says only "contact Support". Add a link to #when-the-depositor-cannot-be-refunded (AGENTS.md §4.2).
  • Deployment of the solver PR 14140 behavior (manual-review payout, refund to the protocol depositor) has not been confirmed (AGENTS.md §1.2).

5. Style (AGENTS.md §2.10, 2–3 sentences per paragraph)

  • :976 is now 6 sentences, and the <Warning> at :986 is 5.
  • withdrawals.mdx:21 is 7 sentences. It mostly restates the new section, so it could shrink to two sentences plus the existing link.
  • The refundTo and recoveryAddress table cells (:26, :27) are now paragraph-length. Per §2.7, keep cell descriptions concise and move the detail into prose.
  • "Relay's recovery job" (:993) is internal jargon.
  • The PR description does not mention that 77862fb extends the requirement to refundTo.

Verified: protocol.deposit.origin.depositor (cited at withdrawals.mdx:21) exists in the v3 response shape (migrating-to-requests-v3.mdx:141). The new anchors resolve.

Not checked: a mintlify dev / broken-link run, and the solver-side behavior behind items 3 and 4. The /code-review skill failed to load in this run, so I did this review by hand.
· Branch: docs/da-recovery-address-must-sign

- **Open deposit addresses**: Set `refundTo` to the user's address for automatic refunds.
- **Strict deposit addresses**: Always set `refundTo`. If the sender is unknown (e.g., CEX withdrawal), use an app-controlled address so your support team can manage refunds.
- **Auto-refund to depositor**: When users deposit from wallets they control, you can pass `refundTo` as the origin chain's native-currency address (EVM `0x0000000000000000000000000000000000000000`, Bitcoin `bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8`, Solana `11111111111111111111111111111111`) to refund to the original depositor automatically. The first of the two resulting `outTxs` is the transfer to the depositor — track that one for refund delivery. Avoid this when deposits may originate from a centralized exchange, or pair it with a [`recoveryAddress`](#recoveryaddress) so funds Relay cannot safely auto-refund remain recoverable.
- **Auto-refund to depositor**: When users deposit from wallets they control, you can pass `refundTo` as the origin chain's native-currency address (EVM `0x0000000000000000000000000000000000000000`, Bitcoin `bc1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqmql8k8`, Solana `11111111111111111111111111111111`) to refund to the original depositor automatically. The first of the two resulting `outTxs` is the transfer to the depositor. Track that one for refund delivery. Avoid this when deposits may originate from a centralized exchange, or pair it with a [`recoveryAddress`](#recoveryaddress) so funds Relay cannot safely auto-refund remain recoverable. Both `refundTo` and `recoveryAddress` must be wallets you can sign with. See [The wallet must be able to sign](#the-wallet-must-be-able-to-sign).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Refund instructions conflict

This bullet recommends the native-currency address for refundTo, then requires both refundTo and recoveryAddress to be wallets the integrator can sign with. The native-currency address is a special value, not a signing wallet, so both instructions cannot be followed. Integrators may replace supported automatic refunds with an unnecessary app-controlled refund address.

Exempt the native-currency value from the signing requirement and explain that withdrawals need the recorded depositor's signature. Also allow the user to sign when refundTo is their wallet.

Knowledge Base Used: Deposit-address workflows

This branch was successfully deployed

1 active deployment
staging — 77862fb3 Deployed Oct 23, 2026 by mintlify[bot]
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.

1 participant