From 82a4b770595818208ea1e3218485a155a3ea944a Mon Sep 17 00:00:00 2001 From: Mohammed Affan Date: Mon, 12 Aug 2024 11:34:07 -0400 Subject: [PATCH] [OTE-544][OTE-545] Add rountable task for leaderboard table (#1965) --- .../postgres/__tests__/helpers/constants.ts | 31 + .../__tests__/stores/pnl-ticks-table.test.ts | 266 ++++- indexer/packages/postgres/src/lib/helpers.ts | 5 + .../postgres/src/lib/vault-addresses.json | 1002 +++++++++++++++++ .../postgres/src/stores/pnl-ticks-table.ts | 182 ++- .../src/types/leaderboard-pnl-types.ts | 8 + .../src/caches/leaderboard-processed-cache.ts | 25 + indexer/packages/redis/src/index.ts | 1 + .../tasks/create-leaderboard.test.ts | 116 ++ indexer/services/roundtable/src/config.ts | 21 + indexer/services/roundtable/src/index.ts | 49 +- .../src/tasks/create-leaderboard.ts | 144 +++ 12 files changed, 1845 insertions(+), 5 deletions(-) create mode 100644 indexer/packages/postgres/src/lib/vault-addresses.json create mode 100644 indexer/packages/redis/src/caches/leaderboard-processed-cache.ts create mode 100644 indexer/services/roundtable/__tests__/tasks/create-leaderboard.test.ts create mode 100644 indexer/services/roundtable/src/tasks/create-leaderboard.ts diff --git a/indexer/packages/postgres/__tests__/helpers/constants.ts b/indexer/packages/postgres/__tests__/helpers/constants.ts index fef7197296..7a8a9a224b 100644 --- a/indexer/packages/postgres/__tests__/helpers/constants.ts +++ b/indexer/packages/postgres/__tests__/helpers/constants.ts @@ -67,6 +67,9 @@ export const dydxChain: string = 'dydx'; export const defaultAddress: string = 'dydx1n88uc38xhjgxzw9nwre4ep2c8ga4fjxc565lnf'; export const defaultAddress2: string = 'dydx1n88uc38xhjgxzw9nwre4ep2c8ga4fjxc575lnf'; export const blockedAddress: string = 'dydx1f9k5qldwmqrnwy8hcgp4fw6heuvszt35egvtx2'; +// Vault address for vault id 0 was generated using +// script protocol/scripts/vault/get_vault.go +export const vaultAddress: string = 'dydx1c0m5x87llaunl5sgv3q5vd7j5uha26d2q2r2q0'; // ============== Subaccounts ============== @@ -91,6 +94,20 @@ export const defaultSubaccount3: SubaccountCreateObject = { updatedAtHeight: createdHeight, }; +export const defaultSubaccountWithAlternateAddress: SubaccountCreateObject = { + address: defaultAddress2, + subaccountNumber: 0, + updatedAt: createdDateTime.toISO(), + updatedAtHeight: createdHeight, +}; + +export const vaultSubaccount: SubaccountCreateObject = { + address: vaultAddress, + subaccountNumber: 0, + updatedAt: createdDateTime.toISO(), + updatedAtHeight: createdHeight, +}; + export const isolatedSubaccount: SubaccountCreateObject = { address: defaultAddress, subaccountNumber: 128, @@ -119,6 +136,10 @@ export const defaultSubaccountId3: string = SubaccountTable.uuid( defaultAddress, defaultSubaccount3.subaccountNumber, ); +export const defaultSubaccountIdWithAlternateAddress: string = SubaccountTable.uuid( + defaultAddress2, + defaultSubaccountWithAlternateAddress.subaccountNumber, +); export const isolatedSubaccountId: string = SubaccountTable.uuid( defaultAddress, isolatedSubaccount.subaccountNumber, @@ -128,6 +149,11 @@ export const isolatedSubaccountId2: string = SubaccountTable.uuid( isolatedSubaccount2.subaccountNumber, ); +export const vaultSubaccountId: string = SubaccountTable.uuid( + vaultAddress, + vaultSubaccount.subaccountNumber, +); + // ============== Wallets ============== export const defaultWallet: WalletCreateObject = { address: defaultAddress, @@ -139,6 +165,11 @@ export const defaultWallet2: WalletCreateObject = { totalTradingRewards: denomToHumanReadableConversion(1), }; +export const vaultWallet: WalletCreateObject = { + address: vaultAddress, + totalTradingRewards: denomToHumanReadableConversion(0), +}; + export const defaultWallet3: WalletCreateObject = { address: defaultAddress2, totalTradingRewards: denomToHumanReadableConversion(0), diff --git a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts index 13a518097b..4f9cd9c078 100644 --- a/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts +++ b/indexer/packages/postgres/__tests__/stores/pnl-ticks-table.test.ts @@ -1,5 +1,6 @@ import { IsoString, + LeaderboardPnlCreateObject, Ordering, PnlTicksColumns, PnlTicksCreateObject, @@ -8,12 +9,22 @@ import * as PnlTicksTable from '../../src/stores/pnl-ticks-table'; import * as BlockTable from '../../src/stores/block-table'; import { clearData, migrate, teardown } from '../../src/helpers/db-helpers'; import { seedData } from '../helpers/mock-generators'; +import * as WalletTable from '../../src/stores/wallet-table'; +import * as SubaccountTable from '../../src/stores/subaccount-table'; import { + defaultAddress, + defaultAddress2, defaultBlock, defaultBlock2, defaultPnlTick, defaultSubaccountId, defaultSubaccountId2, + defaultSubaccountIdWithAlternateAddress, + defaultSubaccountWithAlternateAddress, + defaultWallet2, + vaultSubaccount, + vaultSubaccountId, + vaultWallet, } from '../helpers/constants'; import { DateTime } from 'luxon'; import { ZERO_TIME_ISO_8601 } from '../../src/constants'; @@ -21,6 +32,8 @@ import { ZERO_TIME_ISO_8601 } from '../../src/constants'; describe('PnlTicks store', () => { beforeEach(async () => { await seedData(); + await WalletTable.create(defaultWallet2); + await SubaccountTable.create(defaultSubaccountWithAlternateAddress); }); beforeAll(async () => { @@ -275,12 +288,259 @@ describe('PnlTicks store', () => { }, ]); - const mostRecent: { + const leaderboardRankedData: { [accountId: string]: PnlTicksCreateObject } = await PnlTicksTable.findMostRecentPnlTickForEachAccount( '3', ); - expect(mostRecent[defaultSubaccountId].equity).toEqual('1014'); - expect(mostRecent[defaultSubaccountId2].equity).toEqual('200'); + expect(leaderboardRankedData[defaultSubaccountId].equity).toEqual('1014'); + expect(leaderboardRankedData[defaultSubaccountId2].equity).toEqual('200'); }); + + const testCases = [ + { + description: 'Get all time ranked pnl ticks', + timeSpan: 'ALL_TIME', + expectedLength: 2, + expectedResults: [ + { + address: defaultAddress, + pnl: '1200', + currentEquity: '1100', + timeSpan: 'ALL_TIME', + rank: '1', + }, + { + address: defaultAddress2, + pnl: '300', + currentEquity: '200', + timeSpan: 'ALL_TIME', + rank: '2', + }, + ], + }, + { + description: 'Get one year ranked pnl ticks with missing pnl for one subaccount', + timeSpan: 'ONE_YEAR', + expectedLength: 2, + expectedResults: [ + { + address: defaultAddress2, + pnl: '300', + currentEquity: '200', + timeSpan: 'ONE_YEAR', + rank: '1', + }, + { + address: defaultAddress, + pnl: '40', + currentEquity: '1100', + timeSpan: 'ONE_YEAR', + rank: '2', + }, + ], + }, + { + description: 'Get thirty days ranked pnl ticks', + timeSpan: 'THIRTY_DAYS', + expectedLength: 2, + expectedResults: [ + { + address: defaultAddress, + pnl: '30', + currentEquity: '1100', + timeSpan: 'THIRTY_DAYS', + rank: '1', + }, + { + address: defaultAddress2, + pnl: '-30', + currentEquity: '200', + timeSpan: 'THIRTY_DAYS', + rank: '2', + }, + ], + }, + { + description: 'Get seven days ranked pnl ticks', + timeSpan: 'SEVEN_DAYS', + expectedLength: 2, + expectedResults: [ + { + address: defaultAddress, + pnl: '20', + currentEquity: '1100', + timeSpan: 'SEVEN_DAYS', + rank: '1', + }, + { + address: defaultAddress2, + pnl: '-20', + currentEquity: '200', + timeSpan: 'SEVEN_DAYS', + rank: '2', + }, + ], + }, + { + description: 'Get one day ranked pnl ticks', + timeSpan: 'ONE_DAY', + expectedLength: 2, + expectedResults: [ + { + address: defaultAddress, + pnl: '10', + currentEquity: '1100', + timeSpan: 'ONE_DAY', + rank: '1', + }, + { + address: defaultAddress2, + pnl: '-10', + currentEquity: '200', + timeSpan: 'ONE_DAY', + rank: '2', + }, + ], + }, + ]; + + it.each(testCases)('$description', async ({ timeSpan, expectedLength, expectedResults }) => { + await setupRankedPnlTicksData(); + + const leaderboardRankedData = await PnlTicksTable.getRankedPnlTicks(timeSpan); + + expect(leaderboardRankedData.length).toEqual(expectedLength); + + expectedResults.forEach((expectedResult, index) => { + expect(leaderboardRankedData[index]).toEqual(expect.objectContaining(expectedResult)); + }); + }); + + it('Ensure that vault addresses are not included in the leaderboard', async () => { + await setupRankedPnlTicksData(); + + await WalletTable.create(vaultWallet); + await SubaccountTable.create(vaultSubaccount); + await PnlTicksTable.create({ + subaccountId: vaultSubaccountId, + equity: '100', + createdAt: DateTime.utc().toISO(), + totalPnl: '100', + netTransfers: '50', + blockHeight: '9', + blockTime: defaultBlock.time, + }); + + const leaderboardRankedData: LeaderboardPnlCreateObject[] = await + PnlTicksTable.getRankedPnlTicks( + 'ALL_TIME', + ); + expect(leaderboardRankedData.length).toEqual(2); + }); + }); + +async function setupRankedPnlTicksData() { + await Promise.all([ + BlockTable.create({ + blockHeight: '3', + time: defaultBlock.time, + }), + BlockTable.create({ + blockHeight: '5', + time: defaultBlock.time, + }), + BlockTable.create({ + blockHeight: '7', + time: defaultBlock.time, + }), + BlockTable.create({ + blockHeight: '9', + time: defaultBlock.time, + }), + ]); + await PnlTicksTable.createMany([ + { + subaccountId: defaultSubaccountId, + equity: '1100', + createdAt: DateTime.utc().toISO(), + totalPnl: '1200', + netTransfers: '50', + blockHeight: '9', + blockTime: defaultBlock.time, + }, + { + subaccountId: defaultSubaccountId, + equity: '1090', + createdAt: DateTime.utc().minus({ day: 1 }).toISO(), + totalPnl: '1190', + netTransfers: '50', + blockHeight: '7', + blockTime: defaultBlock.time, + }, + { + subaccountId: defaultSubaccountId, + equity: '1080', + createdAt: DateTime.utc().minus({ day: 7 }).toISO(), + totalPnl: '1180', + netTransfers: '50', + blockHeight: '5', + blockTime: defaultBlock.time, + }, + { + subaccountId: defaultSubaccountId, + equity: '1070', + createdAt: DateTime.utc().minus({ day: 30 }).toISO(), + totalPnl: '1170', + netTransfers: '50', + blockHeight: '3', + blockTime: defaultBlock.time, + }, + { + subaccountId: defaultSubaccountId, + equity: '1060', + createdAt: DateTime.utc().minus({ day: 365 }).toISO(), + totalPnl: '1160', + netTransfers: '50', + blockHeight: '1', + blockTime: defaultBlock.time, + }, + { + subaccountId: defaultSubaccountIdWithAlternateAddress, + equity: '200', + createdAt: DateTime.utc().toISO(), + totalPnl: '300', + netTransfers: '50', + blockHeight: '9', + blockTime: defaultBlock.time, + }, + { + subaccountId: defaultSubaccountIdWithAlternateAddress, + equity: '210', + createdAt: DateTime.utc().minus({ day: 1 }).toISO(), + totalPnl: '310', + netTransfers: '50', + blockHeight: '7', + blockTime: defaultBlock.time, + }, + { + subaccountId: defaultSubaccountIdWithAlternateAddress, + equity: '220', + createdAt: DateTime.utc().minus({ week: 1 }).toISO(), + totalPnl: '320', + netTransfers: '50', + blockHeight: '5', + blockTime: defaultBlock.time, + }, + { + subaccountId: defaultSubaccountIdWithAlternateAddress, + equity: '230', + createdAt: DateTime.utc().minus({ month: 1 }).toISO(), + totalPnl: '330', + netTransfers: '50', + blockHeight: '3', + blockTime: defaultBlock.time, + }, + ]); +} diff --git a/indexer/packages/postgres/src/lib/helpers.ts b/indexer/packages/postgres/src/lib/helpers.ts index 5e3ec1fdf7..9746c61ca6 100644 --- a/indexer/packages/postgres/src/lib/helpers.ts +++ b/indexer/packages/postgres/src/lib/helpers.ts @@ -2,8 +2,13 @@ import { DateTime } from 'luxon'; import { IsoString } from '../types'; +import vaultAddresses from './vault-addresses.json'; export function blockTimeFromIsoString(isoString: IsoString): number { const dateTime: DateTime = DateTime.fromISO(isoString, { zone: 'utc' }); return Math.floor(dateTime.toMillis() / 1000); } + +export function getVaultAddresses(): string[] { + return vaultAddresses; +} diff --git a/indexer/packages/postgres/src/lib/vault-addresses.json b/indexer/packages/postgres/src/lib/vault-addresses.json new file mode 100644 index 0000000000..82c9270cf7 --- /dev/null +++ b/indexer/packages/postgres/src/lib/vault-addresses.json @@ -0,0 +1,1002 @@ +[ + "dydx1c0m5x87llaunl5sgv3q5vd7j5uha26d2q2r2q0", + "dydx14rplxdyycc6wxmgl8fggppgq4774l70zt6phkw", + "dydx190te44zcctdgk0qmqtenve2m00g3r2dn7ntd72", + "dydx1a83cjn83vqh5ss2vccg6uuaeky7947xldp9r2e", + "dydx1nkz8xcar6sxedw0yva6jzjplw7hfg6pp6e7h0l", + "dydx1sgwqvg85g06l233hvyjae8ag3ka4x8gdgdrsau", + "dydx1wcxcrcmapf7n0jq5082q5m9rk2654y2kc8dfqg", + "dydx1c53uruvqn4ss9xg67pn0ealvz3jry8jmq56daz", + "dydx1jnapjg50vmh9plczamn6ea9r45wjgmdw2gq7vn", + "dydx1gr6g4gefm5a493f8f5dk6fnp8m0xp24jqse2cw", + "dydx165fla2xss0wrg8rr04x3e46mneat4fckrcf429", + "dydx1nvq7e9m98the7e65mwm4dflkqd0zl7ks8wfumj", + "dydx13xgfld5p6ww3lsz4htv0rxcxkg36jy8egugltn", + "dydx1g99yaj7339hfu5a80z6yqj7gtssr884g6zkd8x", + "dydx1jwdjsjlhs04udfhsu7yj94rca04ftrwm5ng8va", + "dydx1qm5ryzt33gjjhy7h9apxm8889un7hepw7utg4j", + "dydx1pk8hrguja7terlez4gehm52hlg3mj0cye80px6", + "dydx18g9wvnp6e7f24yqsj0zra0p4jrm2d2mgd8fgsf", + "dydx1lgxs5kdj67uyqgwt6k5rqf427wsmjz3v678cs9", + "dydx1ljjfn2pvh7cvsjfux96gray7c6n5e5fjvevtqy", + "dydx10xrejp69al827t63srxymsdr5rh08lax5rwfhe", + "dydx1qyw5mnsgunyutyhg603gl2wwehd2zy5tcwnrpv", + "dydx1ken0tl943vpfmxhfgxsd5qkvytxrmh9xeyd64x", + "dydx14a4sg762e694e9j5pttpp3gld6j4hyq53u6jps", + "dydx1recg5vv0xgnu856ndp4tdepwzf33glz437j6yl", + "dydx1utsgenwxx6hw04fugte69y2cyzdngm0a0dhqc6", + "dydx147jdyc0xp04x0959nzjrzz7wuw0pm8cxpvehrq", + "dydx1kkztmh6j4nvtls7s2pcwshled8y05supv3hdzm", + "dydx1esh4d4rdl58lqauha8met8g78xry52ry3nnmw6", + "dydx1muttrndpjec7j2jv4je055z2mvhc3dm2xz524a", + "dydx1g5dpm5kecfmtn4uzp5e55k0hhzynqjmdg4n2ul", + "dydx1fhz38ll7xugk3lfgrn9hky6x0knjw7xxvtca85", + "dydx1wct69jh7kx009n7slkkaqj62jjxp07k8azkx5p", + "dydx1h8n2lwx8pzk509cxcd2l0ngvzutrsqrfr42nxk", + "dydx1y3upklze00hprpa8d5gggx05pmrtuglytatjzk", + "dydx1xj8m8jkaqfzgjrpa570n96hyt74c0066yl2ar5", + "dydx19afh84de06lku9995g959l9e64tlf9daa2acr5", + "dydx1g460jgzrc6dy9a8ptn5d3mey0nqasvhdyl6ec4", + "dydx1jatnxzqghex6f924c0h5j5sqagczqh5j48tp84", + "dydx1j62ts3d47m6x9005ljgmxrpxwaxx84a7q9874z", + "dydx144l4uyasuvsr6lnlhpqcrp8djmu3n23ed0wp0j", + "dydx1fksxcmfymhrhvp5244az2fu6q6sllaaka7aqkx", + "dydx1gu0pqrxqucdxrdqxwcts5tpnl855anv76ramnh", + "dydx1x095fjmdaeh20t8juc0hmswdzmm65ugcsdhp4x", + "dydx1yfe992t2hfcfrvpx6dek0z8jxf3ap4xvyx07ap", + "dydx18jel3thzvshjm0ud0rav77gessj2dhh35qzz0d", + "dydx17ps237qx5v2gfp7lqx263qgzq7jd3570vwr7ry", + "dydx1tpnycqu4svhpv46p6uayqvqdmckdzmt83dkh0s", + "dydx1wgzda4vz57pn38yrr507350w7202juzhg4eqyy", + "dydx19seeflkz7rlhxzflc4p8qjnem620jaw655vh7v", + "dydx1jqsjfnwwhuey2wfp7le4fa8dgnjxh5p6d5lxh9", + "dydx1ymaawj692v97k6tfz8mpelxnanl7zgwnzzegmq", + "dydx1p9artkuehv25dyavygfa0amp90qeqaz9j4xn94", + "dydx13zuj68f5jauk5f4fkejpxpqnqaeqygllvpmumx", + "dydx15ww9np5agf9zfkpxcy89xemh3hrz2v34vr9xkr", + "dydx199sr4gnmllvrkyr834gyg8m8d27n07509dkx43", + "dydx1kqpncwy79jh0qwfzaffzdnk9nyngel0xz48t9f", + "dydx16q2pw8q9mfdva7zqakfvt38n7qasna38fsf7ak", + "dydx1jyukv56r5hv87t5ked8tpy0haetlclnzncjtg3", + "dydx1hmhzwq5cn86u00q33kyae6xmljwmdgne2h5akp", + "dydx1pcf5zp2qek6satxnu76hshs8veg4z3f5enj9rd", + "dydx15wy09dd8zfp5z2hkh90jwynve2awk6v8w2ztjg", + "dydx1lkxwl4hdwn2ggt9q2ruuh4nmrhj5hk9m3c73kt", + "dydx1sm7kzlfn6j8gvz37rsumlaeduty3qargklrfju", + "dydx12laptmp99yttf5k7s0uzqer6cj44u3ynj60sx3", + "dydx1a5c5yk88zcax7s3dzt72lrrwex6efnfmweycvs", + "dydx1ev45egc7c2har2jxf6lxwsmtldk295x4rucflq", + "dydx1ecrdl42227qfs7tr9h94k8mfxl6zfsysx3457j", + "dydx1v2p6qj9lvzs46nmyjxw26lwaqa4g2rd4jq9yer", + "dydx1cnd3ftpvqr3khjsfn6ptz0a8fzd99p4dnmkt2l", + "dydx16fg2j83kkzwz9ghqrdgatzsnsfz7mzknz0e8yw", + "dydx1dg0h0468vx3wm2dk4qplj5u8nkkkuyxz0y5zm8", + "dydx12dffextgdnvrv349krs2thvhcdnwr4850d35h7", + "dydx1ge5qgepfxcjetkgvfgcph05zcv78we53w2g5h7", + "dydx1pzaql7h3tkt9uet8yht80me5td6gh0aprf58yk", + "dydx1ht8hrec0tupp00q8afkfn6lzzxrdcmlp8knwyz", + "dydx19ykejm094kzj054wt3lks3fk9kuwfu9h0z8hcs", + "dydx10vwegs2dyjm6443uqpp0y7v9u4snmcnu5wpgnr", + "dydx1qsyv2ug8y0m5xgapfr6avrj9ktu79pyq49pphg", + "dydx1l04nf4udw7swyrzge66j66n09ry4aawfm4xd07", + "dydx1264ag2trux63698h56vduy566yw5qkrlks3e3f", + "dydx1snpcs7pgxfaw6yrtth3gajequ7f9zckp8zltjg", + "dydx14wgrzzurfygjspmrptgy64x8v79q709her47lt", + "dydx1kywd7tcyvpwx8j6dc7t5733wmum4k2dq68trn6", + "dydx1m0pn0ygh37qp345rd44scq58ch7e4h0wfpdrcs", + "dydx1la0t02ztyfch3y42lgp43cfxgjxp7gmghsxun5", + "dydx1vvdpmwsyum9r5y92c3gpks5ednxqazfmpjv8qf", + "dydx1rtkg76uvpll6jhvm79knsrqaf9vx48wcfn7she", + "dydx10kgkxtwtue28hjgtmflhxltwezrc5aw4yuctu0", + "dydx186d03739dhkt8eeqahql3dcscyvgh9f6gsuzxn", + "dydx1jnsphepypg3xczftcxlg8fmlr0djkhjnmxnd4m", + "dydx1c5atsq350zsdwzld4e9k648cx4rkr9792e4xtp", + "dydx1ek2uc2sw25w633fyxhqfkgt335fd4l6hkpvw3j", + "dydx1w9nquxp466tmy58ln69wfv6lwhk9xzaz9lzfkv", + "dydx1peve5pgycz6lx5r3jukaqxxr5tqyyl6xs7j09l", + "dydx1mt66ymx2vv7dzct3t0h6m3afln4s235jf0esja", + "dydx127pntl3k27tfqaqs2f6nqu6vcndfnamt7ylv0c", + "dydx16hklzgswcw9w4xhh4vdajlclaghwzgmr2m6hlp", + "dydx1c6s0pg7hsc9enyr4nn9vhmyk8yps3wkhvlr0fz", + "dydx1y73m286vcnvrjnrcwv7d7l4gs2v2x5r65n7726", + "dydx1czwrmjdehcvmk4xjdg8mwkxecgg67am0ccxdul", + "dydx1ag5jgl436uwa007t6ugvnwvy8hj0ynvvvtcu2g", + "dydx1ypya4270fm4gujvktk67ldvru3y2nx7pvud7ej", + "dydx124wtevhqpjg35m9ph7zf03al55k4a460celcn9", + "dydx10aysrtwzlasf4wavv6tgp40mpkn0vl3qcc0gcq", + "dydx1xe7h0k8qqyupu5z7xh83wpd79d9h0s4w6eukre", + "dydx190wezyn99zsk2hgc3s7nlt5sjltzj04cm657hl", + "dydx1wrt4ytendg0l490m028kt5c0850fkkmjwf5tg4", + "dydx1grnsunqr2dhtgld5vcwu0awzntk88sq9fngz35", + "dydx15t8hzcu3exjnh7lp4ks2m0qzcpfmjw7wnq0fvr", + "dydx10fxlw7umtqdel7n4tx8nrzwtzy4dpev6ls3na0", + "dydx1key68rph9mtmm09f5udr58pfyq586dqr56zusw", + "dydx1ekgna52r076ng7d69c3u8ct7r4k3hw955nedwg", + "dydx1mcskurn6epk8av0whh8rjtjafhs5j7dw4za9us", + "dydx1pjvu4hurzc87l2j887f56s5qqk7dwsapdssvc4", + "dydx1dgrakcus0zwyajzprkfjnf666cnwcdew9zvvly", + "dydx1l3avcq2mwy9w0zua5m6g9j258f2edr5um74fwy", + "dydx1ushp8haqknjw3ur57ddyfhgsmcq0xysmn6m8ex", + "dydx1s55fuhygskjyu2llrvtaqqzuycl9dkvtrh4vs3", + "dydx1ulquskxxqs80lq4s4q78f6g8swu0hr74ap0c27", + "dydx1dc75mue9ylew9ql7yzgn5p4utgwvct747q6zym", + "dydx10ha4kaph74uxsstw547ux8hh6yk7vv7xgykxtl", + "dydx1clduy6jtmju4chl4mfuv0zv6nryhpp07uuumr9", + "dydx1pzy946s6urmt85jpqj830fduvmj0zwu08nd8wq", + "dydx1gh7j9n3hh59zm3r4zhfk69nydnz723yf2gmujm", + "dydx18ndf2h65c3qma5av354nz6kcjdkv5wtvsmj7z4", + "dydx13kcp8ljcef243yv9f2js6nr7uj4yasv5xsk4gy", + "dydx1p56afvyvjaktnnnskqvr4k38jdhr86m4p5dpmu", + "dydx1vpdmlvk8dg6wsqwp0am3t89wzt6c6zdve3qjty", + "dydx1evmk2a3rp6adr5nzwsjh956deps30k08tchugs", + "dydx144wyzc7zfgtmd4rm5l5vs5x0cfmkm7j00uh492", + "dydx1tztgq6lygfvdd94ascze3phunclt5k3r7dc0le", + "dydx1ffwcwp4e7cgll5qyv0z685hw8l5l3q7jp4q2ks", + "dydx1eldxlhm8ass4fcm2s9yvhf5qguy6p5m63tvjkf", + "dydx10pmmpjllyrap2cthfy49axfh8erlw87c8qzt92", + "dydx16ej3jztl5wqhwegsys6y0zkldzj2m984c9066n", + "dydx1r2gfpy5kcr74vve5duat36hygr53f6qp0n7asm", + "dydx1lxg3qjp5xesysr6ceh0rq2zzah6xe7qv2lt0xk", + "dydx169s7gl0a2v74zhqhmluljdx2dpkmpc9pp4snk7", + "dydx1ddhw6s8jdd6lhqx7drs75hkcv4gfht4wh97rn2", + "dydx17aszlxyfvtw6rg2l2s38e4t7dqnn6mwdx4mg7w", + "dydx1krtpfeau4p4n8ffs3wlc0kj63pgx70lxnk3cpd", + "dydx1747mf6l99pqps9gv6tgvfr5wxuvny7ucalr33k", + "dydx1yzqutt4f6wq74sfy7j45s9gfmlxcy7vy3cu9jv", + "dydx138aqldklse4uedzesdfkvhqh399n8gpa8rmzvq", + "dydx1j5t6qm2883hrc2ve0zp68dwp9vzw2xpf3a8lly", + "dydx16nadwv208kckr5tac033z6eslgy03plkhdmgcq", + "dydx162a22u824nkyhkpxq6nl90p0z6s87jcsdg2339", + "dydx10wg8mh5vvqahdjctsyvmrp9evdld43rf9mqwmg", + "dydx1ee78sc27kemjy72u6dtnlc4mys0twd770a3n39", + "dydx1fsug7jh9egyte3sflp3yl8cfxjxphw74w9rd40", + "dydx1rgxkqep7y7h7m8wmaypakwnnzkcv5y8w7c0g73", + "dydx1r72ctwl5a89ne2hskj359na4u4ex2pq9h8e5wp", + "dydx1atludczz9hlmfn7usz0rc33vcwv3pju00djpfh", + "dydx1tfv745rmw9ep5rvaqtd0eetcnxd8mlhyahaj4p", + "dydx1nfuk7ya6mg9aufa8dyrnj4xqr3p4vkpykdst4e", + "dydx1jq26mg54as36u9t8mkv7e8a80ku6ut4rvxjn9e", + "dydx1vzuqq5l7xrgh98k686re0frrh3spwd0rzcn42q", + "dydx1svdf2pzmp3e8yresms5t7ps224rj8qwe6cx9cc", + "dydx1rknuznwafwdempxwmtrql9swh5gmavv998ck94", + "dydx10srpspvwpvsvx7gtkqfgfmneuq27as4nx6mz5c", + "dydx163lhrk2xy9d2ksk9peqa8jevlvphr7y6e6k5gf", + "dydx1465qwu7yua857ucvr869kekj4x6m6a0zr5saf9", + "dydx1d8xq3dn2mq3yp738fq28n0t8ltkh6tpjt6mncw", + "dydx1rz4m8aqkz65gn7j6pdl35l8l5wry3mjzek8lyf", + "dydx1cxmxzgczn9s4q3s7lk5exac0v6muzvc4vaxu3l", + "dydx17wyq6hgpzhm92fhe6unwgzexuk3sklktevnld7", + "dydx18kn6sxxz37rg84pw9yhywj4ewvx5lzantu9te4", + "dydx1v6m9lyzfggthqyqwv98lnedk8egwh3rc2zp77g", + "dydx13g3klv53jkyxryjet64q2yf8p78ql5fsed0lfd", + "dydx1gkkudll4x07ahadh084a480r5acww74zgaz7he", + "dydx1mqcf5ajkgn2w5wk2k2ya04ydgs50js2827jc9k", + "dydx1fjn94dqzglewnwc8lk7yg6yj324wq7m9mxd35t", + "dydx18l2alye8ze70shh4sqt5n826lf4sqxtqsatwhu", + "dydx16e87hdg4k4hn95hyda7dh4270ku4tg9a7w2aq0", + "dydx1l2enmk780spym6pef4pz37fx4yucnfjk02egms", + "dydx1xwhwzesxtm59hxjd0dh5xd4p3fm9tpmsy5nqsg", + "dydx16tx4l0c6krryce4najffwy6q96pfj2rvy722ku", + "dydx104sjxrfn999sr8d9ppwhhvfcrhw2g3q7wjqxmt", + "dydx13xk77qhx6y94pnwx4d763yqaczxgcpuk6yv2yx", + "dydx1y7qlfs7fy7yy5zn9cpwettr2twc8vrq6gde207", + "dydx1lqdsudmzerrcsl7efes00j3ehyvml3p9dx9e0v", + "dydx1hfz9atq8aq92j80yf679d6wq8pxr97k33g3rvl", + "dydx1eugl0m99f266rv9s36nqjcu88jc88fpc2xlerj", + "dydx1gnw4uq2wztzfx9kcves3xx69ws0u7zklmn4td2", + "dydx1mmsqf7zr4vda7gl4xpn0agellwvh5rf3uwvl0m", + "dydx1es6dzttats8rzwk6qwtjmqfqr838cpg7z52r5x", + "dydx1ljyrmnd962jkdmkj6g7acnkyn5wsptgdm55hnd", + "dydx18wajlcu5rnn4ppmkuuhtqql6lyvmpamujlt5ny", + "dydx1argp0zq335j90yr2p762pavr6wmkldnl6c42xd", + "dydx1upx6epct369y9fl3qljjztzfrut7s99kwdzpcw", + "dydx1h5tfjnn07h92v38lqkf3ccugtm03e3hpccee5e", + "dydx1uh7arardvlna668ye9cdjdxmjaqqag935hkv9k", + "dydx1tkfww5xjn206nnqhrwqvhyufunkksgqc0qqjvu", + "dydx1kxpfgvjljqtumvclzm9l9ms56ryenxugt7v2h3", + "dydx1k3zkheck5rpcp60udq0e7muan7296k2aj96zd4", + "dydx1m4kzuxfrtlpdm9x628dwg3n80vu4g49aylqecf", + "dydx1etuym3rn66f45jyf2wujke3sqrpn5rgs5raawp", + "dydx15ktpeq4nnxw0rljvqhxu00wpdnvpdsp6ks9wa6", + "dydx16uxrxr88cv43pcqls7zxzs2m55u2af756fps84", + "dydx1wvryqv74r65jfzj83lfc0je2sywa20npfdpqg7", + "dydx19ytjuh7suvrfr40e5n3ep74zumgm6m4cs52vmx", + "dydx17k39y36e4wd3h4mvdqf0552thdlavtanqzp7ur", + "dydx1lsytpmgm3wepzmqt93fd4u3rd8e85rh8z733dl", + "dydx1cmr8gxvkz7m42wcm7d684v09hyg2eauxk9qpu5", + "dydx12f80sdwa7hu4hepqt2ud4p3j6wtpeyausm5fhd", + "dydx1da2sfd96jdt5q65evavgm9gqtekpv85y4sdqgz", + "dydx137nle0nqdcxap2lnfenezwwkf9c9f5ksajnwc5", + "dydx184qrqg965l0glfthuptjykruzg3wrfzgzj9d5u", + "dydx15eyw8fu5p68qkwt3s8mgw5sgf8spwxm2l45y69", + "dydx1lhm42a37qu9ns5fhupv8phrg6czk655zddvyss", + "dydx1fx528qye79qfey8rupfuvj4parjzs3dcwunrkv", + "dydx1knvjmyqnx86a5qwlumd960qy8rtg9ym9gmru66", + "dydx1eac0dxfmvxdrss764hth9y9s52fgxte0uddqnw", + "dydx1syrc4urxaga0zp2zhwq2ag45xj3nnc6r8a3kz5", + "dydx1jcf75tp0f223ww5m8y4cu2tuxrd8casejrgl0l", + "dydx144pa9xk680pn9kwhnv4knwhk3t2ut4fyz6teff", + "dydx1ulngr7823n4wrxudhn7dqux4xs9mt03vxzk7hq", + "dydx15g48y6keau4gq0mdm5qhs45guljkxg4t0hx43t", + "dydx1vk2ugat3v9lw6a9uw07e4ntwqpk4j0eyh975c3", + "dydx1dgsut5wa3a6g0cw6zkl55sw8xvl708ya2umwy9", + "dydx1mne7l8jaw59x924jcntnd6qzv4n334xdmt978n", + "dydx1x7z6tl8rjpeptkqmfwetzrfjmtgl66fm2xlvqk", + "dydx1eyxur3k02rgrlmgnqph3qg8vlhc5v740r5p2u9", + "dydx1gertu2vs475lq9kzlq5yygnn7xkhcte4sfty96", + "dydx1npukxkq2vynnwsmhcx7yrjzc5fvlfafq8pn6g7", + "dydx1jm730j5gayk642e7xpp7jvengsr9f0qn8cy0h5", + "dydx1q8cxvz3zuxwqx7gj75nlgjp77v7k5etqz845rt", + "dydx1rnue9n6c89zsh23j83yf8jyjr784glxcf7eft7", + "dydx1lgkhv0lyyz0fl2d6t7ck7j2tnpvv5kgrj66wy2", + "dydx17yyqng77pwgydk0w7nnfjx2v5estszmw3nuf0q", + "dydx1zl07n609duu8znraxv84x33wtvmukqyc004v2t", + "dydx1s2ssg8cwewgy2chlxxyug3ktlmapkqda73322r", + "dydx1qge5u3nt22mj2gxdtdre7rzmq7xkngjt8n7aar", + "dydx10vpwv8lzxnxcr7fka4lyazkfmdl8scawul7v73", + "dydx1udgsnvp8aymuqztfdj3nxy6rzgv8wzdypzghux", + "dydx16lhlup55vcm6jn8p3tzgml0vqlft2f5ad0auhn", + "dydx1k4mwqc8uh29fka4vq2xk4fwemmx0mrw8xk9qvk", + "dydx1w7msmu93v4q88vz7342zgysszajaktnga07qae", + "dydx1hmzl64lr2ydj3wjs3hlctmemfg4t6pvrprm7z5", + "dydx1wmxlg3qlq407vmlnf7s03mudlt8d9q7m2xwrfn", + "dydx1eu0se8xez2c7u7cak0dea9fyg7u6lr4hdfstm5", + "dydx1xs4vcgepsfg0lg2kpz4qs32ap9n08tdrsdmy3q", + "dydx1w43q3tzglgwa829kp0z4fhwezs834wytmtvtz8", + "dydx1aznz4wx9svy4u3w6lj7tjgu7a7df3r3y7hgu3l", + "dydx1ghu87amp70jse0gkrc25dcnydd7q8fk3hadyqj", + "dydx1k6ukhgrya3ckvwl7ru2m2e48zm89sxsk7y550p", + "dydx10xjmqkrkr7zmm2mcmm0vm9rg8e5s34v4lu7l2y", + "dydx1hypnjc6h2u880l05vtsydlfc3zlxpc2kna0672", + "dydx1t70zr8vdhj9lny37ym035va4sae0xshf4p7hqy", + "dydx1xteq2x2tqr2t2ltfp30z7ffc4nkeu5sngclehf", + "dydx165rmts0wanx4n3m79tzajxyh5uelcnhl203r60", + "dydx10tv942awlrpdhdw4tfg29dwkqxt3y0k8cmqjdj", + "dydx1p2rkhjfx8gdlshq9lezx7ay0x9ajkelxv5f8un", + "dydx1wqd7ayyksmcyzhews5v5yly4xsmcd5fxdvda8n", + "dydx1u5f26he50phft9x6r2r64t998wxxm4epa6drz8", + "dydx1g8x98ftq478cceh9atyvrlvdfgvnep3vx4njdf", + "dydx1u7gngdkpvakkpq9gee7dy5ds793cm8nm2kcupp", + "dydx1nmdwp0e4w0mz7skwzf23p6kunk9g9jmsmlsmn2", + "dydx10qrg26tzu76yursp4kek2wqx4erhz6a8ltm8ld", + "dydx1zwhjkda0zte2m26h8en8gu5cacunmj58q03rat", + "dydx14l6v3j7mxsmrt85wu4rdnfjt9p7mmnh02ljnns", + "dydx1uaadtwzlguzdgpld8w0k6qr73ezzqly7eayn88", + "dydx16wzgq5lkh5fmyy6cmem923d9sx244wyp79xthh", + "dydx1ugje8rp83mclcj6j7r2fjwpy6qdphak33ynlfs", + "dydx1l6lxuufxvt8s8zfq230fee7de637hd2ulr7hms", + "dydx1yqq0agqsh9ah2rvv0y8cy66tckwt7g2jtzwvdl", + "dydx18rcnds9eggpcnsplvwsv79zs0qxh9g56f07vew", + "dydx1jcsmpkdemse7rvnryvyd6qu4fhkqdnm63ztxc6", + "dydx1ywwh4f3ggrmlntc8j0sq522pklw4fdgnjreu3e", + "dydx1vxa9dzex9fz30tmls45cgvw3c850py6hgaclpg", + "dydx16dcqsrr3ylpw6ta4wclfmuz0aawn270u70cckh", + "dydx1pfsemqfl5exhgmcx26ve82208p6km5n7f8qwcr", + "dydx1qf0ecwjw4y4dj24v3f8qr4n5q9fnp588565l7r", + "dydx1p9wrl82wazsadazyz02ujrf6mpyf0x2mzygt9j", + "dydx1m7kpfc3tplt6p0xg7xy268sdftjnrlcvjnznd9", + "dydx1ufklpzkd7qfg686q7xfgsvn8yc668z5hjcgzfm", + "dydx13dx7cwa07xcat6ddnhd3ep78mvwsjtmhh2tqpk", + "dydx1nvfqk0vrnyw8tpkh6wlu2477evf75k00enwsg3", + "dydx1x8w5m9k2tqtr3jf962a7t7dl3yjv8acxekypa9", + "dydx1khz98cxnmla586f3kgxzrpdmp2u7ejxks93k4c", + "dydx1qdxh7xp0xza5vh0wx4zw875n737tvje93cl7pc", + "dydx1mjqramqh64fxdh235zgymncmcqc2slf0dmzeyr", + "dydx1sh6q6uz29639sss3dudxgj20rfnsxmwelkcenl", + "dydx1cwuk8k45re6txnfk84znjl8glfwgkfsclx83f3", + "dydx1zqmcvwts9gtrumcz2ygxxaq5ahlxsx68dmgyz6", + "dydx1maujg2meqp2ufwz2enp5v4u4ejjnqsfy8su2al", + "dydx1d585rdwk2luwkfgpy4qwxd4zqkn8d0ht2ns5lr", + "dydx1d73xhkuyhxz7udww8k472sx5gvfla75kvxv7u4", + "dydx10gln5pxsx7kfgl5xrmkklttxuxx5caj0hku3nm", + "dydx1p2xtr8n7anjqce5uhjz0qjusns7kg0a0r8trwc", + "dydx105yse3agekzsxa8ut9zhpck3m4r3whnm5lzvz0", + "dydx173njptl9z2cr9gzk8efnfswtuf065t776wuaaj", + "dydx1dr6ux6j3ksgthjcu49sh7laxlxudkzy0ppu4sv", + "dydx1jxvwf5wkvsg74nhjrg80e4ydphcqqazzmrkfmf", + "dydx1y9p9u366rsne6gmrxxcrk6nlh5q9cn7d7e4p9d", + "dydx143c7fgexgjq0tc0pgv5wrgt9z7yegmln67unx2", + "dydx1t8k7xg623tmcqt00lwstsfz07uy6dfu442mydh", + "dydx1vncce7q530suvptmha39xfz4hhqngnec0jelm7", + "dydx1pqgp9adfg0ys9eew4pjkkndznvtaplm4fdlnqx", + "dydx1qxa00mm5rss9yk76t6rfah53j2a6e9jnctafyg", + "dydx1m4ygmzff0xvw27ffyzkksmy574hp9hzpjxsadx", + "dydx1ygzpk9rml9xsnaan2geuxcm3dvxvjlspltnl8w", + "dydx1cr52um3fhlglnfh885gl8m08ad5cvyq9nec5d6", + "dydx1tfxr5ajlz7ssznlxmw77l4x8yazdsd8y05p0jk", + "dydx1gshdv3k4czpfw9qx6t5dynjyr5fuxzyr87kkmu", + "dydx1t57rh98ey27qz7wdnhx8grk9qzh9xs8wzetksy", + "dydx14zp5xhvwrgd4calxnmtlded6wct5gsw69nduua", + "dydx1j3fru6f76n80p087lshfex39cqqlh8yhj5zhf9", + "dydx16verf7jpvecnunczwt4zeznpztvxq3eeweeupw", + "dydx1urh5pxlcn07g5trwh2ad22aufc7jq66ezvavms", + "dydx1t5ag9x6v762hh38atuh7ajdhcfw2r2yp6k9075", + "dydx14r2hywgahganah0pykamklna9xhrfnygpcdpe6", + "dydx1u6z94r8l84jv9w8a2pytkf079kya3tely7csrh", + "dydx1xlm2fqfkj75whq96mnvcusuws5ahm4ddpp63la", + "dydx14z45u3jlsqs5sw2zp26vdqt2ks00yj5lk2k50q", + "dydx1m648r7q24w5yqj0kkgwxs69wu5jx2rwfxpmke9", + "dydx159yahg9wq56m290l7c3p7fg44sase4vjretmwx", + "dydx15ug9zvlylzlll2a4edmu3lkzm994jpu6adpeta", + "dydx1js7sq08kz26eudhccsn06njyw256q8nycwfvlj", + "dydx1vkpaxyjmykw2quwxfwe8mdrre9d0wd6tq08z0f", + "dydx1zj0llv8stsedfmtjhkpwgv38k9qhsw4fv4pvfn", + "dydx1ldv2e2gswu7d4yhg2fm7fg7pw40utmzvc2eag3", + "dydx1h8qvryf0paav8ca4re4tleftemqt5wtahnscky", + "dydx1rw0vcq8rrqj8pc7q3zp3wvczzmtm455pgk9euc", + "dydx16jrtagalpwc7cp5y4jpllvfneacarnem702va3", + "dydx1t8xs7kmlgnft5qd9s6yj6804ltay3kwmn9qnjm", + "dydx1t78t82fqyxsk5w9pg4zsryaj43xyrmvfcdjk0t", + "dydx17gc7knlzzvusvdajmennuvs4qeq6k7rpyds8ec", + "dydx120pudly4qe06r06adf5hg6cfhwwf8tvxqyrnpt", + "dydx1ln60wwc4xadekv9u9sc8k2lk5ucypapt4ld080", + "dydx17qmsv8cl8wc3x4th3uzp07svk6maascs2x7xku", + "dydx1ne2zhv9f50r0c2yc83wqphzn2p9vfct4rqrp7k", + "dydx16qrthwngs9lat43t8z25jk0p5vcfl93zsdn6dz", + "dydx1h5yk5sg78mxggxm00mflfw6myka84j0pfk2anh", + "dydx1wh06tg9zmf3rglz5mdnepzjafkkjf35ht5kn9a", + "dydx1dxz048hlu5mfm5dzmncdczdygg3f2w2n3ja79s", + "dydx1gnt6rtmvum8x7tfgwzz4tfqultuxdfvn959ets", + "dydx1uhzmra93m2n04mw3p6vn2pmzp0zvyulaw75dgr", + "dydx1fmgj8d2rh3nzkjuuxc5hzp3ctnt4q2zw8xq8gr", + "dydx1uy7e30zxdpdjrs5u66cu2q908dljfcj627ts83", + "dydx1p0sq68rjuhkgmedw0fs5v2su50fjan9at7lnvt", + "dydx1f3m02mnsthls9qj7n8atlad7frtv7hl3ka3xrk", + "dydx1lqt006ypsuc89psf7hee3ece7cls9xw7cv5fp4", + "dydx1avmw9ldvzyads8vyq9r8al6hgr2m28mj8ts5du", + "dydx12awnaa4k8u3vdpnn8faw7p8wq9mka4543aukqt", + "dydx1yk5fcvvac3sc4387usply9h9ac923fjunl92ws", + "dydx1x2m477sfzyslh7fmlx8u59nv7js3m88u9vxp9f", + "dydx1a758nxhdlseepwtfswvdsxvxtcsjf73qshv3jj", + "dydx19ua8s30gpjsdzr3s2k4sky888jvzappe7cexh7", + "dydx1uvwp8uyj8usta8tf93xu2dhhwayd9jal9c0dly", + "dydx10the538lt62638tx09utud552ypaefdz0hjxn8", + "dydx1l82v6gtlslhg2y2nrnanj9vu7wv3supn202wlq", + "dydx1wsuahrqphwyc9pyrasghnex409gkyzfmwxyht9", + "dydx13k3n6gjy9n0f9z8ugmhj2w6c942taydcxnypv6", + "dydx1skupl9ncqgj9c27yvn6zx7rstfgxde7h4kp0hp", + "dydx1lyedfevjemhmgsp9pywjju30djk933u8skt455", + "dydx1xh8gwwfau3td7zrj02vr6vjyg60xamt49cjxy6", + "dydx1dnq47aeqgsjrw7d6y3epr3d4vgx3w5lw3uhax8", + "dydx1mtfa8dmq47ejack3keuja52h3jfme6gw5xhu7z", + "dydx1rla7wkv5e7rs09t94lz9lgety7hhw2hxdmj20r", + "dydx1ktxqfvsrs83lmuj8mu2zq45k643fgxcnvvrfqv", + "dydx13ajxut77k3f8lyh43zds7nng6fpcagnz2z75sc", + "dydx1t9vy5me0j6jnreehkeznw4h46x9xukc52dl6u7", + "dydx1e2az46jj79nxlsnjm48660yvvqm5ww397rdksf", + "dydx1u6grn09smxquvjs2mpcqpf5mpqsjp7064jafqz", + "dydx1ke08x2wazgxqtka83l4ce48tdr8scxrcn64m2x", + "dydx1uxsjcehs034fwpkedl2scv2tefnu0ugcycgq98", + "dydx122l67xf0t7cgf6hlwzjus0l07dudlkwzk6q9pp", + "dydx157u6rfr8fj92qnntd6c4qpjc2yzdemrdqmqgdc", + "dydx1a2h30vswjqdv548tz2uj9vu0sxaw7mhdka8wyf", + "dydx1mlt45qqmcnxjlkw73yfeyrsgvrmylfjzxxqzut", + "dydx109gwy6kj4ukg3vunegapcxgdsvkjacuga2jtt8", + "dydx1nlh7pe5f6zjjsl7w6ufuekl6nqpx8e5g5xuzmh", + "dydx1ypfl9vkcj4jqsrm5hw3em8dr0rpz5wlvgp27h5", + "dydx1qfaygwfkqd4ev9k0k5yug5ld4m4v074uqhg84k", + "dydx1f0fy2rwupq0l3cj8z0r0rvnjz4syhfr7dk7fzs", + "dydx1mj5tzczz06qcvpjryr7evcmh6pl09dcw3v20jg", + "dydx1yzjz5l94ec6r7vu87j9dxhcxxvky446npxp0jm", + "dydx1hvnxau6t8s3ap7xxu8ftw63zgcpqdtsr43duzz", + "dydx179t4qpuekqylhvy7k48dgsx4wjazhxnkj0s8kz", + "dydx1qqtmsxut36eqvrjnjss2u2cqytmje7e382mff5", + "dydx1e6t4xhpfgqzjcg60pwkfq6duhnqkk4d86tehqd", + "dydx16ht5a8vzrv7axcq6cxly56066sgqp9rkc4er5l", + "dydx168rp8mmavkl9utk5gum25wncw94qyyks0gcgn9", + "dydx1sstnyetcrv52kvmtkxtc29l880kq7a6kp6gnnn", + "dydx1lwcppkrl0kjywa72s4awer4909s7kl590app0u", + "dydx1znckyu28lt46vw2pr30s2s52atqjkyuc2kk3j6", + "dydx1vhgf238gt8h6c6wz6gew44zt8syd29e24j2ywt", + "dydx164n472f2nz3542mdfg75jt4v0fjdzsnk5vmleg", + "dydx1fu32mr80ueuyuhn95gezzyjjv447pnrt3rzwqw", + "dydx1julvv9v360frmw3t0fl6qpg85600jrdmk3w65j", + "dydx1lsp8kd6e4xgxcwcaum9us9xjy878vu6e4s3u2r", + "dydx1ldx4m9gc5qaf9naj8ny2nw2q8ulx26axxgwwld", + "dydx13wr87v4pxma667hqtetm36t6ygqaqe792a8ggj", + "dydx1d0evgy7dp5k0z095ym7hl99ams22mm4wcsfdg2", + "dydx1ezwd73flvlk376z2xpkk3tqxu8wj6arqdhlht0", + "dydx13r0ddcvtwhd5vfd63sjwwd05vzxwqlg8740jac", + "dydx1a4fr0fqu2xkrxls04we2cwu2cyw8z9dre2j958", + "dydx1p70lhgq2ugd3qfhls3jzh90gtrcpxwc4afealj", + "dydx1mqkc9q3twgmcxkzchlg20tmf00gzhnx699g94m", + "dydx1tlux0e3mezkupy9lznqvu7fqzcqtagxxea9zpn", + "dydx103qm3rke7c0sr95mc4vrm5780fynpm6gqaw8gq", + "dydx1x0havspcy0xymx5elt82fmrcwx6r0hln8e9szr", + "dydx1uuhztdpxjdxvx78vvspyqs0xjeanwnrwfg5gx9", + "dydx13qqyudj7xfdnrn2sm2qzf8avj365suaq8aza4y", + "dydx1tq3rjldj8srxs9vpqvmaqgmrlpu5yyyqpclzyl", + "dydx1vkpqm37hx98f5xdhqyusj5rtuv3w8mwau9h5js", + "dydx1c3qyrladc499nw284es64rc69qs3r8uecklr45", + "dydx1lhtglmwdcq9aj7za4l0j4v78ca94u4uklj0slk", + "dydx1jywtfqsp922kq0a2p4wy93n88k8dzxx37mdt5v", + "dydx148myplswgu5rgnkfa0epaczp76dr9lq23g24n6", + "dydx1w28hpq83m3p23jws7lysrrvqllylkfrmx5e7hw", + "dydx1kqgckzvjfxj33zjwp55rlspe5hlx2n94yyxymm", + "dydx1u0r54a9c3xtdwwjh6yk2z5v0xmj3sn3lg7gxcm", + "dydx18y8vek7sk49mfczgsnxr58hxx0ea75qm9autcs", + "dydx1fynsm96555ze3n5ws7agx7u5kfr9w7hjtzu30d", + "dydx1r2dylxzh8vx4agttkuw0s6yef96e69648304su", + "dydx19mq4hgxfnegfs9rhtedu6twazteuh9vg4vp822", + "dydx17x0yejvkadu6qkqx4m7rv3smzlgu7mfmy78p78", + "dydx127yqx3qf6mvu6550gnn4gu4u6w4z6n3glcgrxf", + "dydx15pm4mtx5tjhhck5nra5ehmjnhpjkz3j04k5cyy", + "dydx1tudx55qcf2x89ng89pt57rcul5htvjkvxhs3lh", + "dydx1x3m65kg8twj47jcz9lm2j7yll3823vppwennnl", + "dydx1qqhhvj9w5d2ezs7cj09u3mkrq7cn38jlun3qx9", + "dydx1ltr7g0n9v0xqzrnvxaq26zg2survlpa5x72zjr", + "dydx1xxtkcukjdgvmct8gmfudwzsghy327dd32fzcfn", + "dydx1aamd4du8wcr2tlxp4lfak63v9ugd4hw2jcuk5z", + "dydx15w7g7qfm5lycetyxduex9gdl43m5txe3gtaja0", + "dydx17yan8r7kp2znscalr5w652y2rldy6jkjawvqgf", + "dydx1n7t2efwe2ps4l2yua9s72nrj3ww8n7e745nnmg", + "dydx1ftpsqg5wzmf2u2k5mve0fk9vn96wr0pk7uvp2h", + "dydx1jwlpsz6tqk05dr3na6vda3djpk3hxfkqmf76et", + "dydx1j60vgxpwqcsnhn25zrzvmrt7mp0cj7ds5pjgmu", + "dydx13mxu2654pwg7gjwc9rtcmzyz6rs8v8tk3tw0xh", + "dydx1hvdu3rd4nwrzc3gamy425nwye7a8hcg3emaw0q", + "dydx1h8kuhfsk6vas67fk95ta4uulnu2s90ed4hp8cq", + "dydx1l5l5pafvqz7c7n0g6g7rr0h7m0k5y0w6v0mgqc", + "dydx1v8h088avkqru5gtj6klz9y62hnyuc8f42q8rqv", + "dydx1ep5ps0t3vc2c4gjagsrc55x3nm7d3lm5d30xr0", + "dydx1xwck2egcxc2zahm0ke6uz6h664k3rx94thy43c", + "dydx1kjjduflfhyn2waf82nggujw632462dxmm9p5en", + "dydx1jzpyt0peau6hvtsrvnnwcfvgxlayx2yy8aed2j", + "dydx1unzd6lthna4yyuzfsvqrg69g928fus7vrxs0rv", + "dydx1cyjc6ax9p3cly83lnpuq6qfcpcdu7pafjfmfqk", + "dydx1a4zz0m73p6unwjyfk0dcevvqjv5jgaunjklsqa", + "dydx1tuwewx4tcd73arr0xwn8gt9jd7ztfvc84czgw0", + "dydx1ucu5gj6qx3yqfe2v2y9njymts4gjkfv9l6edfj", + "dydx1hehr0rj8hwjj64ld4zlwshxmkdf86rwq6x2tht", + "dydx1f4779p7fup47v3akk2f37ryxrswqgqckszsgnx", + "dydx1krfjdwdtjkm9yhct4k3tmv8lpcy83ggycrfhap", + "dydx1xz66r48yjsdg4f4kzpj7kphet9kn8f4tw55vem", + "dydx1shappxqwcp4eemld6hfgukaa4qhwjhyh7qd69h", + "dydx1y257yxjpxslw2zmvhnnplanq8wv6rlny9uyrya", + "dydx1qhjvmuqyvykz9h6eednmktmytw6ljjkt9yz6cj", + "dydx1gz4caexa04z60g4yhm7vwzlv44nl09kct4ml3z", + "dydx1ea5jxaepxjz0nvygua7hgz7vjdcqj9ux72rhax", + "dydx1str8m7mwnd4qzkdvf8swxw6swjtacj2alc0gvu", + "dydx17tj8yas0rwkzetr0m922z45au8nnh08n4uxxv3", + "dydx140w7yjq0r328alpnt6u4yvdp8zjy8rasd3m8fw", + "dydx1nakyxuzq2j4u4a3fer5qpfzgjrnsenxv9twarm", + "dydx1yhj0p8aygrde39kpnzk3nsnj023jwgwuxpr7my", + "dydx13mf3se0fm2yunv6xvyhht0ud4y8lqw4z032rll", + "dydx1dep64mqk7jw405wqg536mkre4hncjarxj3nkgy", + "dydx1zzrgxzquengxmxy3gwxjne9n22avupvucj2wkw", + "dydx153yx6axwtzgwzg2sjeqfp8l4wma9ld7tqvp2l3", + "dydx1fd30jnqjyzycfy5umdphu7cmj9hlag2a6w4vgj", + "dydx128xztru3e740ygfa3leahn2c8e5s5hh257tmzc", + "dydx12ffsxw24542kx9akpj09r5s46fjwshe0h3uexp", + "dydx12hsx3pmf30rcutgrsa6h3p94z356r3z7nacrpk", + "dydx1h9v2acqd60mz65jlu9zqgzmp0rec2yvepr8ctx", + "dydx16f4auwn2e34vweyx3wm5k2a0ag64uks37vqyat", + "dydx1u8nhslzzet7vkvpmmptjms35ax8vz0p68zh3q9", + "dydx1pjf4vh9mltwnc354jnx258f30kpz6swathfpmf", + "dydx1ttswxy4wc7tasze4y9whsgjzmn8lp9c0spa7m2", + "dydx1z5dcy878070l68kt4mma6n0zxmaj9gy2qky5z5", + "dydx1u4fr9988c0dxh0wstcwtvs56fesvyjhjspstfw", + "dydx1rcungxvrvhlkxf88ynz48dwehad94300ss57td", + "dydx1tg6fg2gr758mhwggezklxuqedfvmzj4ku2gp9m", + "dydx1epdp257gnp03lgaz9jng762gz9k6px8uzq0m8j", + "dydx1xu6akl898masmyk6mpxaq7xxqkppqgqf0szcpk", + "dydx1zmvz8885nzaluqugrhw6jwnfpfn9ck4lm39un7", + "dydx18w8pv4w2q2f3nmugwnqqzqzh88g8d00r0v8zar", + "dydx1f0ae7lqq4jkyk77xsa4xzy56frtppvmvq94sds", + "dydx1hwrg695m93vtgxaz7y0ajksdy2dt0z7pywf0mq", + "dydx1866842tvd9l0al02qetplmww2x8vlcfprqu5pq", + "dydx17m75fny66896cez9awq5xzkyl3klsum94gjslm", + "dydx1c942yfrhdtur6y4ruummkty5cjffyfd2n5llyn", + "dydx1a2l9nn0p65n56m7v2rmdr2xywc6rdan0ja0wy3", + "dydx1v0uhz6c3n4es035csyy8g3tj76cm945xc8lcl8", + "dydx16gqg5fzkzygnr8cqve3fx40r3a4l5t4we2f52d", + "dydx1amtgkgdhfgnh9ltdphyejwcfyuw83dq9wy7p88", + "dydx1p9a73wmlxw9y00ryu06z037dtysaag5zgzmx0c", + "dydx1mnvwr24e3fjvhks0qaxpkmqv6yp3a4z982ddln", + "dydx1rrsmmgm2ppwp3la48k9z36s9cmu5r9h9v39pjt", + "dydx1lkc72m2zxgznqnv3fvyzafvx04eyl6wn0rhckq", + "dydx13zv4hquy5x0jzr63283u655vphhn6885qvj3z5", + "dydx1sdkq86fsl6nw6q97py6vu8f8mn4t6cnr5kutt3", + "dydx1u6s5zudf2s8gvdjncf2ulx75mgd03c426nmpl2", + "dydx1dr4cl800cxwpwgh5hucg7j04xvr4u84cwds35d", + "dydx1a97lkzaz9a6e7gfl7drkvg8s74mesxwyf62awy", + "dydx1aflxx7d6vfdcwah4q26khffrymcxa6fhf09s4w", + "dydx14yltmpnjep242c7e50726vw0ndwkhcefccme8w", + "dydx1j62hmzeahk638e0sys0snp46ardnnjyt6zntgr", + "dydx14hcsff5mgyfwmfvk4xt5algtpgfrwwgyjldvkx", + "dydx1cehglfwacqv6u5sjzs6um3pdjj7fneh33h2dda", + "dydx19uamjeurhyluz2u4mz3eseq9cswcwmz34qs604", + "dydx172r5g4kyda0m6t4vcd7dpyv5wqra5uwxg5hfvr", + "dydx1ujrsn6c93xlqzp5k6ruuy9n4ya74lfszl2k7m6", + "dydx14fqkjn2y3lmy25ncdnvx8dtua73vhv6l70qeu2", + "dydx15vkpmul4q4kckxfhu68saf0e38r2sahf48286m", + "dydx173wh9xagdupy3r83fcctmg0de36qqcd34frr3e", + "dydx14ykkwhkkz8k96vhevmv9yekx84mhfgxvl2v9l6", + "dydx1r2ud5tll2kysn7ld53uku4nnl83jsl8xt04cqz", + "dydx1fmryf9800v2e2ffkenvqt0ted6s2r7pe8pg68v", + "dydx1pk3jnpk2ptyq8xe3r8lfjtfmu00tf0genfkrfu", + "dydx12nymdrj3k0r6e0jfhdq38vr42uv0vcldsk03hn", + "dydx1uy0k57kk2ggx6jr3q4tu2003r0ggtdnfyek992", + "dydx152cvrtndflyxnnatmcp65074m6lwz95ykn99pn", + "dydx10s2smh4d37825jxeltnxa92umwuka9dptdksfd", + "dydx1pty7mmryjjzfss7paa2zgm4v34w6w9fssakh7e", + "dydx1el7445n82906lthcdu63pxlzy04p9wq4ag4rkn", + "dydx1pwzhnuhfxsgc8fg7pfura3tumjuzy7wnpd3jgz", + "dydx1ay34zel9z42c8tjed342dcx778z3g9n2wg3wqv", + "dydx1psxcvasr9c3tazgtnuw0kk3rzrk92hfjh78r9r", + "dydx12yy6wfz5jex4df0chkglw3r5h4pdvmvzjuuwa0", + "dydx1hkl5m90l0jy0lwrcq4praklj9gjrhwhdwzfgrq", + "dydx1ecp87ghcm39h0k6uapyt0n6kqfye4kp586rl3p", + "dydx16ummzl2t6yj50f2zxqhrud5a89muwx8d9xh9d8", + "dydx1an577a02sw90p4yk9h6qycpu6h9a0crw9pqw8y", + "dydx19p9xqv3728sdpvmcvzqzl3fa7wtgexpsl9zy4a", + "dydx1zmqgm2q6fhn7p4yzd34dhawfs7nnfmemqtfy85", + "dydx1zyep6n8y6yvd2rd3rnv68hfg09nl2wzcz23ax5", + "dydx1enquft84e8sak7tf8qfja2nc5efjp473znfymm", + "dydx1tntcw5yf8u5979uqwxfx3w3dal0e6muyhnr9l8", + "dydx1m3trl6ge8tujpdff3fxflptryf3qd5zxla23sd", + "dydx1wkqhq2cs28g9zrsf2lw8mm096sh30txrhr4tg9", + "dydx1ewluwzy63xxs6l5wxhvvtmnlsusqkvla852w8e", + "dydx1wgesucaevtva6r52xeyq3nq99v70evxgrh6927", + "dydx12kssnmwmgxcxrqqxlmw8tj5htdyyfn2jqsh4de", + "dydx1ksn6zs4vaqwww6rz3ffz85vt5jjuaxpp97jdwd", + "dydx1jcfuh6grlg8kqss2ph9j65ksfwllzm68alq90d", + "dydx1p438d9qzz64gug5suqcr9hcfgted7500rwxyw8", + "dydx1d6c64wqdx4d787xg2tkha6mz5fpqsehwwslpx3", + "dydx1725xy0l7meatnsf0mduqzksd64keef4zfqlj0x", + "dydx16m4plf0dqtyk7muee4yxq53rr82kluusj5z564", + "dydx17vf2cy3nlkusmhv8al2ky7j0yq23qagazny7va", + "dydx1hsay7qw4dgken38egc2tfm6q5jku6p6ygzfhfz", + "dydx170ke6907mftcfyh4uwswrve5qc0ywkfmxlvgfy", + "dydx1reck93rx8kulva5l9jwk5cj32pyz8qcpnc0yjx", + "dydx1t4dmc92vfkde548cgvynry5mn2dsx6f888hznn", + "dydx1lsy0dspqkas6uu9etnrxm0m56ql8cqjych7a6h", + "dydx12fzxt46p923u4d3kn5qx4vgfndmpznd5a3kzmy", + "dydx1uxfrnn9a85l2frh07ywswyuszwevm0ejsz5eer", + "dydx1daly9xpmtkdmstn5vyehhupkruu2gy05sht3mn", + "dydx1sypeydhyzg3yw0xfzg2u98myev7kx4dh6z4cen", + "dydx1ps4957e20phuj3nzadwn4rk03kjmkx7jh4g77w", + "dydx14ewfnn2l8k6aq0r7fcgr5czm26f0r2zrqsr7qq", + "dydx1zwkthjn05ucaa9yt3kjeecutngn0hwtl8n37uv", + "dydx1dft70yp4raw8qp2p7mekq3q92ltgkf9m93jncx", + "dydx1g8kxcmekxuq69glc5yqxkggjep0szgfxyzdz37", + "dydx1taq7ray3348npcm04z59ja0hkz5gmmep8vp0yn", + "dydx1dlhxt8nq0svetzxts7r8cpm9g3mvhqent2yhjf", + "dydx1hax6psxgfzzg7d7jdq5t46uzggtnjlh3anlxd9", + "dydx1lft8j9hp5ufe84acsu3cqme8drzrnhv8qqjmwf", + "dydx1uqu7dx9jdm6nxktna3jcg9ras9qaq9gwndnwsx", + "dydx1hvmskeqsg40g8crnjd0lv9aheg38hqcyly89x3", + "dydx1d7sns0vaaj8levjjvlnq6j58jhmerhl20jqcca", + "dydx1mx48wzxeqnu7eyx7wuy5m5vsd8ksc9nnwgv0fp", + "dydx1rt0095a0nw4nnh475tvr86eza5xg4gezgecsjd", + "dydx13eculw0thd6wsflrx4f89mpmwvfumsttqm2thd", + "dydx1jnmfqk6es2adxqav9u9rf77uqtz0pskswlvyy2", + "dydx1lz6dcttamwnevxmmgq3c3n5ld9988gjylvny8d", + "dydx1tgyr2l8qrgsmlg4qunnnqgk3pyxr8d0kvj7asl", + "dydx1msh9s27fy7snkv3l5uxsw65pjpp5vhn3u6wd6c", + "dydx1ad36c2nlgnn7j99l2ycg7hn24xawx0z2sflw0j", + "dydx1c3qzq9utcw4pahc7c3gu4pvufjv5q8me6eaxth", + "dydx13nud0wxuq45zex6d3ale3jtelhg69c6x4qje7c", + "dydx1pjwt3s0zm6umkw3ztn5ea66c3wa8kwv5lysaly", + "dydx1jpqpld87ujtre3q8lfa0vtln250w5kzhllvw5g", + "dydx1swevmlygdpan8wf2uysqhu7al6k4000xz4xj23", + "dydx13h8zwhmk7nn035puu3487vdxqn2m7mexmtpffd", + "dydx1htr6e5uetretnjre8ccr5fmq6c3r9uf3a8kuz8", + "dydx1xfy6hgy8vl7jq4n9392v6ptw6y2z482tcfjps9", + "dydx1sqfzckclekr4lajkdl8gn4xd0wqatvwqms3cks", + "dydx1xtlzeytvwlyvsef23qzm9fj9q38fx57z3cgyyw", + "dydx13xya3ajssxedzk78vpwftzdvul2u5jnxgfx2k9", + "dydx1z6upayy9yvn4mhqy9t005f0z9thm4jqtysz6fw", + "dydx1u79x8cr2hnp089y7q4rttkwyxpnu7uvaulgvwr", + "dydx170ecvf6d850y89mzpg0xua4yr2fvu3dtajrlfd", + "dydx16xgae7h7u69eat8ug6njqh5p27853e2j6panry", + "dydx1g9z25g0p6vemtzwkls4ghf83zmhmpdhrg9ecph", + "dydx16vlx2357e49g5g0l78363hv9d0y9fg4u2aq8u5", + "dydx162m38hggr9zd46xy0wgu2ld4q3tsn7jkv6fn73", + "dydx1p7m7hct5j66n73ywat7fn59clsmezmt3amnvag", + "dydx1x5c9hppyp28jeuavqqxmd5w0tj9m0f3wqvw9cz", + "dydx1s9k3mz9x532kwtga47su5vle0y03hwswc06szf", + "dydx1dvavn0hn4fqp0uttf3x087fs3z5xk4nuvtnahs", + "dydx12nz2e9e0ujpjkv7e4n6kqa4sek2yn3r9w3fwsn", + "dydx19lw6x9nl8y028wv8v8284f0wz25veg6ec5sr5c", + "dydx103pz9nvptklv39ksqmnd0722fl4w6wmjqh7rkh", + "dydx1a9jnns7c77c7d2mp52q9s9af5f2pa68fe9hq5q", + "dydx1gh3wthzhvv9j7v2kjtqcr058j3ctkj2hmdkzul", + "dydx18a7xfmgrerxv9tvjnp69lw9644rr86j7njdlyu", + "dydx1y87vgmzwav7kupehxga0eaghx2t0s4e50hjd8g", + "dydx12y260dh4xjaxxma6yf2nj04hyr4rwv8ecsj2dl", + "dydx1da35xu2vnxsfmdrhdxtlxq6zw42ekty25qadk2", + "dydx1v6ec0x9x6un24nhk72yr9vnl2lpxh4wr3254nz", + "dydx1mrcgapexseju7hvgjrwrlm6ea77xj3u8yds8qr", + "dydx1gn58z4velw93dunl6nwepwzhzep8krwrj6n55f", + "dydx1vs2v0emt02wgfm2hrnkf443ehmm39ejw705gzl", + "dydx1cnyscxsssecf07xpcvgfzxfzh4kwvalgq5497u", + "dydx1mcthrs9rtfmuxcvyfua729f8rqsfngzc358m3a", + "dydx14ztmjl9a6zchvagrszfudcjymf0wtkzfxm8h5f", + "dydx1xemyg7d3v874pnu8ffzgfzzz4uzw69g08mv0sp", + "dydx1zjw4uan9kaxngvrlsy5xhhfq6kpmdfxmk65ffc", + "dydx1lu7xgjrx7805kwzh8ts9cy9n7ex65rsdff2cmu", + "dydx1gzndur994qhs3p4lsrnmpjjxkcq87ddd506apf", + "dydx1zr5qkvrd7dhy64flvqu4ap2rqkrl04zrlvw27l", + "dydx17na2tzh6vzt6jg8czqd4dvnycg2yk4qpja3wq8", + "dydx1nssusrtahqx930gt0zgyz0khz5p2dmx920vr83", + "dydx1fz9p9k79s9geqzm6nyk2lyd0mvd9f7epkrssc3", + "dydx16tfy53eyc8zkhwckptvzcz06dvc7acfft63es5", + "dydx10khmrkd6n0sf58haw2dxjmzdzg4hntv7zlgg7z", + "dydx1xnkyltcz403nvmkufd8nv6ryrd6p8twac2vzgq", + "dydx1a8dfq0h3algxm05qlf3a5cqdtc3merqn24rdcw", + "dydx13s6gz59uwppdxjuwcutqkz66hchgculxydyqt4", + "dydx1egh03ke8mrzne270c7ehzr65zgn92x7t4v4l9v", + "dydx1jdrkd7k5k997apys69khajqraesr2z0ttwthds", + "dydx1m80j4vvlt58kehkqg7lgllmudaj6x6a0cj66jd", + "dydx1e93c7sufdhc0fqv27q2pzu68c2587lw5s2g72u", + "dydx1hj9d5rlsh2mpla5lerrahucjw3c2hjcsfjdvx8", + "dydx1kknp6m6zsl579fdyz9ptey5gf3lf506lp9s2ce", + "dydx1szc6we3qtd94ug4edz9ekxd3qdf0nmf4dp7gmk", + "dydx1tl7qm0lgfd0gf6xn2y8zmpd0dmuwc2w6agucgd", + "dydx198mwhgqe9p542y0u6yw6znfnd6g264wchm6dqh", + "dydx1ee66v92a2gcl8u4k3wrwwp392u5k8elm7jvles", + "dydx1gyy2va8graljwskgzpg8eg6rgwwjeyl8gk7p26", + "dydx1dhjnfxlaxwz4p4hq635rwdtdlvzym592eh6upd", + "dydx1dztpegqmn9my5nue47k9v2z9q0kwxsu9ajll5t", + "dydx1snj6wn6ynhetmg70gh55znjhneekj0cgd7rksm", + "dydx1y0g334tmtsr0sz6x5l57dt3j55l3wttzts4z4s", + "dydx1m63gcd2608xk6f8eu3k5hyvasg5xe3am38ehw0", + "dydx105k0ayzf03jjuhx4w76l5j73lphleea47y3rex", + "dydx1tsayf3q5qnt5w7ws45qdcyfnjl793tny397su7", + "dydx1pxrs5w4yg68ncn8ff2tkx07795775dmlsyxgta", + "dydx1p0yq28c3sw39dr0ztdpyksqmn52dt2jjkd3rjd", + "dydx1vkzwnqz9cemrrkktnuztdgwcy40yeddqpstg9v", + "dydx1pcwymxnpnuypm6577ghk5nwyvkt67a6366j6mk", + "dydx1jnw8a70fkw32eqmj02pmhy4zkr0yccprn7q0n4", + "dydx1fhejhkpdcgvprjtfscnzj6w46zdsqstdfrjyds", + "dydx16x4xcvnpadtrqga9p8ujx9zv4majr3fp0ff0sg", + "dydx1xj8ne0gnkz4p0jeyfgqs3tkp9dml5zvxwej2yv", + "dydx1x3tyd05h37ns200fznhppt7sscv99zln6d2lad", + "dydx1t5hx9v64fnucdwvsxkm8763f6flwm23s7fuuzf", + "dydx1m53spxkr7zs5pzddr63r790tshk8suqfwycy3c", + "dydx1xewhtnpuvyw63tnjtsqv50mm68gftwj8tj0xyd", + "dydx1gvmygvdjz8rdtxl92hh09svz70hgwxptp2dv2v", + "dydx19gnk2v40zzg9r3ujgv8ks8m8da9wcg497cy0et", + "dydx1zc5rxqcs5tumexgvr2splg9dtnd9p7pr232wte", + "dydx1ugrn0tjzytepufnd6lrvzk0je226f5xlgzr02f", + "dydx13we9ypxxnz6fvnshrjxv0qasxgunzg55ucpgqd", + "dydx1wcdh022509dj40md797mgsaqcmm3382rcn2390", + "dydx12lnx63ch9dgmcfeh86kn0ka0jurk73xsrqp7k6", + "dydx1yc5wc7l4crpf9rvxux6dsej32nwt3nlf4wgzzh", + "dydx1hr85dgx3rgdrk86f8n9ljgyrte038jsy07vcvp", + "dydx1ay9dwdc4njgar7ux2nulvz6evfkzy8h7tg5hdt", + "dydx10n42faxeky0rz9xelt0eu4md2uvu72nsykrfhr", + "dydx1vm7zjnfhfrvvnhv9lp0vnjkce9927mr4v94wn8", + "dydx16934e7m2dzprqjduqcuhk58zuz9c9tr93g3q0m", + "dydx1p6twfydzvzedujelvp249jetpxdyha2p9uz6gt", + "dydx16vltgyp5ht04lvrx9yv80fh27pjd8erx8lymqp", + "dydx1guvvg7sdqukcwd3qquax3xrswa0x3t6hqnuz3r", + "dydx1xj4ykxdryy27d7g0qmg7pzzlupgp6v5dndctys", + "dydx12vd742hw4c09q9styw9trutu57ydvyqhwe7nm7", + "dydx180y7u9mf8qpm56twdcjfn6e8a8ag7f28rfas99", + "dydx1s2z7q95sc7m4fytq230xjj9ayn43wp75u0vhpr", + "dydx16uhpk5wcqap05hc8cjns8y9974chyq5z37u52c", + "dydx1n0zwuhs3ya4d6wjpcjjx93ytta5lywed8qt9zr", + "dydx1n7pvskfj74nlzmrzmlxxhlmash365hrv9rvuf0", + "dydx17g25n70jqm684rewls574gukqcwh9ng7qcyvsh", + "dydx1cgrwf29ef9r7jvtazlssmqaknuwa6nveqmg563", + "dydx1llj9wjjrc2p6xu7hskxsfw0wttl65c7r3w4s9c", + "dydx1d43ep7c8pujkmhh3rcg4ulcsnsx4k3v9s7wrpu", + "dydx1qjhjv7xeqqk58udwhxptudl4kqpsmfxsxzqngj", + "dydx1q0cz96f5kk9k9gykmwccjpp0lcn3qm8jr4knym", + "dydx1snhancx8dvp06r3wrl4tjkwnwgge97svguld8q", + "dydx13wvt3gagl9anc6tud23p90jycjf78vwxpmjy9g", + "dydx13u4c3zsp5fx9kykxj9g474502l00amm7kaxjq0", + "dydx1e3ct4qua74jvz3qmqf2u40wjs99ftrupnprwk6", + "dydx1mgzamrxp94dmj2ms8e3an3cgmnxw5aukmy8uua", + "dydx1hc0ept6st237a9qc85sqh6njdfdsxh9w74x9cp", + "dydx1lvg79fea2mq0l0w58llsnyg7helxk2lh4zkw00", + "dydx1m0vy290l53wmwmwdgaaauu50m27zqwm6t0q4xe", + "dydx1et8gayjdr0thku5m8mucau75h32qudthulcwyc", + "dydx1wskphz4dl7qn3z9v54wn6gkl48sqalv6j2mk5u", + "dydx1alxvcvxq9zw2wnlhevxhknwa3qkwdqws0et9vv", + "dydx17hvyrh4ud2r3p4ahkp6vgrh52n407cscwcwr23", + "dydx13wjxargunw2yexu9x6eu54vfzep9w5fgxkplyx", + "dydx1u3atmt7z6cn2mvx73ls3zryej2achshj6x28hv", + "dydx154x7l9z8srgnavah60umpyjz877evyaa52ms2w", + "dydx1el8qhp6rys75gyfaj0ld4awm8l06f6khe7ytrh", + "dydx158ywd82k375mlx4ptvfknaep9r5cqnkhkrx5fz", + "dydx1vxdvpkq8tsy3ycqkw78ee4479lrw0pgaghuxeh", + "dydx1583w6cs7ysadsngsyvffuezpg70rtyudjxzkan", + "dydx1vjm6lgqm0mgulud0z4pn2azhsuy52fkg5fscgd", + "dydx12nucjv3sqk6exa8eccuen30u0uxr5cdxl25f0e", + "dydx1t3emt5prajx0z84a50ma37y799awl4uwmhn8ca", + "dydx1rp9tuxv5dejnqzh4a5t8nmjq5sd7d6k696hd6t", + "dydx1p8suhhgtveaeafmu7lfe56cwt7yrp7e2xy2twr", + "dydx1p3y5shu029ztsalvt6dasngmakmj5ua0uaruxs", + "dydx1trrj6ulfvhzfj6t0vyc27x8nd3m8arc2wshls4", + "dydx18ssqz8l9sufnfslxk4wjfl4m0kveeeyphty9s0", + "dydx1gtqr3xx7689r8g868ujhzywmfv727qxtp0250s", + "dydx1a58zes7c8c0gs3k7qnqaxnm5ya0kdne9hnfang", + "dydx1qgsq6r2yw0yhhac8fzq9ckzndkx9jg6gm75x3g", + "dydx1cm94xlc7cem7qcpsmwcw4a2ped95wrcvnzmq8l", + "dydx1tn2terkantqqfa0v36wweack6fhat20r3nd6hd", + "dydx1ur04uh28zc79gsynjyrgdlteeze8ry5t0drpvt", + "dydx147cypps7xa02avn3rnylgcnfzt5fgg5chs076m", + "dydx1pmrupkqjryxfkuzc6qqvaeq9ecu2gy9yxyqdpu", + "dydx1c6xtvjfq6jmn08pj3anupsjcl3llucvvxaflwr", + "dydx14nzk980ftsvshtx0eew7ux0rpkkzhk5d4e0rz8", + "dydx136rmzj43hwwakn7t9a2cxhnhz30e3sft7l4vke", + "dydx15kjv8sk5nnemgusm7pm797ezh8w5vrzr6fgmwg", + "dydx1ca94lmcy578pya4h4tz2gs543alu3y8kxstr6n", + "dydx1u44m85xyw6gs393n3frm8fu27cecey9e5jp5g2", + "dydx1mpqqlrqj5dlgyqnrgyd5ewyum36lr0j6lr8kfk", + "dydx1z5vh5femghuq0l3hevs4z83yg9yscghv4d7lsl", + "dydx1f74psv4d70g8flpa098ucnnlpvp6wpvphh7wf3", + "dydx18wrpv3n0q6sxx5n7ye9l459r38d7k8hldkkcav", + "dydx16q4nrfu8za44d2hunwulpuucct8nnqzz73tz3k", + "dydx1cc88wqnt3w50chefcfz649yzvaul6ahsrf3ata", + "dydx1f7zhr2y4eg5k0878f3qkrf29f3p8dzns5x8f3k", + "dydx1aqc66p68mvkeflwa5lq2cy5lzh77h7fsmgqfym", + "dydx1744xdwzgucvjlq3ry6vj00zg9r78lyad49xdqu", + "dydx1rhulm5cfxge56mqczwp9thpxwlhfn0jut0wz0a", + "dydx1dsctxxzpgtrrp2c34geyhu8vazzyl89t83rezg", + "dydx1heycy86nudghx3ttzkrn7tpmmlmlcrvzk7lpen", + "dydx164cn0xd8kveuq2pln4ga2423ypyfezxhu5te7p", + "dydx12cl9td2emcmc62ftsf0pjukrytzmwlz9ga5tmj", + "dydx1h0dp6694ff6p350yl8w05rkqsrs577jqfd8uxw", + "dydx1uft5stq3lndwrh5vr44anlsktv5a0c22vmehvl", + "dydx1u2zufel7qdzpjdsnlvyfwj0c6afvdcsquwms0z", + "dydx1h74ff6ax42tczcsqn6t9dc8js2h4whr6qz40tq", + "dydx1hv8lgh4x87ra28qpgk9kqrkskypxtmmd5f6az2", + "dydx10jarlu06q38n2nm7uh9kps9j7gymcflmyazyt8", + "dydx1acg35cwv7vm8hn335n0qzwp3vp6rke0dvythld", + "dydx1a57v8gc62x47y5ukxyvqxktq2wuur8882zjkm6", + "dydx1yh65rf8fsygkpxnyh343vrlfnjmaj7hx62qwn4", + "dydx19h6tseske0qh220a7dqe7w42gv9g8xmwg3j0mh", + "dydx1cre0kaxqmf3zea72a5h5dfcymm0t6gr70v2t9d", + "dydx18wv980x4tfxturl0nghyym679qxvazclev8t7w", + "dydx1eg9y07rw38krk9zg7w9eqznpfnd8fjrrd364u4", + "dydx1kcrmt4l6mladdzyfytsga28m22xjwxw9qh8qwl", + "dydx12k58g720ngrpez6s848x4euhe5ha4073w74z6q", + "dydx15wwguh8k07yk8ztk0xh96u5vq25nzn286pyqm6", + "dydx1v30pxq3ay2aty3xg7gdd65wddt534xz260fvn2", + "dydx1am5ehww4fn6lky5uwsste4ckyfdyedwxhgeey7", + "dydx1xv3aywaf87pz8v3588qsehqk4vxckvtquw6yt3", + "dydx17ajk238axgm44zt5kn2g4mgehzltvmy3hruh5c", + "dydx1gzls6x36d0l3jcrf6mxarap2ul43yfldw55ya9", + "dydx166tgpm7ft8888fex4u7dx87j6ytapa0k60s076", + "dydx13zh647wk46wey2egf37fgfqh20dwnchv2ydmnl", + "dydx1v0lttyudznsnhfjz4gpxmaphp0pp8xc58hdms7", + "dydx1k7dz4mkwf2cdf55eq3nln2vmkdflfjgvsy7l3u", + "dydx1v3y37n27aurqsxac7ked7m0k6ffdx2wdrmyu0a", + "dydx14dq72322cqv6fqvalyjygtlpr5a3enawv7fktf", + "dydx12qw9l98nadap337swm4hpk4kfgywnm9tsp7z9n", + "dydx1ccspxhaj2mt79mk2u4d853aums5csw85kad2e2", + "dydx1g9l3fcpvw5a5s2zumyvt4dnq4rj2tghlncsnxy", + "dydx15yjr0sfnkknqtgftgm4tnmwum5uf9zc7l4gu8z", + "dydx13e8nnntg3tex7d25ynymkazgw7yr8ulat53223", + "dydx14mwgsqdm3q45nytgzrystjrpsdjz0ya43l0vvp", + "dydx18q8hu2cyjnts254elhxh5zect79qc49j79qek9", + "dydx134akzn7sd3zm7evq3rayta6tn68szanuvydlt3", + "dydx1xq2c82suthtg8wts4vxknurfaergpgs74dlf09", + "dydx1r33dyyv9h8ytg5q4m5k3anfwy0n06w4clnk7sf", + "dydx1nef33wx387xdjm48yna9qwt8x5q7u4wwsmlpf9", + "dydx1wk28uytqztwnpdgrafwuhsah0yt3ghxwmsvj94", + "dydx139sxrmhenuh94sk86ynxlf7800sg4t4qfsup3u", + "dydx1pdp2yeg5wp3m0xv5rm6j0tm87938fe5jclg6tn", + "dydx15yrk0vnp7hftuzt0vy87kdd8cff80p5e7vs2vp", + "dydx1uwtwj0q3g5c292m0pq2pcplq6newdj9uqaxu9q", + "dydx1wftmu9e8cet3hrsqqxqnz30fsyguf0px48078p", + "dydx1dzkdhjqqq55dy3nx4n8umwq8hv24e6m5efu524", + "dydx16aa34tyy6wwylc4g3lhkd0q6akzcfsr6w8tpmm", + "dydx1qsc75gx20n0pa0g028e72tnnsyky08kh8cnvr0", + "dydx19hnc5vl884d9vkr3npap0ryvpwhkungwqjq0lx", + "dydx1jye5384dx6fpv7yap3txqefyqqek487u03qvfu", + "dydx1xvwmm6vm2k76lmnl5cyj6gznrf0u0uz6k3fhe0", + "dydx1txk5dj73dlm79vvux8ensv24q658md75dvg4j0", + "dydx104phveetaf5hhtny3q5n0633428fxr8qfujqen", + "dydx1cvpnacdcga46lk84mfrra4k83azn7spppdx7lk", + "dydx12nk35x5ujvxd0zrwe4yqr7vryfg5a4vk2zzswq", + "dydx105lh3y0a5863csa9csxgszsa6d2dg505vpsgdw", + "dydx10sutnwn5z0qusmf0a76pdcxgx6hc7rxukqlff5", + "dydx134equcyjwwul8nugwqajnmhqd6zpr452knx4ek", + "dydx1j8syr75fz3dhusgwyydahqsquwkywsnpd8td8j", + "dydx1h7mk8fyf426wmypw9a584j58x8rfgswya6t72m", + "dydx1y5tct8csksvlqqumfh9wc5vj7w4h0juerk2pdy", + "dydx1g6l5pgw2sx6g5j3zywmrzcwqhr4uhk3rtg29f3", + "dydx1ldx9zh60cl2770sy4pms8unggw2das28gefaqn", + "dydx1k5w7e6wc6lukv6pr056hlds4qyv5z750jw95yn", + "dydx14xk3xdlj6ezluzmmwjluaqkd04c6ngvuww5nh9", + "dydx1vkkv3qx9mu7mlmlmjdemyt6fx90w4rw4cehwae", + "dydx16qezz373uxnh9k4043p28zd020zn96utq06c3z", + "dydx177v4p8huqcgya7q87kfh3x826pya4uz5a02hc6", + "dydx1dqsf52kgjj4c4cfpwz8yqjgzza4zvq674l3mn8", + "dydx1f7qwzzuj79v2482flw2apvmnyzj2vfwq8jzfvt", + "dydx1ard8ju79z6d5la65ze8zapcds38ztl5pncllgn", + "dydx1gq0an0qa68a35p8waj0rf6vf3lx5qlkavupqax", + "dydx179ysruaxsdyrlhn55pnw7wanfmhuj8wwnm8lxt", + "dydx144ajlt274rsgc3pn9cn7j9awpupn96rhexcd2q", + "dydx1wpwfu0yvluqcypzphg3tru08kva873dp9fn36j", + "dydx1yuj5eqjyv9y7sfvzpq2nj3xd8dvqca6d44glgq", + "dydx1qjm24rgp2p8h3dj7gplq7jj9sf03m525d4r90y", + "dydx1lmky4qpn5zgjq5qdwnfqls26fgsfkm9urnuyyc", + "dydx15xhneyn2zhykpwdxr7f8pwjyddhjat2upz0xs3", + "dydx1xzsy03267mtyw4v2jrcj8hsyfrhzjdpyx5ltr4", + "dydx1j89hcvh0ujpcp5ayrt39xr9f9c837pcpkvsu4n", + "dydx1hl5gqcpuq8qlgjt4la9x8ugzann00pxa2znmqr", + "dydx1j0hfx693kde5njyyhukupqakq37z85c3vkwtmj", + "dydx1y9fq8p87azdjkzvxc4l0e23czhsfv9zdsu8uzk", + "dydx1dq2m7yptpw9rumwfn4n06n7rv7rxqlzyjetree", + "dydx1d2ar9nh8k27cu70sw6wvswtdkfsefs7jqln45n", + "dydx18xtfqvdvcqp4fy4m7ca69fxuk23q06vptkm63j", + "dydx1dcxhk3nt2phkx0ldzsfmmqlk5as3fq46alcara", + "dydx1v9rywnalzku78d3l6mhwpkhnj40qlnce9fsw27", + "dydx1watrs94e2jqj5d58qqrj3jefe3sls5y70rcd7n", + "dydx10u7e7vhlscgyhmzwj4a7jqfl75lu86xe7kz9vn", + "dydx1pru8ep69cwu42rdegqpp9w30huw3q9xwuc677m", + "dydx17wc0cahedaw2huz8kkd57yvtehyqzxgttlw4qv", + "dydx1ena8wurcnk4mnct6pjldxjuuzhs9pefktafpme", + "dydx1w6842m0uq82xt9rr7ezl8lv87temtqpamqspxq", + "dydx1pez0ym0ylh8gnspqx2t4vq3d5f57r8heznvcwe", + "dydx19ksks8m7lxmpy32m844rkdwy70rrps6pjmaazw", + "dydx190c429xnur9dcnn2vkvtrevkk95yrv45hpxwur", + "dydx1lg0txx3j9l07nz6a0ufmukt3pc3u382jw2jpe4", + "dydx1znegvhqkkr0u6akg8tt0vq3376uqg0ed4nvr50", + "dydx10y3qky4xzcfswntxvu6l6f4w7s84azskw5ww8e", + "dydx1s7kkfurt49ka2dqs77g597xrzm3ch2vcwcw63y", + "dydx15z93j76rh6qaf3x8zmj2w9kjl0fe7x36yyrv0z", + "dydx14x7zgskpdha5r9tzl0f6jqgnux34cxly36wp8y", + "dydx1m59kuaxne48d25zhets92atcq3qh60k4gm7lur", + "dydx17d25dthggz0cu04jma2058ajjg3e4qkvsp3r99", + "dydx106hrfccrtppwdt6y5ewgm6zepfyay87dc5up9c", + "dydx1qemwg5ghxlcwrjkd77aamz7pvc205kc7assrlw", + "dydx15ydzrxml5uxu6k2nxlmcrwxr92t7p0c8rhee4v", + "dydx1lkvxfzctugd0q6q4kln8tze959hal7v37q835n", + "dydx1qdtx0h3sarqt7u88jyvgh0z2jpkxys9nf6npk4", + "dydx159mjm7hzut50h8wvycj7965qmamepgyflhrymm", + "dydx1gf0e7k7lg8vr0jmjl7kuggns00jc0wc7k9v3ef", + "dydx14nauftanlknmdvl4kp92ap0efwp5z5hdjn49j6", + "dydx1cne8l6yhjc0dddr0fejnmcsr7l2f0um2qwzj5q", + "dydx1sydzfw5vfdzxlf4777yskap4kj58cs2uayq857", + "dydx1esj7q5p2ss9kdlqlquxq7y63zj8kuep8smravd", + "dydx1ssjxffz96d2p90w6yqdc92u9e5suffs3nr07x9", + "dydx1rnx7tph30lrufh5dt9krv88glcdhp8x375kqdv", + "dydx176gypeazwtm5pdzk764kgzw0zx3km73mt733rn", + "dydx1w28a0j7fh4c7zdkq0cpg5rrwg7v2j9mwlf0gvy", + "dydx1s9lsvrs8t97s4qrjz7vpu3p74765qcnchx0wpf", + "dydx12qjt49xfc9s884qyd2u30jv8m7jqhh5j079l4t", + "dydx1l5nl60rrpjayg346fs0rx0jj3prnsx5ycjjgwx", + "dydx150lkgpzm9caazlrprh8lrhs3ssudlvt8qmtg3d", + "dydx1g6v53y6yftvxdempgygetkcw5qrggt7xhk3ntd", + "dydx1r3xt74f29vgd68vdd43x6pv2awwsnx8rchangm", + "dydx1lg5yhuagdtqh9nmavnc6nnexdftlk76ymv9mt2", + "dydx1g80n548nt4h8ks5pz5au7tur6eg5pz2vqfqqap", + "dydx1gsl7mznsfl5u6yn7rj9lj4vvvdcu42qs9jmsl8", + "dydx1tf5nlm80y78pz8d9mgr8tsw8qs4p6mldvfhsq7", + "dydx1yj4stddcvk9xrs7ehkl42fr6antf6rmujf7eck", + "dydx1rc3uk8nu5t4fnhhvcpasx48vu894ahddkdrf48", + "dydx1u5hlxc9s5dxae23f6yy7waxe728ujcppzzn5wr", + "dydx1e9fpgjpqerlhw9c0qvryw4qz7z2mxcx7x5pvx2", + "dydx1xc3wpxdsck6v3zjmd66nvfqwpr9my9rs3pxy26", + "dydx1p32np7wpttkxwesr0rnucfjv7rsx4hxdtvqfa3", + "dydx1utsjyk4kfg032hwxnxury0zx37e9vu0naqg8xc", + "dydx1jetc6w334p9rngzhvruqra5dqsqr8zrtj6cc0u", + "dydx1zch9nkvhkmfrw2epsnaq2xu6wahevahyp3g8z8", + "dydx12feeccwj8s5nvp3vl68szctyu9unw367fkrksn", + "dydx1a7eepv6rq68c4fq7mcjhe72tkwl80gqhfwmyuh", + "dydx1fd6aq7nxrwnfmrw4pztsnszzwr7dlvml9p5fv3", + "dydx1tm3s9phqxakpn4md0yu0fjvermdtyaawg0lnv6", + "dydx1f7ew8ju7mwuerrzefm624w7hz66c9yp24wue69", + "dydx1k0hepnqcv57h5rzqs65sda487qrxly5t7nelq5", + "dydx1eez93h7dglmdvz3w56pl8f0kp6yg4mfen0nskl", + "dydx10nndcqg43ehj07pvpd884qtj9ur4cqanxz8kqr", + "dydx16e00qx75703mwh0k2a9nezqpnp52tn7n6qxkzx", + "dydx1wtngmavdwzdvl94e7djg2t88k5ax0t0j2n0xnw", + "dydx10r22j5ah33capns7uwvrfvlup800q26aefd2ue", + "dydx1udm8m9rf7f036v23js80s3y8ax8q0hf4tgf974", + "dydx1kqulhq55q80k8mqa8juc8p9dw53n2365l9s34h", + "dydx13eg3zvyh7p2pu42xwtvlx02hxd8fcgwpdjc6ft", + "dydx1r3uq022a7907xk556r7kpm5mgcq8276x2mvjaf", + "dydx1sdcf7su25a750wes7y4gcf4k27ygx0z9ecluu7", + "dydx1d2jfrah5ayxacma2744htayv65fa3u4pnp6m33", + "dydx1g59zwrx4v0rq34x88538ez50p6eadstjdnkpzh", + "dydx15qndd9j7yzgwlcy30ev7yv6eex64hvcu7h8fkc", + "dydx1dh96qtfwcwsu0nm6uhmqe9fflf3qpr56s3gj6z", + "dydx1rf6a2hpejzld9uwmwrz5p4px0uhw6yutfkp4fe", + "dydx1ae8eyfz8rulxxds94lr4tnrty8yrww9tsx0xez", + "dydx1khd9x7w3suxvckd7r3wtj9sdpuh6ftlrdmcw73", + "dydx1fsyw7nt787uyc5j2nwus6sed9el7e2wzfnmmyu", + "dydx1yglgrk9cwdj3dz289umsesnz9dxuapsdsn65z8", + "dydx124zedcsg3caslcfr5lvdedtl39txx5sr0y857m", + "dydx1alcgfnlqr7p0aq4kk927aklnlkwa6ny49uyszm", + "dydx1wtczdkhmlqlmk3fczynz63f822xs2xcr0e7whw", + "dydx1xr7h5ddakxr4z8azrttewzg63eu4megphhl547", + "dydx1fjgrx4tp9n6kh9kfmv75lnt5l0ajz5hm67e6y6", + "dydx1ukrlr56puh7uuswzay4ztv9xrzwjsfca820ry9", + "dydx1d5ay2pkx7nh89pr3fmat7gwq93nn69l3phlugn", + "dydx14uultvxrrxxx873wtghlwgthww2fa3k75wcrqm", + "dydx1kr7g0z9chgy4evmagq4696w6k9mrv0farrfthn", + "dydx16axql0lmuhtj0nqrw876aqugkh6y632tw6fq2z", + "dydx1t9jf9u2glf3dsqlgcfeqmhc9tusfyrjqd7l5kq", + "dydx1kr0tqjlycaclkeekr8eg05q45qrlu987tquaah", + "dydx15ygz2ve3ec44cad0e2dnf4jz3l2p8mgcj0axg4", + "dydx13qthsx78zht8wpj6hrxf2hgardqq3sllh9wt95", + "dydx13guc6je370lejwxzcdssusc2y5zje7afrn2ncg", + "dydx1gcltv3tgruza2v4rz5m46nyyvptaw2jzpmcjze", + "dydx1mvnautak4342m6uw377y683pv8mgp69yq3cnnw", + "dydx1068w4yx4qyuanulptxyk5ufe53qcnnv4jpjdck", + "dydx16tzx2e42sah0rpw5shn58fyz7wy2dvv7epmr35", + "dydx1zzqjlmzzrdlyaey0nc80ln9am0de8ejnlz0f5d", + "dydx1ys05acd0sg8dq9pqljqgssz6vquqhk9d5y3z7z", + "dydx17hfjyrvvsf7spc8rlzdyeu7l8v5nupyk6k6fqn", + "dydx17tm7vgkvvqt07vfd252ttgf8frr4x4yx3eeeyd", + "dydx189h6gequ0wtvcmttvzee3psm2r9k6jxmdjyc0d", + "dydx102uhfu5cla6gqe23psj9g6k42h2udfphfd9895", + "dydx1x6vv9myu08htm25ts83x7ww774w2whpzclqat0", + "dydx16dx3g3snky779eauc3vrme7kkrmmk8q6d4ayau", + "dydx1vwx2ar6tp7gtdrqg0u93pzjmsvvvjhtrm83g3q", + "dydx1jpxjhxgf3ddgpa4p6gczfcfps0rh352efwxaaf", + "dydx16u6zpd5c3p985878aw7ag05u83hpmf4hj6pr7f", + "dydx19v697kt0c0zt0zje84wk89whqup0pjmlhet2al", + "dydx1r3qsajh25c5w0etsueu8gs9tytjvwhdtn3xc3x", + "dydx18lzhxynv4nckvduw2mh80dpaw7gu6cv4ywhxg3", + "dydx15rdx59r3ehdpjzmwrxpdfl60e0kyxagdjs08q7", + "dydx16zzh9qzkhc3ec4wv4485tperufphgpgrseu5t7", + "dydx1q79af3qexf73cwztkpmyv46xlp0x8z0tyeg5dc", + "dydx1pc2nqq6m6kjadsqq0mhg94w28q0dvh32jvdq6t", + "dydx1kuhxjj5r9k2gjyu3xswq5rld58xgs2yfz6turr", + "dydx1kjvt6p6dfqx5h86a3ca7pzm9h6az38nfr48hm9", + "dydx14pwzdqgr4me7fuvxt62sr9pcjl5z76z9842xj4", + "dydx14pxm8hf9fs0x3nnmvu8tcqyz2eykazvjy9ygz0", + "dydx15j5x37jdvc32zxpm9svyjncenwea5f29z5jrs5", + "dydx190s8huae66cemwlkzq3zaq3npza45jt5rx60hm", + "dydx10ejxqxny4vaqmjrfdc36gz945ezxcwfv76x80k", + "dydx1dwtg4vm3n4fqujm69lzszvyz9m42dyxhvs3kxa", + "dydx15eahvz2mgs2muzdd9j2fcl0srqfd65a900m50n", + "dydx1xdy756j4xnmwdt28p3ge77y9j5s2zneevwmz7s", + "dydx14lkp9ldysw8xaszgay9txrl8chtfk8dcectd9x", + "dydx1p5keacwyf8tr2xprkthkvyledejwe529ge7ppn", + "dydx1c994c8fwg8gr4u8jzh3syjtjtycy7hhsry6jpx", + "dydx1k9d5pjprhak3j7srncxchh4gqdmfrjvk4sdvfa", + "dydx1pzzkeq92d2093qsrpw7l7w70e0cge6667k3ru0", + "dydx1xw96ema5p5kcc34jlghd900vjxv2qrr558s073", + "dydx1h84mqfs3euw0s6z7d2ap3elyn67evn4wlclyz2", + "dydx12976wcy3xx460t2kdpsf7agrw872yq877c9vgl", + "dydx18sqcw9uwkcxy7ypkl9ph6q4kzwwsn5cgcdvwff", + "dydx1xfgaqzd8cwx6fkwlvcxl47ys2aufqknwuqd0g4", + "dydx1pv06w5nf9dmjkk77armmgtw22t8yzeyyrltl49", + "dydx1n0nv9pqxfpsjc2svszqj2r9umy3yf5sznch6v7", + "dydx1ua85dwjc60p387qxfly0r3ekrmz5tv6gd0jv4k", + "dydx1eg6zsqztu3hy6jyxt4j858h6n0lsxmeyv4y5jv", + "dydx1h2gnlmqcuzxke2v5e57ag20fusntajzmgs5pf4", + "dydx18j6r5y77c3rvslgnyjdrde9e70305tre4vd9vj", + "dydx1505637hhv7hnvgrmh7ekwnl7ctmassjuczg6qd", + "dydx1q53dk9murcw2605sf0qefkdzrs4ne6rcr0gtwt", + "dydx1l2nqsadpflyd5xpsez3nmyhfm4v3kp2y63eglu", + "dydx1pdhtxfn3s7s9wm5jdk87uvw7cqz7vd8rc5xqc0", + "dydx1fghe9wtx8wz76qx4v9ff7vsd8auxkjpr9drftd", + "dydx179vun9xsrp8xqesy726mlhvu52mzphxnys9hy4", + "dydx1ck6252yzuvt60u9wyujzu0h5njghhl42s8gs97", + "dydx13edzuadtpmfwluyhyza6mlqeuhcwm8rm8kw56z", + "dydx180zeh5q7cp7mykfms0r2mh4y5nvfy7jpltt3e4", + "dydx135qz2nakg72yj3apee7q4zfuugeuja07gz30f5", + "dydx1ezlhfy4zq4c7ehjmlt46a3ag7fae52q64xls4m", + "dydx13vthwnxrhlcx8qsmzsw0q7n80w97wefs7dmxrv", + "dydx15f42yytljcdep49qh4tklvpll4j5ut6lvc5lxw", + "dydx16vw4lewd0x2kgl5sh6vcg2xrsdeyjhxzj6jj2k", + "dydx1yx862cwexzmdm2z5tm5qnpddcjpcp09czqkpqs", + "dydx1ekmxtc76aqvkxq3p69ss8yq4498zdfqa0rl0n9", + "dydx12wej72a5pcrl49jwtqnqz8l845s28rue56e9x3", + "dydx18hh2nvh43n700s24lur66kesyemmqpac3l4z4a", + "dydx1v0wmlczcdpkcx8f7fxfdq6c9m3maddrmw0frkp", + "dydx15kjfd3m4zt44xhwnr4t4rqzmvupgmp09y66vrm", + "dydx1mevhhp9wuywk68jj6ntd62ee5tyau4hwn7whwx", + "dydx143zdtdfsn78qh443ktzj3wqgsu2kkwyyn7057r", + "dydx15wgfark2lzhcm5aafpjdk4ym62ad0j67sesg63", + "dydx1vvx0fxp2lcwvkz9k5uzqqp2t42vag8a52vpf90", + "dydx1ja4e5xx99x8pwvpypd93gvdvm4x5pvgp2yn89v", + "dydx1edv3ccdzx0uc5wk7gxx7s985w3wz35hp4det60", + "dydx1eqmlcdfde79jsdf5gce6x9eqcek085wehyyn7q", + "dydx1ftqxegmcatf6dj62hs25szz5cpn9kw6ychr5mk", + "dydx1t35yckx0lmygu48jgefr43gyhd2spxu552crnh", + "dydx1yukag0udt0hx267cf289akw0ps6wcwklyzf4ej", + "dydx1ykjpy0ggskjwenqaca6ph3m6a6plhdn7xns4wa", + "dydx1qah94upkd62l6az822hzrwd5cw3r6lszzyy762" +] \ No newline at end of file diff --git a/indexer/packages/postgres/src/stores/pnl-ticks-table.ts b/indexer/packages/postgres/src/stores/pnl-ticks-table.ts index 0cc7be4497..f2d0496b52 100644 --- a/indexer/packages/postgres/src/stores/pnl-ticks-table.ts +++ b/indexer/packages/postgres/src/stores/pnl-ticks-table.ts @@ -1,11 +1,14 @@ import _ from 'lodash'; import { QueryBuilder } from 'objection'; -import { BUFFER_ENCODING_UTF_8, DEFAULT_POSTGRES_OPTIONS, ZERO_TIME_ISO_8601 } from '../constants'; +import { + BUFFER_ENCODING_UTF_8, DEFAULT_POSTGRES_OPTIONS, ZERO_TIME_ISO_8601, +} from '../constants'; import { knexReadReplica } from '../helpers/knex'; import { setupBaseQuery, verifyAllInjectableVariables, verifyAllRequiredFields } from '../helpers/stores-helpers'; import Transaction from '../helpers/transaction'; import { getUuid } from '../helpers/uuid'; +import { getVaultAddresses } from '../lib/helpers'; import PnlTicksModel from '../models/pnl-ticks-model'; import { Options, @@ -17,6 +20,8 @@ import { QueryableField, QueryConfig, PaginationFromDatabase, + LeaderboardPnlCreateObject, + LeaderboardPnlTimeSpan, } from '../types'; export function uuid( @@ -268,3 +273,178 @@ export async function findMostRecentPnlTickForEachAccount( 'subaccountId', ); } + +export async function getRankedPnlTicks( + timeSpan: string, +): Promise { + if (timeSpan === 'ALL_TIME') { + return getAllTimeRankedPnlTicks(); + } + return getRankedPnlTicksForTimeSpan(timeSpan); +} + +function convertTimespanToSQL(timeSpan: string): string { + const timeSpanEnum: LeaderboardPnlTimeSpan = LeaderboardPnlTimeSpan[ + timeSpan as keyof typeof LeaderboardPnlTimeSpan]; + switch (timeSpanEnum) { + case LeaderboardPnlTimeSpan.ONE_DAY: + return '1 days'; + case LeaderboardPnlTimeSpan.SEVEN_DAYS: + return '7 days'; + case LeaderboardPnlTimeSpan.THIRTY_DAYS: + return '30 days'; + case LeaderboardPnlTimeSpan.ONE_YEAR: + return '365 days'; + default: + throw new Error(`Invalid time span: ${timeSpan}`); + } +} + +/** + * Constructs a complex SQL query to calculate the Pnl difference and current equity + * of subaccounts over a specified time span, ranking them by their PnL. + * + * This has 5 main parts + * 1. latest_subaccount_pnl_x_days_ago: Identifies the most recent PnL tick for each subaccount + * before the specified time span. It filters out subaccounts which are not parent + * subaccounts or associated child subaccounts. It also excludes any addresses + * that are vault addresses. + * + * 2. latest_pnl: Finds the latest PnL tick for each subaccount as of the current date, + * applying the same filters as latest_subaccount_pnl_x_days_ago. + * + * 3. subaccount_pnl_difference: Calculates the difference in PnL between the + * current date and the start of the specified time span for each subaccount. + * + * 4. aggregated_results: Aggregates the PnL differences and current equity for + * all subaccounts, grouping by address. + * + * 5. The final SELECT statement then ranks the addresses based on their total PnL + * in descending order, providing a snapshot of subaccount performance over the + * specified time span. + * +*/ +async function getRankedPnlTicksForTimeSpan( + timeSpan: string, +): Promise { + const vaultAddresses: string[] = getVaultAddresses(); + const vaultAddressesString: string = vaultAddresses.map((address) => `'${address}'`).join(','); + const intervalSqlString: string = convertTimespanToSQL(timeSpan); + const result: { + rows: LeaderboardPnlCreateObject[] + } = await knexReadReplica.getConnection().raw( + ` + WITH latest_subaccount_pnl_x_days_ago AS ( + SELECT DISTINCT ON (a."subaccountId") + a."subaccountId", + a."totalPnl", + b."address" + FROM + pnl_ticks a + LEFT JOIN + subaccounts b ON a."subaccountId" = b."id" + WHERE + a."createdAt"::date <= (CURRENT_DATE - INTERVAL '${intervalSqlString}') + AND (b."subaccountNumber" % 128) = 0 + AND b."address" NOT IN (${vaultAddressesString}) + ORDER BY a."subaccountId", a."blockHeight" DESC + ), + latest_pnl as ( + SELECT DISTINCT ON (a."subaccountId") + "subaccountId", + "totalPnl", + "equity" as "currentEquity", + "address" + FROM + pnl_ticks a left join subaccounts b ON a."subaccountId"=b."id" + WHERE + "createdAt"::date = CURRENT_DATE + AND (b."subaccountNumber" % 128) = 0 + AND b."address" NOT IN (${vaultAddressesString}) + ORDER BY a."subaccountId", "blockHeight" DESC + ), + subaccount_pnl_difference as( + SELECT + a."address", + a."totalPnl" - COALESCE(b."totalPnl", 0) as "pnlDifference", + a."currentEquity" as "currentEquity" + FROM latest_pnl a left join latest_subaccount_pnl_x_days_ago b + ON a."subaccountId"=b."subaccountId" + ), aggregated_results as( + SELECT + "address", + sum(subaccount_pnl_difference."pnlDifference") as "totalPnl", + sum(subaccount_pnl_difference."currentEquity") as "currentEquity" + FROM + subaccount_pnl_difference + GROUP BY address + ) + SELECT + "address", + "totalPnl" as "pnl", + '${timeSpan}' as "timeSpan", + "currentEquity", + ROW_NUMBER() over (order by aggregated_results."totalPnl" desc) as rank + FROM + aggregated_results; + `, + ) as { rows: LeaderboardPnlCreateObject[] }; + + return result.rows; +} + +/** + * Constructs a query to calculate and rank the Profit and Loss (PnL) and current equity of + * subaccounts for the current day. This query is divided into 3 main parts: + * 1. latest_pnl: This selects the most recent PnL tick for each Parent subaccount + * and associated child subaccounts. It filters subaccounts based on the current date. + * Additionally, it excludes any addresses that are vault addresses. + * + * 2. aggregated_results: This CTE aggregates the results from latest_pnl by address. + * It sums up the total PnL and current equity for each address. + * + * 3. The final SELECT statement calculates a rank for each address based on the total PnL in + * descending order along with associated fields + */ +async function getAllTimeRankedPnlTicks(): Promise { + const vaultAddresses: string[] = getVaultAddresses(); + const vaultAddressesString: string = vaultAddresses.map((address) => `'${address}'`).join(','); + const result: { + rows: LeaderboardPnlCreateObject[] + } = await knexReadReplica.getConnection().raw( + ` + WITH latest_pnl as ( + SELECT DISTINCT ON (a."subaccountId") + "subaccountId", + "totalPnl", + "equity" as "currentEquity", + "address" + FROM + pnl_ticks a left join subaccounts b ON a."subaccountId"=b."id" + WHERE + "createdAt"::date = CURRENT_DATE + AND (b."subaccountNumber" % 128) = 0 + AND b."address" NOT IN (${vaultAddressesString}) + ORDER BY a."subaccountId", "blockHeight" DESC + ), aggregated_results as( + SELECT + "address", + sum(latest_pnl."totalPnl") as "totalPnl", + sum(latest_pnl."currentEquity") as "currentEquity" + FROM + latest_pnl + GROUP BY address + ) + SELECT + "address", + "totalPnl" as "pnl", + 'ALL_TIME' as "timeSpan", + "currentEquity", + ROW_NUMBER() over (order by aggregated_results."totalPnl" desc) as rank + FROM + aggregated_results; + `, + ) as { rows: LeaderboardPnlCreateObject[] }; + + return result.rows; +} diff --git a/indexer/packages/postgres/src/types/leaderboard-pnl-types.ts b/indexer/packages/postgres/src/types/leaderboard-pnl-types.ts index f248922302..106d2a1fc5 100644 --- a/indexer/packages/postgres/src/types/leaderboard-pnl-types.ts +++ b/indexer/packages/postgres/src/types/leaderboard-pnl-types.ts @@ -15,3 +15,11 @@ export enum LeaderboardPnlColumns { currentEquity = 'currentEquity', rank = 'rank', } + +export enum LeaderboardPnlTimeSpan { + ONE_DAY = 'ONE_DAY', + SEVEN_DAYS = 'SEVEN_DAYS', + THIRTY_DAYS = 'THIRTY_DAYS', + ONE_YEAR = 'ONE_YEAR', + ALL_TIME = 'ALL_TIME', +} diff --git a/indexer/packages/redis/src/caches/leaderboard-processed-cache.ts b/indexer/packages/redis/src/caches/leaderboard-processed-cache.ts new file mode 100644 index 0000000000..5b8825b66c --- /dev/null +++ b/indexer/packages/redis/src/caches/leaderboard-processed-cache.ts @@ -0,0 +1,25 @@ +import { LeaderboardPnlTimeSpan } from '@dydxprotocol-indexer/postgres'; +import { RedisClient } from 'redis'; + +import { getAsync } from '../helpers/redis'; + +export const LEADERBOARD_PNL_TIMESPAN_PROCESSED_CACHE_KEY: string = 'v4/leaderboard_pnl_processed/'; + +function getKey(period: LeaderboardPnlTimeSpan): string { + return `${LEADERBOARD_PNL_TIMESPAN_PROCESSED_CACHE_KEY}${period}`; +} + +export async function getProcessedTime( + timespan: LeaderboardPnlTimeSpan, + client: RedisClient, +): Promise { + return getAsync(getKey(timespan), client); +} + +export async function setProcessedTime( + period: LeaderboardPnlTimeSpan, + timestamp: string, + client: RedisClient, +): Promise { + await client.set(getKey(period), timestamp); +} diff --git a/indexer/packages/redis/src/index.ts b/indexer/packages/redis/src/index.ts index 5f5e8cf176..2ce64e9b88 100644 --- a/indexer/packages/redis/src/index.ts +++ b/indexer/packages/redis/src/index.ts @@ -11,6 +11,7 @@ export * as LatestAccountPnlTicksCache from './caches/latest-account-pnl-ticks-c export * as CanceledOrdersCache from './caches/canceled-orders-cache'; export * as StatefulOrderUpdatesCache from './caches/stateful-order-updates-cache'; export * as StateFilledQuantumsCache from './caches/state-filled-quantums-cache'; +export * as LeaderboardPnlProcessedCache from './caches/leaderboard-processed-cache'; export { placeOrder } from './caches/place-order'; export { removeOrder } from './caches/remove-order'; export { updateOrder } from './caches/update-order'; diff --git a/indexer/services/roundtable/__tests__/tasks/create-leaderboard.test.ts b/indexer/services/roundtable/__tests__/tasks/create-leaderboard.test.ts new file mode 100644 index 0000000000..1d97993129 --- /dev/null +++ b/indexer/services/roundtable/__tests__/tasks/create-leaderboard.test.ts @@ -0,0 +1,116 @@ +import { + BlockTable, + dbHelpers, + PerpetualPositionTable, + PnlTicksTable, + testConstants, + testMocks, + WalletTable, + SubaccountTable, + LeaderboardPnlTable, + LeaderboardPnlFromDatabase, + LeaderboardPnlTimeSpan, +} from '@dydxprotocol-indexer/postgres'; +import { LeaderboardPnlProcessedCache, redis } from '@dydxprotocol-indexer/redis'; + +import generateLeaderboardTaskFromTimespan from '../../src/tasks/create-leaderboard'; +import { DateTime } from 'luxon'; +import { redisClient } from '../../src/helpers/redis'; + +describe('create-leaderboard', () => { + beforeAll(async () => { + await dbHelpers.migrate(); + await dbHelpers.clearData(); + }); + + beforeEach(async () => { + await testMocks.seedData(); + await WalletTable.create(testConstants.defaultWallet3); + await SubaccountTable.create(testConstants.defaultSubaccountWithAlternateAddress); + await setupRankedPnlTicksData(); + }); + + afterAll(async () => { + await dbHelpers.teardown(); + jest.resetAllMocks(); + }); + + afterEach(async () => { + await dbHelpers.clearData(); + await redis.deleteAllAsync(redisClient); + jest.resetAllMocks(); + }); + + it('Succeeds in populating the leaderboard with ranked pnl ticks', async () => { + await Promise.all([ + PerpetualPositionTable.create(testConstants.defaultPerpetualPosition), + PerpetualPositionTable.create({ + ...testConstants.defaultPerpetualPosition, + perpetualId: testConstants.defaultPerpetualMarket2.id, + openEventId: testConstants.defaultTendermintEventId2, + }), + ]); + const task: () => Promise = generateLeaderboardTaskFromTimespan( + LeaderboardPnlTimeSpan.ALL_TIME); + await task(); + const { results: pnlTicks } = await PnlTicksTable.findAll( + {}, + [], + ); + expect(pnlTicks.length).toEqual(2); + const leaderboardResults: LeaderboardPnlFromDatabase[] = await LeaderboardPnlTable.findAll( + {}, + [], + ); + expect(leaderboardResults.length).toEqual(2); + }); + + it('leaderboard not updated if last processed pnl time < cached leaderboard time', async () => { + await LeaderboardPnlProcessedCache.setProcessedTime( + LeaderboardPnlTimeSpan.ALL_TIME, + DateTime.utc().toISO(), + redisClient, + ); + const task: () => Promise = generateLeaderboardTaskFromTimespan( + LeaderboardPnlTimeSpan.ALL_TIME); + await task(); + const leaderboardResults: LeaderboardPnlFromDatabase[] = await LeaderboardPnlTable.findAll( + {}, + [], + ); + expect(leaderboardResults.length).toEqual(0); + }); +}); + +async function setupRankedPnlTicksData() { + await Promise.all([ + BlockTable.create({ + blockHeight: '3', + time: testConstants.defaultBlock.time, + }), + BlockTable.create({ + blockHeight: '5', + time: testConstants.defaultBlock.time, + }), + ]); + await PnlTicksTable.createMany([ + { + subaccountId: testConstants.defaultSubaccountId, + equity: '1100', + createdAt: DateTime.utc().toISO(), + totalPnl: '1200', + netTransfers: '50', + blockHeight: '9', + blockTime: testConstants.defaultBlock.time, + }, + { + subaccountId: testConstants.defaultSubaccountIdWithAlternateAddress, + equity: '1090', + createdAt: DateTime.utc().toISO(), + totalPnl: '1190', + netTransfers: '50', + blockHeight: '7', + blockTime: testConstants.defaultBlock.time, + }, + ]); +} diff --git a/indexer/services/roundtable/src/config.ts b/indexer/services/roundtable/src/config.ts index 6dc659571d..355b48fdcb 100644 --- a/indexer/services/roundtable/src/config.ts +++ b/indexer/services/roundtable/src/config.ts @@ -52,6 +52,11 @@ export const configSchema = { LOOPS_ENABLED_AGGREGATE_TRADING_REWARDS_WEEKLY: parseBoolean({ default: true }), LOOPS_ENABLED_AGGREGATE_TRADING_REWARDS_MONTHLY: parseBoolean({ default: true }), LOOPS_ENABLED_SUBACCOUNT_USERNAME_GENERATOR: parseBoolean({ default: true }), + LOOPS_ENABLED_LEADERBOARD_PNL_ALL_TIME: parseBoolean({ default: true }), + LOOPS_ENABLED_LEADERBOARD_PNL_DAILY: parseBoolean({ default: true }), + LOOPS_ENABLED_LEADERBOARD_PNL_WEEKLY: parseBoolean({ default: true }), + LOOPS_ENABLED_LEADERBOARD_PNL_MONTHLY: parseBoolean({ default: true }), + LOOPS_ENABLED_LEADERBOARD_PNL_YEARLY: parseBoolean({ default: true }), // Loop Timing LOOPS_INTERVAL_MS_MARKET_UPDATER: parseInteger({ @@ -105,6 +110,21 @@ export const configSchema = { LOOPS_INTERVAL_MS_SUBACCOUNT_USERNAME_GENERATOR: parseInteger({ default: THIRTY_SECONDS_IN_MILLISECONDS, }), + LOOPS_INTERVAL_MS_LEADERBOARD_PNL_ALL_TIME: parseInteger({ + default: THIRTY_SECONDS_IN_MILLISECONDS, + }), + LOOPS_INTERVAL_MS_LEADERBOARD_PNL_DAILY: parseInteger({ + default: THIRTY_SECONDS_IN_MILLISECONDS, + }), + LOOPS_INTERVAL_MS_LEADERBOARD_PNL_WEEKLY: parseInteger({ + default: THIRTY_SECONDS_IN_MILLISECONDS, + }), + LOOPS_INTERVAL_MS_LEADERBOARD_PNL_MONTHLY: parseInteger({ + default: THIRTY_SECONDS_IN_MILLISECONDS, + }), + LOOPS_INTERVAL_MS_LEADERBOARD_PNL_YEARLY: parseInteger({ + default: THIRTY_SECONDS_IN_MILLISECONDS, + }), // Start delay START_DELAY_ENABLED: parseBoolean({ default: true }), @@ -126,6 +146,7 @@ export const configSchema = { PNL_TICK_UPDATE_INTERVAL_MS: parseInteger({ default: ONE_HOUR_IN_MILLISECONDS }), PNL_TICK_MAX_ROWS_PER_UPSERT: parseInteger({ default: 1000 }), PNL_TICK_MAX_ACCOUNTS_PER_RUN: parseInteger({ default: 65000 }), + LEADERBOARD_PNL_MAX_ROWS_PER_UPSERT: parseInteger({ default: 1000 }), // Remove expired orders BLOCKS_TO_DELAY_EXPIRY_BEFORE_SENDING_REMOVES: parseInteger({ default: 20 }), diff --git a/indexer/services/roundtable/src/index.ts b/indexer/services/roundtable/src/index.ts index 53f6f00e8c..d3ad79696c 100644 --- a/indexer/services/roundtable/src/index.ts +++ b/indexer/services/roundtable/src/index.ts @@ -1,6 +1,6 @@ import { logger, startBugsnag, wrapBackgroundTask } from '@dydxprotocol-indexer/base'; import { producer } from '@dydxprotocol-indexer/kafka'; -import { TradingRewardAggregationPeriod } from '@dydxprotocol-indexer/postgres'; +import { LeaderboardPnlTimeSpan, TradingRewardAggregationPeriod } from '@dydxprotocol-indexer/postgres'; import config from './config'; import { complianceProvider } from './helpers/compliance-clients'; @@ -11,6 +11,7 @@ import { } from './helpers/redis'; import aggregateTradingRewardsTasks from './tasks/aggregate-trading-rewards'; import cancelStaleOrdersTask from './tasks/cancel-stale-orders'; +import createLeaderboardTask from './tasks/create-leaderboard'; import createPnlTicksTask from './tasks/create-pnl-ticks'; import deleteOldFastSyncSnapshots from './tasks/delete-old-fast-sync-snapshots'; import deleteZeroPriceLevelsTask from './tasks/delete-zero-price-levels'; @@ -201,6 +202,52 @@ async function start(): Promise { ); } + if (config.LOOPS_ENABLED_LEADERBOARD_PNL_ALL_TIME) { + const allTimeLeaderboardTask: () => Promise = createLeaderboardTask( + LeaderboardPnlTimeSpan.ALL_TIME); + startLoop( + allTimeLeaderboardTask, + 'create_leaderboard_pnl_all_time', + config.LOOPS_INTERVAL_MS_LEADERBOARD_PNL_ALL_TIME, + ); + } + if (config.LOOPS_ENABLED_LEADERBOARD_PNL_DAILY) { + const dailyLeaderboardTask: () => Promise = createLeaderboardTask( + LeaderboardPnlTimeSpan.ONE_DAY); + startLoop( + dailyLeaderboardTask, + 'create_leaderboard_pnl_daily', + config.LOOPS_INTERVAL_MS_LEADERBOARD_PNL_DAILY, + ); + } + if (config.LOOPS_ENABLED_LEADERBOARD_PNL_WEEKLY) { + const weeklyLeaderboardTask: () => Promise = createLeaderboardTask( + LeaderboardPnlTimeSpan.SEVEN_DAYS); + startLoop( + weeklyLeaderboardTask, + 'create_leaderboard_pnl_weekly', + config.LOOPS_INTERVAL_MS_LEADERBOARD_PNL_WEEKLY, + ); + } + if (config.LOOPS_ENABLED_LEADERBOARD_PNL_MONTHLY) { + const monthlyLeaderboardTask: () => Promise = createLeaderboardTask( + LeaderboardPnlTimeSpan.THIRTY_DAYS); + startLoop( + monthlyLeaderboardTask, + 'create_leaderboard_pnl_monthly', + config.LOOPS_INTERVAL_MS_LEADERBOARD_PNL_MONTHLY, + ); + } + if (config.LOOPS_ENABLED_LEADERBOARD_PNL_YEARLY) { + const yearlyLeaderboardTask: () => Promise = createLeaderboardTask( + LeaderboardPnlTimeSpan.ONE_YEAR); + startLoop( + yearlyLeaderboardTask, + 'create_leaderboard_pnl_yearly', + config.LOOPS_INTERVAL_MS_LEADERBOARD_PNL_YEARLY, + ); + } + logger.info({ at: 'index', message: 'Successfully started', diff --git a/indexer/services/roundtable/src/tasks/create-leaderboard.ts b/indexer/services/roundtable/src/tasks/create-leaderboard.ts new file mode 100644 index 0000000000..cca97328c3 --- /dev/null +++ b/indexer/services/roundtable/src/tasks/create-leaderboard.ts @@ -0,0 +1,144 @@ +import { logger } from '@dydxprotocol-indexer/base'; +import { + LeaderboardPnlTimeSpan, + LeaderboardPnlCreateObject, + LeaderboardPnlTable, + PnlTicksTable, + Transaction, +} from '@dydxprotocol-indexer/postgres'; +import { LeaderboardPnlProcessedCache } from '@dydxprotocol-indexer/redis'; +import _ from 'lodash'; + +import config from '../config'; +import { redisClient } from '../helpers/redis'; + +export default function generateLeaderboardTaskFromTimespan( + timespan: LeaderboardPnlTimeSpan, +): () => Promise { + return async () => { + const leaderboardPnlProcessor: LeaderboardPnlProcessor = new LeaderboardPnlProcessor(timespan); + await leaderboardPnlProcessor.runTask(); + + }; +} + +class LeaderboardPnlProcessor { + + constructor( + private timespan: LeaderboardPnlTimeSpan, + ) { + this.timespan = timespan; + } + + async runTask(): Promise { + await this.updateLeaderboardPnlTable(this.timespan); + } + + /** + * Updates the leaderboard PnL table for a specified time span. + * This function performs several operations to ensure the leaderboard PnL table is up-to-date: + * 1. Check for updates: It first retrieves the last processed time for the given + * time span from a cache. It then fetches the latest processed block time from + * the PnL ticks table. If the last processed time is greater than or equal to + * the latest block time, it logs a message indicating that the current PnL ticks + * have already been processed for the leaderboard and exits the function. + * + * 2. Retrieve PnL Objects: If updates are needed, it retrieves + * leaderboard PnL objects for the specified time span by calling + * `getLeaderboardPnlObjects`. + * + * 3. Insert PnL Objects: It then inserts the leaderboard PnL objects into the + * leaderboard PnL table by calling `insertLeaderboardPnlObjects`. + * + * 4. Update Cache: After successful insertion, it updates the cache with the + * latest processed block time for the given time span. + * + */ + async updateLeaderboardPnlTable(timespan: LeaderboardPnlTimeSpan) { + const lastProcessedTime: string | null = await LeaderboardPnlProcessedCache.getProcessedTime( + timespan, redisClient); + const lastProcessedPnlTime: string = ( + await PnlTicksTable.findLatestProcessedBlocktimeAndCount()).maxBlockTime; + + // Check if the last processed time is greater than or equal to the latest block time. + // In cases where indexer is from previous state, the last processed time may be null. + // In that case update the leaderboard. + if (lastProcessedTime && Date.parse(lastProcessedTime) >= Date.parse(lastProcessedPnlTime)) { + logger.info({ + at: 'create-leaderboard#runTask', + message: 'Skipping run because the current pnl ticks have been processed for the leaderboard', + pnlTickLatestBlocktime: lastProcessedPnlTime, + latestBlockTime: lastProcessedTime, + threshold: config.PNL_TICK_UPDATE_INTERVAL_MS, + timespan, + }); + return; + } + try { + const leaderboardPnlObjects: LeaderboardPnlCreateObject[] = await + this.getLeaderboardPnlObjects(timespan); + await this.insertLeaderboardPnlObjects(leaderboardPnlObjects); + await this.updateLeaderboardPnlProcessedCache(timespan, lastProcessedPnlTime); + } catch (error) { + logger.error({ + at: 'create-leaderboard#runTask', + message: 'Error when updating leaderboard pnl table', + error, + timespan, + }); + } + } + + async getLeaderboardPnlObjects(timespan: LeaderboardPnlTimeSpan) { + const leaderboardPnlObjects: LeaderboardPnlCreateObject[] = []; + try { + leaderboardPnlObjects.push(...await PnlTicksTable.getRankedPnlTicks(timespan)); + } catch (error) { + logger.error({ + at: 'create-leaderboard#runTask', + message: `Error when getting ranked pnl ticks for timespan${timespan.toString()}`, + error, + timespan, + }); + throw error; + } + return leaderboardPnlObjects; + } + + async insertLeaderboardPnlObjects(leaderboardPnlObjects: LeaderboardPnlCreateObject[], + ) { + const txId: number = await Transaction.start(); + try { + const chunkedLeaderboardPnlObjects = _.chunk( + leaderboardPnlObjects, + config.LEADERBOARD_PNL_MAX_ROWS_PER_UPSERT); + for (const leaderboardPnlObjectsToUpsert of chunkedLeaderboardPnlObjects) { + await LeaderboardPnlTable.bulkUpsert(leaderboardPnlObjectsToUpsert, { txId }); + } + await Transaction.commit(txId); + } catch (error) { + logger.error({ + at: 'create-leaderboard#runTask', + message: 'Error when inserting leaderboard pnl objects', + error, + }); + await Transaction.rollback(txId); + throw error; + } + } + + async updateLeaderboardPnlProcessedCache(timespan: LeaderboardPnlTimeSpan, + lastProcessedPnlTime: string) { + try { + await LeaderboardPnlProcessedCache.setProcessedTime(timespan, lastProcessedPnlTime, + redisClient); + } catch (error) { + logger.error({ + at: 'create-leaderboard#runTask', + message: 'Error when setting processed time', + error, + }); + } + } + +}