mirror of
https://github.com/luxfi/bridge.git
synced 2026-09-24 00:26:46 +00:00
feat(bridge): humanize XRP engine_result errors for SDK + UI
broadcast_driver.go::humanizeBroadcastErr now recognizes the most
common XRP engine_result codes and renders them as one-line user-
facing messages, matching the EVM-side humanization pattern:
tecINSUFFICIENT_FUNDS / tecUNFUNDED → "Insufficient funds in release
address — fund the MPC address"
tecNO_DST → "XRP destination account is
not activated — recipient must
hold ≥10 XRP reserve"
tefPAST_SEQ → "XRP sequence already used —
bridge will requery and retry"
tem* → "XRP transaction was malformed
— bridge will retry"
telINSUF_FEE_P → "XRP network fee too low for
current load — bridge will retry"
The SDK + UI surface these from Swap.LastError so users see the
actionable cause (fund the wallet, activate the destination, etc.)
instead of the raw rippled error string.
Test: TestHumanizeBroadcastErr_XRPEngineResults covers all five new
mappings plus regression for the existing EVM mappings.
This commit is contained in:
@@ -291,12 +291,22 @@ func humanizeBroadcastErr(err error) string {
|
||||
msg := err.Error()
|
||||
low := strings.ToLower(msg)
|
||||
switch {
|
||||
case strings.Contains(low, "insufficient funds"):
|
||||
case strings.Contains(low, "insufficient funds"),
|
||||
strings.Contains(msg, "tecINSUFFICIENT_FUNDS"),
|
||||
strings.Contains(msg, "tecUNFUNDED"):
|
||||
return "Insufficient funds in release address — fund the MPC address with destination-chain gas tokens"
|
||||
case strings.Contains(low, "nonce too low"):
|
||||
return "Nonce stale — release address already has a tx with this nonce; bridge will catch up"
|
||||
case strings.Contains(low, "invalid sender"):
|
||||
return "Destination chain rejected the signature (invalid sender) — possible MPC pubkey mismatch"
|
||||
case strings.Contains(msg, "tecNO_DST"):
|
||||
return "XRP destination account is not activated — recipient must hold ≥10 XRP reserve"
|
||||
case strings.Contains(msg, "tefPAST_SEQ"):
|
||||
return "XRP sequence already used — bridge will requery account_info and retry"
|
||||
case strings.Contains(msg, "temBAD_AMOUNT"), strings.Contains(msg, "tem"):
|
||||
return "XRP transaction was malformed — bridge will retry with a fresh build"
|
||||
case strings.Contains(msg, "telINSUF_FEE_P"):
|
||||
return "XRP network fee too low for current load — bridge will retry with a higher fee"
|
||||
case strings.Contains(low, "http 502"), strings.Contains(low, "http 503"), strings.Contains(low, "http 504"):
|
||||
return "Destination RPC unreachable (gateway error) — retrying"
|
||||
case strings.Contains(low, "context deadline exceeded"), strings.Contains(low, "timeout"):
|
||||
|
||||
@@ -530,3 +530,48 @@ func TestEndToEnd_AllDriversChained(t *testing.T) {
|
||||
t.Errorf("DestTxHash = %q, want 0xfinal-tx-hash-e2e", got.DestTxHash)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// humanizeBroadcastErr — XRP engine_result mapping
|
||||
// =============================================================================
|
||||
|
||||
func TestHumanizeBroadcastErr_XRPEngineResults(t *testing.T) {
|
||||
cases := []struct {
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
// XRP-specific mappings.
|
||||
{"submit rpc 0: fatal engine_result=tecINSUFFICIENT_FUNDS: insufficient", "Insufficient funds in release address"},
|
||||
{"submit rpc 0: fatal engine_result=tecNO_DST: destination not found", "destination account is not activated"},
|
||||
{"submit rpc 0: fatal engine_result=tefPAST_SEQ: sequence already used", "sequence already used"},
|
||||
{"submit rpc 0: fatal engine_result=temBAD_AMOUNT: bad amount", "malformed"},
|
||||
{"submit rpc 0: retryable engine_result=telINSUF_FEE_P: fee too low", "fee too low for current load"},
|
||||
// Existing EVM behaviour preserved.
|
||||
{"insufficient funds for gas * price + value", "Insufficient funds in release address"},
|
||||
{"nonce too low", "Nonce stale"},
|
||||
{"HTTP 503: gateway", "Destination RPC unreachable"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
got := humanizeBroadcastErr(stringErr(c.in))
|
||||
if !contains(got, c.want) {
|
||||
t.Errorf("humanizeBroadcastErr(%q) = %q\n want substring %q", c.in, got, c.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func contains(haystack, needle string) bool {
|
||||
return haystack != "" && (haystack == needle || (len(needle) > 0 && stringIndex(haystack, needle) >= 0))
|
||||
}
|
||||
|
||||
func stringIndex(s, sub string) int {
|
||||
for i := 0; i+len(sub) <= len(s); i++ {
|
||||
if s[i:i+len(sub)] == sub {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
type stringErr string
|
||||
|
||||
func (s stringErr) Error() string { return string(s) }
|
||||
|
||||
Reference in New Issue
Block a user