tapimo 0.8.0 → 0.9.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 +2095 -2111
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +0 -1
- package/dist/App.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +166 -166
- package/dist/apps/data/App.d.ts +4084 -3845
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +270 -270
- package/dist/apps/data/routes/addresses.d.ts +248 -17
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +272 -2
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +114 -2
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +123 -123
- package/dist/apps/data/routes/earn.d.ts +65 -3
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +259 -14
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokens.d.ts +454 -454
- package/dist/apps/data/routes/transfers.d.ts +81 -81
- package/dist/apps/funding/App.d.ts +40 -299
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/App.js +20 -5
- package/dist/apps/funding/App.js.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +43 -434
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +21 -103
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/quotes.d.ts +89 -89
- package/dist/apps/funding/routes/transfers.js +5 -5
- package/dist/apps/funding/routes/transfers.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +134 -134
- package/dist/apps/management/routes/orgs.d.ts +98 -98
- package/dist/apps/management/routes/verified-token-requests.d.ts +83 -83
- package/dist/apps/mpp/App.d.ts +44 -44
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +8 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/tables/fundingDeposits.d.ts +18 -1
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +46 -2
- package/dist/db/tables/fundingDeposits.js.map +1 -1
- package/dist/internal/Auth.d.ts +4 -0
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +11 -2
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +6 -6
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +101 -16
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +6 -2
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/package.json +1 -1
- package/src/App.test.ts +32 -0
- package/src/App.ts +4 -1
- package/src/Client.test-d.ts +56 -33
- package/src/apps/composition.test.ts +39 -10
- package/src/apps/data/routes/balances.test.ts +42 -0
- package/src/apps/data/routes/balances.ts +218 -85
- package/src/apps/data/routes/earn.test.ts +89 -9
- package/src/apps/data/routes/earn.ts +355 -14
- package/src/apps/funding/App.ts +20 -5
- package/src/apps/funding/routes/deposit-addresses.test.ts +43 -22
- package/src/apps/funding/routes/deposit-addresses.ts +34 -117
- package/src/apps/funding/routes/transfers.test.ts +6 -25
- package/src/apps/funding/routes/transfers.ts +5 -5
- package/src/db/Db.ts +9 -0
- package/src/db/tables/fundingDeposits.test.ts +101 -0
- package/src/db/tables/fundingDeposits.ts +71 -2
- package/src/internal/Auth.test.ts +28 -0
- package/src/internal/Auth.ts +15 -1
- package/src/internal/funding/Provider.ts +4 -4
- package/src/internal/funding/Reconciliation.test.ts +292 -54
- package/src/internal/funding/Reconciliation.ts +160 -53
- package/src/internal/funding/SourceObservation.test.ts +2 -2
- package/src/internal/funding/providers/relay.ts +7 -2
|
@@ -1152,10 +1152,16 @@ describe('POST /funding/deposit-addresses/:id/reconcile', () => {
|
|
|
1152
1152
|
})
|
|
1153
1153
|
})
|
|
1154
1154
|
|
|
1155
|
-
describe('GET /funding/
|
|
1155
|
+
describe('GET /funding/deposits?depositAddress', () => {
|
|
1156
1156
|
test('pages deposits newest-first under an owned address', async () => {
|
|
1157
1157
|
const app = transfersApp()
|
|
1158
|
-
const address = await seedDepositAddress(
|
|
1158
|
+
const address = await seedDepositAddress({
|
|
1159
|
+
snapshot: TestFunding.depositAddressSnapshot({
|
|
1160
|
+
address: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
|
|
1161
|
+
provider: { id: 'relay_list_deposit_address', name: 'Relay' },
|
|
1162
|
+
recipient: `0x${'41'.repeat(20)}`,
|
|
1163
|
+
}),
|
|
1164
|
+
})
|
|
1159
1165
|
const first = await seedDeposit(address)
|
|
1160
1166
|
const second = await seedDeposit(address)
|
|
1161
1167
|
const third = await seedDeposit(address)
|
|
@@ -1164,7 +1170,7 @@ describe('GET /funding/deposit-addresses/:id/deposits', () => {
|
|
|
1164
1170
|
const sixth = await seedDeposit(address)
|
|
1165
1171
|
|
|
1166
1172
|
const response = await app.request(
|
|
1167
|
-
`/v1/funding/
|
|
1173
|
+
`/v1/funding/deposits?depositAddress=${address.address}&limit=5`,
|
|
1168
1174
|
as(fundingReader),
|
|
1169
1175
|
)
|
|
1170
1176
|
expect(response.status).toBe(200)
|
|
@@ -1177,10 +1183,7 @@ describe('GET /funding/deposit-addresses/:id/deposits', () => {
|
|
|
1177
1183
|
"etag": null,
|
|
1178
1184
|
}
|
|
1179
1185
|
`)
|
|
1180
|
-
const body = await TestApp.json(
|
|
1181
|
-
response,
|
|
1182
|
-
Funding.schema.listFundingDepositAddressDeposits.Response,
|
|
1183
|
-
)
|
|
1186
|
+
const body = await TestApp.json(response, Funding.schema.listFundingDeposits.Response)
|
|
1184
1187
|
expect(body.data.map((deposit) => deposit.id)).toEqual([
|
|
1185
1188
|
sixth.id,
|
|
1186
1189
|
fifth.id,
|
|
@@ -1192,10 +1195,10 @@ describe('GET /funding/deposit-addresses/:id/deposits', () => {
|
|
|
1192
1195
|
|
|
1193
1196
|
const next = await TestApp.json(
|
|
1194
1197
|
await app.request(
|
|
1195
|
-
`/v1/funding/
|
|
1198
|
+
`/v1/funding/deposits?depositAddress=${address.address}&limit=5&cursor=${body.nextCursor}`,
|
|
1196
1199
|
as(fundingReader),
|
|
1197
1200
|
),
|
|
1198
|
-
Funding.schema.
|
|
1201
|
+
Funding.schema.listFundingDeposits.Response,
|
|
1199
1202
|
)
|
|
1200
1203
|
expect(next.data.map((deposit) => deposit.id)).toEqual([first.id])
|
|
1201
1204
|
expect(next.nextCursor).toBeNull()
|
|
@@ -1205,6 +1208,7 @@ describe('GET /funding/deposit-addresses/:id/deposits', () => {
|
|
|
1205
1208
|
const app = transfersApp()
|
|
1206
1209
|
const address = await seedDepositAddress({
|
|
1207
1210
|
snapshot: TestFunding.depositAddressSnapshot({
|
|
1211
|
+
address: 'TF17BgPaZYbz8oxbjhriubPDsA7ArKoLX3',
|
|
1208
1212
|
provider: { id: 'relay_observed', name: 'Relay' },
|
|
1209
1213
|
recipient: `0x${'31'.repeat(20)}`,
|
|
1210
1214
|
}),
|
|
@@ -1228,14 +1232,11 @@ describe('GET /funding/deposit-addresses/:id/deposits', () => {
|
|
|
1228
1232
|
})
|
|
1229
1233
|
|
|
1230
1234
|
const response = await app.request(
|
|
1231
|
-
`/v1/funding/
|
|
1235
|
+
`/v1/funding/deposits?depositAddress=${address.address}`,
|
|
1232
1236
|
as(fundingReader),
|
|
1233
1237
|
)
|
|
1234
1238
|
expect(response.status).toBe(200)
|
|
1235
|
-
const body = await TestApp.json(
|
|
1236
|
-
response,
|
|
1237
|
-
Funding.schema.listFundingDepositAddressDeposits.Response,
|
|
1238
|
-
)
|
|
1239
|
+
const body = await TestApp.json(response, Funding.schema.listFundingDeposits.Response)
|
|
1239
1240
|
expect(body).toMatchInlineSnapshot(
|
|
1240
1241
|
{
|
|
1241
1242
|
data: [
|
|
@@ -1305,21 +1306,30 @@ describe('GET /funding/deposit-addresses/:id/deposits', () => {
|
|
|
1305
1306
|
)
|
|
1306
1307
|
})
|
|
1307
1308
|
|
|
1308
|
-
test('
|
|
1309
|
+
test('hides a foreign deposit address', async () => {
|
|
1309
1310
|
const app = transfersApp()
|
|
1310
|
-
const address = await seedDepositAddress({
|
|
1311
|
+
const address = await seedDepositAddress({
|
|
1312
|
+
orgId: fundingForeign.orgId,
|
|
1313
|
+
snapshot: TestFunding.depositAddressSnapshot({
|
|
1314
|
+
address: 'TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb',
|
|
1315
|
+
provider: { id: 'relay_foreign_deposit_address', name: 'Relay' },
|
|
1316
|
+
recipient: `0x${'42'.repeat(20)}`,
|
|
1317
|
+
}),
|
|
1318
|
+
})
|
|
1319
|
+
await seedDeposit(address)
|
|
1311
1320
|
const response = await app.request(
|
|
1312
|
-
`/v1/funding/
|
|
1321
|
+
`/v1/funding/deposits?depositAddress=${address.address}`,
|
|
1313
1322
|
as(fundingReader),
|
|
1314
1323
|
)
|
|
1315
|
-
expect(response.status).toBe(
|
|
1316
|
-
expect(
|
|
1317
|
-
|
|
1318
|
-
|
|
1324
|
+
expect(response.status).toBe(200)
|
|
1325
|
+
expect(await TestApp.json(response, Funding.schema.listFundingDeposits.Response)).toEqual({
|
|
1326
|
+
data: [],
|
|
1327
|
+
nextCursor: null,
|
|
1328
|
+
})
|
|
1319
1329
|
})
|
|
1320
1330
|
})
|
|
1321
1331
|
|
|
1322
|
-
describe('GET /funding/deposits', () => {
|
|
1332
|
+
describe('GET /funding/deposits?recipient', () => {
|
|
1323
1333
|
test('requires funding:read and pages recipient deposits across addresses', async () => {
|
|
1324
1334
|
const app = transfersApp()
|
|
1325
1335
|
const recipient = '0xABcdEFABcdEFabcdEfAbCdefabcdeFABcDEFabCD'
|
|
@@ -1327,6 +1337,17 @@ describe('GET /funding/deposits', () => {
|
|
|
1327
1337
|
expect((await app.request(path)).status).toBe(401)
|
|
1328
1338
|
expect((await app.request(path, as(TestApp.key))).status).toBe(403)
|
|
1329
1339
|
expect((await app.request('/v1/funding/deposits', as(fundingReader))).status).toBe(400)
|
|
1340
|
+
expect(
|
|
1341
|
+
(await app.request('/v1/funding/deposits?depositAddress=', as(fundingReader))).status,
|
|
1342
|
+
).toBe(400)
|
|
1343
|
+
expect(
|
|
1344
|
+
(
|
|
1345
|
+
await app.request(
|
|
1346
|
+
`${path}&depositAddress=TJRabPrwbZy45sbavfcjinPJC18kjpRTv8`,
|
|
1347
|
+
as(fundingReader),
|
|
1348
|
+
)
|
|
1349
|
+
).status,
|
|
1350
|
+
).toBe(400)
|
|
1330
1351
|
|
|
1331
1352
|
const firstAddress = await seedDepositAddress({
|
|
1332
1353
|
snapshot: TestFunding.depositAddressSnapshot({
|
|
@@ -132,35 +132,36 @@ export namespace schema {
|
|
|
132
132
|
export const Params = getFundingDepositAddress.Params
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
/** Schemas for the listFundingDepositAddressDeposits operation. */
|
|
136
|
-
export namespace listFundingDepositAddressDeposits {
|
|
137
|
-
/** Path parameters for listing one address's deposits. */
|
|
138
|
-
export const Params = getFundingDepositAddress.Params
|
|
139
|
-
|
|
140
|
-
/** Query parameters for listing one address's deposits. */
|
|
141
|
-
export const Query = z
|
|
142
|
-
.object({ cursor: Schema.Cursor, limit: Schema.Limit })
|
|
143
|
-
.check(z.describe('Paging parameters for one funding deposit address.'))
|
|
144
|
-
|
|
145
|
-
/** One page of detected deposits. */
|
|
146
|
-
export const Response = z
|
|
147
|
-
.object({
|
|
148
|
-
data: z.array(Deposit).check(z.describe('Detected deposits on this page, newest first.')),
|
|
149
|
-
nextCursor: Schema.NextCursor,
|
|
150
|
-
})
|
|
151
|
-
.check(z.describe('A page of deposits detected for one funding deposit address.'))
|
|
152
|
-
}
|
|
153
|
-
|
|
154
135
|
/** Schemas for the listFundingDeposits operation. */
|
|
155
136
|
export namespace listFundingDeposits {
|
|
156
|
-
/** Query parameters for listing
|
|
137
|
+
/** Query parameters for listing funding deposits. */
|
|
157
138
|
export const Query = z
|
|
158
139
|
.object({
|
|
140
|
+
depositAddress: z
|
|
141
|
+
.optional(z.string().check(z.minLength(1)))
|
|
142
|
+
.check(
|
|
143
|
+
z.describe('Reusable source-chain deposit address that received the funds.'),
|
|
144
|
+
z.meta({ examples: ['TJRabPrwbZy45sbavfcjinPJC18kjpRTv8'] }),
|
|
145
|
+
),
|
|
159
146
|
cursor: Schema.Cursor,
|
|
160
147
|
limit: Schema.Limit,
|
|
161
|
-
recipient:
|
|
148
|
+
recipient: z
|
|
149
|
+
.optional(Schema.Address)
|
|
150
|
+
.check(
|
|
151
|
+
z.describe('Tempo account that received the deposits.'),
|
|
152
|
+
z.meta({ examples: [`0x${'11'.repeat(20)}`] }),
|
|
153
|
+
),
|
|
162
154
|
})
|
|
163
|
-
.check(
|
|
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
|
+
)
|
|
164
165
|
|
|
165
166
|
/** One page of detected deposits. */
|
|
166
167
|
export const Response = z
|
|
@@ -168,7 +169,7 @@ export namespace schema {
|
|
|
168
169
|
data: z.array(Deposit).check(z.describe('Detected deposits on this page, newest first.')),
|
|
169
170
|
nextCursor: Schema.NextCursor,
|
|
170
171
|
})
|
|
171
|
-
.check(z.describe('A page of deposits
|
|
172
|
+
.check(z.describe('A page of funding deposits matching the selected filter.'))
|
|
172
173
|
}
|
|
173
174
|
|
|
174
175
|
/** Schemas for the getFundingDeposit operation. */
|
|
@@ -273,11 +274,6 @@ const fundingDepositExample = {
|
|
|
273
274
|
updatedAt: '2026-08-04T00:01:00.000Z',
|
|
274
275
|
} satisfies z.output<typeof schema.Deposit>
|
|
275
276
|
|
|
276
|
-
const fundingDepositAddressDepositsExample = {
|
|
277
|
-
data: [fundingDepositExample],
|
|
278
|
-
nextCursor: null,
|
|
279
|
-
} satisfies z.output<typeof schema.listFundingDepositAddressDeposits.Response>
|
|
280
|
-
|
|
281
277
|
const fundingDepositsExample = {
|
|
282
278
|
data: [fundingDepositExample],
|
|
283
279
|
nextCursor: null,
|
|
@@ -650,88 +646,6 @@ export function depositAddresses(options: depositAddresses.Options = {}) {
|
|
|
650
646
|
}
|
|
651
647
|
},
|
|
652
648
|
)
|
|
653
|
-
.get(
|
|
654
|
-
'/v1/funding/deposit-addresses/:id{fda_[A-Za-z0-9_-]+}/deposits',
|
|
655
|
-
Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
|
|
656
|
-
OpenApi.validate('param', schema.listFundingDepositAddressDeposits.Params, {
|
|
657
|
-
code: 'param_invalid',
|
|
658
|
-
message: 'Check the path parameters and try again.',
|
|
659
|
-
}),
|
|
660
|
-
OpenApi.validate('query', schema.listFundingDepositAddressDeposits.Query, {
|
|
661
|
-
code: 'query_invalid',
|
|
662
|
-
message: 'Check the query parameters and try again.',
|
|
663
|
-
}),
|
|
664
|
-
OpenApi.describeRoute({
|
|
665
|
-
description: 'Lists deposits detected for one funding deposit address, newest first.',
|
|
666
|
-
operationId: 'listFundingDepositAddressDeposits',
|
|
667
|
-
responses: OpenApi.responses({
|
|
668
|
-
errors: {
|
|
669
|
-
400: { codes: ['param_invalid', 'query_invalid'] },
|
|
670
|
-
404: {
|
|
671
|
-
codes: ['funding_deposit_address_not_found'],
|
|
672
|
-
description: 'No visible funding deposit address exists for the identifier.',
|
|
673
|
-
},
|
|
674
|
-
},
|
|
675
|
-
success: {
|
|
676
|
-
description: 'A page of deposits detected for the address.',
|
|
677
|
-
example: fundingDepositAddressDepositsExample,
|
|
678
|
-
schema: schema.listFundingDepositAddressDeposits.Response,
|
|
679
|
-
},
|
|
680
|
-
}),
|
|
681
|
-
summary: 'List deposits by deposit address',
|
|
682
|
-
tags: ['Deposit Addresses'],
|
|
683
|
-
}),
|
|
684
|
-
async (c) => {
|
|
685
|
-
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
686
|
-
if (OpenApi.narrowValidation)
|
|
687
|
-
return OpenApi.validationError(c, {
|
|
688
|
-
code: 'query_invalid',
|
|
689
|
-
message: 'Check the request parameters and try again.',
|
|
690
|
-
})
|
|
691
|
-
|
|
692
|
-
const owner = Access.owner(c)
|
|
693
|
-
if (!owner) return Access.unauthorized(c)
|
|
694
|
-
|
|
695
|
-
const db = Db.get(c.get('db'))
|
|
696
|
-
const { id } = c.req.valid('param')
|
|
697
|
-
const { cursor, limit } = c.req.valid('query')
|
|
698
|
-
const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined
|
|
699
|
-
try {
|
|
700
|
-
const address = await FundingDepositAddresses.getOwned(db, owner, id)
|
|
701
|
-
if (!address)
|
|
702
|
-
return Response.error(c, {
|
|
703
|
-
code: 'funding_deposit_address_not_found',
|
|
704
|
-
message: 'Funding deposit address not found',
|
|
705
|
-
status: 404,
|
|
706
|
-
})
|
|
707
|
-
|
|
708
|
-
const rows = await FundingDeposits.listByAddress(db, owner, id, {
|
|
709
|
-
...(decoded
|
|
710
|
-
? { cursor: { createdAt: String(decoded[0]), id: String(decoded[1]) } }
|
|
711
|
-
: {}),
|
|
712
|
-
limit: limit + 1,
|
|
713
|
-
})
|
|
714
|
-
const page = Cursor.paginate({
|
|
715
|
-
key: (record) => [record.createdAt, record.id],
|
|
716
|
-
limit,
|
|
717
|
-
rows,
|
|
718
|
-
})
|
|
719
|
-
const data = page.rows.map(FundingDeposit.toPublic)
|
|
720
|
-
|
|
721
|
-
c.header('Cache-Control', Cache.policies.noStore)
|
|
722
|
-
c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key, X-API-Key')
|
|
723
|
-
return c.json(
|
|
724
|
-
Response.validated(schema.listFundingDepositAddressDeposits.Response, {
|
|
725
|
-
data,
|
|
726
|
-
nextCursor: page.nextCursor,
|
|
727
|
-
}),
|
|
728
|
-
200,
|
|
729
|
-
)
|
|
730
|
-
} catch (cause) {
|
|
731
|
-
return Response.upstream(c, cause)
|
|
732
|
-
}
|
|
733
|
-
},
|
|
734
|
-
)
|
|
735
649
|
.get(
|
|
736
650
|
'/v1/funding/deposit-addresses/:id{fda_[A-Za-z0-9_-]+}',
|
|
737
651
|
Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
|
|
@@ -868,19 +782,19 @@ export function depositAddresses(options: depositAddresses.Options = {}) {
|
|
|
868
782
|
message: 'Check the query parameters and try again.',
|
|
869
783
|
}),
|
|
870
784
|
OpenApi.describeRoute({
|
|
871
|
-
description: 'Lists deposits
|
|
785
|
+
description: 'Lists deposits by deposit address or Tempo recipient, newest first.',
|
|
872
786
|
operationId: 'listFundingDeposits',
|
|
873
787
|
responses: OpenApi.responses({
|
|
874
788
|
errors: {
|
|
875
789
|
400: { codes: ['query_invalid'] },
|
|
876
790
|
},
|
|
877
791
|
success: {
|
|
878
|
-
description: 'A page of funding deposits
|
|
792
|
+
description: 'A page of funding deposits matching the selected filter.',
|
|
879
793
|
example: fundingDepositsExample,
|
|
880
794
|
schema: schema.listFundingDeposits.Response,
|
|
881
795
|
},
|
|
882
796
|
}),
|
|
883
|
-
summary: 'List deposits
|
|
797
|
+
summary: 'List deposits',
|
|
884
798
|
tags: ['Deposit Addresses'],
|
|
885
799
|
}),
|
|
886
800
|
async (c) => {
|
|
@@ -895,17 +809,20 @@ export function depositAddresses(options: depositAddresses.Options = {}) {
|
|
|
895
809
|
if (!owner) return Access.unauthorized(c)
|
|
896
810
|
|
|
897
811
|
const db = Db.get(c.get('db'))
|
|
898
|
-
const { cursor, limit, recipient } = c.req.valid('query')
|
|
812
|
+
const { cursor, depositAddress, limit, recipient } = c.req.valid('query')
|
|
899
813
|
const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined
|
|
900
814
|
try {
|
|
901
|
-
const
|
|
815
|
+
const paging = {
|
|
902
816
|
...(decoded
|
|
903
817
|
? { cursor: { createdAt: String(decoded[0]), id: String(decoded[1]) } }
|
|
904
818
|
: {}),
|
|
905
819
|
limit: limit + 1,
|
|
906
820
|
owner,
|
|
907
|
-
|
|
908
|
-
|
|
821
|
+
}
|
|
822
|
+
const rows =
|
|
823
|
+
depositAddress === undefined
|
|
824
|
+
? await FundingDeposits.listByRecipient(db, { ...paging, recipient: recipient! })
|
|
825
|
+
: await FundingDeposits.listByDepositAddress(db, { ...paging, depositAddress })
|
|
909
826
|
const page = Cursor.paginate({
|
|
910
827
|
key: (record) => [record.createdAt, record.id],
|
|
911
828
|
limit,
|
|
@@ -830,9 +830,6 @@ describe('funding transfers OpenAPI', () => {
|
|
|
830
830
|
!path.endsWith('/deposits') &&
|
|
831
831
|
!path.endsWith('/reconcile'),
|
|
832
832
|
)!
|
|
833
|
-
const depositAddressDepositsPath = Object.keys(spec.paths).find(
|
|
834
|
-
(path) => path.includes('/v1/funding/deposit-addresses/{') && path.endsWith('/deposits'),
|
|
835
|
-
)!
|
|
836
833
|
const depositPath = Object.keys(spec.paths).find((path) =>
|
|
837
834
|
path.includes('/v1/funding/deposits/{'),
|
|
838
835
|
)!
|
|
@@ -847,7 +844,6 @@ describe('funding transfers OpenAPI', () => {
|
|
|
847
844
|
deposit: summarize(spec.paths[depositPath]!.get!),
|
|
848
845
|
depositAddress: summarize(spec.paths[depositAddressPath]!.get!),
|
|
849
846
|
depositAddressCreate: summarize(spec.paths['/v1/funding/deposit-addresses']!.post!),
|
|
850
|
-
depositAddressDeposits: summarize(spec.paths[depositAddressDepositsPath]!.get!),
|
|
851
847
|
depositAddressReconcile: summarize(spec.paths[reconcilePath]!.post!),
|
|
852
848
|
deposits: summarize(spec.paths['/v1/funding/deposits']!.get!),
|
|
853
849
|
detail: summarize(spec.paths[detailPath]!.get!),
|
|
@@ -896,17 +892,13 @@ describe('funding transfers OpenAPI', () => {
|
|
|
896
892
|
"description": "Creates or returns a reusable deposit address for funding a Tempo account.",
|
|
897
893
|
"summary": "Create deposit address",
|
|
898
894
|
},
|
|
899
|
-
"depositAddressDeposits": {
|
|
900
|
-
"description": "Lists deposits detected for one funding deposit address, newest first.",
|
|
901
|
-
"summary": "List deposits by deposit address",
|
|
902
|
-
},
|
|
903
895
|
"depositAddressReconcile": {
|
|
904
896
|
"description": "Queues an immediate provider reconciliation for one reusable funding deposit address.",
|
|
905
897
|
"summary": "Reconcile deposit address",
|
|
906
898
|
},
|
|
907
899
|
"deposits": {
|
|
908
|
-
"description": "Lists deposits
|
|
909
|
-
"summary": "List deposits
|
|
900
|
+
"description": "Lists deposits by deposit address or Tempo recipient, newest first.",
|
|
901
|
+
"summary": "List deposits",
|
|
910
902
|
},
|
|
911
903
|
"detail": {
|
|
912
904
|
"description": "Returns a transfer by ID.",
|
|
@@ -934,7 +926,6 @@ describe('funding transfers OpenAPI', () => {
|
|
|
934
926
|
},
|
|
935
927
|
},
|
|
936
928
|
"sidebar": [
|
|
937
|
-
"List transfers",
|
|
938
929
|
"Get quotes",
|
|
939
930
|
"Get chains",
|
|
940
931
|
"Get providers",
|
|
@@ -947,8 +938,7 @@ describe('funding transfers OpenAPI', () => {
|
|
|
947
938
|
"Get deposit address",
|
|
948
939
|
"Get transfer",
|
|
949
940
|
"Get deposit",
|
|
950
|
-
"List deposits
|
|
951
|
-
"List deposits by recipient",
|
|
941
|
+
"List deposits",
|
|
952
942
|
],
|
|
953
943
|
}
|
|
954
944
|
`)
|
|
@@ -1100,8 +1090,9 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1100
1090
|
const operation = spec.paths['/v1/funding/transfers']!.get
|
|
1101
1091
|
const fields = Object.keys(operation.responses['200']!.content['application/json']!.example.data[0]!) // prettier-ignore
|
|
1102
1092
|
expect(fields).toEqual([...fields].sort())
|
|
1103
|
-
for (const
|
|
1104
|
-
|
|
1093
|
+
for (const route of [operation, spec.paths['/v1/funding/deposits']!.get])
|
|
1094
|
+
for (const parameter of route.parameters ?? [])
|
|
1095
|
+
expect(parameter.example ?? parameter.schema?.examples, parameter.name).toBeDefined()
|
|
1105
1096
|
})
|
|
1106
1097
|
|
|
1107
1098
|
test('publishes operation ids and required scopes', async () => {
|
|
@@ -1128,9 +1119,6 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1128
1119
|
!path.endsWith('/deposits') &&
|
|
1129
1120
|
!path.endsWith('/reconcile'),
|
|
1130
1121
|
)
|
|
1131
|
-
const depositAddressDepositsPath = Object.keys(spec.paths).find(
|
|
1132
|
-
(path) => path.includes('/v1/funding/deposit-addresses/{') && path.endsWith('/deposits'),
|
|
1133
|
-
)
|
|
1134
1122
|
const depositPath = Object.keys(spec.paths).find((path) =>
|
|
1135
1123
|
path.includes('/v1/funding/deposits/{'),
|
|
1136
1124
|
)
|
|
@@ -1141,11 +1129,6 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1141
1129
|
depositAddressCreate: spec.paths['/v1/funding/deposit-addresses']?.post?.operationId,
|
|
1142
1130
|
depositAddressCreateScopes:
|
|
1143
1131
|
spec.paths['/v1/funding/deposit-addresses']?.post?.['x-required-scopes'],
|
|
1144
|
-
depositAddressDeposits:
|
|
1145
|
-
depositAddressDepositsPath && spec.paths[depositAddressDepositsPath]?.get.operationId,
|
|
1146
|
-
depositAddressDepositsScopes:
|
|
1147
|
-
depositAddressDepositsPath &&
|
|
1148
|
-
spec.paths[depositAddressDepositsPath]?.get['x-required-scopes'],
|
|
1149
1132
|
depositAddressReconcile: reconcilePath && spec.paths[reconcilePath]?.post?.operationId,
|
|
1150
1133
|
depositAddressReconcileScopes:
|
|
1151
1134
|
reconcilePath && spec.paths[reconcilePath]?.post?.['x-required-scopes'],
|
|
@@ -1170,8 +1153,6 @@ describe('funding transfers OpenAPI', () => {
|
|
|
1170
1153
|
depositAddress: 'getFundingDepositAddress',
|
|
1171
1154
|
depositAddressCreate: 'createFundingDepositAddress',
|
|
1172
1155
|
depositAddressCreateScopes: ['funding:write'],
|
|
1173
|
-
depositAddressDeposits: 'listFundingDepositAddressDeposits',
|
|
1174
|
-
depositAddressDepositsScopes: ['funding:read'],
|
|
1175
1156
|
depositAddressReconcile: 'reconcileFundingDepositAddress',
|
|
1176
1157
|
depositAddressReconcileScopes: ['funding:write'],
|
|
1177
1158
|
depositAddressScopes: ['funding:read'],
|
|
@@ -290,7 +290,7 @@ export function transfers() {
|
|
|
290
290
|
},
|
|
291
291
|
}),
|
|
292
292
|
summary: 'List transfers',
|
|
293
|
-
tags: ['Transfers'],
|
|
293
|
+
tags: ['Funding Transfers'],
|
|
294
294
|
}),
|
|
295
295
|
fundingEtag,
|
|
296
296
|
async (c) => {
|
|
@@ -373,7 +373,7 @@ export function transfers() {
|
|
|
373
373
|
},
|
|
374
374
|
}),
|
|
375
375
|
summary: 'Get transfer',
|
|
376
|
-
tags: ['Transfers'],
|
|
376
|
+
tags: ['Funding Transfers'],
|
|
377
377
|
}),
|
|
378
378
|
async (c) => {
|
|
379
379
|
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
@@ -448,7 +448,7 @@ export function transfers() {
|
|
|
448
448
|
},
|
|
449
449
|
}),
|
|
450
450
|
summary: 'Create transfer',
|
|
451
|
-
tags: ['Transfers'],
|
|
451
|
+
tags: ['Funding Transfers'],
|
|
452
452
|
}),
|
|
453
453
|
async (c) => {
|
|
454
454
|
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
@@ -637,7 +637,7 @@ export function transfers() {
|
|
|
637
637
|
operationId: 'createFundingTransferVault',
|
|
638
638
|
responses: unimplementedFundingTransferResponses,
|
|
639
639
|
summary: 'Create transfer into vault',
|
|
640
|
-
tags: ['Transfers'],
|
|
640
|
+
tags: ['Funding Transfers'],
|
|
641
641
|
}),
|
|
642
642
|
(c) => {
|
|
643
643
|
if (Auth.narrowAccess) return Auth.accessError(c)
|
|
@@ -671,7 +671,7 @@ export function transfers() {
|
|
|
671
671
|
operationId: 'createFundingTransferZone',
|
|
672
672
|
responses: unimplementedFundingTransferResponses,
|
|
673
673
|
summary: 'Create transfer into zone',
|
|
674
|
-
tags: ['Transfers'],
|
|
674
|
+
tags: ['Funding Transfers'],
|
|
675
675
|
}),
|
|
676
676
|
(c) => {
|
|
677
677
|
if (Auth.narrowAccess) return Auth.accessError(c)
|
package/src/db/Db.ts
CHANGED
|
@@ -1271,6 +1271,15 @@ export const migrations: readonly Migration[] = [
|
|
|
1271
1271
|
sql`ALTER TABLE funding_deposits DROP CONSTRAINT IF EXISTS funding_deposits_detection_trigger_check, ADD CONSTRAINT funding_deposits_detection_trigger_check CHECK (detection_trigger IN ('chain', 'manual', 'poll', 'webhook'))`,
|
|
1272
1272
|
),
|
|
1273
1273
|
},
|
|
1274
|
+
{
|
|
1275
|
+
name: '0114_funding_deposit_addresses_owner_address_index',
|
|
1276
|
+
up: (db) =>
|
|
1277
|
+
db.schema
|
|
1278
|
+
.createIndex('funding_deposit_addresses_owner_address_idx')
|
|
1279
|
+
.ifNotExists()
|
|
1280
|
+
.on('fundingDepositAddresses')
|
|
1281
|
+
.columns(['orgId', 'environment', 'address', 'projectId']),
|
|
1282
|
+
},
|
|
1274
1283
|
]
|
|
1275
1284
|
|
|
1276
1285
|
/**
|
|
@@ -77,6 +77,107 @@ describe('listByAddress', () => {
|
|
|
77
77
|
})
|
|
78
78
|
})
|
|
79
79
|
|
|
80
|
+
describe('listByDepositAddress', () => {
|
|
81
|
+
const seedAddressDeposit = (address: Awaited<ReturnType<typeof seedAddress>>, index: number) => {
|
|
82
|
+
const sourceTransactionHash = `fe${index.toString(16).padStart(62, '0')}`
|
|
83
|
+
return Deposit.create(db, {
|
|
84
|
+
now: new Date(Date.UTC(2026, 1, 2, 0, 0, index)),
|
|
85
|
+
providerRequestId: `relay_deposit_address_request_${index}`,
|
|
86
|
+
snapshot: TestFunding.depositSnapshot(address.id, {
|
|
87
|
+
provider: address.snapshot.provider,
|
|
88
|
+
sourceTransactionHashes: [sourceTransactionHash],
|
|
89
|
+
}),
|
|
90
|
+
sourceTransactionHash,
|
|
91
|
+
sourceTransferIndex: 0,
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
test('pages one reusable address across provider records newest-first', async () => {
|
|
96
|
+
const depositAddress = 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE'
|
|
97
|
+
const firstAddress = await seedAddress({
|
|
98
|
+
snapshot: TestFunding.depositAddressSnapshot({
|
|
99
|
+
address: depositAddress,
|
|
100
|
+
provider: { id: 'relay_deposit_address_first', name: 'Relay' },
|
|
101
|
+
}),
|
|
102
|
+
})
|
|
103
|
+
const secondAddress = await seedAddress({
|
|
104
|
+
snapshot: TestFunding.depositAddressSnapshot({
|
|
105
|
+
address: depositAddress,
|
|
106
|
+
provider: { id: 'relay_deposit_address_second', name: 'Relay' },
|
|
107
|
+
subsidize: true,
|
|
108
|
+
}),
|
|
109
|
+
})
|
|
110
|
+
const first = await seedAddressDeposit(firstAddress, 1)
|
|
111
|
+
const second = await seedAddressDeposit(secondAddress, 2)
|
|
112
|
+
const third = await seedAddressDeposit(firstAddress, 3)
|
|
113
|
+
const unrelatedAddress = await seedAddress({
|
|
114
|
+
snapshot: TestFunding.depositAddressSnapshot({
|
|
115
|
+
address: 'TF17BgPaZYbz8oxbjhriubPDsA7ArKoLX3',
|
|
116
|
+
provider: { id: 'relay_deposit_address_unrelated', name: 'Relay' },
|
|
117
|
+
recipient: `0x${'fe'.repeat(20)}`,
|
|
118
|
+
}),
|
|
119
|
+
})
|
|
120
|
+
await seedAddressDeposit(unrelatedAddress, 4)
|
|
121
|
+
|
|
122
|
+
const page = await FundingDeposits.listByDepositAddress(db, {
|
|
123
|
+
depositAddress,
|
|
124
|
+
limit: 2,
|
|
125
|
+
owner,
|
|
126
|
+
})
|
|
127
|
+
expect(page.map((row) => row.id)).toEqual([third.id, second.id])
|
|
128
|
+
const rest = await FundingDeposits.listByDepositAddress(db, {
|
|
129
|
+
cursor: { createdAt: second.createdAt, id: second.id },
|
|
130
|
+
depositAddress,
|
|
131
|
+
limit: 2,
|
|
132
|
+
owner,
|
|
133
|
+
})
|
|
134
|
+
expect(rest.map((row) => row.id)).toEqual([first.id])
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('narrows project-attributed owners to their project', async () => {
|
|
138
|
+
const depositAddress = 'TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb'
|
|
139
|
+
const address = await seedAddress({
|
|
140
|
+
projectId: 'prj_deposit',
|
|
141
|
+
snapshot: TestFunding.depositAddressSnapshot({
|
|
142
|
+
address: depositAddress,
|
|
143
|
+
provider: { id: 'relay_deposit_address_project', name: 'Relay' },
|
|
144
|
+
}),
|
|
145
|
+
})
|
|
146
|
+
await seedAddressDeposit(address, 5)
|
|
147
|
+
|
|
148
|
+
expect(
|
|
149
|
+
await FundingDeposits.listByDepositAddress(db, {
|
|
150
|
+
depositAddress,
|
|
151
|
+
limit: 10,
|
|
152
|
+
owner: { ...owner, projectId: 'prj_other' },
|
|
153
|
+
}),
|
|
154
|
+
).toEqual([])
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
test('normalizes EVM address casing', async () => {
|
|
158
|
+
const depositAddress = `0x${'Ab'.repeat(20)}`
|
|
159
|
+
const source = TestFunding.transferSnapshot()
|
|
160
|
+
const address = await seedAddress({
|
|
161
|
+
snapshot: TestFunding.depositAddressSnapshot({
|
|
162
|
+
address: depositAddress,
|
|
163
|
+
provider: { id: 'relay_deposit_address_evm', name: 'Relay' },
|
|
164
|
+
recipient: `0x${'fd'.repeat(20)}`,
|
|
165
|
+
refundAddress: depositAddress,
|
|
166
|
+
sourceChain: source.sourceChain,
|
|
167
|
+
sourceToken: source.sourceToken,
|
|
168
|
+
}),
|
|
169
|
+
})
|
|
170
|
+
const record = await seedAddressDeposit(address, 6)
|
|
171
|
+
|
|
172
|
+
const rows = await FundingDeposits.listByDepositAddress(db, {
|
|
173
|
+
depositAddress,
|
|
174
|
+
limit: 10,
|
|
175
|
+
owner,
|
|
176
|
+
})
|
|
177
|
+
expect(rows.map((row) => row.id)).toEqual([record.id])
|
|
178
|
+
})
|
|
179
|
+
})
|
|
180
|
+
|
|
80
181
|
describe('listByRecipient', () => {
|
|
81
182
|
test('pages recipient deposits across addresses and hides foreign ownership', async () => {
|
|
82
183
|
const recipient = '0xABcdEFABcdEFabcdEfAbCdefabcdeFABcDEFabCD'
|