Skip to content
Draft
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
- added: "-m" tag on the version number in the Help scene for Maestro test builds
- added: Sign Message option in the wallet list menu for Bitcoin-family wallets, letting users prove self-hosted wallet ownership to exchanges by signing an exchange-provided message.
- added: `edge://buy` and `edge://sell` deep links (and their `https://deep.edge.app` equivalents) that open the buy/sell flow, optionally pinning a provider and payment method to the top of the quote options for that visit.
- added: Provider priority in the buy/sell options for affiliated accounts, configured through the info server promo card data.
- added: Provider priority in the buy/sell options for affiliated accounts, configured through the info server's `rampProviderPriority` document. An account matches an entry by its installer id or by holding the promotion, and entries scope by country and date.
- changed: Target Android 16 (API level 36), which Google Play requires for app updates submitted after Aug 30, 2026. Predictive back is opted out of for now, since React Native 0.79 cannot handle it, so the back button behaves exactly as it did before.
- changed: Sign MoonPay buy/sell widget URLs and bind them to the customer's IP via the info server, for MoonPay's on-ramp IP-matching security upgrade.
- changed: Style the entire "Already have an account? Sign in" line in the getting-started USP carousel with the tertiary link color, not just "Sign in".
Expand Down
1 change: 0 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,6 @@ export default [
'src/util/getAccountUsername.ts',
'src/util/GuiPluginTools.ts',
'src/util/haptic.ts',
'src/util/infoUtils.ts',

'src/util/memoUtils.ts',
'src/util/middleware/perfLogger.ts',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@
"edge-currency-accountbased": "^4.87.0",
"edge-currency-plugins": "^3.12.0",
"edge-exchange-plugins": "^2.52.2",
"edge-info-server": "^3.12.0",
"edge-info-server": "^3.14.0",
"edge-login-ui-rn": "^3.37.0",
"ethers": "^5.7.2",
"expo": "^53.0.0",
Expand Down
165 changes: 165 additions & 0 deletions src/__tests__/util/rampProviderPriority.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import { describe, expect, it } from '@jest/globals'
import type { RampProviderPriority } from 'edge-info-server'

import {
getRampPreferredProviders,
getRampPriorityPromoIds
} from '../../util/rampProviderPriority'

const CURRENT_DATE = new Date('2026-06-01T00:00:00.000Z')

const priority: RampProviderPriority = {
installPromo: { buy: ['moonpay'], sell: ['banxa'] },
linkPromo: { buy: ['paybis'] },
usOnlyPromo: { buy: ['simplex'], countryCodes: ['US'] },
notUsPromo: { buy: ['bity'], excludeCountryCodes: ['US'] },
expiredPromo: { buy: ['expired'], endIsoDate: '2026-01-01T00:00:00.000Z' },
futurePromo: { buy: ['future'], startIsoDate: '2027-01-01T00:00:00.000Z' },
windowPromo: {
buy: ['window'],
startIsoDate: '2026-01-01T00:00:00.000Z',
endIsoDate: '2027-01-01T00:00:00.000Z'
},
badDatePromo: { buy: ['badDate'], endIsoDate: 'not a date' }
}

const query = (
props: Partial<Parameters<typeof getRampPreferredProviders>[0]> = {}
): string[] =>
getRampPreferredProviders({
activePromotions: [],
currentDate: CURRENT_DATE,
direction: 'buy',
priority,
...props
})

describe('getRampPreferredProviders', () => {
it('returns nothing when the document is missing', () => {
expect(query({ installerId: 'installPromo', priority: undefined })).toEqual(
[]
)
})

it('returns nothing for an unaffiliated account', () => {
expect(query()).toEqual([])
})

it('matches the installer id', () => {
expect(query({ installerId: 'installPromo' })).toEqual(['moonpay'])
})

it('matches an active promotion', () => {
expect(query({ activePromotions: ['linkPromo'] })).toEqual(['paybis'])
})

it('matches an active promotion even when the installer differs', () => {
// The old promoCards2 path ANDed these two, which excluded a user who
// picked up the promotion after installing from somewhere else.
expect(
query({ activePromotions: ['linkPromo'], installerId: 'somewhereElse' })
).toEqual(['paybis'])
})

it('splits buy and sell', () => {
expect(query({ direction: 'sell', installerId: 'installPromo' })).toEqual([
'banxa'
])
expect(
query({ direction: 'sell', activePromotions: ['linkPromo'] })
).toEqual([])
})

it('concatenates every matching entry, deduplicated', () => {
expect(
query({
activePromotions: ['linkPromo', 'windowPromo', 'linkPromo'],
installerId: 'installPromo'
})
).toEqual(['moonpay', 'paybis', 'window'])
})

it('honors an include country list', () => {
expect(
query({ activePromotions: ['usOnlyPromo'], countryCode: 'us' })
).toEqual(['simplex'])
expect(
query({ activePromotions: ['usOnlyPromo'], countryCode: 'GB' })
).toEqual([])
})

it('honors an exclude country list', () => {
expect(
query({ activePromotions: ['notUsPromo'], countryCode: 'GB' })
).toEqual(['bity'])
expect(
query({ activePromotions: ['notUsPromo'], countryCode: 'US' })
).toEqual([])
})

it('drops a country-scoped entry when the country is unknown', () => {
expect(query({ activePromotions: ['usOnlyPromo'] })).toEqual([])
})

it('ignores country scoping on an unscoped entry', () => {
expect(
query({ activePromotions: ['linkPromo'], countryCode: 'US' })
).toEqual(['paybis'])
})

it('honors date scoping', () => {
expect(query({ activePromotions: ['expiredPromo'] })).toEqual([])
expect(query({ activePromotions: ['futurePromo'] })).toEqual([])
expect(query({ activePromotions: ['windowPromo'] })).toEqual(['window'])
})

it('treats an unparseable date as an unset bound', () => {
expect(query({ activePromotions: ['badDatePromo'] })).toEqual(['badDate'])
})
})

describe('getRampPriorityPromoIds', () => {
it('returns nothing when the document is missing', () => {
expect(
getRampPriorityPromoIds({
activePromotions: ['linkPromo'],
currentDate: CURRENT_DATE,
priority: undefined
})
).toEqual([])
})

it('names every entry that applies, in document key order', () => {
expect(
getRampPriorityPromoIds({
activePromotions: ['windowPromo', 'expiredPromo'],
currentDate: CURRENT_DATE,
installerId: 'installPromo',
priority
})
).toEqual(['installPromo', 'windowPromo'])
})

it('names a sell-only match, which the buy providers would not show', () => {
// getActivePromoIds has no direction, so an entry that only configures the
// other direction still counts as an active promotion.
expect(
getRampPriorityPromoIds({
activePromotions: [],
currentDate: CURRENT_DATE,
installerId: 'installPromo',
priority: { installPromo: { sell: ['banxa'] } }
})
).toEqual(['installPromo'])
})

it('tolerates an absent activePromotions list', () => {
expect(
getRampPriorityPromoIds({
currentDate: CURRENT_DATE,
installerId: 'installPromo',
priority
})
).toEqual(['installPromo'])
})
})
Loading
Loading