From d9897f13eac8508f75aaca985d60e5033975e1d7 Mon Sep 17 00:00:00 2001 From: jwinterm Date: Thu, 2 Jul 2026 16:41:45 -0400 Subject: [PATCH] MoonPay: key-gated enablement + inject MOONPAY_API_KEY/EXCHANGE_HELPER_API_KEY in CI getAvailableBuyProviderTypes() now returns [moonpay] only when moonPayApiKey is non-empty, so MoonPay's Buy button appears only in builds where the CI secret is present (and the exchange-helper signer is live). Both build workflows now inject MOONPAY_API_KEY -> moonPayApiKey and EXCHANGE_HELPER_API_KEY -> exchangeHelperApiKey alongside Trocador + WalletConnect. moonPaySecretKey is server-side and never injected. --- .github/workflows/build-android.yml | 9 +++++++-- .github/workflows/build-ios-testflight.yml | 7 ++++++- lib/entities/provider_types.dart | 17 +++++++++++------ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-android.yml b/.github/workflows/build-android.yml index aa6ea19ad..bd6513d60 100644 --- a/.github/workflows/build-android.yml +++ b/.github/workflows/build-android.yml @@ -166,20 +166,25 @@ jobs: - name: Generate per-module secrets.g.dart files (all empty defaults) run: dart run tool/generate_new_secrets.dart - - name: Inject Trocador + WalletConnect secrets into lib/.secrets.g.dart + - name: Inject Trocador + WalletConnect + MoonPay secrets into lib/.secrets.g.dart env: TROCADOR_API_KEY: ${{ secrets.TROCADOR_API_KEY }} TROCADOR_MONERO_API_KEY: ${{ secrets.TROCADOR_MONERO_API_KEY }} TROCADOR_EXCHANGE_MARKUP: ${{ secrets.TROCADOR_EXCHANGE_MARKUP }} WALLETCONNECT_PROJECT_ID: ${{ secrets.WALLETCONNECT_PROJECT_ID }} + # MoonPay: publishable key only. NEVER inject moonPaySecretKey (server-side). + MOONPAY_API_KEY: ${{ secrets.MOONPAY_API_KEY }} + EXCHANGE_HELPER_API_KEY: ${{ secrets.EXCHANGE_HELPER_API_KEY }} run: | sed -i \ -e "s|const trocadorApiKey = '';|const trocadorApiKey = '${TROCADOR_API_KEY}';|" \ -e "s|const trocadorMoneroApiKey = '';|const trocadorMoneroApiKey = '${TROCADOR_MONERO_API_KEY}';|" \ -e "s|const trocadorExchangeMarkup = '';|const trocadorExchangeMarkup = '${TROCADOR_EXCHANGE_MARKUP:-1}';|" \ -e "s|const walletConnectProjectId = '';|const walletConnectProjectId = '${WALLETCONNECT_PROJECT_ID}';|" \ + -e "s|const moonPayApiKey = '';|const moonPayApiKey = '${MOONPAY_API_KEY}';|" \ + -e "s|const exchangeHelperApiKey = '';|const exchangeHelperApiKey = '${EXCHANGE_HELPER_API_KEY}';|" \ lib/.secrets.g.dart - grep -E "^const (trocador|walletConnectProjectId)" lib/.secrets.g.dart + grep -E "^const (trocador|walletConnectProjectId|moonPayApiKey|exchangeHelperApiKey)" lib/.secrets.g.dart # ---- Signing: decode upload keystore + write key.properties --------- - name: Decode Android upload keystore + write key.properties diff --git a/.github/workflows/build-ios-testflight.yml b/.github/workflows/build-ios-testflight.yml index 668820f27..26d81cba1 100644 --- a/.github/workflows/build-ios-testflight.yml +++ b/.github/workflows/build-ios-testflight.yml @@ -224,18 +224,23 @@ jobs: - name: Generate per-module secrets.g.dart files (empty defaults) run: dart run tool/generate_new_secrets.dart - - name: Inject Trocador + WalletConnect secrets + - name: Inject Trocador + WalletConnect + MoonPay secrets env: TROCADOR_API_KEY: ${{ secrets.TROCADOR_API_KEY }} TROCADOR_MONERO_API_KEY: ${{ secrets.TROCADOR_MONERO_API_KEY }} TROCADOR_EXCHANGE_MARKUP: ${{ secrets.TROCADOR_EXCHANGE_MARKUP }} WALLETCONNECT_PROJECT_ID: ${{ secrets.WALLETCONNECT_PROJECT_ID }} + # MoonPay: publishable key only. NEVER inject moonPaySecretKey (server-side). + MOONPAY_API_KEY: ${{ secrets.MOONPAY_API_KEY }} + EXCHANGE_HELPER_API_KEY: ${{ secrets.EXCHANGE_HELPER_API_KEY }} run: | sed -i '' \ -e "s|const trocadorApiKey = '';|const trocadorApiKey = '${TROCADOR_API_KEY}';|" \ -e "s|const trocadorMoneroApiKey = '';|const trocadorMoneroApiKey = '${TROCADOR_MONERO_API_KEY}';|" \ -e "s|const trocadorExchangeMarkup = '';|const trocadorExchangeMarkup = '${TROCADOR_EXCHANGE_MARKUP:-1}';|" \ -e "s|const walletConnectProjectId = '';|const walletConnectProjectId = '${WALLETCONNECT_PROJECT_ID}';|" \ + -e "s|const moonPayApiKey = '';|const moonPayApiKey = '${MOONPAY_API_KEY}';|" \ + -e "s|const exchangeHelperApiKey = '';|const exchangeHelperApiKey = '${EXCHANGE_HELPER_API_KEY}';|" \ lib/.secrets.g.dart # ---- Apple signing setup -------------------------------------------- diff --git a/lib/entities/provider_types.dart b/lib/entities/provider_types.dart index 6d27653b3..274c622aa 100644 --- a/lib/entities/provider_types.dart +++ b/lib/entities/provider_types.dart @@ -6,6 +6,7 @@ import 'package:hash_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:hash_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:hash_wallet/buy/robinhood/robinhood_buy_provider.dart'; import 'package:hash_wallet/di.dart'; +import 'package:hash_wallet/.secrets.g.dart' as secrets; enum ProviderType { robinhood, dfx, onramper, moonpay, meld, kriptonim } @@ -46,12 +47,16 @@ extension ProviderTypeName on ProviderType { } class ProvidersHelper { - // Hash Bags: all buy/sell providers (Robinhood Connect, DFX, Onramper, - // MoonPay, Kryptonim, Meld) gutted. Each requires a formal partner - // agreement with a revenue-share contract — no free integrations exist. - // Re-add providers here once Such Software has registered the affiliate - // relationship for that provider. - static List getAvailableBuyProviderTypes() => []; + // Hash Bags: buy/sell providers stay gutted EXCEPT MoonPay, which is + // KEY-GATED — it becomes available only when a real moonPayApiKey is baked + // into the build (injected in CI from the MOONPAY_API_KEY secret; the + // exchange-helper signer must also be live). If the key is empty the Buy + // button stays hidden, so v1.0.0 ships cleanly with or without MoonPay. + // The other providers (Robinhood/DFX/Onramper/Kryptonim/Meld) still need + // their own partner agreements before re-adding here. + // Sell/off-ramp: enable by mirroring the buy line below once tested. + static List getAvailableBuyProviderTypes() => + secrets.moonPayApiKey.isNotEmpty ? [ProviderType.moonpay] : []; static List getAvailableSellProviderTypes() => []; static BuyProvider getProviderByType(ProviderType type) {