Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
- added: Add Revolut fiat payment provider
- added: Add Swapter reporting
- added: Add NYM Swap (nymswap) reporting
- added: Isolated v2 reports dashboard at /v2/ (real /v1 API data, apiKey auth with redirect on a bad key)
- added: Partner-reported revenue (revenueUsd/revenueSource) on StandardTx, summed through the analytics cache; Revolut reports it, and the v2 dashboard uses reported figures where present with volume x revShareRate as the estimate elsewhere
- changed: Update sideshift plugin with new optional API fields
- changed: Query both old and new Sideshift affiliate accounts and merge completed orders to preserve full shift history across an affiliate-account rotation
- changed: Add signature header support to Exolix
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"scripts": {
"build.lib": "sucrase -q -t typescript,imports,jsx -d ./lib ./src",
"build.types": "tsc",
"build.dist": "parcel build",
"build.dist": "parcel build && parcel build src/demoV2/index.html --dist-dir dist/v2 --public-url ./",
"clean": "rimraf lib dist .parcel-cache",
"configure": "configure",
"fix": "npm run lint -- --fix",
Expand All @@ -27,6 +27,7 @@
"stats": "node -r sucrase/register src/bin/partitionStats.ts",
"test": "mocha -r sucrase/register 'test/**/*.test.ts'",
"demo": "parcel serve src/demo/index.html",
"demo.v2": "parcel serve src/demoV2/index.html --dist-dir dist/v2 --public-url ./ -p 1235",
"lint": "eslint --ext .js,.jsx,.ts,.tsx ."
},
"lint-staged": {
Expand Down
7 changes: 7 additions & 0 deletions src/apiAnalytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ interface DbTx {
payoutCurrency: string
timestamp: number
usdValue: number
revenueUsd?: number
}

interface Bucket {
start: number
usdValue: number
numTxs: number
revenueUsd: number
isoDate: string
currencyCodes: { [currencyCode: string]: number }
currencyPairs: { [currencyPair: string]: number }
Expand Down Expand Up @@ -49,6 +51,7 @@ export const getAnalytics = (
isoDate: monthStart.toISOString(),
usdValue: 0,
numTxs: 0,
revenueUsd: 0,
currencyCodes: {},
currencyPairs: {}
})
Expand All @@ -66,6 +69,7 @@ export const getAnalytics = (
isoDate: dayStart.toISOString(),
usdValue: 0,
numTxs: 0,
revenueUsd: 0,
currencyCodes: {},
currencyPairs: {}
})
Expand All @@ -83,6 +87,7 @@ export const getAnalytics = (
isoDate: hourStart.toISOString(),
usdValue: 0,
numTxs: 0,
revenueUsd: 0,
currencyCodes: {},
currencyPairs: {}
})
Expand Down Expand Up @@ -160,6 +165,8 @@ const bucketAdder = (bucket: Bucket, tx: DbTx): void => {
bucket.numTxs++
// usdValue
bucket.usdValue += tx.usdValue != null ? tx.usdValue : 0
// reported revenue (partners that do not report one contribute 0)
bucket.revenueUsd += tx.revenueUsd != null ? tx.revenueUsd : 0
// currencyCode
if (bucket.currencyCodes[tx.depositCurrency] == null) {
bucket.currencyCodes[tx.depositCurrency] = 0
Expand Down
4 changes: 3 additions & 1 deletion src/cacheEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ export async function cacheEngine(): Promise<void> {
'depositCurrency',
'payoutCurrency',
'timestamp',
'usdValue'
'usdValue',
'revenueUsd'
],
use_index: 'timestamp-p',
sort: ['timestamp'],
Expand Down Expand Up @@ -145,6 +146,7 @@ export async function cacheEngine(): Promise<void> {
timestamp: bucket.start,
usdValue: bucket.usdValue,
numTxs: bucket.numTxs,
revenueUsd: bucket.revenueUsd,
currencyCodes: bucket.currencyCodes,
currencyPairs: bucket.currencyPairs
}
Expand Down
6 changes: 4 additions & 2 deletions src/dbutils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { asArray, asNumber, asObject, asString } from 'cleaners'
import { asArray, asNumber, asObject, asOptional, asString } from 'cleaners'
import nano from 'nano'

import { config } from './config'
Expand All @@ -15,7 +15,8 @@ export const asDbReq = asObject({
depositCurrency: asString,
payoutCurrency: asString,
timestamp: asNumber,
usdValue: asNumber
usdValue: asNumber,
revenueUsd: asOptional(asNumber)
})
)
})
Expand Down Expand Up @@ -106,6 +107,7 @@ export const cacheAnalytic = async (
start: cacheObj.timestamp,
usdValue: cacheObj.usdValue,
numTxs: cacheObj.numTxs,
revenueUsd: cacheObj.revenueUsd,
isoDate: new Date(cacheObj.timestamp).toISOString(),
currencyCodes: cacheObj.currencyCodes,
currencyPairs: cacheObj.currencyPairs
Expand Down
1 change: 1 addition & 0 deletions src/demo/clientUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export const createQuarterBuckets = (analytics: AnalyticsResult): Bucket[] => {
start: realTimestamp / 1000,
usdValue: 0,
numTxs: 0,
revenueUsd: undefined,
isoDate: new Date(realTimestamp).toISOString(),
currencyCodes: {},
currencyPairs: {}
Expand Down
88 changes: 88 additions & 0 deletions src/demoV2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Edge Reports v2 dashboard

An isolated redesign of the reports dashboard, served at `/v2/`. It is fully
separate from the v1 demo (`src/demo`): its own entry point, its own parcel
bundle (`dist/v2/`), and its own URL. v1 routes, components and build output are
untouched. Where behavior overlaps, code is duplicated into v2 rather than
refactoring anything v1 depends on.

## What it is

The full rendering and interaction engine is ported from the design prototype:
one summary card, a Providers section and a Currency pairs section, each with a
trend chart (stacked bars or lines), a share card (donut + ranked bars, hover
linked), and a detail table (the pair table paginated). Global filters (range,
type, providers, pairs) scope every card. Top-8-plus-Other color rules keep the
chart, share card and table in agreement.

The only thing that changed from the prototype is the data source: the baked-in
sample generator is replaced by the real `/v1` reporting API.

## Data flow

- `GET /v1/getAppId?apiKey=` — validates the key. Returns plain text on a bad
key (400); v2 checks the response before parsing and redirects to the
key-entry screen instead of hanging (the v1 spinner-forever bug).
- `GET /v2/config?apiKey=` — per-provider rev-share rates and fiat/swap
classification. Isolated v2 route; no v1 route touched.
- `GET /v1/getPluginIds?apiKey=` — the app's registered providers.
- `POST /v1/analytics` — one call for all providers, `timePeriod: "day"`, last
24 months. v2 rebuckets to month client-side for the longer presets.

Auth reuses v1's simple apiKey model: the key lives in the `apiKey` cookie (a
`?apiKey=` query param also seeds it). No new auth system.

### Est. revenue

Where the partner's API reports Edge's actual fee per order (e.g. Revolut's
`partner_fee`, pre-converted to USD), the plugin stores it on the transaction as
`revenueUsd` with `revenueSource: 'reported'`, the cache engine sums it into the
analytics buckets, and the dashboard uses it directly, marked with a check in
the provider table. That figure is a fact about the order and never recomputed.

For partners that report no fee, revenue is estimated at read time as
`volume * revShareRate`. The rate lives on the app doc in `reports_apps`, as an
optional `revShareRate` beside that partner's `apiKeys`:

```json
"partnerIds": {
"moonpay": { "apiKeys": { "apiKey": "..." }, "revShareRate": 0.008 }
}
```

Per app and per partner, because the rate is a property of the deal. Estimating
at read time rather than at ingest means correcting a rate fixes history
immediately, while reported figures stay immutable. The rates are deliberately
not in source or `config.json`: they are commercial terms and this repo is
public. A partner with neither reported fees nor a rate contributes 0.

## Local testing

Run against a local CouchDB (never point at production).

```bash
# 1) Build v1 + v2 bundles (v2 lands in dist/v2/).
npm run build.dist

# 2) Serve the API + built dashboards.
npm run start.api # http://localhost:8008

# 3) Open the dashboard and enter an API key registered in reports_apps.
open http://localhost:8008/v2/
```

The dashboard needs an app registered in the `reports_apps` database: a document
whose `_id` is the API key, with an `appId` and a `partnerIds` map. That is what
`getAppId`, `getPluginIds` and `validateApiKey` read. Populate transaction data
with the normal engines (`npm run start` to query partners, `npm run start.cache`
to build the hour/day/month cache buckets that `/v1/analytics` serves).

For live-reload development of the v2 UI only: `npm run demo.v2` (Parcel dev
server on :1235). Point it at a running API via same-origin or a proxy; the
`build.dist` + `start.api` path above is the end-to-end test.

Partner credentials belong in the `reports_apps` document, not in `config.json`
and not in any tracked file. Writing real keys into production is a human ops
step. A null apiKey makes a partner plugin skip silently and return no rows, so
"no data" and "never wired" look the same; check the key is populated before
concluding a plugin is broken.
Loading
Loading