tapimo 0.10.1 → 0.10.2
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 +1776 -1543
- package/dist/App.d.ts.map +1 -1
- package/dist/Webhooks.d.ts +1 -1
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +1 -1
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +4 -4
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +206 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +4 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +2 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +2 -0
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +340 -340
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +2 -6
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +63 -34
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +4 -0
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +115 -77
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +34 -34
- package/dist/apps/data/routes/receipts.d.ts +4 -3
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +15 -14
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +1 -1
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +4 -3
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +281 -281
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +25 -7
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +380 -364
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +16 -9
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +31 -31
- package/dist/apps/funding/App.d.ts +233 -0
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +330 -0
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +207 -11
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/providers.d.ts +13 -13
- package/dist/apps/management/routes/faucet.d.ts +32 -32
- package/dist/apps/management/routes/members.d.ts +79 -79
- package/dist/apps/management/routes/usage.d.ts +80 -80
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +32 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.d.ts +20 -0
- package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.js +17 -0
- package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
- package/dist/db/tables/fundingDeposits.d.ts +41 -0
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +69 -0
- package/dist/db/tables/fundingDeposits.js.map +1 -1
- package/dist/internal/Auth.d.ts +0 -26
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +0 -33
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Credential.d.ts +27 -0
- package/dist/internal/Credential.d.ts.map +1 -0
- package/dist/internal/Credential.js +34 -0
- package/dist/internal/Credential.js.map +1 -0
- package/dist/internal/EarnRates.js +11 -7
- package/dist/internal/EarnRates.js.map +1 -1
- package/dist/internal/Log.d.ts +8 -0
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +4 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +24 -0
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +25 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Tidx.d.ts +4 -3
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +2 -2
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts +4 -3
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +3 -2
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +2 -0
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +10 -3
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -0
- package/dist/internal/index.js.map +1 -1
- package/package.json +1 -1
- package/src/App.test.ts +30 -0
- package/src/Client.test-d.ts +33 -1
- package/src/Webhooks.ts +1 -1
- package/src/analytics/Analytics.test.ts +34 -0
- package/src/analytics/Analytics.ts +206 -0
- package/src/analytics/tables/requestEvents.ts +6 -0
- package/src/apps/composition.test.ts +1 -0
- package/src/apps/data/App.ts +2 -0
- package/src/apps/data/routes/balances.ts +2 -6
- package/src/apps/data/routes/earn.ts +64 -34
- package/src/apps/data/routes/exchanges.test.ts +8 -3
- package/src/apps/data/routes/exchanges.ts +134 -82
- package/src/apps/data/routes/receipts.ts +16 -14
- package/src/apps/data/routes/tokens.ts +3 -2
- package/src/apps/data/routes/transactions.bench.ts +8 -0
- package/src/apps/data/routes/transfers.ts +36 -10
- package/src/apps/data/routes/webhooks.ts +31 -10
- package/src/apps/funding/routes/deposit-addresses.test.ts +216 -2
- package/src/apps/funding/routes/deposit-addresses.ts +254 -18
- package/src/apps/funding/routes/transfers.test.ts +17 -2
- package/src/db/Db.ts +36 -0
- package/src/db/tables/fundingDepositAddresses.test.ts +28 -0
- package/src/db/tables/fundingDepositAddresses.ts +39 -0
- package/src/db/tables/fundingDeposits.test.ts +82 -0
- package/src/db/tables/fundingDeposits.ts +130 -0
- package/src/internal/Auth.test.ts +0 -48
- package/src/internal/Auth.ts +0 -59
- package/src/internal/Credential.test.ts +51 -0
- package/src/internal/Credential.ts +58 -0
- package/src/internal/EarnRates.ts +13 -7
- package/src/internal/Log.test.ts +3 -1
- package/src/internal/Log.ts +14 -0
- package/src/internal/MetricSink.test.ts +50 -1
- package/src/internal/MetricSink.ts +52 -0
- package/src/internal/Tidx.ts +5 -4
- package/src/internal/Viem.test.ts +4 -0
- package/src/internal/Viem.ts +6 -4
- package/src/internal/Webhooks.test.ts +9 -0
- package/src/internal/Webhooks.ts +10 -3
- package/src/internal/index.test.ts +1 -0
- package/src/internal/index.ts +1 -0
|
@@ -976,9 +976,10 @@ function countFilters(options: countReceipts.Options): string[] {
|
|
|
976
976
|
|
|
977
977
|
/**
|
|
978
978
|
* Capped total count of receipts matching the same filters as the page. The
|
|
979
|
-
* address scope reuses the page's `UNION` workaround
|
|
980
|
-
*
|
|
981
|
-
*
|
|
979
|
+
* address scope reuses the page's `UNION` workaround. Fee-payer filters make a
|
|
980
|
+
* direct `receipts.to` predicate selective; otherwise the `to` side resolves
|
|
981
|
+
* hashes via the `txs` index. The deduped keyset avoids double-counting
|
|
982
|
+
* self-receipts (`from = to`).
|
|
982
983
|
* `totalCountCapped: true` is a lower bound — at least {@link Schema.countCap} matches.
|
|
983
984
|
*/
|
|
984
985
|
export function countReceipts(
|
|
@@ -995,10 +996,11 @@ export function countReceipts(
|
|
|
995
996
|
let query: string
|
|
996
997
|
if (address !== undefined) {
|
|
997
998
|
const fromWhere = [`"from" = '${address}'`, ...filters].join(' AND ')
|
|
998
|
-
const
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
999
|
+
const toPredicate =
|
|
1000
|
+
options.feePayer !== undefined
|
|
1001
|
+
? `"to" = '${address}'`
|
|
1002
|
+
: `tx_hash IN (SELECT hash FROM txs WHERE "to" = '${address}')`
|
|
1003
|
+
const toWhere = [toPredicate, ...filters].join(' AND ')
|
|
1002
1004
|
query = `
|
|
1003
1005
|
SELECT count(*) AS total
|
|
1004
1006
|
FROM (
|
|
@@ -1069,13 +1071,13 @@ export async function embedReceipts(
|
|
|
1069
1071
|
const { chainId, include, rows } = options
|
|
1070
1072
|
const found = new Map<string, humanizeReceipt.Output>()
|
|
1071
1073
|
|
|
1072
|
-
// Only mined rows can have a receipt; their block numbers also
|
|
1073
|
-
//
|
|
1074
|
-
const blocks
|
|
1074
|
+
// Only mined rows can have a receipt; their block numbers also constrain the
|
|
1075
|
+
// `logs` lookup to the page's exact blocks.
|
|
1076
|
+
const blocks = new Set<number>()
|
|
1075
1077
|
const hashes = new Set<string>()
|
|
1076
1078
|
for (const row of rows) {
|
|
1077
1079
|
if (row.blockNumber === null) continue
|
|
1078
|
-
blocks.
|
|
1080
|
+
blocks.add(row.blockNumber)
|
|
1079
1081
|
hashes.add(row.hash.toLowerCase())
|
|
1080
1082
|
}
|
|
1081
1083
|
if (hashes.size === 0) return new Map()
|
|
@@ -1086,8 +1088,8 @@ export async function embedReceipts(
|
|
|
1086
1088
|
// Raw-table queries (no signature-decoded CTE), so the inline SQL is cast to
|
|
1087
1089
|
// `string`. The receipts lookup needs no block bound (`receipts.tx_hash` is
|
|
1088
1090
|
// indexed — see the `sideQuery` comment in `getReceipts`); the logs lookup
|
|
1089
|
-
//
|
|
1090
|
-
//
|
|
1091
|
+
// uses the page's exact blocks so sparse transaction pages do not scan every
|
|
1092
|
+
// block between their oldest and newest rows.
|
|
1091
1093
|
const [receipts_result, logs_result] = await Timing.time(c, 'receipts_embed', () =>
|
|
1092
1094
|
Promise.all([
|
|
1093
1095
|
options.receipts
|
|
@@ -1100,7 +1102,7 @@ export async function embedReceipts(
|
|
|
1100
1102
|
tidx.fetch({
|
|
1101
1103
|
chainId,
|
|
1102
1104
|
query:
|
|
1103
|
-
`SELECT tx_hash, block_num, tx_idx, log_idx, address, selector, topic1, topic2, topic3, data, block_timestamp FROM logs WHERE block_num
|
|
1105
|
+
`SELECT tx_hash, block_num, tx_idx, log_idx, address, selector, topic1, topic2, topic3, data, block_timestamp FROM logs WHERE block_num IN (${[...blocks].join(', ')}) AND tx_hash IN (${inList}) ORDER BY block_num ASC, log_idx ASC` as string,
|
|
1104
1106
|
}),
|
|
1105
1107
|
]),
|
|
1106
1108
|
)
|
|
@@ -1228,12 +1228,13 @@ export async function resolveTokens(
|
|
|
1228
1228
|
const logos = new Map(logoByAddress)
|
|
1229
1229
|
const metadataMap = new Map(metadataByAddress)
|
|
1230
1230
|
const onchainLogos = new Map(
|
|
1231
|
-
includeLogoUri
|
|
1231
|
+
includeLogoUri && options.metadata !== undefined
|
|
1232
1232
|
? await Timing.time(c, 'token_logo_uri', () =>
|
|
1233
1233
|
Promise.all(
|
|
1234
1234
|
addresses
|
|
1235
1235
|
.filter(
|
|
1236
1236
|
(address) =>
|
|
1237
|
+
options.metadata?.has(address) === true &&
|
|
1237
1238
|
!logos.get(address) &&
|
|
1238
1239
|
!snapshot?.byAddress.get(address)?.logoUri &&
|
|
1239
1240
|
!metadataMap.get(address)?.logoUri,
|
|
@@ -1321,7 +1322,7 @@ function getTokenLogoUri(c: Context<App.Environment>, options: getTokenMetadata.
|
|
|
1321
1322
|
|
|
1322
1323
|
/**
|
|
1323
1324
|
* Resolves a token's on-chain metadata (`name`/`symbol`/`decimals`/`currency`/
|
|
1324
|
-
* `totalSupply`) via a single `token.getMetadata` RPC, memoized under the shared
|
|
1325
|
+
* `logoUri`/`totalSupply`) via a single `token.getMetadata` RPC, memoized under the shared
|
|
1325
1326
|
* `token:v1:{chainId}:{address}:metadata` key. Exported so feed enrichers (e.g.
|
|
1326
1327
|
* `activities`) reuse the same cache entry as the token resource rather than
|
|
1327
1328
|
* fetching metadata through a parallel path.
|
|
@@ -30,6 +30,14 @@ QueryBench.bench({
|
|
|
30
30
|
},
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
+
QueryBench.bench({
|
|
34
|
+
name: 'getTransactions?address&include=receipt',
|
|
35
|
+
path: {
|
|
36
|
+
4217: `/v1/transactions?address=${address[4217]}&include=receipt`,
|
|
37
|
+
42431: `/v1/transactions?address=${address[42431]}&include=receipt`,
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
|
|
33
41
|
QueryBench.bench({
|
|
34
42
|
name: 'getTransaction',
|
|
35
43
|
path: {
|
|
@@ -437,10 +437,10 @@ export function transfers(options: transfers.Options = {}) {
|
|
|
437
437
|
transactionHash: row.transactionHash,
|
|
438
438
|
}))
|
|
439
439
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
//
|
|
443
|
-
if (
|
|
440
|
+
const includeCrossToken = options.include.includes('crossToken')
|
|
441
|
+
const includeMemo = options.include.includes('memo')
|
|
442
|
+
// Attach memos before cross-token folding changes the matching fields.
|
|
443
|
+
if (includeMemo && includeCrossToken && data.length > 0) {
|
|
444
444
|
const memoByRow = await memoMap(c, { chainId, rows: data })
|
|
445
445
|
data = data.map((transfer) => {
|
|
446
446
|
const entry = memoByRow.get(
|
|
@@ -469,7 +469,7 @@ export function transfers(options: transfers.Options = {}) {
|
|
|
469
469
|
// own event token is the delivered token), so a `token=` filter matches
|
|
470
470
|
// cross-token rows by their destination token, and each transfer still
|
|
471
471
|
// appears exactly once across pages.
|
|
472
|
-
if (
|
|
472
|
+
if (includeCrossToken && data.length > 0) {
|
|
473
473
|
const { fold, suppress } = await crossTokenMap(c, { chainId, rows: data })
|
|
474
474
|
data = data.flatMap((transfer) => {
|
|
475
475
|
const key = eventKey(transfer.transactionHash, transfer.logIndex)
|
|
@@ -502,11 +502,17 @@ export function transfers(options: transfers.Options = {}) {
|
|
|
502
502
|
]),
|
|
503
503
|
),
|
|
504
504
|
)
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
505
|
+
// Memo and token metadata depend only on the page rows, so resolve
|
|
506
|
+
// them concurrently when cross-token folding is not requested.
|
|
507
|
+
const memoPromise =
|
|
508
|
+
includeMemo && !includeCrossToken && data.length > 0
|
|
509
|
+
? memoMap(c, { chainId, rows: data })
|
|
510
|
+
: undefined
|
|
511
|
+
const tokensPromise = Timing.time(c, 'tokens', async () => {
|
|
512
|
+
const tokensByAddress = new Map<
|
|
513
|
+
string,
|
|
514
|
+
Awaited<ReturnType<typeof Tokens.getTokenMetadata>>
|
|
515
|
+
>()
|
|
510
516
|
for (let index = 0; index < uniqueTokens.length; index += tokenReadChunkSize) {
|
|
511
517
|
const group = uniqueTokens.slice(index, index + tokenReadChunkSize)
|
|
512
518
|
const entries = await Promise.all(
|
|
@@ -536,7 +542,27 @@ export function transfers(options: transfers.Options = {}) {
|
|
|
536
542
|
)
|
|
537
543
|
for (const entry of entries) tokensByAddress.set(...entry)
|
|
538
544
|
}
|
|
545
|
+
return tokensByAddress
|
|
539
546
|
})
|
|
547
|
+
const [memoByRow, tokensByAddress] = await Promise.all([memoPromise, tokensPromise])
|
|
548
|
+
if (memoByRow)
|
|
549
|
+
data = data.map((transfer) => {
|
|
550
|
+
const entry = memoByRow.get(
|
|
551
|
+
transferKey(
|
|
552
|
+
transfer.transactionHash,
|
|
553
|
+
transfer.sender,
|
|
554
|
+
transfer.recipient,
|
|
555
|
+
transfer.sourceToken.amount,
|
|
556
|
+
transfer.sourceToken.address,
|
|
557
|
+
),
|
|
558
|
+
)
|
|
559
|
+
if (!entry) return transfer
|
|
560
|
+
return {
|
|
561
|
+
...transfer,
|
|
562
|
+
...(entry.attribution ? { attribution: entry.attribution } : {}),
|
|
563
|
+
...(entry.memo ? { memo: entry.memo } : {}),
|
|
564
|
+
}
|
|
565
|
+
})
|
|
540
566
|
|
|
541
567
|
// Value each leg via its curated display currency, in the requested
|
|
542
568
|
// denomination.
|
|
@@ -2276,16 +2276,11 @@ export async function poll(options: poll.Options): Promise<poll.Result> {
|
|
|
2276
2276
|
// Token metadata is best-effort for webhooks: transfers use it for display,
|
|
2277
2277
|
// while transactions expose fee-token references without blocking delivery.
|
|
2278
2278
|
const enrichStartedAt = Date.now()
|
|
2279
|
-
const data =
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
cache: cache_metadata,
|
|
2285
|
-
chainId,
|
|
2286
|
-
client,
|
|
2287
|
-
})
|
|
2288
|
-
: item.data
|
|
2279
|
+
const data = await enrichEventData(eventType, item.data, {
|
|
2280
|
+
cache: cache_metadata,
|
|
2281
|
+
chainId,
|
|
2282
|
+
client,
|
|
2283
|
+
})
|
|
2289
2284
|
durations.enrich += Date.now() - enrichStartedAt
|
|
2290
2285
|
observed.add('enrich')
|
|
2291
2286
|
options.signal?.throwIfAborted()
|
|
@@ -3074,6 +3069,32 @@ function decodeScanLog(log: ScanLog, event: AbiEvent): ScanLog {
|
|
|
3074
3069
|
}
|
|
3075
3070
|
}
|
|
3076
3071
|
|
|
3072
|
+
/**
|
|
3073
|
+
* Attaches best-effort token metadata to one matched row before delivery.
|
|
3074
|
+
* Transfers gain display metadata; transactions resolve their fee token.
|
|
3075
|
+
*/
|
|
3076
|
+
export async function enrichEventData(
|
|
3077
|
+
eventType: EventType,
|
|
3078
|
+
data: unknown,
|
|
3079
|
+
options: enrichEventData.Options,
|
|
3080
|
+
): Promise<unknown> {
|
|
3081
|
+
if (eventType === 'token:transfer') return withTokenMetadata(data, options)
|
|
3082
|
+
if (eventType === 'transaction:included') return withFeeTokenMetadata(data, options)
|
|
3083
|
+
return data
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
export declare namespace enrichEventData {
|
|
3087
|
+
/** Metadata lookup dependencies. */
|
|
3088
|
+
type Options = {
|
|
3089
|
+
/** Memoization store for token metadata (5 minute TTL). */
|
|
3090
|
+
cache: Store.Store
|
|
3091
|
+
/** Chain the row was produced on. */
|
|
3092
|
+
chainId: number
|
|
3093
|
+
/** Client for `token.getMetadata` reads. */
|
|
3094
|
+
client: Viem.getClient.ReturnType
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
|
|
3077
3098
|
/** Scans contract logs for one subscription, then re-checks every filter in memory. */
|
|
3078
3099
|
async function scanLogs(options: {
|
|
3079
3100
|
batchSize: number
|
|
@@ -966,6 +966,67 @@ describe.skipIf(!relayApiKey)('POST /funding/deposit-addresses (relay)', () => {
|
|
|
966
966
|
})
|
|
967
967
|
})
|
|
968
968
|
|
|
969
|
+
describe('GET /funding/deposit-addresses', () => {
|
|
970
|
+
test('pages organization addresses and narrows project-attributed keys', async () => {
|
|
971
|
+
const app = transfersApp()
|
|
972
|
+
const first = await seedDepositAddress({ orgId: fundingAddressListReader.orgId })
|
|
973
|
+
const second = await seedDepositAddress({ orgId: fundingAddressListReader.orgId })
|
|
974
|
+
const third = await seedDepositAddress({ orgId: fundingAddressListReader.orgId })
|
|
975
|
+
const project = await seedDepositAddress({
|
|
976
|
+
orgId: fundingAddressListReader.orgId,
|
|
977
|
+
projectId: fundingAddressListProjectReader.projectId,
|
|
978
|
+
})
|
|
979
|
+
const sibling = await seedDepositAddress({
|
|
980
|
+
orgId: fundingAddressListReader.orgId,
|
|
981
|
+
projectId: 'prj_funding_address_list_sibling',
|
|
982
|
+
})
|
|
983
|
+
const sixth = await seedDepositAddress({ orgId: fundingAddressListReader.orgId })
|
|
984
|
+
await seedDepositAddress({ orgId: fundingForeign.orgId })
|
|
985
|
+
await seedDepositAddress({
|
|
986
|
+
environment: 'sandbox',
|
|
987
|
+
orgId: fundingAddressListReader.orgId,
|
|
988
|
+
})
|
|
989
|
+
|
|
990
|
+
const path = '/v1/funding/deposit-addresses'
|
|
991
|
+
expect((await app.request(path)).status).toBe(401)
|
|
992
|
+
expect((await app.request(path, as(TestApp.key))).status).toBe(403)
|
|
993
|
+
|
|
994
|
+
const response = await app.request(`${path}?limit=5`, as(fundingAddressListReader))
|
|
995
|
+
expect(response.status).toBe(200)
|
|
996
|
+
expect({
|
|
997
|
+
cacheControl: response.headers.get('cache-control'),
|
|
998
|
+
etag: response.headers.get('etag'),
|
|
999
|
+
vary: response.headers.get('vary'),
|
|
1000
|
+
}).toEqual({
|
|
1001
|
+
cacheControl: 'no-store',
|
|
1002
|
+
etag: null,
|
|
1003
|
+
vary: 'Accept-Encoding, Authorization, Tempo-API-Key, X-API-Key',
|
|
1004
|
+
})
|
|
1005
|
+
const body = await TestApp.json(response, Funding.schema.listFundingDepositAddresses.Response)
|
|
1006
|
+
expect(body.data.map((address) => address.id)).toEqual([
|
|
1007
|
+
sixth.id,
|
|
1008
|
+
sibling.id,
|
|
1009
|
+
project.id,
|
|
1010
|
+
third.id,
|
|
1011
|
+
second.id,
|
|
1012
|
+
])
|
|
1013
|
+
expect(body.nextCursor).toBe(Cursor.encode([second.createdAt, second.id]))
|
|
1014
|
+
|
|
1015
|
+
const next = await TestApp.json(
|
|
1016
|
+
await app.request(`${path}?limit=5&cursor=${body.nextCursor}`, as(fundingAddressListReader)),
|
|
1017
|
+
Funding.schema.listFundingDepositAddresses.Response,
|
|
1018
|
+
)
|
|
1019
|
+
expect(next.data.map((address) => address.id)).toEqual([first.id])
|
|
1020
|
+
expect(next.nextCursor).toBeNull()
|
|
1021
|
+
|
|
1022
|
+
const projectBody = await TestApp.json(
|
|
1023
|
+
await app.request(path, as(fundingAddressListProjectReader)),
|
|
1024
|
+
Funding.schema.listFundingDepositAddresses.Response,
|
|
1025
|
+
)
|
|
1026
|
+
expect(projectBody.data.map((address) => address.id)).toEqual([project.id])
|
|
1027
|
+
})
|
|
1028
|
+
})
|
|
1029
|
+
|
|
969
1030
|
describe('GET /funding/deposit-addresses/:id', () => {
|
|
970
1031
|
test('requires funding:read and returns the owned address', async () => {
|
|
971
1032
|
const app = transfersApp()
|
|
@@ -1329,6 +1390,63 @@ describe('GET /funding/deposits?depositAddress', () => {
|
|
|
1329
1390
|
})
|
|
1330
1391
|
})
|
|
1331
1392
|
|
|
1393
|
+
describe('GET /funding/deposits', () => {
|
|
1394
|
+
test('pages organization deposits and narrows project-attributed keys', async () => {
|
|
1395
|
+
const app = transfersApp()
|
|
1396
|
+
const firstAddress = await seedDepositAddress({ orgId: fundingDepositListReader.orgId })
|
|
1397
|
+
const secondAddress = await seedDepositAddress({ orgId: fundingDepositListReader.orgId })
|
|
1398
|
+
const thirdAddress = await seedDepositAddress({ orgId: fundingDepositListReader.orgId })
|
|
1399
|
+
const projectAddress = await seedDepositAddress({
|
|
1400
|
+
orgId: fundingDepositListReader.orgId,
|
|
1401
|
+
projectId: fundingDepositListProjectReader.projectId,
|
|
1402
|
+
})
|
|
1403
|
+
const siblingAddress = await seedDepositAddress({
|
|
1404
|
+
orgId: fundingDepositListReader.orgId,
|
|
1405
|
+
projectId: 'prj_funding_deposit_list_sibling',
|
|
1406
|
+
})
|
|
1407
|
+
const sixthAddress = await seedDepositAddress({ orgId: fundingDepositListReader.orgId })
|
|
1408
|
+
const first = await seedDeposit(firstAddress)
|
|
1409
|
+
const second = await seedDeposit(secondAddress)
|
|
1410
|
+
const third = await seedDeposit(thirdAddress)
|
|
1411
|
+
const project = await seedDeposit(projectAddress)
|
|
1412
|
+
const sibling = await seedDeposit(siblingAddress)
|
|
1413
|
+
const sixth = await seedDeposit(sixthAddress)
|
|
1414
|
+
const foreignAddress = await seedDepositAddress({ orgId: fundingForeign.orgId })
|
|
1415
|
+
await seedDeposit(foreignAddress)
|
|
1416
|
+
const sandboxAddress = await seedDepositAddress({
|
|
1417
|
+
environment: 'sandbox',
|
|
1418
|
+
orgId: fundingDepositListReader.orgId,
|
|
1419
|
+
})
|
|
1420
|
+
await seedDeposit(sandboxAddress)
|
|
1421
|
+
|
|
1422
|
+
const path = '/v1/funding/deposits'
|
|
1423
|
+
const response = await app.request(`${path}?limit=5`, as(fundingDepositListReader))
|
|
1424
|
+
expect(response.status).toBe(200)
|
|
1425
|
+
const body = await TestApp.json(response, Funding.schema.listFundingDeposits.Response)
|
|
1426
|
+
expect(body.data.map((deposit) => deposit.id)).toEqual([
|
|
1427
|
+
sixth.id,
|
|
1428
|
+
sibling.id,
|
|
1429
|
+
project.id,
|
|
1430
|
+
third.id,
|
|
1431
|
+
second.id,
|
|
1432
|
+
])
|
|
1433
|
+
expect(body.nextCursor).toBe(Cursor.encode([second.createdAt, second.id]))
|
|
1434
|
+
|
|
1435
|
+
const next = await TestApp.json(
|
|
1436
|
+
await app.request(`${path}?limit=5&cursor=${body.nextCursor}`, as(fundingDepositListReader)),
|
|
1437
|
+
Funding.schema.listFundingDeposits.Response,
|
|
1438
|
+
)
|
|
1439
|
+
expect(next.data.map((deposit) => deposit.id)).toEqual([first.id])
|
|
1440
|
+
expect(next.nextCursor).toBeNull()
|
|
1441
|
+
|
|
1442
|
+
const projectBody = await TestApp.json(
|
|
1443
|
+
await app.request(path, as(fundingDepositListProjectReader)),
|
|
1444
|
+
Funding.schema.listFundingDeposits.Response,
|
|
1445
|
+
)
|
|
1446
|
+
expect(projectBody.data.map((deposit) => deposit.id)).toEqual([project.id])
|
|
1447
|
+
})
|
|
1448
|
+
})
|
|
1449
|
+
|
|
1332
1450
|
describe('GET /funding/deposits?recipient', () => {
|
|
1333
1451
|
test('requires funding:read and pages recipient deposits across addresses', async () => {
|
|
1334
1452
|
const app = transfersApp()
|
|
@@ -1336,7 +1454,6 @@ describe('GET /funding/deposits?recipient', () => {
|
|
|
1336
1454
|
const path = `/v1/funding/deposits?recipient=${recipient.toLowerCase()}`
|
|
1337
1455
|
expect((await app.request(path)).status).toBe(401)
|
|
1338
1456
|
expect((await app.request(path, as(TestApp.key))).status).toBe(403)
|
|
1339
|
-
expect((await app.request('/v1/funding/deposits', as(fundingReader))).status).toBe(400)
|
|
1340
1457
|
expect(
|
|
1341
1458
|
(await app.request('/v1/funding/deposits?depositAddress=', as(fundingReader))).status,
|
|
1342
1459
|
).toBe(400)
|
|
@@ -1347,7 +1464,7 @@ describe('GET /funding/deposits?recipient', () => {
|
|
|
1347
1464
|
as(fundingReader),
|
|
1348
1465
|
)
|
|
1349
1466
|
).status,
|
|
1350
|
-
).toBe(
|
|
1467
|
+
).toBe(200)
|
|
1351
1468
|
|
|
1352
1469
|
const firstAddress = await seedDepositAddress({
|
|
1353
1470
|
snapshot: TestFunding.depositAddressSnapshot({
|
|
@@ -1438,6 +1555,61 @@ describe('GET /funding/deposits?recipient', () => {
|
|
|
1438
1555
|
})
|
|
1439
1556
|
})
|
|
1440
1557
|
|
|
1558
|
+
describe('GET /funding/deposits?filters', () => {
|
|
1559
|
+
test('AND-combines canonical aliases and includes the matching count', async () => {
|
|
1560
|
+
const app = transfersApp()
|
|
1561
|
+
const snapshot = TestFunding.depositAddressSnapshot({
|
|
1562
|
+
address: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
|
1563
|
+
})
|
|
1564
|
+
const address = await seedDepositAddress({
|
|
1565
|
+
orgId: fundingDepositFilterReader.orgId,
|
|
1566
|
+
snapshot,
|
|
1567
|
+
})
|
|
1568
|
+
const deposit = await seedDeposit(address)
|
|
1569
|
+
const unrelatedAddress = await seedDepositAddress({
|
|
1570
|
+
orgId: fundingDepositFilterReader.orgId,
|
|
1571
|
+
snapshot: TestFunding.depositAddressSnapshot({
|
|
1572
|
+
provider: { id: 'relay_unrelated', name: 'Relay' },
|
|
1573
|
+
recipient: `0x${'22'.repeat(20)}`,
|
|
1574
|
+
}),
|
|
1575
|
+
})
|
|
1576
|
+
await seedDeposit(unrelatedAddress)
|
|
1577
|
+
const query = new URLSearchParams({
|
|
1578
|
+
depositAddress: snapshot.address,
|
|
1579
|
+
destinationToken: 'usdt0',
|
|
1580
|
+
include: 'totalCount',
|
|
1581
|
+
provider: 'relay',
|
|
1582
|
+
recipient: snapshot.recipient.toLowerCase(),
|
|
1583
|
+
sourceChain: 'tron',
|
|
1584
|
+
sourceToken: 'usdt',
|
|
1585
|
+
status: 'detected',
|
|
1586
|
+
})
|
|
1587
|
+
|
|
1588
|
+
const body = await TestApp.json(
|
|
1589
|
+
await app.request(`/v1/funding/deposits?${query}`, as(fundingDepositFilterReader)),
|
|
1590
|
+
Funding.schema.listFundingDeposits.Response,
|
|
1591
|
+
)
|
|
1592
|
+
expect(body).toEqual({
|
|
1593
|
+
data: [FundingDeposit.toPublic(deposit)],
|
|
1594
|
+
meta: { totalCount: 1, totalCountCapped: false },
|
|
1595
|
+
nextCursor: null,
|
|
1596
|
+
})
|
|
1597
|
+
|
|
1598
|
+
const empty = await TestApp.json(
|
|
1599
|
+
await app.request(
|
|
1600
|
+
'/v1/funding/deposits?sourceToken=unknown&include=totalCount',
|
|
1601
|
+
as(fundingDepositFilterReader),
|
|
1602
|
+
),
|
|
1603
|
+
Funding.schema.listFundingDeposits.Response,
|
|
1604
|
+
)
|
|
1605
|
+
expect(empty).toEqual({
|
|
1606
|
+
data: [],
|
|
1607
|
+
meta: { totalCount: 0, totalCountCapped: false },
|
|
1608
|
+
nextCursor: null,
|
|
1609
|
+
})
|
|
1610
|
+
})
|
|
1611
|
+
})
|
|
1612
|
+
|
|
1441
1613
|
describe('GET /funding/deposits/:id', () => {
|
|
1442
1614
|
test('returns the owned deposit without private reconciliation state', async () => {
|
|
1443
1615
|
const app = transfersApp()
|
|
@@ -1544,6 +1716,43 @@ const fundingReader = {
|
|
|
1544
1716
|
token: 'secret_funding_reader',
|
|
1545
1717
|
} satisfies TestApp.kvStore.Key
|
|
1546
1718
|
|
|
1719
|
+
const fundingAddressListReader = {
|
|
1720
|
+
id: 'key_funding_address_list',
|
|
1721
|
+
orgId: 'org_funding_address_list',
|
|
1722
|
+
scopes: ['funding:read'],
|
|
1723
|
+
token: 'secret_funding_address_list',
|
|
1724
|
+
} satisfies TestApp.kvStore.Key
|
|
1725
|
+
|
|
1726
|
+
const fundingAddressListProjectReader = {
|
|
1727
|
+
id: 'key_funding_address_list_project',
|
|
1728
|
+
orgId: fundingAddressListReader.orgId,
|
|
1729
|
+
projectId: 'prj_funding_address_list',
|
|
1730
|
+
scopes: ['funding:read'],
|
|
1731
|
+
token: 'secret_funding_address_list_project',
|
|
1732
|
+
} satisfies TestApp.kvStore.Key
|
|
1733
|
+
|
|
1734
|
+
const fundingDepositListReader = {
|
|
1735
|
+
id: 'key_funding_deposit_list',
|
|
1736
|
+
orgId: 'org_funding_deposit_list',
|
|
1737
|
+
scopes: ['funding:read'],
|
|
1738
|
+
token: 'secret_funding_deposit_list',
|
|
1739
|
+
} satisfies TestApp.kvStore.Key
|
|
1740
|
+
|
|
1741
|
+
const fundingDepositFilterReader = {
|
|
1742
|
+
id: 'key_funding_deposit_filter',
|
|
1743
|
+
orgId: 'org_funding_deposit_filter',
|
|
1744
|
+
scopes: ['funding:read'],
|
|
1745
|
+
token: 'secret_funding_deposit_filter',
|
|
1746
|
+
} satisfies TestApp.kvStore.Key
|
|
1747
|
+
|
|
1748
|
+
const fundingDepositListProjectReader = {
|
|
1749
|
+
id: 'key_funding_deposit_list_project',
|
|
1750
|
+
orgId: fundingDepositListReader.orgId,
|
|
1751
|
+
projectId: 'prj_funding_deposit_list',
|
|
1752
|
+
scopes: ['funding:read'],
|
|
1753
|
+
token: 'secret_funding_deposit_list_project',
|
|
1754
|
+
} satisfies TestApp.kvStore.Key
|
|
1755
|
+
|
|
1547
1756
|
const fundingProjectReader = {
|
|
1548
1757
|
id: 'key_funding_project',
|
|
1549
1758
|
orgId: 'org_funding',
|
|
@@ -1589,6 +1798,11 @@ function transfersApp(options: TestApp.create.Options = {}) {
|
|
|
1589
1798
|
auth: {
|
|
1590
1799
|
keys: [
|
|
1591
1800
|
TestApp.key,
|
|
1801
|
+
fundingAddressListProjectReader,
|
|
1802
|
+
fundingAddressListReader,
|
|
1803
|
+
fundingDepositFilterReader,
|
|
1804
|
+
fundingDepositListProjectReader,
|
|
1805
|
+
fundingDepositListReader,
|
|
1592
1806
|
fundingForeign,
|
|
1593
1807
|
fundingForeignWriter,
|
|
1594
1808
|
fundingProjectReader,
|