Skip to content

Conversation

samholmes
Copy link
Contributor

@samholmes samholmes commented Aug 26, 2025

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Requirements

If you have made any visual changes to the GUI. Make sure you have:

  • Tested on iOS device
  • Tested on Android device
  • Tested on small-screen device (iPod Touch)
  • Tested on large-screen device (tablet)


Note

Introduces the Infinite ramp plugin with end-to-end auth/KYC/TOS/bank/transfer flows, new ramp scenes, deep-link handling, plugin loading and env inits, plus supporting utilities, tests, and UI tweaks.

  • Ramp Platform:
    • Infinite plugin: New plugin infinite with API/types, auth challenge/sign (secp256k1), quotes/transfers, KYC/TOS status, bank accounts, currencies/countries, and normalized currency mapping.
    • Workflows: authenticateWorkflow, kycWorkflow (pending polling), tosWorkflow, bankAccountWorkflow, confirmationWorkflow, nav flow helper.
    • Deep Links: Add ramp link parsing/handling and deeplink registration in webview utilities; updated DeepLinkingActions and types.
    • Env/Loading: Add ENV.RAMP_PLUGIN_INITS.infinite; load only plugins with init present; register in allRampPlugins.
  • UI/Navigation:
    • New scenes: RampKycFormScene, RampBankFormScene, RampBankRoutingDetailsScene, RampConfirmationScene, RampPendingScene; wired into Buy/Sell stacks; dev test entries.
  • Vault & Tests:
    • Add edgeVault (personal/address/bank records) with comprehensive unit tests and sample records.
  • Shared Utils/Components:
    • CurrencyInfoHelpers: add getContractAddress.
    • cleanFetch: typed URL resources, options; add asSearchParams.
    • WebView helpers: support deeplink registration/unregister; close detection.
    • useBackEvent hook.
    • FilledTextInput: minLength, improved messages; Paragraph fit text; GuiFormField/GuiFormRow enhancements.
    • AirshipInstance.showToastSpinner accepts function.
  • Locales/Config:
    • Add extensive ramp KYC/TOS/bank strings; add string_close_cap; confirm buy/sell titles.
    • Jest testMatch; add @noble/{curves,hashes} deps.
    • Minor provider fixes (Ionia/Kado) and URL handling.

Written by Cursor Bugbot for commit 85e99f7. This will update automatically on new commits. Configure here.

@samholmes samholmes marked this pull request as draft August 26, 2025 18:01
cursor[bot]

This comment was marked as outdated.

@samholmes samholmes force-pushed the sam/infinite-ramp-plugin branch 4 times, most recently from 2311b66 to 9ebe8d3 Compare September 11, 2025 21:47
@Jon-edge Jon-edge force-pushed the sam/infinite-ramp-plugin branch 2 times, most recently from 4a14ce2 to 00f5b8c Compare September 16, 2025 17:27
@samholmes samholmes force-pushed the sam/infinite-ramp-plugin branch 3 times, most recently from 6e2b3b7 to 5cf5c68 Compare October 8, 2025 18:05
@samholmes samholmes marked this pull request as ready for review October 8, 2025 18:43
cursor[bot]

This comment was marked as outdated.

@samholmes samholmes force-pushed the sam/infinite-ramp-plugin branch from 5cf5c68 to 53d9054 Compare October 8, 2025 23:32
cursor[bot]

This comment was marked as outdated.

Copy link
Contributor

@swansontec swansontec left a comment

Choose a reason for hiding this comment

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

First round of review. I need to take a closer look at the Infinite API and some other related things.

getAuthState: false, // This is always local, no API call
saveCustomerId: false, // This is always local, no API call
isAuthenticated: false // This is always local, no API call
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I disagree with Cursor on this one. This is critical for development since we don't want to use real funds for testing.

import type { NavigationBase } from '../../../../types/routerTypes'

export interface NavigationFlow {
navigate: (route: string, params?: any) => void
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't like the any. Since we can go to scenes like rampPending, it seems like we are dealing with the buy / sell tab navigator, so we should type this using those route names / params.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There's a high cog-load to the navigation types, so I'll just make it typed to NavigationBase

}

resolve({ confirmed: true, transfer })
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Cursor makes a valid point - the funds could come from many utxo's, not necessarily our latest receive address (indeed, this is usually a fresh address for utxo coins). If Infinite.dev is just using this as a refund address, then it's fine. If not, we might need a rethink.

