Skip to content

Commit 078e694

Browse files
authored
feat(api): 932 record historical synthetic prices based on emp latest price (#3108)
Signed-off-by: David Adams <[email protected]>
1 parent 5c08647 commit 078e694

File tree

5 files changed

+86
-6
lines changed

5 files changed

+86
-6
lines changed

packages/api/src/apps/api/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,14 @@ Get only tvl (and other future statistics) for a single emp.
120120
#### listEmpStats(currency: "usd" = "usd") => StatData[]
121121

122122
Get all tvl (and other future statistics) for all known emps.
123+
124+
### historicalSynthPricesByAddress(empAddress:string,start:number=0,end:number=Date.now()) => PriceSample[]
125+
126+
Get a range of historical prices synthetic prices by emp address between start and end in MS, sorted by timestamp
127+
ascending. This is useful for charting betwen two dates. Note non synth historical prices are specified by erc20 address.
128+
129+
### sliceHistoricalSynthPricesByAddress(address:string,start:number=0,length:number=1) => PriceSample[]
130+
131+
Get a range of historical synthetic prices by emp address between start and count of price samples, sorted by timestamp
132+
ascending. This is useful for paginating data when you have X elements per page, and you know the last element seen.
133+
Note non synth historical prices are specified by erc20 address.

packages/api/src/apps/api/app.ts

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ async function run(env: ProcessEnv) {
4444
},
4545
synthPrices: {
4646
latest: {},
47+
history: {},
4748
},
4849
erc20s: tables.erc20s.JsMap(),
4950
stats: {

packages/api/src/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ export type AppState = {
4343
latest: {
4444
[empAddress: string]: PriceSample;
4545
};
46+
history: {
47+
[empAddress: string]: uma.tables.historicalPrices.SortedJsMap;
48+
};
4649
};
4750
erc20s: uma.tables.erc20s.JsMap;
4851
stats: {

packages/api/src/services/actions.ts

+20
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,26 @@ export function Handlers(config: Config, appState: Dependencies): Actions {
100100
assert(stats[currency], "No stats for currency: " + currency);
101101
return stats[currency].latest.values();
102102
},
103+
async historicalSynthPricesByAddress(
104+
empAddress: string,
105+
start = 0,
106+
end: number = Date.now()
107+
): Promise<PriceSample[]> {
108+
assert(empAddress, "requires emp address");
109+
assert(start >= 0, "requires a start value >= 0");
110+
assert(exists(synthPrices.history[empAddress]), "No synthetic prices for emp address: " + empAddress);
111+
const results = await synthPrices.history[empAddress].betweenByTimestamp(start, end);
112+
// convert this to tuple to save bytes.
113+
return results.map(({ price, timestamp }) => [timestamp, price]);
114+
},
115+
async sliceHistoricalSynthPricesByAddress(empAddress: string, start = 0, length = 1): Promise<PriceSample[]> {
116+
assert(empAddress, "requires emp address");
117+
assert(start >= 0, "requires a start value >= 0");
118+
assert(exists(synthPrices.history[empAddress]), "No synthetic prices for emp address: " + empAddress);
119+
const results = await synthPrices.history[empAddress].sliceByTimestamp(start, length);
120+
// convert this to tuple to save bytes.
121+
return results.map(({ price, timestamp }) => [timestamp, price]);
122+
},
103123
};
104124

105125
// list all available actions

packages/api/src/services/synthetic-prices.ts

+51-6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import assert from "assert";
12
import SynthPrices from "../libs/synthPrices";
23
import { AppState, PriceSample } from "..";
4+
import * as uma from "@uma/sdk";
35

46
type Config = {
57
cryptowatchApiKey?: string;
@@ -15,30 +17,73 @@ export default function (config: Config, appState: Dependencies) {
1517
const { web3, emps, synthPrices } = appState;
1618
const getSynthPrices = SynthPrices(config, web3);
1719

18-
async function updatePrice(empAddress: string) {
20+
// if we have a new emp address, this will create a new price table structure to store historical price data
21+
function getOrCreateHistoryTable(empAddress: string) {
22+
if (synthPrices.history[empAddress] == null) {
23+
synthPrices.history[empAddress] = uma.tables.historicalPrices.SortedJsMap();
24+
}
25+
return synthPrices.history[empAddress];
26+
}
27+
28+
// utility to grab last price based on address
29+
function getLatestPriceFromTable(empAddress: string) {
30+
const result = synthPrices.latest[empAddress];
31+
assert(uma.utils.exists(result), "no latest sythetic price found for: " + empAddress);
32+
return result;
33+
}
34+
35+
// pulls price from latest and stuffs it into historical table.
36+
async function updatePriceHistory(empAddress: string) {
37+
const table = getOrCreateHistoryTable(empAddress);
38+
const [timestamp, price] = getLatestPriceFromTable(empAddress);
39+
// if this timestamp exists in the table, dont bother re-adding it
40+
assert(uma.utils.exists(price), "No latest price available for: " + empAddress);
41+
assert(
42+
!(await table.hasByTimestamp(timestamp)),
43+
`Synthetic price already exists for emp address ${empAddress} at timestamp: ${timestamp}`
44+
);
45+
return table.create({ timestamp, price });
46+
}
47+
48+
async function updatePriceHistories(addresses: string[]) {
49+
return Promise.allSettled(addresses.map(updatePriceHistory));
50+
}
51+
52+
async function updateLatestPrice(empAddress: string) {
1953
const result: PriceSample = await getSynthPrices.getCurrentPrice(empAddress);
2054
synthPrices.latest[empAddress] = result;
2155
return result;
2256
}
23-
async function updatePrices(empAddresses: string[]) {
24-
return Promise.allSettled(empAddresses.map(updatePrice));
57+
58+
async function updateLatestPrices(empAddresses: string[]) {
59+
return Promise.allSettled(empAddresses.map(updateLatestPrice));
2560
}
2661

2762
async function update() {
2863
// synth prices are looked up by emp address, not synthetic token address
2964
const empAddresses = Array.from(await emps.active.keys());
30-
await updatePrices(empAddresses).then((results) => {
65+
await updateLatestPrices(empAddresses).then((results) => {
3166
results.forEach((result) => {
3267
if (result.status === "rejected") console.error("Error updating synthetic price: " + result.reason.message);
3368
});
3469
});
70+
await updatePriceHistories(empAddresses).then((results) => {
71+
results.forEach((result) => {
72+
if (result.status === "rejected")
73+
console.error("Error updating historical synthetic price: " + result.reason.message);
74+
});
75+
});
3576
}
3677

3778
return {
3879
update,
3980
utils: {
40-
updatePrice,
41-
updatePrices,
81+
getOrCreateHistoryTable,
82+
getLatestPriceFromTable,
83+
updatePriceHistories,
84+
updatePriceHistory,
85+
updateLatestPrice,
86+
updateLatestPrices,
4287
},
4388
};
4489
}

0 commit comments

Comments
 (0)