feat(epic): bridge integration#413
Merged
Merged
Conversation
…63 ENG-297 fix(bridge): apply audit hardening and hosted KYC refinements
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 fix(wallets): support USDT cash wallet parity * fix(wallets): support USDT intraledger sends
* 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(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(wallets): preserve USDT transaction history precision * chore(wallets): restore base relay import * fix(wallets): handle missing IBEX USDT history amounts
…et (#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.
Require a prepared runnable migration row before Cash Wallet cutover start can move config to in_progress.
#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.
#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>
- 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.
Co-authored-by: Vandana <forge@getflash.io>
Co-authored-by: Vandana <forge@getflash.io>
* 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
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>
#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
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 * fix: make quickstart use flash config * docs: fix bridge sandbox mongodb example --------- Co-authored-by: Patoo <262265744+patoo0x@users.noreply.github.com>
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.
…#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>
Co-authored-by: Vandana <forge@getflash.io>
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>
heyolaniran
approved these changes
Jul 2, 2026
* 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 * test(bridge): mock default external account helper --------- Co-authored-by: Patoo <262265744+patoo0x@users.noreply.github.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.
Big bridge integration PR.
The following must be complete before this is merged: