Skip to content

Add Unstoppable Wallet as an OpenCryptoPay payment-link wallet - #4437

Open
joshuakrueger-dfx wants to merge 3 commits into
developfrom
feat/unstoppable-wallet-app
Open

Add Unstoppable Wallet as an OpenCryptoPay payment-link wallet#4437
joshuakrueger-dfx wants to merge 3 commits into
developfrom
feat/unstoppable-wallet-app

Conversation

@joshuakrueger-dfx

Copy link
Copy Markdown
Collaborator

Register Unstoppable Wallet (Horizontal Systems) in the wallet_app catalog so it is offered as a payment app on the payment-link pages (app.dfx.swiss/pl). The wallet list on those pages is served from this table via GET /v1/paymentLink/walletApp.

Supported transfer methods

Unstoppable ships native OpenCryptoPay support on iOS and Android. The eleven methods below were read off the app sources on 2026-07-28, not taken from documentation:

  • iOS — OpenCryptoPayBroadcasterFactory.unstoppable registers EvmHex, TronHash, BitcoinHex, SolanaHash, ZanoHash and MoneroHash broadcasters; the union of their supportedChains maps yields the method names.
  • Android — OcpTransferAmount.supportedBlockchainTypes() in OpenCryptoPayRepository.kt lists the same eleven.

Ethereum;BinanceSmartChain;Polygon;Arbitrum;Optimism;Base;Bitcoin;Solana;Tron;Zano;Monero

The six EVM chains are listed explicitly rather than via the EvmBlockchains placeholder: EvmBlockchains additionally contains Gnosis, Haqq, Citrea and CitreaTestnet, which Unstoppable does not OCP-broadcast. See WalletApp.supportedBlockchainList.

Columns left unset

recommended, semiCompatible and assets stay NULL — a normal, fully compatible wallet with no asset restriction.

hasActionDeepLink also stays NULL, and that is deliberate. Per SetRealUnitHasActionDeepLink, the flag means a non-Lightning wallet takes the payment through <deepLink>lightning:<LNURL>. For Unstoppable that path does not exist: OpenCryptoPayUrl.detect accepts only URLs with scheme == "https" carrying a lightning query parameter, so unstoppable.money:lightning:… falls through DeepLinkManager.handle into the address parser and ends in the "cannot recognize" banner. Behaviour matches Cake Wallet (flag NULL), not RealUnit.

deepLink is the bare custom scheme unstoppable.money:, registered on both platforms (iOS Info.plist CFBundleURLTypes, AndroidManifest.xml), so the frontend's open-app action works.

down() ownership

up() skips an existing Unstoppable Wallet row via its SELECT guard. A name-only DELETE in down() would therefore remove a row this migration never created — a hand-created or later edited row would be destroyed by an up/down cycle. down() additionally matches the characteristic values up() writes (deepLink + blockchains), the same exact-value idiom used by SetRealUnitHasActionDeepLink and PopulateNativeCoinDecimals.

Test

add-unstoppable-wallet-app.migration.spec.ts mirrors AddSavingZchfAsset: a mocked-queryRunner suite for the SQL content and a real-Postgres suite gated on MIGRATION_TEST_PG (set in api-pr.yaml).

The ownership test creates a foreign Unstoppable Wallet row with a different deepLink before up() and asserts that an up()down() cycle leaves it intact. Verified to fail without the guard: reverting down() to the unconditional form turns the suite red — 1 of 1 suite, 2 of 7 tests, the Postgres one with Expected length: 1, Received length: 0. With the guard: 7 of 7 pass.

Precedent

wallet_app is maintained by migration, not by hand: AQUA (#3909), urble (#3674) and RealUnit (#4339) all landed that way and none was ever withdrawn (git log --diff-filter=D over migration/*allet* is empty). The id-less INSERT is equally established — RealUnit went in through the same shape and was assigned id 27, the highest in production, so the sequence is not lagging.

Not verified

The migration has not been run against the production database; idempotency and the up/down cycle are covered by the Postgres spec against a fixture table. No real payment was made from the Unstoppable app — this PR makes the wallet selectable, it does not exercise a payment.

Register Unstoppable Wallet (Horizontal Systems) in the wallet_app catalog so it is
offered on payment-link pages. Unstoppable ships native OpenCryptoPay support on iOS
and Android; the eleven supported transfer methods were verified against the app
sources (iOS OpenCryptoPayBroadcasterFactory.unstoppable, Android
OcpTransferAmount.supportedBlockchainTypes).

The six EVM chains are listed explicitly instead of the EvmBlockchains placeholder,
because Unstoppable does not OCP-broadcast Gnosis, Haqq or Citrea. hasActionDeepLink
stays NULL: OpenCryptoPayUrl.detect only accepts an https URL with a lightning query,
so the deepLink+lightning: action link does not apply here.

Idempotent SELECT-then-skip guard against the UNIQUE(name) constraint, matching the
existing migration convention.
…gration spec

up() skips an existing 'Unstoppable Wallet' row via its SELECT guard, so a name-only DELETE in
down() would remove a row this migration never created - a hand-created or later edited row would
be destroyed by an up/down cycle. down() now additionally matches the characteristic values up()
writes (deepLink + blockchains), the same exact-value idiom the sibling migrations use.

The new spec covers both layers, mirroring AddSavingZchfAsset: mocked queryRunner for the SQL
content (all eleven methods in order, no hasActionDeepLink/recommended/semiCompatible/assets, no
INSERT when the row exists) and a real-Postgres suite that creates a pre-existing foreign row
before up() and asserts an up/down cycle leaves it intact.
@joshuakrueger-dfx
joshuakrueger-dfx marked this pull request as ready for review July 29, 2026 10:38
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.

1 participant