fix: route quickstart through flash service#415
Merged
islandbitcoin merged 3 commits intoJun 24, 2026
Merged
Conversation
e168521 to
ea65b42
Compare
ac1ddce
into
tmp/bridge-rebase-pr-ready
10 of 12 checks passed
islandbitcoin
added a commit
that referenced
this pull request
Jul 2, 2026
* ENG-297 feat(bridge): add core Bridge integration * ENG-278 ENG-280 ENG-281 ENG-282 ENG-283 ENG-284 ENG-285 ENG-349 ENG-363 ENG-297 fix(bridge): apply audit hardening and hosted KYC refinements * ENG-276 feat(bridge): add reconciliation and replay tooling * ENG-394 feat(accounts): create ETH-USDT Cash Wallet for new accounts * ENG-297 docs: document Bridge rebase PR-readiness plan * ENG-376 fix(bridge): reuse pending withdrawal idempotency rows * ENG-297 chore(bridge): satisfy scoped lint on branch changes * ENG-297 fix(graphql): add isExternal to UsdtWallet * fix(graphql): preserve latest account upgrade request query * feat: bridge reconciliation + USDT provisioning additions (#354) Ready to merge. Verified locally: - `yarn check:sdl` passes - targeted bridge/IBEX unit tests pass - `git diff --check` passes **squash & merge** used to keep `tmp/bridge-rebase-pr-ready` clean. * ENG-297 - IBEX USD -> USDT Parity (#355) * ENG-297 fix(wallets): support USDT cash wallet parity * fix(wallets): support USDT intraledger sends * fix(graphql): serialize USDT fee probe amounts (#358) * Feat/bridge reconciliation (#360) * feat: add timeout for Bridge request * feat(bridge): ENG-350 — reset withdrawal state on transfer.failed and surface failure reason * feat(bridge): ENG-350 harden transfer webhook edge cases and notify users * feat(bridge): ENG-350 introduce full KYC status lifecycle with offboarded support * refactor(bridge): rename BridgeDepositLog model to BridgeDeposits * refactor(ibex): rename IbexCryptoReceiveLog model to IbexCryptoReceive * refactor(bridge): rename BridgeReplayLog model to BridgeReplay * fix(bridge) : ENG-289 timeout increase to 15s * feat: adding full kyc status to reflect them stored as bridgeKycStatus in mongoDB instead of just pending state * feat: Kyc status * fix: Duplicated webhook event message * fix: Duplicated webhook deposit event message * fix: Virtual Account Edge case * fix: expose usdt amounts as usd cents * fix: keep usdt cent boundaries explicit * feat(cutover): prepare cash wallet migration for bridge (#377) * fix(graphql): make usdt wallet balance nullable (#369) * feat(cutover): add cash wallet migration state primitives * feat(cutover): persist cash wallet cutover state * feat(cutover): add cash wallet write guard * feat(cutover): classify cash wallet migration candidates * feat(cutover): add cash wallet preflight summary * feat(cutover): collect cash wallet discovery results * feat(cutover): plan primary cash wallet migrations * feat(cutover): upsert planned migration records * feat(cutover): prepare primary migration batch * feat(cutover): start migration worker checkpoint * feat(cutover): record migration source balance * feat(cutover): create balance move invoice checkpoint * feat(cutover): send balance move payment checkpoint * feat(cutover): verify balance move checkpoint * feat(cutover): create fee reimbursement invoice checkpoint * feat(cutover): complete fee reimbursement checkpoint * feat(cutover): flip default wallet checkpoint * feat(cutover): complete migration worker checkpoints * feat(cutover): provision destination checkpoint * feat(cutover): dispatch migration worker steps * feat(cutover): run locked migration batches * feat(cutover): build migration step handlers * feat(cutover): wire migration runtime services * feat(cutover): orchestrate primary migration batches * feat(cutover): add migration lifecycle controls * fix(cutover): align migration indexes with run ids * feat(cutover): add operator command script * chore(cutover): format migration state helpers * feat(cutover): preview dry-run migration plan * fix(cutover): satisfy production build types * feat(cutover): add operator controls and verification * feat(cutover): add client-aware cash wallet presentation * fix(cutover): harden operator cutover run (#376) * feat(cutover): add operator dashboard and provisioning tools * fix(cutover): use ibex oauth credentials in local setup * fix(cutover): retry wallet provisioning and refresh stale invoices * fix(cutover): retry ibex rate limits during migration payments * fix(cutover): clarify dashboard run anomalies * chore(cutover): remove local run artifacts * chore(cutover): trim bridge PR noise * chore(cutover): move unit tests to separate PR * Potential fix for pull request finding * Potential fix for pull request finding * Potential fix for pull request finding Co-authored-by: Island Bitcoin <34528298+islandbitcoin@users.noreply.github.com> * fix(bridge): restore cutover repository build * fix(cutover): preserve cash wallet history after cutover (#380) * fix(wallets): preserve USDT transaction history precision (#382) * fix(wallets): preserve USDT transaction history precision * chore(wallets): restore base relay import * fix(wallets): handle missing IBEX USDT history amounts * feat(bridge): external account webhook + ENG-350 transfer failure reset (#379) * feat(externalAccounts): Webhook endpoint firing for external accounts feat: external accounts event triggering this commit initialize the webhook endpoint to register on Bridge to trigger external account link after plaid completion * fix(ENG-350) : reset pending withdraw state on transfer failure. this commit mark the transfer as failed in our bridge withdrawals collection when something went wrong during the trasfer flow, the user is notified with a popup and on return state, as well for transfer successfully completed * fix(bridge): normalize currency display in withdrawal notification Map "usdt" to "USD" and uppercase other currency codes so the push notification shows a human-readable label instead of a raw enum value. * test(bridge): verify webhook signature contract (#383) * fix(cash-wallet): guard cutover start Require a prepared runnable migration row before Cash Wallet cutover start can move config to in_progress. * feat: add BridgeTransferRequest ERPNext audit model and webhook wiring (#386) * feat: add BridgeTransferRequest ERPNext audit model and webhook wiring Adds a full Bridge Transfer Request audit record system that writes ERPNext DocType rows from Bridge deposit, IBEX crypto receive, and Bridge transfer webhook events. - BridgeTransferRequest model with toErpnext() serialization - BridgeTransferRequestWriter with 4 event handlers - ErpNext.upsertBridgeTransferRequest with idempotent upsert - Webhook wiring in deposit, transfer, and crypto-receive routes - Bridge deposit log made idempotent by eventId * fix: normalize IBEX network casing for ERPNext consistency The IBEX crypto receive route lowercases the network field for Mongo storage, but was passing the lowercased value to the BridgeTransferRequest model. This created inconsistent casing in ERPNext records: deposits used 'Ethereum' (model default) while IBEX receives used 'ethereum'. Fix: capitalize the normalized network before passing to the writer. The lowercase value is still stored in the IBEX log and raw payload. * fix(bridge): lower account level requirement from level 2 to level > 0 (#385) * fix(bridge): lower account level requirement from level 2 to level > 0 Bridge operations now allow any non-zero account level instead of requiring Pro (level 2+). * fix: account denomination for non zero level --------- Co-authored-by: Island Bitcoin <34528298+islandbitcoin@users.noreply.github.com> * fix(bridge): correct replay tooling for Bridge webhook_events API (#381) - Rename createBridgeReplayLog → createBridgeReplay in replay.spec.ts to match the production export; spec was failing on every run before this. - Remove start_date/end_date from ListEventsParams — Bridge /webhook_events only supports starting_after, ending_before, limit and category; date range filtering is now applied locally inside listAllEvents. - Update replay-bridge-events.ts to pass start/end (local filter) instead of the silently-dropped start_date/end_date. - Update client.spec.ts to assert local filtering behaviour rather than asserting those params are forwarded to the Bridge API. * fix(bridge): add external account webhook config (#390) Co-authored-by: Vandana <forge@getflash.io> * fix(bridge): align GraphQL contract and error codes (#393) Co-authored-by: Vandana <forge@getflash.io> * fix(ibex): wire USDT LNURL-pay msat conversion (#389) * fix(ibex): convert payToLnurl amount to explicit millisatoshis The IBEX LNURL-pay API (POST /v2/lnurl/pay/send) expects the amount in millisatoshis, but PayLnurlArgs.send.amount passed the wallet currency's base unit directly (USDT micros, USD cents, or BTC sats). Changed PayLnurlArgs to accept amountMsat: number instead of send: IbexCurrency. This makes the expected unit explicit and forces callers to perform the msat conversion at the app layer where the DealerPriceService is available. ENG-406 * feat(lnurl): add USD wallet LNURL payment mutation * feat(bridge): push notification on deposit settlement [ENG-275] (#392) Fires a best-effort push when a Bridge USDT deposit settles via the IBEX crypto.received webhook (the must-have launch gate). Mirrors the existing sendBridgeWithdrawalNotification pattern: - new src/app/bridge/send-deposit-notification.ts - wired at the crypto-receive settlement success (idempotent — inside the per-txHash lock; fires once) - notification.bridgeDeposit i18n phrases (en + es) Withdrawal-completion push already exists (transfer.ts) — this completes the deposit side. IBEX→USD currency display mirrors the withdrawal notif. Co-authored-by: Dread <bobodread@bobodread.com> * feat(bridge): split withdrawal into request, confirm, and cancel steps (#387) * feat(bridge): split withdrawal into request, confirm, and cancel steps Introduce a two-step off-ramp flow so users review a pending withdrawal before Bridge is called, with deduplication, cancellation, and push notifications for the cancelled outcome. * docs(bridge): document two-step withdrawal flow in FLOWS and API Update bridge-integration docs to describe bridgeRequestWithdrawal, bridgeInitiateWithdrawal(withdrawalId), and bridgeCancelWithdrawalRequest. * chore(bruno): add GraphQL requests for bridge withdrawal flow Add Bruno mutations and query for request, initiate, cancel, and fetch pending withdrawal, with local env vars for bridgeWithdrawalId. * fix(config): add external_account webhook public key to dev config Required by the Bridge webhook schema so local write-sdl and config validation succeed. * chore(graphql): regenerate supergraph for bridge withdrawal mutations Add bridgeRequestWithdrawal, bridgeCancelWithdrawalRequest, and bridgeWithdrawalRequest to the federated supergraph schema. * fix(bridge): map withdrawal request errors to Bridge codes BridgeWithdrawalNotFoundError and BridgeWithdrawalAlreadyInitiatedError now map to dedicated BRIDGE_WITHDRAWAL_NOT_FOUND and BRIDGE_WITHDRAWAL_ALREADY_INITIATED codes instead of the generic INVALID_INPUT. Tests updated to assert the correct Bridge-specific codes. * test(bridge): add resolver coverage for request/initiate/cancel withdrawal Eight resolver-layer tests across the three bridge withdrawal mutations: happy-path delegation to BridgeService and the two new Bridge-specific error codes (BRIDGE_WITHDRAWAL_NOT_FOUND, BRIDGE_WITHDRAWAL_ALREADY_INITIATED) flowing through the full error-map pipeline. * fix(bridge): resolve duplicate const declarations in deposit webhook handler Auto-merge conflict left two `const lockKey`/`const lockResult` pairs in the same function scope. Rename the audit-idempotency pair to `auditLockKey`/`auditLockResult` to fix TS2451. * fix(graphql): align BridgeWithdrawal type with new id/status contract The type had transferId: NonNull and state: NonNull left over from the old single-step flow. All new resolvers (request/initiate/cancel mutations plus bridgeWithdrawalRequest query) return id + status, so those NonNull fields were always null at runtime. Changes: - bridge-withdrawal.ts: transferId → id (NonNull), state removed, bridgeTransferId added (nullable) - index.ts: WithdrawalResult and InitiateWithdrawalResult updated to match; getWithdrawals map rewritten (id/status/bridgeTransferId/ externalAccountId) and the always-true filter removed - bridge-withdrawal-request.ts: add bridgeTransferId to return object - bridge-contract.spec.ts: assertions updated to new field set * test(bridge): cover getWithdrawals mapping to id/status/bridgeTransferId Five tests asserting that getWithdrawals emits the new GQL-facing shape (id, status, bridgeTransferId) and not the old transferId/state fields. Also adds findWithdrawalsByAccountId to the bridge-accounts mock so the describe block can exercise the mapping path. * feat(bridge): add submitted status for initiated withdrawals Introduces an intermediary "submitted" status so the UI can distinguish between a pending withdrawal (awaiting user confirmation) and one that has already been submitted to Bridge (awaiting Bridge processing). - schema.ts: adds "submitted" to IBridgeWithdrawalRecord.status union and Mongoose enum - bridge-accounts.ts: updateWithdrawalTransferId now sets status → "submitted" atomically with the bridgeTransferId write; updateWithdrawalStatus query filter changed from "pending" to "submitted" so webhook-driven transitions (completed/failed) match the correct row - index.spec.ts: mock fixture updated; initiate test now asserts result.status === "submitted" and result.bridgeTransferId is set * fix(bridge): restore getWithdrawals filter with correct && operator The original filter `w.bridgeTransferId !== null || w.bridgeTransferId !== undefined` was a tautology (always true), so pending rows with no bridgeTransferId leaked through — surfacing a null id on a NonNull field when transferId was the source. The filter is now `!== null && !== undefined`, preserving the intended invariant: getWithdrawals (bridgeWithdrawals query) returns only rows that have been submitted to Bridge. Clients use bridgeWithdrawalRequest(id) to inspect individual pending/cancelled rows. Tests updated to assert the exclusion of pending/cancelled rows and the inclusion of submitted/completed/failed rows. * fix(graphql): map bridge withdrawal errors through bridgeGqlError Use BRIDGE_WITHDRAWAL_NOT_FOUND and BRIDGE_WITHDRAWAL_ALREADY_INITIATED via the shared Bridge error helper so these codes match the documented contract and other Bridge GraphQL mappings. * test(bridge): cover withdrawal request confirm and cancel flow Add chained service tests for request, dedupe, initiate, cancel, and error guards, and tighten resolver assertions for pending/submitted status and cancelled delegation. * chore(graphql): regenerate schema for withdrawal request flow Align BridgeWithdrawal SDL with id/status/bridgeTransferId and recompose the federated supergraph. * test(bridge): guard getWithdrawals id/status GraphQL contract Add return-shape regression tests so getWithdrawals and initiateWithdrawal emit id/status (not legacy transferId/state) and exclude rows without a bridgeTransferId. Add bridgeWithdrawals resolver coverage for passthrough. * chore(bridge): fix surgical lint issues in withdrawal flow * chore(bridge): finish withdrawal lint and docs cleanup * feat(bridge): add KYC tier ceiling error code (ENG-354) (#394) * feat(alerts): Bridge alerting to PagerDuty / Slack / Discord [ENG-361] (#391) * feat(alerts): add Bridge AlertService (PagerDuty/Slack/Discord) [ENG-361] Severity-routed best-effort alert fan-out: critical pages PagerDuty + informs Slack/Mattermost + Discord; warning informs only. Each sender no-ops when its env credential is unset. Config: ALERT_PAGERDUTY_ROUTING_KEY / ALERT_SLACK_WEBHOOK_URL / ALERT_DISCORD_WEBHOOK_URL. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(alerts): wire Bridge alert sources to AlertService [ENG-361] Fire-and-forget alertBridge() at the Bridge failure points (alongside existing logging), all critical/page: - ERPNext audit-write failures (deposit + transfer completed/failed) - Bridge webhook processing exceptions (deposit + transfer catch) - Bridge API outage in client.request(): 5xx / timeout / network (4xx not alerted) IBEX-error source deferred: on-receive.ts is general LN/onchain receive handling, not the Bridge<->IBEX movement path; needs the exact call site (warning sev). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(alerts): add Bridge alerting setup guide [ENG-361] Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(alerts): clean bridge alert lint and deposit test * feat(alerts): dedupe Bridge alerts and wire IBEX movement warnings [ENG-361] Group PagerDuty triggers with dedup_key, suppress duplicate Slack/Discord informs, and alert IBEX crypto-receive and reconciliation failures as warnings. * chore(alerts): format bridge alert updates * chore(alerts): trim dedup cleanup noise --------- Co-authored-by: Dread <bobodread@bobodread.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Vandana <forge@getflash.io> Co-authored-by: heyolaniran <olaniran.abd@gmail.com> * feat(cashout): route Cashout V1 wallets via cutover guard (ENG-357) (#395) * feat(cashout): route Cashout V1 wallets via cutover guard (ENG-357) Cashout V1 always debited the legacy USD wallet and credited the bank-owner USD wallet. Post-cutover the user's funds live in an ETH-USDT cash wallet, so the offer must debit USDT and credit the bank-owner's USDT wallet. The Flash bank-owner account holds both a USD and a USDT wallet, so the route simply selects the matching pair on both sides — no cross-currency swap. - Add resolveCashoutWalletSelection: reads the cutover config + per-account migration and runs evaluateCashWalletCutoverGuard to pick the route. Source and destination wallets are resolved server-side from the guard, NOT from the client-supplied walletId (trusted only for wallet-level auth). The guard blocks the cashout while a migration is in-flight or has failed. - CashoutManager.createOffer builds a USD or USDT invoice per route; the USD/JMD payout math is unchanged (1 USDT = 1 USD). - executeCashout authorizes by account, since an old client may still present the zeroed legacy USD walletId while the offer settles in USDT, instead of an exact wallet-id match. - CashoutValidator and CashoutDetails.payment.amount are currency-aware; the USD path stays byte-identical. ErpNext.draftCashout records the USDT amount. Adds unit coverage for the routing decision tree. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(cashout): add ENG-357 Bruno smoke coverage * fix(cashout): harden offer redis deserialization * chore(bridge): add sandbox e2e test suite (#388) * feat(bridge): add opt-in sandbox e2e test suite ENG-274. Covers KYC, virtual account, external account, deposit, withdrawal, post-cutover state, ETH-USDT LN parity, and ERPNext audit-row verification. Guarded by RUN_BRIDGE_SANDBOX_E2E=true. Includes preflight check for Level 1 service guard and documentation drift cleanup (Level 2->Level 1, Tron->ETH-USDT). * chore(bridge): automate sandbox webhook setup * chore(bridge): add ERPNext audit snapshot * test(bridge): gate hosted sandbox success paths * test(bridge): quiet sandbox e2e service warnings * chore(bridge): polish sandbox e2e PR cleanup * chore(bridge): refresh sandbox e2e backup * test(bridge): address sandbox e2e review feedback * chore(bridge): add refreshed ERPNext dev backup (#400) * improve backup and restore scripts, updated to latest frappe backup * fix(dev): make local Frappe startup repair frontend * fix(bridge): resolve type errors in bridge-sandbox-e2e suite (#409) The Bridge sandbox e2e suite never passed through tsc (CI was disabled), so type errors accumulated and now fail Check Code. All test-only: - helpers.ts: route mock req/res through 'unknown' before casting to the Express handler param types (TS2352). - execQuery: make generic (default Record<string, unknown>, backward compatible) so callers can type the GraphQL payload (TS2339). - HandlerResponse.body: type as Record<string, unknown> instead of unknown, fixing .body access in the deposit/external-account specs without per-site casts (TS18046). - cutover-state.spec: type the execQuery result and narrow the error union before asserting. No production code touched. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(bridge): remove leaked sandbox API key from base config (#412) The Bridge sandbox API key was committed in dev/config/base-config.yaml. The key has been rotated/revoked at Bridge (so the value is now inert), and this removes it from the live tree, replacing it with an empty placeholder — the real key is injected via config overrides, consistent with the ibex credentials in the same file. Note: the rotated key still exists in commit history; since it is revoked this is no longer a live secret, so a history rewrite of the shared branch is optional and should be coordinated separately. Co-authored-by: Dread <bobodread@bobodread.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Normalize Bridge webhook payload envelopes (#406) * fix(bridge): normalize webhook payload envelopes * fix: key bridge deposit audit rows by stable id * fix: handle uncreditable bridge deposit activity * test(bridge): update transfer webhook spec to Bridge envelope + fix kyc status type - transfer.spec.ts now posts Bridge's real webhook envelope (event_type/event_object, nested source.failure_reason) instead of the legacy { event, data } shape the handler no longer reads; restores coverage for failed-cashout audit and already_terminal paths. - kyc.ts: cast bridgeKycStatus to Account["bridgeKycStatus"] (BridgeKycStatus was referenced but never defined/imported). --------- Co-authored-by: Vandana <forge@getflash.io> * Map IBEX crypto sends as USDT on-chain transactions (#405) * fix(ibex): map crypto send transactions as USDT sends * fix: sign IBEX crypto send display amounts --------- Co-authored-by: Vandana <forge@getflash.io> * feat(bridge): withdrawal fee estimates — request/confirm/cancel flow (#403) * feat(bridge): add withdrawal fee estimate config Introduce developerFeePercent and withdrawalFeeEstimate settings (bridge fixed fee, gas limit, RPC URL, and fallbacks) for customer fee breakdown on Bridge withdrawals. * feat(bridge): compute withdrawal customer fee estimates Add flash fee, Bridge rail fee (0.6%), and buffered Ethereum gas estimates, plus presenter helpers for pending vs receipt amounts. * feat(bridge): persist withdrawal fee breakdown in MongoDB Store flash, Bridge, gas, and total customer fee estimates on pending withdrawals and refresh them when reusing an existing pending request. * feat(bridge): surface fee estimates in withdrawal service flow Resolve and persist fee estimates on request, map Bridge transfer receipt fees on initiate, improve Ibex balance error mapping, and pass developer_fee_percent when creating virtual accounts. * feat(bridge): add localized flash fee notice for withdrawals Expose flashFeeNotice copy explaining Flash, Bridge, and gas buffer components while totals remain estimates until Bridge settles. * feat(bridge): expose withdrawal fee fields in GraphQL API Add estimated fee breakdown, subtotal/final amounts, receipt fees, and flashFeeNotice on BridgeWithdrawal for request and query paths. * test(bridge): fix unit test failures from fee field additions - replay.spec.ts: spread jest.requireActual('@config') so getFeesConfig survives the partial @config mock - index.spec.ts: mock updateWithdrawalFeeEstimates in the dedup test so stale clearAllMocks() impl doesn't bleed in from a prior test - return-shapes.spec.ts: toEqual → toMatchObject for getWithdrawals shape check, now that presentBridgeWithdrawal emits fee fields - client-usd-wallet.spec.ts: spread jest.requireActual('ibex-client') so IbexUrls survives the partial ibex-client mock * fix: remove the hardcoded bridge developer fees percentage * fix: address bridge withdrawal fee review * fix: address bridge withdrawal fee review --------- Co-authored-by: Patoo <262265744+patoo0x@users.noreply.github.com> Co-authored-by: Vandana <forge@getflash.io> * Send Bridge cashout USDT to transfer deposit addresses (#407) * feat(bridge): send cashout USDT to Bridge deposits * fix: show pending bridge cashouts in erp * fix: omit idempotency key when deleting bridge transfers * fix: preserve accepted bridge cashout sends * chore(graphql): regenerate SDL + supergraph for bridgeCreateExternalAccount The bridgeCreateExternalAccount resolver was registered in src/graphql/public/mutations.ts but the generated schema.graphql and the Apollo supergraph were never regenerated, so the field was absent from the SDL/supergraph (and would fail check:sdl in CI). Regenerate both so the mutation is exposed on the public API. --------- Co-authored-by: Vandana <forge@getflash.io> * feat: Add notification on Kyc system and extend notification for deposit and withdrawal processes. (#414) * feat: support deposit notification outcoumes (funds received, processing, completed) Add an outcome parameter to deposit push notifications so each lifecycle stage can use its own title and body. * feat: send withdrawal push notifications on submit and USDT send Notify users when a withdrawal is submitted to Bridge and when USDT is sent, and centralize send-failure handling with push alerts. * feat: add kyc push notification module with imcomplete deep-link support send localized kyc status notifications, skip pushes before initiation, use a dedicated incomplete message, and attach latest KYC links for incomplete notifications. * feat: wire kyc webhook status changes to push notifications Notify users on KYC status transitions from bridge webhook handler, with guards for unchanged status and pre-initiation states. * feat(bridge): wire withdrawal submit and USDT-sent push notifications Notify users when a withdrawal is submitted to Bridge and when USDT is sent. Centralize send-failure handling with failed push alerts and add unit tests for the new notification outcomes. * fix: send developer_fee on Bridge withdrawal transfers bridge only applies developer_fee_percent when flexible_amount is enabled, fixed amount offramps (implemented in flash) must use developer_fee (USD value), so pass the stored flashFee from the pending withdrawal instead * fix: harden bridge webhook remediation * fix: restore public ibex client install * fix: point quickstart vendir at flash repo * fix: clean up bridge remediation ci noise * fix: address bridge ci security findings * fix: repair bridge ci followups * fix: provide config for integration tests * fix: repair integration ibex mock * fix: complete integration ibex mock * fix: avoid duplicate integration wallet ids * ci: run checks on the bridge-rebase integration branch (#408) Test/check workflows were filtered to `pull_request: branches: [main]`, so PRs targeting the long-lived `tmp/bridge-rebase-pr-ready` integration branch never triggered CI. Add the integration branch to the pull_request filter on all gating workflows, and add a push trigger on the core build/test/lint workflows (check-code, unit-test, integration-test) so the branch tip is checked as PRs stack onto it. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix: route quickstart through flash service (#415) * fix: route quickstart through flash service * fix: make quickstart use flash config * docs: fix bridge sandbox mongodb example --------- Co-authored-by: Patoo <262265744+patoo0x@users.noreply.github.com> * test: clean up bridge CI checks * ci: retry supergraph composition * fix: harden ibex webhook routes * fix(bridge): use shared BridgeApiError class so instanceof checks work client.ts defined its own BridgeApiError (extends Error) while the rest of the codebase imports BridgeApiError from errors.ts (extends BridgeError → DomainError). The catch block in addExternalAccount uses the errors.ts version for its instanceof check, so it never matched the error thrown by the client — the 401 from the Plaid link endpoint was surfaced as a generic BRIDGE_API_ERROR instead of BRIDGE_PLAID_NOT_AVAILABLE, preventing the mobile app from falling back to manual bank entry. Remove the duplicate class from client.ts and import from ./errors. * fix: delegate account-scoped crypto receive info to ibex-client 3.1.x (#419) * fix: delegate account-scoped crypto receive info to ibex-client 3.1.x ibex-client 3.1.x exposes the same contract, so replace the raw ibexPost wrapper with the SDK and align CryptoReceiveInfo types with the library. * chore: update yarn.lock for ibex-client 3.1.x * style(ibex): fix prettier formatting in findEthereumUsdtReceiveOption Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cash-wallet-cutover): preserve precise USD migration amounts (#420) * fix(wallets): return USDT transaction amounts in cents instead of micros * fix(notifications): format USDT pushes as USD (#422) Co-authored-by: Vandana <forge@getflash.io> * feat(topup): add topup.enabled flag exposed via Globals (#421) Adds an instance-wide `topup.enabled` config flag (default OFF) and surfaces it to clients on the Globals query as `topupEnabled`, so the mobile app can gate the two top-up entry points it owns — the credit-card webview and the bank-transfer-to-support handoff — via YAML + rolling restart, consistent with how cashout/bridge are flagged (and with the KILL_MONEY SOP). International bank top-up is intentionally NOT covered here: it flows through the bridge and is already gated by `bridge.enabled`. - config: schema.ts + schema.types.d.ts + yaml.ts (Topup.Enabled); default false and optional, so it's non-breaking for existing deploys - graphql: Globals.topupEnabled field + resolver wiring - regenerated src/graphql/public/schema.graphql - dev base-config.yaml: explicit topup.enabled: false for parity with cashout Co-authored-by: Dread <bobodread@bobodread.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(bridge): harden webhook and withdrawal guards (#424) * fix(bridge): harden webhook and withdrawal guards * fix(bridge): close review gaps in webhook/withdrawal hardening Follow-ups from review of this PR: - return-shapes.spec.ts: mock BridgeClient.getCustomer (status "active") — the new live-KYC check made initiateWithdrawal call getCustomer, which this suite never stubbed, so the contract test got a TypeError (CI red). - replay ingress: grant the loopback exemption from the socket address only. request-ip prefers X-Forwarded-For, which any external caller can set to "127.0.0.1" and walk through the gate without knowing the allowlist. Loopback now reads req.socket.remoteAddress; the allowlist path still uses request-ip (meaningful only behind trusted-XFF ingress). Added a spoofed-XFF regression test. - rate limiters (crypto-receive, bridge webhook, replay): disable express-rate-limit v7's xForwardedForHeader validation. Without Express `trust proxy`, any request carrying X-Forwarded-For (i.e. anything behind an LB) throws ERR_ERL_UNEXPECTED_X_FORWARDED_FOR and 500s — which would have broken every IBEX crypto-receive webhook in prod. Unset trust proxy now degrades to one shared bucket keyed on the LB socket address instead of an outage; set `trust proxy` for per-sender buckets. - authenticate.spec.ts: pin the fail-closed behavior for unconfigured secrets (the old `!==` compare passed when both sides were undefined, silently disabling auth). Full unit suite green locally (104 suites / 783 passed), tsc-check and eslint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Dread <bobodread@bobodread.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(bridge): manage external account defaults (#423) * feat(bridge): manage external account defaults * test(bridge): mock default external account helper --------- Co-authored-by: Patoo <262265744+patoo0x@users.noreply.github.com> * fix(bridge): harden mongoose query filters --------- Co-authored-by: Vandana <forge@getflash.io> Co-authored-by: Olaniran ⚡ <93789719+heyolaniran@users.noreply.github.com> Co-authored-by: Patoo <patoo@getflash.io> Co-authored-by: Dread <bobodread@bobodread.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: heyolaniran <olaniran.abd@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
galoytoflashand render Apollo Router, Kratos, and Oathkeeper upstreams tohttp://flash:...instead of legacy Galoy/Bats service names.flashandtriggercontainers, including a quickstart-only overlay.ibex.mock: truein the quickstart overlay andFLASH_ENABLE_IBEX_MOCK=truein Compose, so normal environments still use real IBEX.quickstart.shto a Flash public GraphQL smoke test instead of running the old Galoy BTC-wallet funding flow.Note
The previous revision used
galoybecause that was the inherited Compose service name for the local app container. Dread was right to call that out: Flash quickstart should not encode Galoy service routing, and CI quickstart should not need real IBEX sandbox credentials.Verification
GALOY_QUICKSTART_PATH=./ docker compose -f quickstart/docker-compose.yml config --quietbash -n quickstart/bin/re-render.sh quickstart/bin/quickstart.shgit diff --checkytt -f ./docker-compose.tmpl.yml -f galoy/docker-compose.yml -f galoy/docker-compose.override.yml | rg -n "/app/quickstart/config|FLASH_ENABLE_IBEX_MOCK|http://(galoy|bats-tests):|firebaseappcheck.googleapis.com/72279297366|projects/72279297366"GALOY_ENDPOINT=localhost:14002 COMPOSE_PROJECT_NAME=qs415ci ./bin/quickstart.sh→regtest,DONE