Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- fixed: Staked "locked" balance in the wallet view no longer gets cut off. The crypto amount is truncated to an exchange-rate-appropriate number of decimals, and the text is no longer clamped to a fraction of the card width.
- fixed: Improve the unstake error experience by replacing the popup alert and generic "unknown error occurred" with the real error in the scene's error field, and showing a clear message when the wallet lacks the balance to cover the unstaking network fee.
- fixed: Tapping Max on the Sell scene no longer briefly shows the entered fiat amount in the crypto field while the max is being calculated.
- fixed: Banxa Google Pay purchases now use Banxa's current Google Pay processor instead of the legacy one it replaced. Banxa keeps both live during a migration, and Edge was picking whichever processor had the lower id, which was always the legacy one.
- fixed: An info card no longer disappears into an empty gap when the carousel's card list shrinks. A card's position comes entirely from an animated transform keyed on its index, and that transform is not re-applied when a surviving card shifts slots, so dropping a card left the ones after it parked a full card-width off-screen. The carousel now remounts a card whose slot changes. Reproduces wherever the list shrinks after mount - most visibly when a `noBalance` card is filtered out as balances finish loading.

## 4.50.2 (2026-08-06)
Expand Down
54 changes: 54 additions & 0 deletions src/__tests__/plugins/ramps/banxa/pickPreferredPayment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { describe, expect, it } from '@jest/globals'

import type { BanxaPaymentIdLimit } from '../../../../plugins/ramps/banxa/banxaRampPlugin'
import { pickPreferredPayment } from '../../../../plugins/ramps/banxa/banxaRampPlugin'

// Banxa's live USD Google Pay methods: four legacy WorldPay PSPs that all sort
// below the consolidated PRIMERGP PSP that replaced them.
const worldpayGoogleUsd: BanxaPaymentIdLimit = {
id: 6036,
paymentType: 'WORLDPAYGOOGLE',
type: 'googlepay',
min: '20',
max: '15000'
}
const primerGooglePay: BanxaPaymentIdLimit = {
id: 6142,
paymentType: 'PRIMERGP',
type: 'googlepay',
min: '20',
max: '15000'
}
const primerCredit: BanxaPaymentIdLimit = {
id: 6098,
paymentType: 'PRIMERCC',
type: 'credit',
min: '20',
max: '15000'
}

