Add pre-submit consent gate for amounts exceeding auto-pay approval

Symmetric across both bridge directions, checked in priority order before
any funds move:
1. Real reserve exceeded (withdrawal only) - the multisig doesn't hold
   enough real USDT on Polkadot Asset Hub. No signature can fix this, so
   this stays a hard block (existing bridge_wusdt_to_usdt_blocked message),
   never offered as "proceed anyway".
2. Automation-key approval exceeded (either direction) - funds exist, the
   automation key just isn't currently approved to auto-pay that much.
   Resolvable by 3-of-5 signing, so this is now an explicit opt-in: a
   warning banner names the ~2h typical review window and the signatories'
   contact channel (t.me/pezkuwidestek), gated behind a WarningCheckBox the
   user must tick before Swap re-enables. Previously there was no signal at
   all here - a large amount would just silently queue for manual review
   after debiting the user, with no way to know that going in.

Both getWusdtRemainingAllowance/getPolkadotUsdtRemainingAllowance query the
real on-chain Assets.Approvals amount per leg - not a guess. Confirmed
on-chain (2026-07-16) the Polkadot leg has never had an approval granted at
all, so every wUSDT->USDT withdrawal currently hits the consent gate
regardless of amount, symmetric with what happens once a signatory grants
one (mirrors the existing wUSDT-side renewal flow exactly, including the
same 40,000/200,000 20%-threshold pattern) - added a second sign button so
a signatory can actually grant it from this screen.

swapClicked()'s confirmation gate now also enforces the consent requirement
server-side (well, client-domain-side) rather than only via the button's
enabled look, consistent with how it already re-checks balance/reserve.
This commit is contained in:
2026-07-15 19:46:24 -07:00
parent cf8127c8f8
commit 3c06ae00e0
6 changed files with 363 additions and 73 deletions
@@ -170,7 +170,9 @@
</com.google.android.material.card.MaterialCardView>
<!-- Warning for USDT liquidity -->
<!-- Warning for USDT liquidity, or the consent message when the amount exceeds the
automation key's current approval (resolvable by 3-of-5 review, unlike the
reserve case above which isn't - see BridgeViewModel.updateWarningState). -->
<io.novafoundation.nova.common.view.AlertView
android:id="@+id/bridgeWarningAlert"
android:layout_width="match_parent"
@@ -179,8 +181,21 @@
android:visibility="gone"
app:alertMode="warning" />
<!-- Multisig signatory-only bridge allowance sign button. Only visible to a wallet
that is one of the bridge multisig's 5 known signatories. -->
<!-- Only shown alongside the consent message above (consentRequired) - explicit
opt-in required before the Swap button re-enables, never enabled by default. -->
<io.novafoundation.nova.common.view.WarningCheckBox
android:id="@+id/bridgeConsentCheckbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:icon="@drawable/ic_warning_filled"
android:text="@string/bridge_consent_checkbox_label"
android:visibility="gone"
app:iconTint="@color/icon_warning" />
<!-- Multisig signatory-only bridge allowance sign buttons. Only visible to a wallet
that is one of the bridge multisig's 5 known signatories - one row per leg, since
a signatory may need to renew one chain's approval without the other. -->
<io.novafoundation.nova.common.view.PrimaryButton
android:id="@+id/bridgeSignButton"
style="@style/Widget.Nova.Button.Primary"
@@ -190,6 +205,15 @@
android:text="@string/bridge_sign_button"
android:visibility="gone" />
<io.novafoundation.nova.common.view.PrimaryButton
android:id="@+id/bridgePolkadotSignButton"
style="@style/Widget.Nova.Button.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/bridge_sign_button"
android:visibility="gone" />
</LinearLayout>
</ScrollView>