Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BigInt basics + balance sheet report #11

Merged
merged 41 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a82d5b7
Add bigint basic types with basic math
sophialittlejohn Oct 31, 2024
976a47b
Fix Decimal imports
sophialittlejohn Oct 31, 2024
ad8a729
Fix division
sophialittlejohn Oct 31, 2024
38659b7
Lay ground work for reports
sophialittlejohn Oct 31, 2024
e7849c3
Add pool id to query
sophialittlejohn Oct 31, 2024
9e9ec84
Move BigInt test to tests
sophialittlejohn Oct 31, 2024
9405574
Mark old types as deprecated
sophialittlejohn Oct 31, 2024
646d302
Clean up types
sophialittlejohn Nov 1, 2024
2d9a9b3
Add tranche snapshot query
sophialittlejohn Nov 5, 2024
f8c1101
Start collecting and formatting data for balance sheet report
sophialittlejohn Nov 5, 2024
82f8c78
Fix currency and token mutliply so it respects BigInts "decimals"
sophialittlejohn Nov 6, 2024
cfade2a
Improve and clean up poolSnapshots
sophialittlejohn Nov 6, 2024
04ed056
Fix tranchesnapshot processing
sophialittlejohn Nov 7, 2024
bd8f77b
Improve merging of tranches and pools by indexing instead of looping
sophialittlejohn Nov 7, 2024
f23b8cd
Fix reports test
sophialittlejohn Nov 7, 2024
2555f28
Merge branch 'queries' of github.com:centrifuge/centrifuge-sdk into b…
sophialittlejohn Nov 7, 2024
0dd1efc
Update tsconfig to support json imports in nodejs env
sophialittlejohn Nov 8, 2024
ffbc80e
Add caching to balance sheet report
sophialittlejohn Nov 8, 2024
01947c3
Refactor
sophialittlejohn Nov 8, 2024
370c5f5
Add report service for better cache and processing management
sophialittlejohn Nov 8, 2024
b04be1b
Add test for report service
sophialittlejohn Nov 12, 2024
da47b6e
Merge branch 'queries' of github.com:centrifuge/centrifuge-sdk into b…
sophialittlejohn Nov 12, 2024
c6caea4
Fix division and add better tests for BigInt class
sophialittlejohn Nov 13, 2024
7b2aae9
Fix types
sophialittlejohn Nov 13, 2024
ba8d1b3
Merge branch 'queries' of github.com:centrifuge/centrifuge-sdk into b…
sophialittlejohn Nov 13, 2024
1b2eb78
Fix test and add jsdocs
sophialittlejohn Nov 13, 2024
5b21459
Add lt, lte, gt, gte and multiply currency by price
sophialittlejohn Nov 14, 2024
7c20f5f
Fix merge error
sophialittlejohn Nov 14, 2024
a5a4ba4
Make reports gettable
sophialittlejohn Nov 14, 2024
8dc21d0
Fix naming
sophialittlejohn Nov 14, 2024
6d7cc4a
Merge branch 'queries' of github.com:centrifuge/centrifuge-sdk into b…
sophialittlejohn Nov 14, 2024
154022c
Merge branch 'queries' of github.com:centrifuge/centrifuge-sdk into b…
sophialittlejohn Nov 14, 2024
5662a96
Remove comment
sophialittlejohn Nov 14, 2024
3cb1996
Remove async
sophialittlejohn Nov 14, 2024
bc921c8
Add eql
sophialittlejohn Nov 14, 2024
29f443a
Fix multiplying with decimals
sophialittlejohn Nov 19, 2024
0abc7ab
Add more test scripts
sophialittlejohn Nov 19, 2024
4ef299f
Serialize cache keys to include support for BigInt
sophialittlejohn Nov 19, 2024
46eff05
Remove caching and use _query in favor to generate and cache reports
sophialittlejohn Nov 19, 2024
88c77ff
Readme for new scripts
sophialittlejohn Nov 19, 2024
3826bd7
Merge branch 'queries' of github.com:centrifuge/centrifuge-sdk into b…
sophialittlejohn Nov 19, 2024
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
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,22 @@
"dev": "tsc -w --importHelpers",
"build": "tsc --importHelpers",
"prepare": "yarn build",
"test": "mocha --loader=ts-node/esm --require $(pwd)/src/tests/setup.ts --exit --timeout 60000 'src/**/*.test.ts'"
"test": "mocha --loader=ts-node/esm --require $(pwd)/src/tests/setup.ts --exit --timeout 60000 'src/**/*.test.ts'",
"test:simple:single": "mocha --loader=ts-node/esm --exit --timeout 60000",
"test:single": "mocha --loader=ts-node/esm --require $(pwd)/src/tests/setup.ts --exit --timeout 60000"
},
"dependencies": {
"decimal.js-light": "^2.5.1",
"eth-permit": "^0.2.3",
"isomorphic-ws": "^5.0.0",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@types/chai": "^5.0.0",
"@types/mocha": "^10.0.9",
"@types/node": "^22.7.8",
"@types/sinon": "^17.0.3",
"@types/sinon-chai": "^4",
"chai": "^5.1.2",
"dotenv": "^16.4.5",
"eslint": "^9.12.0",
Expand All @@ -44,6 +49,7 @@
"npm-run-all": "4.1.5",
"prettier": "^3.3.3",
"sinon": "^19.0.2",
"sinon-chai": "^4.0.0",
"ts-node": "^10.9.2",
"typescript": "~5.6.3",
"typescript-eslint": "^8.8.1",
Expand Down
8 changes: 5 additions & 3 deletions src/Centrifuge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,16 @@ import { Pool } from './Pool.js'
import type { HexString } from './types/index.js'
import type { CentrifugeQueryOptions, Query } from './types/query.js'
import type { OperationStatus, Signer, Transaction, TransactionCallbackParams } from './types/transaction.js'
import { hashKey } from './utils/query.js'
import { hashKey, serializeForCache } from './utils/query.js'
import { makeThenable, shareReplayWithDelayedReset } from './utils/rx.js'
import { doTransaction, isLocalAccount } from './utils/transaction.js'

export type Config = {
environment: 'mainnet' | 'demo' | 'dev'
rpcUrls?: Record<number | string, string>
indexerUrl: string
}

export type UserProvidedConfig = Partial<Config>
type EnvConfig = {
indexerUrl: string
Expand Down Expand Up @@ -224,7 +226,7 @@ export class Centrifuge {

#memoized = new Map<string, any>()
#memoizeWith<T = any>(keys: any[], callback: () => T): T {
const cacheKey = hashKey(keys)
const cacheKey = hashKey(serializeForCache(keys))
if (this.#memoized.has(cacheKey)) {
return this.#memoized.get(cacheKey)
}
Expand Down Expand Up @@ -428,7 +430,7 @@ export class Centrifuge {
params: TransactionCallbackParams
) => AsyncGenerator<OperationStatus> | Observable<OperationStatus>,
chainId?: number
): Transaction {
) {
const targetChainId = chainId ?? this.config.defaultChain
const self = this
async function* transact() {
Expand Down
5 changes: 5 additions & 0 deletions src/Pool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { catchError, combineLatest, map, of, switchMap, timeout } from 'rxjs'
import type { Centrifuge } from './Centrifuge.js'
import { Entity } from './Entity.js'
import { Reports } from './Reports/index.js'
import { PoolNetwork } from './PoolNetwork.js'

export class Pool extends Entity {
Expand All @@ -11,6 +12,10 @@ export class Pool extends Entity {
super(_root, ['pool', id])
}

get reports() {
return new Reports(this._root, this.id)
}

trancheIds() {
return this._root._queryIndexer(
`query($poolId: String!) {
Expand Down
53 changes: 53 additions & 0 deletions src/Reports/Reports.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { expect } from 'chai'
import { Centrifuge } from '../Centrifuge.js'
import { spy } from 'sinon'
import { ReportFilter, Reports } from '../Reports/index.js'
import * as balanceSheetProcessor from '../Reports/processors/balanceSheet.js'
import { firstValueFrom } from 'rxjs'

describe('Reports', () => {
let centrifuge: Centrifuge

before(async () => {
centrifuge = new Centrifuge({
environment: 'mainnet',
indexerUrl: 'https://subql.embrio.tech/',
})
})

it('should get balance sheet report', async () => {
const ns3PoolId = '1615768079'
const pool = await centrifuge.pool(ns3PoolId)
const balanceSheetReport = await pool.reports.balanceSheet({
from: '2024-11-03T22:11:29.776Z',
to: '2024-11-06T22:11:29.776Z',
groupBy: 'day',
})
expect(balanceSheetReport.length).to.be.eql(3)
expect(balanceSheetReport?.[0]?.tranches?.length ?? 0).to.be.eql(2) // ns3 has 2 tranches
expect(balanceSheetReport?.[0]?.tranches?.[0]?.timestamp.slice(0, 10)).to.be.eql(
balanceSheetReport?.[0]?.date.slice(0, 10)
)
})

it('should use cached data for repeated queries', async () => {
const processBalanceSheetSpy = spy(balanceSheetProcessor, 'processBalanceSheetData')

const ns3PoolId = '1615768079'
const reports = new Reports(centrifuge, ns3PoolId)

const filter: ReportFilter = {
from: '2024-11-03T22:11:29.776Z',
to: '2024-11-06T22:11:29.776Z',
groupBy: 'day',
}

await firstValueFrom(reports.balanceSheet(filter))
expect(processBalanceSheetSpy.callCount).to.equal(1)

// Same query should use cache
await firstValueFrom(reports.balanceSheet(filter))
// TODO: Can't spy on es module
expect(processBalanceSheetSpy.callCount).to.equal(1)
})
})
86 changes: 86 additions & 0 deletions src/Reports/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Entity } from '../Entity.js'
import { Centrifuge } from '../Centrifuge.js'
import { PoolSnapshotFilter, poolSnapshotsPostProcess, poolSnapshotsQuery } from '../queries/poolSnapshots.js'
import {
TrancheSnapshotFilter,
trancheSnapshotsPostProcess,
trancheSnapshotsQuery,
} from '../queries/trancheSnapshots.js'
import { processBalanceSheetData } from './processors/balanceSheet.js'
import { combineLatest, of } from 'rxjs'
import { ReportProcessorType } from './processors/index.js'

import { map } from 'rxjs'
import { GroupBy } from '../utils/date.js'
export interface ReportFilter {
from?: string
to?: string
groupBy?: GroupBy
}

export interface ReportData {
timestamp: string
[key: string]: unknown
}

export class Reports extends Entity {
constructor(
centrifuge: Centrifuge,
public poolId: string
) {
super(centrifuge, ['reports', poolId])
}

balanceSheet(filter?: ReportFilter) {
return this.generateReport('balanceSheet', filter)
}

generateReport(type: ReportProcessorType, filter?: ReportFilter) {
return this._root._query(
[type, ...(filter ? [filter] : [])],
() => {
const dateFilter = {
timestamp: {
greaterThan: filter?.from,
lessThan: filter?.to,
},
}

return combineLatest([
this.poolSnapshots({
...dateFilter,
poolId: { equalTo: this.poolId },
}),
this.trancheSnapshots({
...dateFilter,
tranche: { poolId: { equalTo: this.poolId } },
}),
]).pipe(
map(([poolSnapshots, trancheSnapshots]) => {
switch (type) {
case 'balanceSheet':
return processBalanceSheetData({ poolSnapshots, trancheSnapshots }, filter)
default:
throw new Error(`Unsupported report type: ${type}`)
}
})
)
},
{
valueCacheTime: 120,
}
)
}

poolSnapshots(filter?: PoolSnapshotFilter) {
return this._root._queryIndexer(poolSnapshotsQuery, { filter }, poolSnapshotsPostProcess)
}

trancheSnapshots(filter?: TrancheSnapshotFilter) {
return this._root._queryIndexer(trancheSnapshotsQuery, { filter }, trancheSnapshotsPostProcess)
}

_processBalanceSheet(data: any, filter?: ReportFilter) {
return processBalanceSheetData(data, filter)
}
}
70 changes: 70 additions & 0 deletions src/Reports/processors/balanceSheet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { PoolSnapshot } from '../../queries/poolSnapshots.js'
import { TrancheSnapshot } from '../../queries/trancheSnapshots.js'
import { Currency, Price } from '../../utils/BigInt.js'
import { groupByPeriod } from '../../utils/date.js'
import { ReportData, ReportFilter } from '../types.js'

export interface BalanceSheetReport extends ReportData {
date: string
assetValuation: Currency
onchainReserve: Currency
offchainCash: Currency
accruedFees: Currency
netAssetValue: Currency
tranches?: {
name: string
timestamp: string
tokenId: string
tokenSupply: Currency
tokenPrice: Price | null
trancheValue: Currency
}[]
totalCapital?: Currency
}

type BalanceSheetData = {
poolSnapshots: PoolSnapshot[]
trancheSnapshots: TrancheSnapshot[]
}

export function processBalanceSheetData(data: BalanceSheetData, filter?: ReportFilter): BalanceSheetReport[] {
const trancheSnapshotsByDate = groupTranchesByDate(data.trancheSnapshots)
const items = data?.poolSnapshots?.map((snapshot) => {
const tranches = trancheSnapshotsByDate.get(snapshot.timestamp.slice(0, 10)) ?? []
return {
timestamp: snapshot.timestamp,
date: snapshot.timestamp,
assetValuation: snapshot.portfolioValuation,
onchainReserve: snapshot.totalReserve,
offchainCash: snapshot.offchainCashValue,
accruedFees: snapshot.sumPoolFeesPendingAmount,
netAssetValue: snapshot.netAssetValue,
tranches: tranches?.map((tranche) => ({
name: tranche.pool.currency.symbol,
timestamp: tranche.timestamp,
tokenId: tranche.trancheId,
tokenSupply: tranche.tokenSupply,
tokenPrice: tranche.price,
trancheValue: tranche.tokenSupply.mul(tranche?.price?.toBigInt() ?? 0n),
})),
totalCapital: tranches.reduce(
(acc, curr) => acc.add(curr.tokenSupply.mul(curr?.price?.toBigInt() ?? 0n).toBigInt()),
new Currency(0, snapshot.poolCurrency.decimals)
),
}
})
return filter?.groupBy ? groupByPeriod(items, filter.groupBy) : items
}

function groupTranchesByDate(trancheSnapshots: TrancheSnapshot[]): Map<string, TrancheSnapshot[]> {
const grouped = new Map<string, TrancheSnapshot[]>()
if (!trancheSnapshots) return grouped
trancheSnapshots?.forEach((snapshot) => {
const date = snapshot.timestamp.slice(0, 10)
if (!grouped.has(date)) {
grouped.set(date, [])
}
grouped.get(date)!.push(snapshot)
})
return grouped
}
7 changes: 7 additions & 0 deletions src/Reports/processors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { processBalanceSheetData } from './balanceSheet.js'

export const processors = {
balanceSheet: processBalanceSheetData,
} as const

export type ReportProcessorType = keyof typeof processors
12 changes: 12 additions & 0 deletions src/Reports/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { GroupBy } from '../utils/date.js'

export interface ReportFilter {
from?: string
to?: string
groupBy?: GroupBy
}

export interface ReportData {
timestamp: string
[key: string]: unknown
}
Loading