describe('pickPreferredPayment', function () {
it('prefers the current PSP over the deprecated one it replaced', function () {
const payments = [worldpayGoogleUsd, primerGooglePay]

expect(pickPreferredPayment(payments, 'googlepay')).toBe(primerGooglePay)
})

it('ignores the order Banxa payment ids happen to iterate in', function () {
const payments = [primerGooglePay, worldpayGoogleUsd]

expect(pickPreferredPayment(payments, 'googlepay')).toBe(primerGooglePay)
})

it('falls back to the deprecated PSP when it is the only candidate', function () {
const payments = [worldpayGoogleUsd, primerCredit]

expect(pickPreferredPayment(payments, 'googlepay')).toBe(worldpayGoogleUsd)
})

it('returns undefined when no payment method matches the type', function () {
const payments = [worldpayGoogleUsd, primerGooglePay]

expect(pickPreferredPayment(payments, 'ach')).toBeUndefined()
})
})
36 changes: 34 additions & 2 deletions src/plugins/gui/providers/banxaProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ const asBanxaStates = asObject({

interface BanxaPaymentIdLimit {
id: number
paymentType: BanxaPaymentType
type: FiatPaymentType
min: string
max: string
Expand Down Expand Up @@ -1107,6 +1108,19 @@ const typeMap: Record<BanxaPaymentType, FiatPaymentType> = {
ZHACHSELL: 'ach'
}

/**
* Banxa PSPs that have been superseded by a newer PSP serving the same
* `FiatPaymentType`. They remain mapped because they still cover fiats and coins
* the replacement PSP does not, but they are only selected when no current PSP
* is available for the requested fiat/coin pair.
*/
const deprecatedPaymentTypes = new Set<BanxaPaymentType>([
// Superseded by PRIMERGP
'WORLDPAYGOOGLE',
// Superseded by BRDGACHSELL
'ZHACHSELL'
])

// While this could use Array.find(), this is an inner loop routine used hundreds of times interating over
// hundreds entries, so I'm opting for a more optimal for loop
const findLimit = (
Expand Down Expand Up @@ -1148,6 +1162,7 @@ const buildPaymentsMap = (
// There shouldn't be an existing payment for this fiat/coin combo
const newMap: BanxaPaymentIdLimit = {
id: pm.id,
paymentType,
min: limit.min,
max: limit.max,
type: pt
Expand Down Expand Up @@ -1179,13 +1194,30 @@ const getPaymentIdLimit = (
): BanxaPaymentIdLimit | undefined => {
try {
const payments = banxaPaymentsMap[direction][fiat][banxaCoin]
const paymentId = Object.values(payments).find(p => p.type === type)
return paymentId
return pickPreferredPayment(Object.values(payments), type)
} catch (e) {
return undefined
}
}

/**
* Banxa often exposes several payment methods for the same `FiatPaymentType`,
* because a replacement PSP runs alongside the PSP it supersedes until the old
* one is switched off. Always pick a current PSP, and only fall back to a
* deprecated one when it is the sole option for the fiat/coin pair.
*/
const pickPreferredPayment = (
payments: BanxaPaymentIdLimit[],
type: FiatPaymentType
): BanxaPaymentIdLimit | undefined => {
const candidates = payments.filter(payment => payment.type === type)
return (
candidates.find(
payment => !deprecatedPaymentTypes.has(payment.paymentType)
) ?? candidates[0]
)
}

// Takes an EdgeAsset and returns the corresponding Banxa chain code and coin code
const edgeToBanxaCrypto = (
pluginId: string,
Expand Down
38 changes: 35 additions & 3 deletions src/plugins/ramps/banxa/banxaRampPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,9 @@ const ensureIsoPrefix = (currencyCode: string): string => {
return currencyCode.startsWith('iso:') ? currencyCode : `iso:${currencyCode}`
}

interface BanxaPaymentIdLimit {
export interface BanxaPaymentIdLimit {
id: number
paymentType: BanxaPaymentType
type: FiatPaymentType
min: string
max: string
Expand Down Expand Up @@ -358,6 +359,19 @@ const typeMap: Record<BanxaPaymentType, FiatPaymentType> = {
ZHACHSELL: 'ach'
}

/**
* Banxa PSPs that have been superseded by a newer PSP serving the same
* `FiatPaymentType`. They remain mapped because they still cover fiats and coins
* the replacement PSP does not, but they are only selected when no current PSP
* is available for the requested fiat/coin pair.
*/
const deprecatedPaymentTypes = new Set<BanxaPaymentType>([
// Superseded by PRIMERGP
'WORLDPAYGOOGLE',
// Superseded by BRDGACHSELL
'ZHACHSELL'
])

// Provider configuration cache
interface ProviderConfigCache {
data: {
Expand Down Expand Up @@ -530,6 +544,7 @@ const buildPaymentsMap = (
} else {
const newMap: BanxaPaymentIdLimit = {
id: pm.id,
paymentType,
min: limit.min,
max: limit.max,
type: pt
Expand Down Expand Up @@ -562,11 +577,28 @@ const getPaymentIdLimit = (
): BanxaPaymentIdLimit | undefined => {
try {
const payments = banxaPaymentsMap[direction][fiat][banxaCoin]
const paymentId = Object.values(payments).find(p => p.type === type)
return paymentId
return pickPreferredPayment(Object.values(payments), type)
} catch (e) {}
}

/**
* Banxa often exposes several payment methods for the same `FiatPaymentType`,
* because a replacement PSP runs alongside the PSP it supersedes until the old
* one is switched off. Always pick a current PSP, and only fall back to a
* deprecated one when it is the sole option for the fiat/coin pair.
*/
export const pickPreferredPayment = (
payments: BanxaPaymentIdLimit[],
type: FiatPaymentType
): BanxaPaymentIdLimit | undefined => {
const candidates = payments.filter(payment => payment.type === type)
return (
candidates.find(
payment => !deprecatedPaymentTypes.has(payment.paymentType)
) ?? candidates[0]
)
}

// Takes an EdgeAsset and returns the corresponding Banxa chain code and coin code
const edgeToBanxaCrypto = (
pluginId: string,
Expand Down
Loading