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
|
@@ -126,6 +126,27 @@ export namespace schema {
|
|
|
126
126
|
export const Response = DepositAddress
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
/** Schemas for the listFundingDepositAddresses operation. */
|
|
130
|
+
export namespace listFundingDepositAddresses {
|
|
131
|
+
/** Query parameters for listing funding deposit addresses. */
|
|
132
|
+
export const Query = z
|
|
133
|
+
.object({
|
|
134
|
+
cursor: Schema.Cursor,
|
|
135
|
+
limit: Schema.Limit,
|
|
136
|
+
})
|
|
137
|
+
.check(z.describe('Paging parameters for listing funding deposit addresses.'))
|
|
138
|
+
|
|
139
|
+
/** One page of reusable funding deposit addresses. */
|
|
140
|
+
export const Response = z
|
|
141
|
+
.object({
|
|
142
|
+
data: z
|
|
143
|
+
.array(DepositAddress)
|
|
144
|
+
.check(z.describe('Funding deposit addresses on this page, newest first.')),
|
|
145
|
+
nextCursor: Schema.NextCursor,
|
|
146
|
+
})
|
|
147
|
+
.check(z.describe('A page of reusable funding deposit addresses.'))
|
|
148
|
+
}
|
|
149
|
+
|
|
129
150
|
/** Schemas for the reconcileFundingDepositAddress operation. */
|
|
130
151
|
export namespace reconcileFundingDepositAddress {
|
|
131
152
|
/** Path parameters for reconciling a funding deposit address. */
|
|
@@ -144,32 +165,49 @@ export namespace schema {
|
|
|
144
165
|
z.meta({ examples: ['TJRabPrwbZy45sbavfcjinPJC18kjpRTv8'] }),
|
|
145
166
|
),
|
|
146
167
|
cursor: Schema.Cursor,
|
|
168
|
+
destinationToken: z
|
|
169
|
+
.optional(z.string().check(z.minLength(1)))
|
|
170
|
+
.check(
|
|
171
|
+
z.describe('Destination token symbol, contract address, or token key.'),
|
|
172
|
+
z.meta({ examples: ['usdt0'] }),
|
|
173
|
+
),
|
|
174
|
+
include: Schema.totalCountInclude,
|
|
147
175
|
limit: Schema.Limit,
|
|
176
|
+
provider: z
|
|
177
|
+
.optional(z.string().check(z.minLength(1)))
|
|
178
|
+
.check(z.describe('Funding provider id.'), z.meta({ examples: ['relay'] })),
|
|
148
179
|
recipient: z
|
|
149
180
|
.optional(Schema.Address)
|
|
150
181
|
.check(
|
|
151
182
|
z.describe('Tempo account that received the deposits.'),
|
|
152
183
|
z.meta({ examples: [`0x${'11'.repeat(20)}`] }),
|
|
153
184
|
),
|
|
185
|
+
sourceChain: z
|
|
186
|
+
.optional(z.string().check(z.minLength(1)))
|
|
187
|
+
.check(
|
|
188
|
+
z.describe('Source chain CAIP-2 id, slug, or alias.'),
|
|
189
|
+
z.meta({ examples: ['tron'] }),
|
|
190
|
+
),
|
|
191
|
+
sourceToken: z
|
|
192
|
+
.optional(z.string().check(z.minLength(1)))
|
|
193
|
+
.check(
|
|
194
|
+
z.describe('Source token symbol, contract address, or token key.'),
|
|
195
|
+
z.meta({ examples: ['usdt'] }),
|
|
196
|
+
),
|
|
197
|
+
status: z.optional(FundingDeposit.schema.Status),
|
|
154
198
|
})
|
|
155
|
-
.check(
|
|
156
|
-
z.refine(
|
|
157
|
-
(query) => (query.depositAddress === undefined) !== (query.recipient === undefined),
|
|
158
|
-
{
|
|
159
|
-
error: 'Pass exactly one of `depositAddress` or `recipient`.',
|
|
160
|
-
path: ['depositAddress'],
|
|
161
|
-
},
|
|
162
|
-
),
|
|
163
|
-
z.describe('Exactly one deposit filter and optional paging parameters.'),
|
|
164
|
-
)
|
|
199
|
+
.check(z.describe('Optional deposit filters and paging parameters.'))
|
|
165
200
|
|
|
166
201
|
/** One page of detected deposits. */
|
|
167
202
|
export const Response = z
|
|
168
203
|
.object({
|
|
169
204
|
data: z.array(Deposit).check(z.describe('Detected deposits on this page, newest first.')),
|
|
205
|
+
meta: z
|
|
206
|
+
.optional(Schema.CountMeta)
|
|
207
|
+
.check(z.describe('Extra response metadata requested with `include`.')),
|
|
170
208
|
nextCursor: Schema.NextCursor,
|
|
171
209
|
})
|
|
172
|
-
.check(z.describe('A page of funding deposits
|
|
210
|
+
.check(z.describe('A page of visible funding deposits.'))
|
|
173
211
|
}
|
|
174
212
|
|
|
175
213
|
/** Schemas for the getFundingDeposit operation. */
|
|
@@ -276,9 +314,114 @@ const fundingDepositExample = {
|
|
|
276
314
|
|
|
277
315
|
const fundingDepositsExample = {
|
|
278
316
|
data: [fundingDepositExample],
|
|
317
|
+
meta: { totalCount: 1, totalCountCapped: false },
|
|
279
318
|
nextCursor: null,
|
|
280
319
|
} satisfies z.output<typeof schema.listFundingDeposits.Response>
|
|
281
320
|
|
|
321
|
+
const fundingDepositAddressesExample = {
|
|
322
|
+
data: [fundingDepositAddressExample],
|
|
323
|
+
nextCursor: null,
|
|
324
|
+
} satisfies z.output<typeof schema.listFundingDepositAddresses.Response>
|
|
325
|
+
|
|
326
|
+
function resolveDepositFilters(options: resolveDepositFilters.Options): FundingDeposits.Filters {
|
|
327
|
+
const { catalog, parameters } = options
|
|
328
|
+
return {
|
|
329
|
+
...(parameters.depositAddress === undefined
|
|
330
|
+
? {}
|
|
331
|
+
: { depositAddress: parameters.depositAddress }),
|
|
332
|
+
...(parameters.destinationToken === undefined
|
|
333
|
+
? {}
|
|
334
|
+
: {
|
|
335
|
+
destinationTokenKeys: resolveTokenKeys({
|
|
336
|
+
catalog,
|
|
337
|
+
input: parameters.destinationToken,
|
|
338
|
+
side: 'destination',
|
|
339
|
+
}),
|
|
340
|
+
}),
|
|
341
|
+
...(parameters.provider === undefined
|
|
342
|
+
? {}
|
|
343
|
+
: { providerId: parameters.provider.trim().toLowerCase() }),
|
|
344
|
+
...(parameters.recipient === undefined ? {} : { recipient: parameters.recipient }),
|
|
345
|
+
...(parameters.sourceChain === undefined
|
|
346
|
+
? {}
|
|
347
|
+
: { sourceChainIds: resolveChainIds(catalog, parameters.sourceChain) }),
|
|
348
|
+
...(parameters.sourceToken === undefined
|
|
349
|
+
? {}
|
|
350
|
+
: {
|
|
351
|
+
sourceTokenKeys: resolveTokenKeys({
|
|
352
|
+
catalog,
|
|
353
|
+
input: parameters.sourceToken,
|
|
354
|
+
side: 'source',
|
|
355
|
+
}),
|
|
356
|
+
}),
|
|
357
|
+
...(parameters.status === undefined ? {} : { status: parameters.status }),
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
declare namespace resolveDepositFilters {
|
|
362
|
+
type Options = {
|
|
363
|
+
catalog?: FundingCatalog.Snapshot | undefined
|
|
364
|
+
parameters: z.output<typeof schema.listFundingDeposits.Query>
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function resolveChainIds(catalog: FundingCatalog.Snapshot | undefined, input: string): string[] {
|
|
369
|
+
const trimmed = input.trim()
|
|
370
|
+
const chain = catalog?.chainsByKey.get(trimmed.toLowerCase())
|
|
371
|
+
if (chain) return [chain.id]
|
|
372
|
+
return /^[a-z][a-z0-9-]*:.+$/i.test(trimmed) ? [trimmed] : []
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function resolveTokenKeys(options: resolveTokenKeys.Options): string[] {
|
|
376
|
+
const trimmed = options.input.trim()
|
|
377
|
+
const normalized = trimmed.toLowerCase()
|
|
378
|
+
const keys =
|
|
379
|
+
options.catalog?.routes
|
|
380
|
+
.map((entry) => entry.route[options.side])
|
|
381
|
+
.filter((token) => matchesToken(token, trimmed, normalized))
|
|
382
|
+
.map(tokenKey) ?? []
|
|
383
|
+
if (keys.length) return [...new Set(keys)]
|
|
384
|
+
if (!/^[a-z][a-z0-9-]*:.+\/(?:erc20|trc20|token):.+$/i.test(trimmed)) return []
|
|
385
|
+
return [/\/erc20:/i.test(trimmed) ? normalized : trimmed]
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
declare namespace resolveTokenKeys {
|
|
389
|
+
type Options = {
|
|
390
|
+
catalog?: FundingCatalog.Snapshot | undefined
|
|
391
|
+
input: string
|
|
392
|
+
side: 'destination' | 'source'
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
type ResolvedToken = FundingCatalog.Snapshot['routes'][number]['route']['source']
|
|
397
|
+
|
|
398
|
+
function matchesToken(token: ResolvedToken, trimmed: string, normalized: string): boolean {
|
|
399
|
+
return (
|
|
400
|
+
token.slug === normalized ||
|
|
401
|
+
token.symbol.toLowerCase() === normalized ||
|
|
402
|
+
matchesTokenIdentifier(token, token.address, trimmed, normalized) ||
|
|
403
|
+
matchesTokenIdentifier(token, tokenKey(token), trimmed, normalized)
|
|
404
|
+
)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function matchesTokenIdentifier(
|
|
408
|
+
token: ResolvedToken,
|
|
409
|
+
candidate: string,
|
|
410
|
+
trimmed: string,
|
|
411
|
+
normalized: string,
|
|
412
|
+
): boolean {
|
|
413
|
+
return token.chain.kind === 'evm' ? candidate.toLowerCase() === normalized : candidate === trimmed
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function tokenKey(token: ResolvedToken): string {
|
|
417
|
+
const namespace = (() => {
|
|
418
|
+
if (token.chain.kind === 'solana') return 'token'
|
|
419
|
+
if (token.chain.kind === 'tron') return 'trc20'
|
|
420
|
+
return 'erc20'
|
|
421
|
+
})()
|
|
422
|
+
return `${token.chain.id}/${namespace}:${token.address}`
|
|
423
|
+
}
|
|
424
|
+
|
|
282
425
|
const reconcileFundingDepositAddressResponses = (() => {
|
|
283
426
|
const responses = OpenApi.responses({
|
|
284
427
|
errors: {
|
|
@@ -646,6 +789,72 @@ export function depositAddresses(options: depositAddresses.Options = {}) {
|
|
|
646
789
|
}
|
|
647
790
|
},
|
|
648
791
|
)
|
|
792
|
+
.get(
|
|
793
|
+
'/v1/funding/deposit-addresses',
|
|
794
|
+
Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
|
|
795
|
+
OpenApi.validate('query', schema.listFundingDepositAddresses.Query, {
|
|
796
|
+
code: 'query_invalid',
|
|
797
|
+
message: 'Check the query parameters and try again.',
|
|
798
|
+
}),
|
|
799
|
+
OpenApi.describeRoute({
|
|
800
|
+
description: 'Lists reusable funding deposit addresses, newest first.',
|
|
801
|
+
operationId: 'listFundingDepositAddresses',
|
|
802
|
+
responses: OpenApi.responses({
|
|
803
|
+
errors: {
|
|
804
|
+
400: { codes: ['query_invalid'] },
|
|
805
|
+
},
|
|
806
|
+
success: {
|
|
807
|
+
description: 'A page of reusable funding deposit addresses.',
|
|
808
|
+
example: fundingDepositAddressesExample,
|
|
809
|
+
schema: schema.listFundingDepositAddresses.Response,
|
|
810
|
+
},
|
|
811
|
+
}),
|
|
812
|
+
summary: 'List deposit addresses',
|
|
813
|
+
tags: ['Deposit Addresses'],
|
|
814
|
+
}),
|
|
815
|
+
async (c) => {
|
|
816
|
+
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
817
|
+
if (OpenApi.narrowValidation)
|
|
818
|
+
return OpenApi.validationError(c, {
|
|
819
|
+
code: 'query_invalid',
|
|
820
|
+
message: 'Check the query parameters and try again.',
|
|
821
|
+
})
|
|
822
|
+
|
|
823
|
+
const owner = Access.owner(c)
|
|
824
|
+
if (!owner) return Access.unauthorized(c)
|
|
825
|
+
|
|
826
|
+
const db = Db.get(c.get('db'))
|
|
827
|
+
const { cursor, limit } = c.req.valid('query')
|
|
828
|
+
const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined
|
|
829
|
+
try {
|
|
830
|
+
const rows = await FundingDepositAddresses.list(db, {
|
|
831
|
+
...(decoded
|
|
832
|
+
? { cursor: { createdAt: String(decoded[0]), id: String(decoded[1]) } }
|
|
833
|
+
: {}),
|
|
834
|
+
limit: limit + 1,
|
|
835
|
+
owner,
|
|
836
|
+
})
|
|
837
|
+
const page = Cursor.paginate({
|
|
838
|
+
key: (record) => [record.createdAt, record.id],
|
|
839
|
+
limit,
|
|
840
|
+
rows,
|
|
841
|
+
})
|
|
842
|
+
const data = page.rows.map(FundingDepositAddress.toPublic)
|
|
843
|
+
|
|
844
|
+
c.header('Cache-Control', Cache.policies.noStore)
|
|
845
|
+
c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key, X-API-Key')
|
|
846
|
+
return c.json(
|
|
847
|
+
Response.validated(schema.listFundingDepositAddresses.Response, {
|
|
848
|
+
data,
|
|
849
|
+
nextCursor: page.nextCursor,
|
|
850
|
+
}),
|
|
851
|
+
200,
|
|
852
|
+
)
|
|
853
|
+
} catch (cause) {
|
|
854
|
+
return Response.upstream(c, cause)
|
|
855
|
+
}
|
|
856
|
+
},
|
|
857
|
+
)
|
|
649
858
|
.get(
|
|
650
859
|
'/v1/funding/deposit-addresses/:id{fda_[A-Za-z0-9_-]+}',
|
|
651
860
|
Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
|
|
@@ -782,14 +991,14 @@ export function depositAddresses(options: depositAddresses.Options = {}) {
|
|
|
782
991
|
message: 'Check the query parameters and try again.',
|
|
783
992
|
}),
|
|
784
993
|
OpenApi.describeRoute({
|
|
785
|
-
description: 'Lists deposits
|
|
994
|
+
description: 'Lists visible deposits, optionally filtered, newest first.',
|
|
786
995
|
operationId: 'listFundingDeposits',
|
|
787
996
|
responses: OpenApi.responses({
|
|
788
997
|
errors: {
|
|
789
998
|
400: { codes: ['query_invalid'] },
|
|
790
999
|
},
|
|
791
1000
|
success: {
|
|
792
|
-
description: 'A page of funding deposits
|
|
1001
|
+
description: 'A page of visible funding deposits.',
|
|
793
1002
|
example: fundingDepositsExample,
|
|
794
1003
|
schema: schema.listFundingDeposits.Response,
|
|
795
1004
|
},
|
|
@@ -809,32 +1018,59 @@ export function depositAddresses(options: depositAddresses.Options = {}) {
|
|
|
809
1018
|
if (!owner) return Access.unauthorized(c)
|
|
810
1019
|
|
|
811
1020
|
const db = Db.get(c.get('db'))
|
|
812
|
-
const
|
|
1021
|
+
const parameters = c.req.valid('query')
|
|
1022
|
+
const { cursor, include, limit } = parameters
|
|
813
1023
|
const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined
|
|
814
1024
|
try {
|
|
1025
|
+
const catalog =
|
|
1026
|
+
parameters.destinationToken !== undefined ||
|
|
1027
|
+
parameters.sourceChain !== undefined ||
|
|
1028
|
+
parameters.sourceToken !== undefined
|
|
1029
|
+
? await FundingCatalog.snapshot(c)
|
|
1030
|
+
: undefined
|
|
1031
|
+
const filters = resolveDepositFilters({ catalog, parameters })
|
|
1032
|
+
// Counts are best-effort and share every ownership and filter predicate
|
|
1033
|
+
// with the page query. A failure omits `meta` rather than failing the page.
|
|
1034
|
+
const countPromise = include.includes('totalCount')
|
|
1035
|
+
? FundingDeposits.count(db, {
|
|
1036
|
+
...filters,
|
|
1037
|
+
cap: Schema.countCap,
|
|
1038
|
+
owner,
|
|
1039
|
+
}).catch(() => {
|
|
1040
|
+
c.set('fundingDepositCountFailed', true)
|
|
1041
|
+
return undefined
|
|
1042
|
+
})
|
|
1043
|
+
: undefined
|
|
815
1044
|
const paging = {
|
|
1045
|
+
...filters,
|
|
816
1046
|
...(decoded
|
|
817
1047
|
? { cursor: { createdAt: String(decoded[0]), id: String(decoded[1]) } }
|
|
818
1048
|
: {}),
|
|
819
1049
|
limit: limit + 1,
|
|
820
1050
|
owner,
|
|
821
1051
|
}
|
|
822
|
-
const rows =
|
|
823
|
-
depositAddress === undefined
|
|
824
|
-
? await FundingDeposits.listByRecipient(db, { ...paging, recipient: recipient! })
|
|
825
|
-
: await FundingDeposits.listByDepositAddress(db, { ...paging, depositAddress })
|
|
1052
|
+
const rows = await FundingDeposits.list(db, paging)
|
|
826
1053
|
const page = Cursor.paginate({
|
|
827
1054
|
key: (record) => [record.createdAt, record.id],
|
|
828
1055
|
limit,
|
|
829
1056
|
rows,
|
|
830
1057
|
})
|
|
831
1058
|
const data = page.rows.map(FundingDeposit.toPublic)
|
|
1059
|
+
const count = await countPromise
|
|
832
1060
|
|
|
833
1061
|
c.header('Cache-Control', Cache.policies.noStore)
|
|
834
1062
|
c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key')
|
|
835
1063
|
return c.json(
|
|
836
1064
|
Response.validated(schema.listFundingDeposits.Response, {
|
|
837
1065
|
data,
|
|
1066
|
+
...(count === undefined
|
|
1067
|
+
? {}
|
|
1068
|
+
: {
|
|
1069
|
+
meta: {
|
|
1070
|
+
totalCount: Math.min(count, Schema.countCap),
|
|
1071
|
+
totalCountCapped: count > Schema.countCap,
|
|
1072
|
+
},
|
|
1073
|
+
}),
|
|
838
1074
|
nextCursor: page.nextCursor,
|
|
839
1075
|
}),
|
|
840
1076
|
200,
|
|
@@ -841,6 +841,7 @@ describe('funding transfers OpenAPI', () => {
|
|
|
841
841
|
deposit: summarize(spec.paths[depositPath]!.get!),
|
|
842
842
|
depositAddress: summarize(spec.paths[depositAddressPath]!.get!),
|
|
843
843
|
depositAddressCreate: summarize(spec.paths['/v1/funding/deposit-addresses']!.post!),
|
|
844
|
+
depositAddressList: summarize(spec.paths['/v1/funding/deposit-addresses']!.get!),
|
|
844
845
|
depositAddressReconcile: summarize(spec.paths[reconcilePath]!.post!),
|
|
845
846
|
deposits: summarize(spec.paths['/v1/funding/deposits']!.get!),
|
|
846
847
|
detail: summarize(spec.paths[detailPath]!.get!),
|
|
@@ -889,12 +890,16 @@ describe('funding transfers OpenAPI', () => {
|
|
|
889
890
|
"description": "Creates or returns a reusable deposit address for funding a Tempo account.",
|
|
890
891
|
"summary": "Create deposit address",
|
|
891
892
|
},
|
|
893
|
+
"depositAddressList": {
|
|
894
|
+
"description": "Lists reusable funding deposit addresses, newest first.",
|
|
895
|
+
"summary": "List deposit addresses",
|
|
896
|
+
},
|
|
892
897
|
"depositAddressReconcile": {
|
|
893
898
|
"description": "Queues an immediate provider reconciliation for one reusable funding deposit address.",
|
|
894
899
|
"summary": "Reconcile deposit address",
|
|
895
900
|
},
|
|
896
901
|
"deposits": {
|
|
897
|
-
"description": "Lists deposits
|
|
902
|
+
"description": "Lists visible deposits, optionally filtered, newest first.",
|
|
898
903
|
"summary": "List deposits",
|
|
899
904
|
},
|
|
900
905
|
"detail": {
|
|
@@ -926,6 +931,7 @@ describe('funding transfers OpenAPI', () => {
|
|
|
926
931
|
"Get quotes",
|
|
927
932
|
"Get chains",
|
|
928
933
|
"Get providers",
|
|
934
|
+
"List deposit addresses",
|
|
929
935
|
"Create deposit address",
|
|
930
936
|
"List transfers",
|
|
931
937
|
"Create transfer",
|
|
@@ -1087,7 +1093,11 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1087
1093
|
const operation = spec.paths['/v1/funding/transfers']!.get
|
|
1088
1094
|
const fields = Object.keys(operation.responses['200']!.content['application/json']!.example.data[0]!) // prettier-ignore
|
|
1089
1095
|
expect(fields).toEqual([...fields].sort())
|
|
1090
|
-
for (const route of [
|
|
1096
|
+
for (const route of [
|
|
1097
|
+
operation,
|
|
1098
|
+
spec.paths['/v1/funding/deposit-addresses']!.get,
|
|
1099
|
+
spec.paths['/v1/funding/deposits']!.get,
|
|
1100
|
+
])
|
|
1091
1101
|
for (const parameter of route.parameters ?? [])
|
|
1092
1102
|
expect(parameter.example ?? parameter.schema?.examples, parameter.name).toBeDefined()
|
|
1093
1103
|
})
|
|
@@ -1126,6 +1136,9 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1126
1136
|
depositAddressCreate: spec.paths['/v1/funding/deposit-addresses']?.post?.operationId,
|
|
1127
1137
|
depositAddressCreateScopes:
|
|
1128
1138
|
spec.paths['/v1/funding/deposit-addresses']?.post?.['x-required-scopes'],
|
|
1139
|
+
depositAddressList: spec.paths['/v1/funding/deposit-addresses']?.get.operationId,
|
|
1140
|
+
depositAddressListScopes:
|
|
1141
|
+
spec.paths['/v1/funding/deposit-addresses']?.get['x-required-scopes'],
|
|
1129
1142
|
depositAddressReconcile: reconcilePath && spec.paths[reconcilePath]?.post?.operationId,
|
|
1130
1143
|
depositAddressReconcileScopes:
|
|
1131
1144
|
reconcilePath && spec.paths[reconcilePath]?.post?.['x-required-scopes'],
|
|
@@ -1150,6 +1163,8 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1150
1163
|
depositAddress: 'getFundingDepositAddress',
|
|
1151
1164
|
depositAddressCreate: 'createFundingDepositAddress',
|
|
1152
1165
|
depositAddressCreateScopes: ['funding:write'],
|
|
1166
|
+
depositAddressList: 'listFundingDepositAddresses',
|
|
1167
|
+
depositAddressListScopes: ['funding:read'],
|
|
1153
1168
|
depositAddressReconcile: 'reconcileFundingDepositAddress',
|
|
1154
1169
|
depositAddressReconcileScopes: ['funding:write'],
|
|
1155
1170
|
depositAddressScopes: ['funding:read'],
|
package/src/db/Db.ts
CHANGED
|
@@ -1362,6 +1362,42 @@ export const migrations: readonly Migration[] = [
|
|
|
1362
1362
|
sql`DELETE FROM webhook_queue_events WHERE status = 'pending' AND attempting_at IS NULL AND expires_at <= to_char(now() AT TIME ZONE 'utc', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`,
|
|
1363
1363
|
),
|
|
1364
1364
|
},
|
|
1365
|
+
{
|
|
1366
|
+
name: '0123_funding_deposit_addresses_owner_list_index',
|
|
1367
|
+
up: (db) =>
|
|
1368
|
+
db.schema
|
|
1369
|
+
.createIndex('funding_deposit_addresses_owner_list_idx')
|
|
1370
|
+
.ifNotExists()
|
|
1371
|
+
.on('fundingDepositAddresses')
|
|
1372
|
+
.columns(['orgId', 'environment', 'createdAt', 'id']),
|
|
1373
|
+
},
|
|
1374
|
+
{
|
|
1375
|
+
name: '0124_funding_deposit_addresses_project_list_index',
|
|
1376
|
+
up: (db) =>
|
|
1377
|
+
db.schema
|
|
1378
|
+
.createIndex('funding_deposit_addresses_project_list_idx')
|
|
1379
|
+
.ifNotExists()
|
|
1380
|
+
.on('fundingDepositAddresses')
|
|
1381
|
+
.columns(['orgId', 'environment', 'projectId', 'createdAt', 'id']),
|
|
1382
|
+
},
|
|
1383
|
+
{
|
|
1384
|
+
name: '0125_funding_deposits_owner_list_index',
|
|
1385
|
+
up: (db) =>
|
|
1386
|
+
db.schema
|
|
1387
|
+
.createIndex('funding_deposits_owner_list_idx')
|
|
1388
|
+
.ifNotExists()
|
|
1389
|
+
.on('fundingDeposits')
|
|
1390
|
+
.columns(['orgId', 'environment', 'createdAt', 'id']),
|
|
1391
|
+
},
|
|
1392
|
+
{
|
|
1393
|
+
name: '0126_funding_deposits_project_list_index',
|
|
1394
|
+
up: (db) =>
|
|
1395
|
+
db.schema
|
|
1396
|
+
.createIndex('funding_deposits_project_list_idx')
|
|
1397
|
+
.ifNotExists()
|
|
1398
|
+
.on('fundingDeposits')
|
|
1399
|
+
.columns(['orgId', 'environment', 'projectId', 'createdAt', 'id']),
|
|
1400
|
+
},
|
|
1365
1401
|
]
|
|
1366
1402
|
|
|
1367
1403
|
/**
|
|
@@ -578,3 +578,31 @@ describe('renewPoll', () => {
|
|
|
578
578
|
`)
|
|
579
579
|
})
|
|
580
580
|
})
|
|
581
|
+
|
|
582
|
+
describe('list', () => {
|
|
583
|
+
test('pages organization addresses and narrows project-attributed owners', async () => {
|
|
584
|
+
const listOwner = { environment: owner.environment, orgId: 'org_address_list' } as const
|
|
585
|
+
const first = await seed({ orgId: listOwner.orgId })
|
|
586
|
+
const project = await seed({ orgId: listOwner.orgId, projectId: 'prj_address_list' })
|
|
587
|
+
const sibling = await seed({ orgId: listOwner.orgId, projectId: 'prj_address_sibling' })
|
|
588
|
+
await seed({ orgId: 'org_address_list_foreign' })
|
|
589
|
+
await seed({ environment: 'sandbox', orgId: listOwner.orgId })
|
|
590
|
+
|
|
591
|
+
const page = await FundingDepositAddresses.list(db, { limit: 2, owner: listOwner })
|
|
592
|
+
expect(page.map((record) => record.id)).toEqual([sibling.id, project.id])
|
|
593
|
+
const rest = await FundingDepositAddresses.list(db, {
|
|
594
|
+
cursor: { createdAt: project.createdAt, id: project.id },
|
|
595
|
+
limit: 2,
|
|
596
|
+
owner: listOwner,
|
|
597
|
+
})
|
|
598
|
+
expect(rest.map((record) => record.id)).toEqual([first.id])
|
|
599
|
+
expect(
|
|
600
|
+
(
|
|
601
|
+
await FundingDepositAddresses.list(db, {
|
|
602
|
+
limit: 10,
|
|
603
|
+
owner: { ...listOwner, projectId: 'prj_address_list' },
|
|
604
|
+
})
|
|
605
|
+
).map((record) => record.id),
|
|
606
|
+
).toEqual([project.id])
|
|
607
|
+
})
|
|
608
|
+
})
|
|
@@ -148,6 +148,45 @@ export function getOwned(db: Db.Db, owner: Owner, id: string): Promise<Record |
|
|
|
148
148
|
return query.executeTakeFirst()
|
|
149
149
|
}
|
|
150
150
|
|
|
151
|
+
/** Lists owner-visible funding deposit addresses newest-first. */
|
|
152
|
+
export function list(db: Db.Db, options: list.Options): Promise<Record[]> {
|
|
153
|
+
const { cursor, limit, owner } = options
|
|
154
|
+
let query = db.kysely
|
|
155
|
+
.selectFrom('funding_deposit_addresses')
|
|
156
|
+
.selectAll()
|
|
157
|
+
.where('environment', '=', owner.environment)
|
|
158
|
+
.where('orgId', '=', owner.orgId)
|
|
159
|
+
if (owner.projectId !== undefined) query = query.where('projectId', '=', owner.projectId)
|
|
160
|
+
if (cursor)
|
|
161
|
+
query = query.where((eb) =>
|
|
162
|
+
eb.or([
|
|
163
|
+
eb('createdAt', '<', cursor.createdAt),
|
|
164
|
+
eb.and([eb('createdAt', '=', cursor.createdAt), eb('id', '<', cursor.id)]),
|
|
165
|
+
]),
|
|
166
|
+
)
|
|
167
|
+
return query.orderBy('createdAt', 'desc').orderBy('id', 'desc').limit(limit).execute()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export declare namespace list {
|
|
171
|
+
/** Ownership and paging options for funding deposit addresses. */
|
|
172
|
+
type Options = {
|
|
173
|
+
/** Exclusive lower bound from the previous page. */
|
|
174
|
+
cursor?: Cursor | undefined
|
|
175
|
+
/** Maximum rows to return. */
|
|
176
|
+
limit: number
|
|
177
|
+
/** Ownership filter derived from the authenticated API key. */
|
|
178
|
+
owner: Owner
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Cursor fields for the last address returned by the previous page. */
|
|
182
|
+
type Cursor = {
|
|
183
|
+
/** Deposit address creation time. */
|
|
184
|
+
createdAt: string
|
|
185
|
+
/** Deposit address id, used as a deterministic tie-breaker. */
|
|
186
|
+
id: string
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
151
190
|
/** Gets the reusable address that matches one owner and route identity. */
|
|
152
191
|
export function getMatching(db: Db.Db, match: Match): Promise<Record | undefined> {
|
|
153
192
|
let query = db.kysely
|
|
@@ -498,3 +498,85 @@ describe('toPublic', () => {
|
|
|
498
498
|
})
|
|
499
499
|
})
|
|
500
500
|
})
|
|
501
|
+
|
|
502
|
+
describe('list', () => {
|
|
503
|
+
test('pages organization deposits and narrows project-attributed owners', async () => {
|
|
504
|
+
const listOwner = { environment: owner.environment, orgId: 'org_deposit_list' } as const
|
|
505
|
+
const firstAddress = await seedAddress({ orgId: listOwner.orgId })
|
|
506
|
+
const projectAddress = await seedAddress({
|
|
507
|
+
orgId: listOwner.orgId,
|
|
508
|
+
projectId: 'prj_deposit_list',
|
|
509
|
+
})
|
|
510
|
+
const siblingAddress = await seedAddress({
|
|
511
|
+
orgId: listOwner.orgId,
|
|
512
|
+
projectId: 'prj_deposit_sibling',
|
|
513
|
+
})
|
|
514
|
+
const first = await seedDeposit(firstAddress)
|
|
515
|
+
const project = await seedDeposit(projectAddress)
|
|
516
|
+
const sibling = await seedDeposit(siblingAddress)
|
|
517
|
+
const foreignAddress = await seedAddress({ orgId: 'org_deposit_list_foreign' })
|
|
518
|
+
await seedDeposit(foreignAddress)
|
|
519
|
+
const sandboxAddress = await seedAddress({
|
|
520
|
+
environment: 'sandbox',
|
|
521
|
+
orgId: listOwner.orgId,
|
|
522
|
+
})
|
|
523
|
+
await seedDeposit(sandboxAddress)
|
|
524
|
+
|
|
525
|
+
const page = await FundingDeposits.list(db, { limit: 2, owner: listOwner })
|
|
526
|
+
expect(page.map((record) => record.id)).toEqual([sibling.id, project.id])
|
|
527
|
+
const rest = await FundingDeposits.list(db, {
|
|
528
|
+
cursor: { createdAt: project.createdAt, id: project.id },
|
|
529
|
+
limit: 2,
|
|
530
|
+
owner: listOwner,
|
|
531
|
+
})
|
|
532
|
+
expect(rest.map((record) => record.id)).toEqual([first.id])
|
|
533
|
+
expect(
|
|
534
|
+
(
|
|
535
|
+
await FundingDeposits.list(db, {
|
|
536
|
+
limit: 10,
|
|
537
|
+
owner: { ...listOwner, projectId: 'prj_deposit_list' },
|
|
538
|
+
})
|
|
539
|
+
).map((record) => record.id),
|
|
540
|
+
).toEqual([project.id])
|
|
541
|
+
})
|
|
542
|
+
|
|
543
|
+
test('AND-combines filters and counts through the same ownership scope', async () => {
|
|
544
|
+
const listOwner = { environment: owner.environment, orgId: 'org_deposit_filters' } as const
|
|
545
|
+
const recipient = `0x${'ab'.repeat(20)}`
|
|
546
|
+
const address = await seedAddress({
|
|
547
|
+
orgId: listOwner.orgId,
|
|
548
|
+
snapshot: TestFunding.depositAddressSnapshot({
|
|
549
|
+
provider: { id: 'relay_filters', name: 'Relay' },
|
|
550
|
+
recipient,
|
|
551
|
+
}),
|
|
552
|
+
})
|
|
553
|
+
const match = await seedDeposit(address)
|
|
554
|
+
const unrelatedAddress = await seedAddress({ orgId: listOwner.orgId })
|
|
555
|
+
await seedDeposit(unrelatedAddress)
|
|
556
|
+
const filters = {
|
|
557
|
+
depositAddress: address.address,
|
|
558
|
+
destinationTokenKeys: [address.destinationTokenKey],
|
|
559
|
+
providerId: address.providerId,
|
|
560
|
+
recipient: recipient.toLowerCase(),
|
|
561
|
+
sourceChainIds: [address.sourceChainId],
|
|
562
|
+
sourceTokenKeys: [address.sourceTokenKey],
|
|
563
|
+
status: 'detected',
|
|
564
|
+
} as const
|
|
565
|
+
|
|
566
|
+
expect(
|
|
567
|
+
(await FundingDeposits.list(db, { ...filters, limit: 10, owner: listOwner })).map(
|
|
568
|
+
(record) => record.id,
|
|
569
|
+
),
|
|
570
|
+
).toEqual([match.id])
|
|
571
|
+
expect(await FundingDeposits.count(db, { ...filters, cap: 10, owner: listOwner })).toBe(1)
|
|
572
|
+
expect(await FundingDeposits.count(db, { ...filters, cap: 0, owner: listOwner })).toBe(1)
|
|
573
|
+
expect(
|
|
574
|
+
await FundingDeposits.count(db, {
|
|
575
|
+
...filters,
|
|
576
|
+
destinationTokenKeys: [],
|
|
577
|
+
cap: 10,
|
|
578
|
+
owner: listOwner,
|
|
579
|
+
}),
|
|
580
|
+
).toBe(0)
|
|
581
|
+
})
|
|
582
|
+
})
|