Batch reports-server umbrella: fix test suite + CI and add Revolut, Swapter, NYM, nexchange plugins - #228
Batch reports-server umbrella: fix test suite + CI and add Revolut, Swapter, NYM, nexchange plugins#228j0ntz wants to merge 11 commits into
Conversation
util.test.ts imported createQuarterBuckets, movingAveDataSort, and sevenDayDataMerge from '../lib/util', which does not exist (lib/ is gitignored build output). The functions live in src/demo/clientUtil.ts, so the import resolved to undefined and crashed the whole suite at load. Point the import at '../src/demo/clientUtil', matching the repo convention of importing source directly under sucrase/register. The analytics fixtures (outputOne..Four in testData.json) still expected the stale keys 'appId' and 'pluginId', but getAnalytics and the asAnalyticsResult cleaner both use 'app' and 'partnerId'. Rename the fixture keys so they match the type contract and the function output.
The repo had no CI job that runs the tests, so PRs showed green while npm test failed. Add a GitHub Actions workflow that runs the mocha suite on every pull request. The suite loads src/demo/clientUtil.ts, which imports clientConfig.json (gitignored local dev config). The workflow generates it with defaults via the existing configure.ts (the same step scripts/prepare.sh runs) before running the tests, so the job is self-contained without needing the committed config.
Recreate the Revolut partner plugin following the moonpay pattern: - src/partners/revolut.ts queries the Revolut partner transactions API, cleans results, and maps completed buy/sell txs to StandardTx. - Register the plugin in queryEngine.ts. - Add the revolut demo entry (fiat, #191C33).
Adds the Swapter exchange partner integration: querySwapter pages the /personal/exchange/tool-history endpoint with retry handling and latestIsoDate tracking, and processSwapterTx converts Swapter deposit/ withdraw records into StandardTx. Registers the swapter plugin in the query engine.
Query NYM's partner reporting API for completed swap orders and map them into StandardTx, modeled on the clean shape of swapuz. Register nymswap in queryEngine and the demo partners config. Add a transform unit test. The reporting endpoint, auth scheme, and order field names are the documented assumption to confirm with NYM; credentials are human/ops set.
Port the nexchange reporting plugin (queries https://api.n.exchange audit-orders endpoint with the x-api-key header) from partner PR #217/#212, without the unrelated changenow/rango/sideshift refactors bundled in those PRs. Wire it into queryEngine and the demo partner list.
- swapter: buffer each page and only advance latestIsoDate on clean completion, so a mid-page throw no longer re-appends rows (duplicate orderIds / Couch _id conflicts) and an error-driven exit keeps the pre-run progress marker instead of skipping older unfetched pages (High + Medium). - swapter: relax strict cleaners on fields that never feed StandardTx (info.type/link, deposit/withdraw.network, the partner block) to asMaybe so an unexpected encoding degrades that field instead of aborting the page (Medium). - swapter: add the missing demo partners.ts entry so getPartnerIds surfaces it (Medium). - nexchange: switch non-critical cleaners (address, txid, countryCode, nextCursor, network, contract_address) from asOptional to asMaybe so a single odd encoding degrades that field rather than aborting the query block (Medium). - nym: make apiKey optional and return [] on a null/empty key so an unprovisioned partner entry no-ops like the other couch plugins instead of erroring every cycle (Medium). - nym test: replace live-derived addresses and txids with clearly-synthetic placeholders; only field structure and amount math are load-bearing (High, privacy).
- revolut: percent-encode the pagination cursor so an opaque next_cursor containing + or / does not break paging after the first page (Medium). - nexchange: treat a zero contract address (0x000...0) as the chain's native asset instead of minting a tokenId for the gas asset, which would mis-route its rates and volume (Medium, matches Banxa/Moonpay).
Confirmation, Exchanging, and Sending are in-flight phases; map them to 'processing' (matching the sibling swap plugins) instead of 'pending', so active exchange volume is not under-reported in status-aware views.
asPreRevolutTx.state is read only to pre-filter for completed orders, so make it asMaybe: a strict cleaner aborted the whole page on one odd row (including pending/refunded rows that would be skipped anyway), exhausting retries and stalling the window so the same bad row was re-hit every cycle.
Compute timeTo once before the paging loop instead of per request, so orders arriving mid-walk cannot shift the window and page boundaries and skip or duplicate rows before completion is decided.
b26a08c to
e522418
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e522418. Configure here.
| export function processNexchangeTx( | ||
| rawTx: unknown, | ||
| currencyMap: NexchangeCurrencyInfoMap | ||
| ): StandardTx { |
There was a problem hiding this comment.
Nexchange processTx signature mismatch
Medium Severity
processNexchangeTx takes a currencyMap instead of pluginParams, so it does not match the uniform (rawTx, pluginParams) shape used by sibling plugins and required for upcoming backfill callers. A backfill invoking it with pluginParams would treat that object as the catalog and drop all chain/token enrichment.
Additional Locations (1)
Triggered by learned rule: process*Tx must have uniform signature for backfill scripts
Reviewed by Cursor Bugbot for commit e522418. Configure here.
| export function processNymTx( | ||
| rawTx: unknown, | ||
| decimals: DecimalsMap = {} | ||
| ): StandardTx { |
There was a problem hiding this comment.
NYM processTx signature mismatch
Medium Severity
processNymTx takes a decimals map instead of pluginParams, so it breaks the uniform (rawTx, pluginParams) processor contract. Backfill callers that pass pluginParams would not get a real decimals lookup and would fall back to incomplete defaults or throw for newer assets.
Additional Locations (1)
Triggered by learned rule: process*Tx must have uniform signature for backfill scripts
Reviewed by Cursor Bugbot for commit e522418. Configure here.
| } catch (e) { | ||
| log.error(`Skipping unparseable transaction: ${String(e)}`) | ||
| continue | ||
| } |
There was a problem hiding this comment.
NYM silently skips bad orders
High Severity
Per-order try/catch around processNymTx logs and continues on failure. Unparseable or unpriceable orders are dropped while pagination can still complete and advance latestIsoDate, so those orders can fall outside the lookback window and never be retried.
Triggered by learned rule: Throw-on-unknown enum values is intentional in partner plugins
Reviewed by Cursor Bugbot for commit e522418. Configure here.


CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
noneDescription
Batched umbrella PR consolidating all open edge-reports-server subtask work into one branch
off current
master, per the umbrella taskreports-server: Umbrella.
Order matters: the broken test suite is fixed first (everything else depends on
npm testpassing), then each provider lands as its own commit.
Commits (one concern per commit)
test/util.test.tsimported three utils from the gitignoredcompiled
../lib/util; repointed to../src/demo/clientUtil(where they actually live) andrenamed 4 stale
pluginId→partnerIdfixture keys intestData.json.npm testwas fullybroken on master (module-not-found); now 71 passing, 1 pending.
.github/workflows/test.ymlgenerates the gitignoredclientConfig.json(configure.ts) then runsnpm teston every PR. No test job existed before,so
pr-land'snpm testgate was un-guarded.unrelated changenow/rango/sideshift refactors those PRs bundled).
Plus review-hardening commits addressing 12 cursor reviewer-bot findings (High to Low): swapter
idempotent-retry buffering + progress guard + tolerant cleaners + demo entry + in-flight status
mapping + frozen pagination window; nexchange tolerant cleaners + zero-address native handling;
nym silent-skip on missing key + sanitized test fixtures; revolut cursor encoding + tolerant state
pre-filter. All CI checks and both cursor reviewers pass on HEAD with zero unresolved threads.
Supersedes (close on merge)
This PR replaces these Edge-owned PRs - close them with a pointer here and delete their branches:
#225 (test suite + CI),
#227 (Revolut),
#224 (Swapter),
#226 (NYM),
and the nexchange code originates from #217 /
#212.
Partner-owned fork PRs are lineage only, do NOT delete their branches:
#211 (claudinethelobster, Revolut),
#219 (markovo4, Swapter),
#214 (n.exchange, nexchange).
Requirements audit (vs edge-exchange-plugins
docs/API_REQUIREMENTS.md)Lens: §6 (Reporting API) primary; §1 (chain/token id), §2 (order id), §5 (status set), §10 (fiat/region) where touched.
StandardTxfield set persrc/types.ts. Only nexchange and NYM are live-validated against real reporting data this run; Revolut and Simplex are blocked on human-provisioned credentials; Swapter is validated on a throwaway key; Bridgeless has no API.nymswap)swapter)time.success)nexchange)revolut)simplex)Highlights per provider (full field-by-field tables + copy-paste post-merge setup blocks + cleanup ledger are in the consolidated-audit doc attached to the Asana task):
processNymTxdrops the API'ssourceTokenId/destinationTokenIdandsourceNetwork(no chainPluginId/evmChainId enrichment); pricing rides currency code.completedDatenull on settled orders is a partner-ask (plugin correctly keys offcreatedDate).time.successis returned but unused (ours-to-fix, could adopt for settled orders); no deposit/withdraw txids in the API (partner-ask);deposit.network/withdraw.networkunmapped.chainPluginId+tokenId+evmChainId, throws rather than mis-price). Blocked only on an affiliate-authorized reporting key (GUI key 403s on the resource).getRevolutPaymentTypeTHROWS on an unmappedpayment_method(one novel value aborts a tx - should degrade to null); completed-only reporting means pending/refunded volume never surfaces. No crypto network field from the API (partner-ask).starting_at=0hardcoded (no server-side date range, walks all history client-side), legacysettings.lastTimestampnotlatestIsoDate, approved-only + buy-only, no addresses/txids/paymentType. Re-enabling is gated on Edge resolving the prior API-key-exposure issue (business + security decision, not just a couch write).Cross-cutting: every couch plugin returns
[]silently on a null/absentapiKey, so post-deploy "no rows" is indistinguishable from "not wired" without checkingpm2 logs- each setup block's verify step accounts for this.Not shipped (findings, no code)
edge-exchange-plugins/src/swap/defi/bridgeless.ts,isDex: true)whose only endpoints are on-chain bridge RPC and a single-order status check. No order-listing /
partner reporting API exists; the
referralId(uint16 = 2 for Edge) is on-chain attribution only.A reports plugin needs an on-chain indexer keyed on that referralId; not implementable against a
partner API. No plugin shipped.
src/partners/simplex.tsendpointturnkey.api.simplex.com/transactionsis still alive (HTTP 403 with a bogus key this run). Simplex was disabled by Edge over API-key
exposure (last volume ~May 7) and env.json carries no plain reporting
apiKey(only publishablepublicKey+jwtTokenProvider). No credential to confirm data flow; no new-API rewrite shipped.Testing
npm test: 71 passing, 1 pending.npm run build.types(tsc): clean. eslint: clean.processSwapterTxran onreal rawTx. WORKING (throwaway account).
not authorized for the audit-orders resource. Code ready; needs a partner-issued reporting key.
api.revolut.com/partner/v1/transactions→ 404 (key not provisioned); GUI ramp host→ 403. Code ready; needs a prod Revolut reporting key.
Note
Medium Risk
New partner ingestion paths affect reported volume and USD pricing (nexchange deliberately throws on mis-mapped tokens; Revolut can abort a row on an unknown
payment_method), but changes are additive with progress/dedup safeguards rather than core auth or payment execution.Overview
Repairs
npm testby fixingtest/util.test.tsimports and renaming stalepluginIdkeys topartnerIdin analytics fixtures, and adds a GitHub Actions workflow that generatesclientConfig.jsonthen runs the mocha suite on every PR.Registers four new partner reporting plugins in the query engine and demo partner list: Revolut (fiat buy/sell with time-window + cursor pagination), Swapter (swap history POST API), NYM (
nymswap) (native-unit amounts → major units via/currencies), and nexchange (audit orders plus v2 currency catalog forchainPluginId/tokenId/evmChainIdenrichment). Each plugin follows the same couch pattern: silent no-op withoutapiKey, lookback re-query, buffered pages/windows for idempotent retries, and guardedlatestIsoDateadvancement when pagination does not finish.Adds focused unit tests for nexchange (asset resolution, dates, status mapping) and NYM (
processNymTxamount/status behavior).Reviewed by Cursor Bugbot for commit e522418. Bugbot is set up for automated code reviews on this repo. Configure here.