try {
const record = await makeRecord(info)
const json = wasVaultPersonalRecord(record)
await vaultDisklet.setText(`${info.type}/${record.uuid}.json`, json)
Copy link
Contributor

Choose a reason for hiding this comment

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

So we are saving data in edgeVault/{randomHex}.json. Ok, I guess this is fine.

They don't really need to be UUID's - we could do 128 bits of base58 (about 21 characters). This would simplify creating these things, and since we don't really parse them or read into them, it wouldn't affect anybody.

Copy link
Contributor

@swansontec swansontec left a comment

Choose a reason for hiding this comment

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

Found a few more things. Now the review is complete.

/**
* Exit error class for gracefully exiting workflows
*/
export class Exit extends Error {
Copy link
Contributor

Choose a reason for hiding this comment

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

This should probably be called ExitError. It's not really an error as such, because we expect it, but calling it ExitError makes it clear that we want to catch it.

Comment on lines +38 to +42
if (
kycStatus.kycStatus === 'not_started' ||
kycStatus.kycStatus === 'incomplete'
) {
// Fall through to show KYC form
Copy link
Contributor

Choose a reason for hiding this comment

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

This logic threw me for a loop. Would it make more sense:

if (
  kycStatus.kycStatus !== 'not_started' &&
   kycStatus.kycStatus !== 'incomplete'
) {
  // For all other statuses (under_review, awaiting_ubo, etc.), show pending scene
      await showKycPendingScene(...
}

}
let privateKey: Uint8Array
// Try to load from storage (stored as hex string)
const itemIds = await account.dataStore.listItemIds(pluginId)
Copy link
Contributor

Choose a reason for hiding this comment

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

Just do const key: string | undefined = account.dataStore.getItem(pluginId, INFINITE_PRIVATE_KEY).catch(() => {}). It will either return the key if we have it, or undefined. Saves a round-trip over the bridge and a bunch of expensive disk access.

): Promise<void> => {
const itemIds = await account.dataStore.listItemIds(pluginId)
if (itemIds.includes(INFINITE_PRIVATE_KEY) === true) {
await account.dataStore.deleteItem(pluginId, INFINITE_PRIVATE_KEY)
Copy link
Contributor

Choose a reason for hiding this comment

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

Just call account.dataStore.deleteItem(pluginId, INFINITE_PRIVATE_KEY) directly. Deleting missing items is a no-op - it's not an error, so there's no need to guard the call.

Comment on lines +121 to +122
let currenciesCache: CacheEntry<InfiniteCurrenciesResponse> | null = null
let normalizedCurrenciesCache: CacheEntry<NormalizedCurrenciesMap> | null =
Copy link
Contributor

Choose a reason for hiding this comment

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

These two caches contain the same data, just in two formats. Could they be merged somehow?

let currenciesCache: CacheEntry<{
  raw: InfiniteCurrenciesResponse
  normalized: NormalizedCurrenciesMap
}> | null = null

...

const getCurrenciesWithCache =
 ...
   const currenciesData = await infiniteApi.getCurrencies()
   const data = { 
      raw: currenciesData,
      normalized: normalizeCurrencies(currenciesData)
    }
    currenciesCache = { data, timestamp: Date.now() }
    return data

})

// Get fresh quote before confirmation using existing params
const freshQuote = await infiniteApi.createQuote(quoteParams)
Copy link
Contributor

Choose a reason for hiding this comment

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

So we're re-quoting in case the KYC took a long time, and the old quote expired?

Comment on lines +639 to +652
const hexToBytes = (hex: string): Uint8Array => {
if (hex.startsWith('0x')) hex = hex.slice(2)
const bytes = new Uint8Array(hex.length / 2)
for (let i = 0; i < hex.length; i += 2) {
bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16)
}
return bytes
}

const bytesToHex = (bytes: Uint8Array): string => {
return Array.from(bytes)
.map(b => b.toString(16).padStart(2, '0'))
.join('')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

These should be base16.parse / base16.stringify from rfc4648

Comment on lines +100 to +103
const shouldIncludeAuth =
options?.includeAuth ?? (authState.token != null && !isTokenExpired())

if (shouldIncludeAuth && authState.token != null && !isTokenExpired()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This way should be equivalent, and simpler:

const hasAuth = authState.token != null && !isTokenExpired()
if (options?.includeAuth !== false && hasAuth) {

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.

2 participants