tapimo 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/App.d.ts +4887 -5074
- package/dist/App.d.ts.map +1 -1
- package/dist/Webhooks.d.ts +21 -20
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +66 -23
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +168 -168
- package/dist/apps/Zones.d.ts +34 -22
- package/dist/apps/Zones.d.ts.map +1 -1
- package/dist/apps/Zones.js +9 -8
- package/dist/apps/Zones.js.map +1 -1
- package/dist/apps/data/App.d.ts +5198 -5099
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +4 -5
- package/dist/apps/data/FxOracle.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.js +19 -4
- package/dist/apps/data/FxOracle.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +270 -270
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +32 -27
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +52 -52
- package/dist/apps/data/routes/balances.d.ts +36 -36
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +135 -81
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +125 -125
- package/dist/apps/data/routes/coingecko.d.ts +54 -54
- package/dist/apps/data/routes/earn.d.ts +512 -58
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +368 -44
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +120 -120
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +114 -118
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +68 -47
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokens.d.ts +461 -459
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +59 -51
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +25 -23
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +108 -86
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +618 -105
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +21 -21
- package/dist/apps/data/routes/valuation.d.ts.map +1 -1
- package/dist/apps/data/routes/valuation.js +2 -2
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +37 -37
- package/dist/apps/data/routes/webhooks.d.ts +102 -505
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +484 -342
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +12 -12
- package/dist/apps/data/routes/zones.d.ts.map +1 -1
- package/dist/apps/data/routes/zones.js +3 -1
- package/dist/apps/data/routes/zones.js.map +1 -1
- package/dist/apps/funding/App.d.ts +96 -96
- package/dist/apps/funding/routes/deposit-addresses.d.ts +52 -52
- package/dist/apps/funding/routes/quotes.d.ts +89 -89
- package/dist/apps/funding/routes/transfers.d.ts +4 -4
- package/dist/apps/management/App.d.ts +429 -727
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +24 -24
- package/dist/apps/management/routes/billing.d.ts +72 -72
- package/dist/apps/management/routes/faucet.d.ts +6 -6
- package/dist/apps/management/routes/invitations.d.ts +134 -134
- package/dist/apps/management/routes/invite-links.d.ts +38 -38
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/members.d.ts +30 -30
- package/dist/apps/management/routes/orgs.d.ts +98 -98
- package/dist/apps/management/routes/projects.d.ts +20 -20
- package/dist/apps/management/routes/usage.d.ts +24 -24
- package/dist/apps/management/routes/verified-token-requests.d.ts +83 -83
- package/dist/apps/management/routes/webhooks.d.ts +158 -573
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/mpp/App.d.ts +56 -44
- package/dist/apps/mpp/App.d.ts.map +1 -1
- package/dist/apps/mpp/Mpp.d.ts +3 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -1
- package/dist/apps/mpp/Mpp.js +3 -0
- package/dist/apps/mpp/Mpp.js.map +1 -1
- package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
- package/dist/apps/mpp/internal/Adapter.js +9 -2
- package/dist/apps/mpp/internal/Adapter.js.map +1 -1
- package/dist/db/Db.d.ts +2 -2
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +17 -15
- package/dist/db/Db.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +4 -1
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +56 -3
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +76 -3
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +112 -3
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +116 -3
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Schema.js +1 -1
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Tidx.d.ts +1 -3
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +1 -3
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +2 -1
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/WebhookDestination.d.ts +2 -0
- package/dist/internal/WebhookDestination.d.ts.map +1 -1
- package/dist/internal/WebhookDestination.js +11 -0
- package/dist/internal/WebhookDestination.js.map +1 -1
- package/dist/internal/WebhookTransfer.d.ts +55 -0
- package/dist/internal/WebhookTransfer.d.ts.map +1 -0
- package/dist/internal/WebhookTransfer.js +296 -0
- package/dist/internal/WebhookTransfer.js.map +1 -0
- package/dist/internal/Webhooks.d.ts +37 -0
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +87 -13
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +4 -0
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +11 -7
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +128 -0
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts +3 -0
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +12 -1
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/package.json +3 -3
- package/src/Client.test-d.ts +60 -5
- package/src/Webhooks.bench.ts +714 -0
- package/src/Webhooks.memory.bench.ts +250 -0
- package/src/Webhooks.test.ts +77 -6
- package/src/Webhooks.ts +83 -29
- package/src/apps/Zones.test.ts +5 -4
- package/src/apps/Zones.ts +9 -8
- package/src/apps/data/FxOracle.test.ts +18 -0
- package/src/apps/data/FxOracle.ts +28 -5
- package/src/apps/data/routes/activities.test.ts +19 -0
- package/src/apps/data/routes/activities.ts +34 -30
- package/src/apps/data/routes/balances.test.ts +119 -10
- package/src/apps/data/routes/balances.ts +154 -95
- package/src/apps/data/routes/blocks.test.ts +12 -6
- package/src/apps/data/routes/earn.test.ts +197 -0
- package/src/apps/data/routes/earn.ts +486 -44
- package/src/apps/data/routes/exchanges.test.ts +7 -6
- package/src/apps/data/routes/exchanges.ts +124 -117
- package/src/apps/data/routes/receipts.test.ts +24 -7
- package/src/apps/data/routes/receipts.ts +67 -50
- package/src/apps/data/routes/tokens.test.ts +51 -0
- package/src/apps/data/routes/transactions.test.ts +42 -0
- package/src/apps/data/routes/transactions.ts +44 -39
- package/src/apps/data/routes/transfers.test.ts +402 -24
- package/src/apps/data/routes/transfers.ts +708 -101
- package/src/apps/data/routes/valuation.test.ts +26 -0
- package/src/apps/data/routes/valuation.ts +6 -2
- package/src/apps/data/routes/webhooks.test.ts +414 -130
- package/src/apps/data/routes/webhooks.ts +603 -419
- package/src/apps/data/routes/zones.ts +6 -1
- package/src/apps/funding/routes/quotes.test.ts +1 -1
- package/src/apps/mpp/Mpp.test.ts +13 -1
- package/src/apps/mpp/Mpp.ts +8 -0
- package/src/apps/mpp/internal/Adapter.ts +10 -2
- package/src/db/Db.test.ts +40 -6
- package/src/db/Db.ts +33 -19
- package/src/db/tables/sponsoredTransactions.ts +9 -2
- package/src/db/tables/webhookSubscriptions.ts +116 -4
- package/src/internal/EarnVaults.test.ts +2 -2
- package/src/internal/MetricSink.test.ts +518 -0
- package/src/internal/MetricSink.ts +266 -9
- package/src/internal/Schema.ts +1 -1
- package/src/internal/Tidx.test.ts +4 -4
- package/src/internal/Tidx.ts +1 -3
- package/src/internal/Viem.test.ts +8 -0
- package/src/internal/Viem.ts +2 -1
- package/src/internal/WebhookDestination.test.ts +20 -0
- package/src/internal/WebhookDestination.ts +10 -0
- package/src/internal/WebhookTransfer.ts +426 -0
- package/src/internal/Webhooks.test.ts +137 -0
- package/src/internal/Webhooks.ts +131 -17
- package/src/internal/funding/Provider.test.ts +47 -7
- package/src/internal/funding/Provider.ts +12 -7
- package/src/internal/funding/Reconciliation.local.test.ts +3 -1
- package/src/internal/funding/Reconciliation.test.ts +1240 -0
- package/src/internal/funding/Reconciliation.ts +136 -0
- package/src/internal/funding/SourceObservation.test.ts +1 -0
- package/src/internal/funding/providers/relay.test.ts +1 -4
- package/src/internal/funding/providers/relay.ts +16 -1
|
@@ -4,6 +4,8 @@ import * as Ttl from '../../internal/Ttl.js'
|
|
|
4
4
|
|
|
5
5
|
/** A pluggable fiat FX rate oracle consumed by the valuation endpoint. */
|
|
6
6
|
export type Oracle = {
|
|
7
|
+
/** Returns the cache-key suffix for the oracle's current publication window. */
|
|
8
|
+
cacheKey?: (() => string) | undefined
|
|
7
9
|
/** Oracle identifier, surfaced as valuation rate provenance (e.g. `ecb`). */
|
|
8
10
|
name: string
|
|
9
11
|
/** Fetches the oracle's current rate set. */
|
|
@@ -34,6 +36,7 @@ export const ecbUrl = 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily
|
|
|
34
36
|
*/
|
|
35
37
|
export function from(oracle: from.Value): Oracle {
|
|
36
38
|
return {
|
|
39
|
+
...(oracle.cacheKey ? { cacheKey: oracle.cacheKey } : {}),
|
|
37
40
|
name: oracle.name,
|
|
38
41
|
rates: async () => {
|
|
39
42
|
try {
|
|
@@ -48,19 +51,17 @@ export function from(oracle: from.Value): Oracle {
|
|
|
48
51
|
|
|
49
52
|
export declare namespace from {
|
|
50
53
|
/** Oracle definition accepted by {@link from}. */
|
|
51
|
-
type Value = Pick<Oracle, 'name' | 'rates'> & {
|
|
54
|
+
type Value = Pick<Oracle, 'cacheKey' | 'name' | 'rates'> & {
|
|
52
55
|
/** Cache lifetime for a fetched rate set, in milliseconds. @default `Ttl.hours(1)` */
|
|
53
56
|
ttl?: number | undefined
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
/**
|
|
58
|
-
* European Central Bank euro reference rates: one EUR-based set covering ~30
|
|
59
|
-
* currencies, published once per working day.
|
|
60
|
-
*/
|
|
60
|
+
/** European Central Bank reference rates, cached for one day to match their publication cadence. */
|
|
61
61
|
export function ecb(options: ecb.Options = {}): Oracle {
|
|
62
62
|
const { fetch = globalThis.fetch, url = ecbUrl } = options
|
|
63
63
|
return from({
|
|
64
|
+
cacheKey: () => `ecb:${ecbPublication(new Date())}`,
|
|
64
65
|
name: 'ecb',
|
|
65
66
|
rates: async () => {
|
|
66
67
|
const response = await fetch(url, {
|
|
@@ -90,9 +91,31 @@ export function ecb(options: ecb.Options = {}): Oracle {
|
|
|
90
91
|
if (!asOf || rates['USD'] === undefined) throw new ParseError()
|
|
91
92
|
return { asOf: `${asOf}T00:00:00.000Z`, base: 'EUR', rates }
|
|
92
93
|
},
|
|
94
|
+
ttl: Ttl.days(1),
|
|
93
95
|
})
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
const ecbTime = new Intl.DateTimeFormat('en-CA', {
|
|
99
|
+
day: '2-digit',
|
|
100
|
+
hour: '2-digit',
|
|
101
|
+
hourCycle: 'h23',
|
|
102
|
+
month: '2-digit',
|
|
103
|
+
timeZone: 'Europe/Brussels',
|
|
104
|
+
year: 'numeric',
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
function ecbPublication(now: Date) {
|
|
108
|
+
const parts = Object.fromEntries(
|
|
109
|
+
ecbTime.formatToParts(now).map((part) => [part.type, part.value]),
|
|
110
|
+
)
|
|
111
|
+
// Rotate one hour after the ECB's usual 16:00 CET publication time.
|
|
112
|
+
const date = Number(parts['hour']) >= 17 ? now : new Date(now.getTime() - Ttl.days(1))
|
|
113
|
+
const publication = Object.fromEntries(
|
|
114
|
+
ecbTime.formatToParts(date).map((part) => [part.type, part.value]),
|
|
115
|
+
)
|
|
116
|
+
return `${publication['year']}-${publication['month']}-${publication['day']}`
|
|
117
|
+
}
|
|
118
|
+
|
|
96
119
|
export declare namespace ecb {
|
|
97
120
|
/** Options for the ECB oracle. */
|
|
98
121
|
type Options = {
|
|
@@ -328,6 +328,25 @@ describe('GET /addresses/:address/activities', () => {
|
|
|
328
328
|
`)
|
|
329
329
|
})
|
|
330
330
|
|
|
331
|
+
test.skipIf(runtime.mode !== 'localnet')('omits activity valuations by default', async () => {
|
|
332
|
+
const response = await client.v1.transactions[':transactionHash'].activities.$get(
|
|
333
|
+
{
|
|
334
|
+
param: { transactionHash: runtime.fixtures!.faucetTransactionHashes[0]! },
|
|
335
|
+
query: {},
|
|
336
|
+
},
|
|
337
|
+
TestApp.auth,
|
|
338
|
+
)
|
|
339
|
+
const body = await TestApp.json(response, Activities.schema.getTransactionActivities.Response)
|
|
340
|
+
expect(response.status).toBe(200)
|
|
341
|
+
|
|
342
|
+
const amounts = body.data.flatMap((item) =>
|
|
343
|
+
item.type === 'transfer' || item.type === 'mint' ? [item.data.sourceAmount] : [],
|
|
344
|
+
)
|
|
345
|
+
expect(amounts.length).toBeGreaterThan(0)
|
|
346
|
+
for (const amount of amounts) expect('valuation' in amount).toBe(false)
|
|
347
|
+
expect(response.headers.get('server-timing')).not.toContain('valuation_rates;dur=')
|
|
348
|
+
})
|
|
349
|
+
|
|
331
350
|
test('returns every item at the root by default (no group entries)', async () => {
|
|
332
351
|
const body = await activities(accounts.channel)
|
|
333
352
|
expect(body.data.every((entry) => entry.type !== 'group')).toBe(true)
|
|
@@ -1853,7 +1853,7 @@ export function addresses(options: addresses.Options = {}) {
|
|
|
1853
1853
|
const { address } = c.req.valid('param')
|
|
1854
1854
|
const query = c.req.valid('query')
|
|
1855
1855
|
|
|
1856
|
-
// Cursor pages carry current valuations and curated verification data.
|
|
1856
|
+
// Cursor pages can carry current valuations and curated verification data.
|
|
1857
1857
|
if (query.cursor !== undefined) Cache.setPolicy(c, Cache.policies.metadata)
|
|
1858
1858
|
|
|
1859
1859
|
try {
|
|
@@ -1884,7 +1884,7 @@ export function addresses(options: addresses.Options = {}) {
|
|
|
1884
1884
|
// Build immutable token and structured amount copies from RPC metadata.
|
|
1885
1885
|
const { items, rates } = await enrichTokens(c, {
|
|
1886
1886
|
chainId,
|
|
1887
|
-
denomination: query['valuation.currency']
|
|
1887
|
+
denomination: query['valuation.currency'],
|
|
1888
1888
|
include: query.include,
|
|
1889
1889
|
items: unenrichedItems,
|
|
1890
1890
|
oracle,
|
|
@@ -1964,7 +1964,7 @@ export function transactions(options: transactions.Options = {}) {
|
|
|
1964
1964
|
}),
|
|
1965
1965
|
Cache.response<App.Environment>({
|
|
1966
1966
|
// Default to no-store so a 404 does not mask the eventual mined result.
|
|
1967
|
-
// Successful responses use the metadata policy for
|
|
1967
|
+
// Successful responses use the metadata policy for optional valuations.
|
|
1968
1968
|
cacheControl: Cache.policies.noStore,
|
|
1969
1969
|
name: 'tempo-api:transactions:v1',
|
|
1970
1970
|
key: (c) => Cache.urlKey(c, schema.getTransactionActivities.Query),
|
|
@@ -2012,13 +2012,13 @@ export function transactions(options: transactions.Options = {}) {
|
|
|
2012
2012
|
status: 404,
|
|
2013
2013
|
})
|
|
2014
2014
|
|
|
2015
|
-
// Mined activity
|
|
2015
|
+
// Mined activity can carry current valuations and verification data.
|
|
2016
2016
|
Cache.setPolicy(c, Cache.policies.metadata)
|
|
2017
2017
|
|
|
2018
2018
|
// Build immutable token and structured amount copies from RPC metadata.
|
|
2019
2019
|
const { items, rates } = await enrichTokens(c, {
|
|
2020
2020
|
chainId,
|
|
2021
|
-
denomination: query['valuation.currency']
|
|
2021
|
+
denomination: query['valuation.currency'],
|
|
2022
2022
|
include: query.include,
|
|
2023
2023
|
items: unenrichedItems,
|
|
2024
2024
|
oracle,
|
|
@@ -4088,27 +4088,27 @@ async function enrichTokens(
|
|
|
4088
4088
|
const { chainId, denomination, include, items, oracle } = options
|
|
4089
4089
|
const addresses = new Set<Address.Address>()
|
|
4090
4090
|
for (const item of items) collectTokens(item, addresses)
|
|
4091
|
-
|
|
4092
|
-
|
|
4091
|
+
const includeLogoUri = include.includes('token.logoUri')
|
|
4092
|
+
const includeVerified = include.includes('token.verified')
|
|
4093
|
+
// Curated data is needed only for requested valuation or token fields.
|
|
4093
4094
|
const snapshot =
|
|
4094
|
-
addresses.size > 0
|
|
4095
|
+
addresses.size > 0 && (denomination !== undefined || includeLogoUri || includeVerified)
|
|
4095
4096
|
? await VerifiedTokens.snapshot(c, chainId).catch(() => undefined)
|
|
4096
4097
|
: undefined
|
|
4097
|
-
const rates =
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4098
|
+
const rates = denomination
|
|
4099
|
+
? await Valuation.ratesFor(c, {
|
|
4100
|
+
currencies: snapshot
|
|
4101
|
+
? [...addresses].flatMap((address) => {
|
|
4102
|
+
const held = snapshot.byAddress.get(address)?.currency
|
|
4103
|
+
return held === undefined ? [] : [held]
|
|
4104
|
+
})
|
|
4105
|
+
: [],
|
|
4106
|
+
denomination,
|
|
4107
|
+
oracle,
|
|
4108
|
+
})
|
|
4109
|
+
: undefined
|
|
4107
4110
|
if (addresses.size === 0) return { items: items as Item[], rates }
|
|
4108
4111
|
|
|
4109
|
-
const includeLogoUri = include.includes('token.logoUri')
|
|
4110
|
-
const includeVerified = include.includes('token.verified')
|
|
4111
|
-
|
|
4112
4112
|
const resolved = await Timing.time(c, 'tokens', () =>
|
|
4113
4113
|
Promise.all(
|
|
4114
4114
|
[...addresses].map(async (address) => {
|
|
@@ -4145,7 +4145,7 @@ declare namespace enrichTokens {
|
|
|
4145
4145
|
type Options = {
|
|
4146
4146
|
chainId: z.output<typeof Schema.ChainId>
|
|
4147
4147
|
/** Currency the amount valuations are denominated in (uppercase). */
|
|
4148
|
-
denomination
|
|
4148
|
+
denomination?: string | undefined
|
|
4149
4149
|
include: readonly z.output<typeof schema.Include>[]
|
|
4150
4150
|
items: readonly UnenrichedItem[]
|
|
4151
4151
|
/** FX rate oracle backing amount valuation. */
|
|
@@ -4173,13 +4173,17 @@ function enrichItem(
|
|
|
4173
4173
|
currency: meta.currency,
|
|
4174
4174
|
decimals: meta.decimals,
|
|
4175
4175
|
}),
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4176
|
+
...(valuation.denomination
|
|
4177
|
+
? {
|
|
4178
|
+
// Valued via the curated display currency, never the on-chain metadata.
|
|
4179
|
+
valuation: Valuation.valuationFor({
|
|
4180
|
+
amount: BigInt(t.amount),
|
|
4181
|
+
denomination: valuation.denomination,
|
|
4182
|
+
rates: valuation.rates,
|
|
4183
|
+
token: valuation.snapshot?.byAddress.get(t.address.toLowerCase()),
|
|
4184
|
+
}),
|
|
4185
|
+
}
|
|
4186
|
+
: {}),
|
|
4183
4187
|
}
|
|
4184
4188
|
data[key] = {
|
|
4185
4189
|
address: t.address,
|
|
@@ -4198,7 +4202,7 @@ declare namespace enrichItem {
|
|
|
4198
4202
|
/** Valuation inputs threaded from {@link enrichTokens}. */
|
|
4199
4203
|
type Valuation = {
|
|
4200
4204
|
/** Currency the amount valuations are denominated in (uppercase). */
|
|
4201
|
-
denomination
|
|
4205
|
+
denomination?: string | undefined
|
|
4202
4206
|
/** Oracle rate set, when loaded. */
|
|
4203
4207
|
rates: FxOracle.RateSet | undefined
|
|
4204
4208
|
/** Compiled verified-token snapshot, when available. */
|
|
@@ -297,8 +297,8 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
297
297
|
currency: 'USD',
|
|
298
298
|
decimals: 6,
|
|
299
299
|
logoUri: 'https://example.com/pathusd.svg',
|
|
300
|
-
name: 'PathUSD',
|
|
301
|
-
symbol: '
|
|
300
|
+
name: 'Curated PathUSD',
|
|
301
|
+
symbol: 'curatedUSD',
|
|
302
302
|
},
|
|
303
303
|
],
|
|
304
304
|
})
|
|
@@ -321,9 +321,49 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
321
321
|
const body = await TestApp.json(response, Balances.schema.getAddressBalances.Response)
|
|
322
322
|
expect(response.status).toBe(200)
|
|
323
323
|
expect(body.data.length).toBeGreaterThan(0)
|
|
324
|
+
expect(response.headers.get('server-timing')).not.toContain('token_logo;dur=')
|
|
325
|
+
expect(response.headers.get('server-timing')).not.toContain('token_metadata;dur=')
|
|
324
326
|
for (const balance of body.data) expect(balance.token.verified).toBe(true)
|
|
325
327
|
const pathUsd = body.data.find((balance) => balance.token.address === TestApp.tokenWithHolders)
|
|
326
|
-
expect(pathUsd?.token
|
|
328
|
+
expect(pathUsd?.token).toMatchObject({
|
|
329
|
+
logoUri: 'https://example.com/pathusd.svg',
|
|
330
|
+
name: 'Curated PathUSD',
|
|
331
|
+
symbol: 'curatedUSD',
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
test('includes uploaded logos for verified tokens without a logo URI', async () => {
|
|
336
|
+
const logoDb = TestApp.database()
|
|
337
|
+
await TestApp.verifiedSeed(logoDb, runtime.chainId, {
|
|
338
|
+
tokens: [
|
|
339
|
+
{
|
|
340
|
+
address: TestApp.tokenWithHolders,
|
|
341
|
+
currency: 'USD',
|
|
342
|
+
decimals: 6,
|
|
343
|
+
name: 'Curated PathUSD',
|
|
344
|
+
symbol: 'curatedUSD',
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
})
|
|
348
|
+
const client = app({ db: logoDb })
|
|
349
|
+
const holder = await holderWithBalance
|
|
350
|
+
|
|
351
|
+
const response = await client.v1.addresses[':address'].balances.$get(
|
|
352
|
+
{
|
|
353
|
+
param: { address: holder },
|
|
354
|
+
query: { limit: '20', verified: 'true' },
|
|
355
|
+
},
|
|
356
|
+
TestApp.auth,
|
|
357
|
+
)
|
|
358
|
+
const body = await TestApp.json(response, Balances.schema.getAddressBalances.Response)
|
|
359
|
+
const pathUsd = body.data.find((balance) => balance.token.address === TestApp.tokenWithHolders)
|
|
360
|
+
|
|
361
|
+
expect(response.status).toBe(200)
|
|
362
|
+
expect(response.headers.get('server-timing')).toContain('token_logo;dur=')
|
|
363
|
+
expect(response.headers.get('server-timing')).not.toContain('token_metadata;dur=')
|
|
364
|
+
expect(pathUsd?.token.logoUri).toBe(
|
|
365
|
+
`http://localhost/assets/${runtime.chainId}/icons/${TestApp.tokenWithHolders}`,
|
|
366
|
+
)
|
|
327
367
|
})
|
|
328
368
|
|
|
329
369
|
test('narrows balances by currency and retains verification', async () => {
|
|
@@ -360,7 +400,23 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
360
400
|
for (const balance of body.data) expect(typeof balance.feeEligible).toBe('boolean')
|
|
361
401
|
})
|
|
362
402
|
|
|
363
|
-
test('
|
|
403
|
+
test('skips the fee-token lookup when an address has no holdings', async () => {
|
|
404
|
+
const client = app()
|
|
405
|
+
const response = await client.v1.addresses[':address'].balances.$get(
|
|
406
|
+
{
|
|
407
|
+
param: { address: privateKeyToAccount(generatePrivateKey()).address },
|
|
408
|
+
query: {},
|
|
409
|
+
},
|
|
410
|
+
TestApp.auth,
|
|
411
|
+
)
|
|
412
|
+
const body = await TestApp.json(response, Balances.schema.getAddressBalances.Response)
|
|
413
|
+
|
|
414
|
+
expect(response.status).toBe(200)
|
|
415
|
+
expect(body.data).toEqual([])
|
|
416
|
+
expect(response.headers.get('server-timing')).not.toContain('fee_token_set;dur=')
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
test('omits valuations unless a denomination is requested', async () => {
|
|
364
420
|
await TestApp.verifiedSeed(db, runtime.chainId)
|
|
365
421
|
const client = app()
|
|
366
422
|
const holder = await holderWithBalance
|
|
@@ -371,12 +427,8 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
371
427
|
)
|
|
372
428
|
const body = await TestApp.json(response, Balances.schema.getAddressBalances.Response)
|
|
373
429
|
expect(response.status).toBe(200)
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
// holding itself; unverified holdings are never valued.
|
|
377
|
-
const pathUsd = body.data.find((entry) => entry.token.address === TestApp.tokenWithHolders)
|
|
378
|
-
expect(pathUsd?.valuation).toEqual({ amount: pathUsd!.formatted, currency: 'USD' })
|
|
379
|
-
for (const entry of body.data) if (!entry.token.verified) expect(entry.valuation).toBeNull()
|
|
430
|
+
expect(response.headers.get('server-timing')).not.toContain('valuation_rates;dur=')
|
|
431
|
+
for (const entry of body.data) expect('valuation' in entry).toBe(false)
|
|
380
432
|
})
|
|
381
433
|
|
|
382
434
|
test('values holdings in a requested denomination', async () => {
|
|
@@ -444,6 +496,26 @@ describe('GET /addresses/:address/balances', () => {
|
|
|
444
496
|
expect(stable.error.code).toMatchInlineSnapshot(`"query_invalid"`)
|
|
445
497
|
})
|
|
446
498
|
|
|
499
|
+
test('rejects unsupported denominations before balance RPC failures', async () => {
|
|
500
|
+
const client = app({
|
|
501
|
+
fx: { oracle: fixed },
|
|
502
|
+
rpc: { url: () => 'http://127.0.0.1:1' },
|
|
503
|
+
})
|
|
504
|
+
const holder = await holderWithBalance
|
|
505
|
+
|
|
506
|
+
const response = await client.v1.addresses[':address'].balances.$get(
|
|
507
|
+
{
|
|
508
|
+
param: { address: holder },
|
|
509
|
+
query: { 'valuation.currency': 'JPY' },
|
|
510
|
+
},
|
|
511
|
+
TestApp.auth,
|
|
512
|
+
)
|
|
513
|
+
const body = await TestApp.json(response, Schema.ErrorResponse)
|
|
514
|
+
|
|
515
|
+
expect(response.status).toMatchInlineSnapshot(`400`)
|
|
516
|
+
expect(body.error.code).toMatchInlineSnapshot(`"query_invalid"`)
|
|
517
|
+
})
|
|
518
|
+
|
|
447
519
|
test('restricts the page to fee tokens when feeEligible=true', async () => {
|
|
448
520
|
const client = app()
|
|
449
521
|
const holder = await holderWithBalance
|
|
@@ -489,7 +561,44 @@ describe('GET /v1/addresses/:address/balances/:token', () => {
|
|
|
489
561
|
expect(body.token.address).toBe(TestApp.tokenWithHolders)
|
|
490
562
|
expect(body.amount).toMatch(/^\d+$/)
|
|
491
563
|
expect(core_Value.from(body.formatted, body.decimals)).toBe(BigInt(body.amount))
|
|
564
|
+
expect('valuation' in body).toBe(false)
|
|
565
|
+
expect(response.headers.get('server-timing')).not.toContain('valuation_rates;dur=')
|
|
566
|
+
})
|
|
567
|
+
|
|
568
|
+
test('values one balance when a denomination is requested', async () => {
|
|
569
|
+
const client = app()
|
|
570
|
+
const holder = await holderWithBalance
|
|
571
|
+
const response = await client.v1.addresses[':address'].balances[':token'].$get(
|
|
572
|
+
{
|
|
573
|
+
param: { address: holder, token: TestApp.tokenWithHolders },
|
|
574
|
+
query: { 'valuation.currency': 'USD' },
|
|
575
|
+
},
|
|
576
|
+
TestApp.auth,
|
|
577
|
+
)
|
|
578
|
+
const body = await TestApp.json(response, Balances.schema.getAddressBalance.Response)
|
|
579
|
+
|
|
580
|
+
expect(response.status).toBe(200)
|
|
492
581
|
expect(body.valuation).toEqual({ amount: body.formatted, currency: 'USD' })
|
|
582
|
+
expect(response.headers.get('server-timing')).toContain('valuation_rates;dur=')
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
test('rejects an unsupported denomination before a balance RPC failure', async () => {
|
|
586
|
+
const client = app({
|
|
587
|
+
fx: { oracle: fixed },
|
|
588
|
+
rpc: { url: () => 'http://127.0.0.1:1' },
|
|
589
|
+
})
|
|
590
|
+
const holder = await holderWithBalance
|
|
591
|
+
const response = await client.v1.addresses[':address'].balances[':token'].$get(
|
|
592
|
+
{
|
|
593
|
+
param: { address: holder, token: TestApp.tokenWithHolders },
|
|
594
|
+
query: { 'valuation.currency': 'JPY' },
|
|
595
|
+
},
|
|
596
|
+
TestApp.auth,
|
|
597
|
+
)
|
|
598
|
+
const body = await TestApp.json(response, Schema.ErrorResponse)
|
|
599
|
+
|
|
600
|
+
expect(response.status).toMatchInlineSnapshot(`400`)
|
|
601
|
+
expect(body.error.code).toMatchInlineSnapshot(`"query_invalid"`)
|
|
493
602
|
})
|
|
494
603
|
|
|
495
604
|
test('returns zero for a token the account does not hold', async () => {
|