tapimo 0.7.2 → 0.8.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/README.md +10 -7
- package/dist/ApiKey.d.ts +3 -0
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +10 -0
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +7 -3
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +9 -2
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +20434 -19484
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +62 -4
- package/dist/App.js.map +1 -1
- package/dist/admin/App.d.ts +84 -74
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +14 -1
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +6 -2
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +2 -0
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +168 -168
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +67 -67
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +2 -0
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +3 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/apps/data/App.d.ts +10305 -12785
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +4 -8
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +24 -24
- package/dist/apps/data/routes/addresses.d.ts +288 -288
- package/dist/apps/data/routes/balances.d.ts +6 -6
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/coingecko.d.ts +170 -170
- package/dist/apps/data/routes/earn.d.ts +436 -6
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +631 -4
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +10 -10
- package/dist/apps/data/routes/fee-amm.d.ts +2 -2
- package/dist/apps/data/routes/indexer.d.ts +6 -6
- package/dist/apps/data/routes/indexer.js +4 -1
- package/dist/apps/data/routes/indexer.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts +235 -235
- package/dist/apps/data/routes/rpc.d.ts +1 -1
- package/dist/apps/data/routes/rpc.js +4 -1
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +36 -36
- package/dist/apps/data/routes/tokens.d.ts +6 -6
- package/dist/apps/data/routes/transactions.d.ts +2 -2
- package/dist/apps/data/routes/transfers.d.ts +1 -1
- package/dist/apps/data/routes/valuation.d.ts +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/webhooks.d.ts +9 -9
- package/dist/apps/data/routes/zones.d.ts +14 -14
- package/dist/apps/funding/Access.d.ts +16 -0
- package/dist/apps/funding/Access.d.ts.map +1 -0
- package/dist/apps/funding/Access.js +22 -0
- package/dist/apps/funding/Access.js.map +1 -0
- package/dist/apps/{data/routes/funding.d.ts → funding/App.d.ts} +1717 -3035
- package/dist/apps/funding/App.d.ts.map +1 -0
- package/dist/apps/funding/App.js +46 -0
- package/dist/apps/funding/App.js.map +1 -0
- package/dist/apps/funding/Inventory.d.ts +5 -0
- package/dist/apps/funding/Inventory.d.ts.map +1 -0
- package/dist/apps/funding/Inventory.js +25 -0
- package/dist/apps/funding/Inventory.js.map +1 -0
- package/dist/apps/funding/ProviderAttempts.d.ts +5 -0
- package/dist/apps/funding/ProviderAttempts.d.ts.map +1 -0
- package/dist/apps/funding/ProviderAttempts.js +24 -0
- package/dist/apps/funding/ProviderAttempts.js.map +1 -0
- package/dist/apps/funding/index.d.ts +2 -0
- package/dist/apps/funding/index.d.ts.map +1 -0
- package/dist/apps/funding/index.js +2 -0
- package/dist/apps/funding/index.js.map +1 -0
- package/dist/apps/funding/routes/chains.d.ts +169 -0
- package/dist/apps/funding/routes/chains.d.ts.map +1 -0
- package/dist/apps/funding/routes/chains.js +80 -0
- package/dist/apps/funding/routes/chains.js.map +1 -0
- package/dist/apps/funding/routes/deposit-addresses.d.ts +2388 -0
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -0
- package/dist/apps/funding/routes/deposit-addresses.js +860 -0
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -0
- package/dist/apps/funding/routes/providers.d.ts +91 -0
- package/dist/apps/funding/routes/providers.d.ts.map +1 -0
- package/dist/apps/funding/routes/providers.js +56 -0
- package/dist/apps/funding/routes/providers.js.map +1 -0
- package/dist/apps/funding/routes/quotes.d.ts +426 -0
- package/dist/apps/funding/routes/quotes.d.ts.map +1 -0
- package/dist/apps/funding/routes/quotes.js +188 -0
- package/dist/apps/funding/routes/quotes.js.map +1 -0
- package/dist/apps/funding/routes/transfers.d.ts +1999 -0
- package/dist/apps/funding/routes/transfers.d.ts.map +1 -0
- package/dist/apps/funding/routes/transfers.js +614 -0
- package/dist/apps/funding/routes/transfers.js.map +1 -0
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -0
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +430 -165
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +298 -21
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +75 -1
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +6 -6
- package/dist/apps/management/routes/faucet.d.ts +35 -35
- package/dist/apps/management/routes/invitations.d.ts +48 -48
- package/dist/apps/management/routes/invite-links.d.ts +38 -38
- package/dist/apps/management/routes/members.d.ts +69 -69
- package/dist/apps/management/routes/orgs.d.ts +37 -37
- package/dist/apps/management/routes/projects.d.ts +25 -25
- package/dist/apps/management/routes/scopes.d.ts +1 -1
- package/dist/apps/management/routes/usage.d.ts +72 -72
- package/dist/apps/management/routes/verified-token-requests.d.ts +18 -18
- package/dist/apps/management/routes/webhooks.d.ts +128 -128
- package/dist/apps/mcp.d.ts +2 -3
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +16 -14
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/metadata.d.ts +4 -0
- package/dist/apps/metadata.d.ts.map +1 -1
- package/dist/apps/metadata.js +10 -0
- package/dist/apps/metadata.js.map +1 -1
- package/dist/apps/mpp/App.d.ts +2 -2
- package/dist/cloudflare.d.ts +50 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +178 -0
- package/dist/cloudflare.js.map +1 -1
- package/dist/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +37 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +12 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +20 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.d.ts +13 -0
- package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.js +12 -0
- package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
- package/dist/db/tables/fundingDepositRequestObservations.d.ts +45 -0
- package/dist/db/tables/fundingDepositRequestObservations.d.ts.map +1 -0
- package/dist/db/tables/fundingDepositRequestObservations.js +46 -0
- package/dist/db/tables/fundingDepositRequestObservations.js.map +1 -0
- package/dist/db/tables/fundingDeposits.d.ts +57 -1
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +82 -0
- package/dist/db/tables/fundingDeposits.js.map +1 -1
- package/dist/internal/Auth.d.ts +11 -11
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +41 -15
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cache.js +1 -1
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +2 -2
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +10 -4
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/IpAllowlist.d.ts +15 -0
- package/dist/internal/IpAllowlist.d.ts.map +1 -0
- package/dist/internal/IpAllowlist.js +27 -0
- package/dist/internal/IpAllowlist.js.map +1 -0
- package/dist/internal/OpenApi.d.ts +1 -1
- package/dist/internal/OpenApi.js +1 -1
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Tidx.d.ts +6 -0
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +13 -3
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts +8 -0
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +12 -5
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/funding/Deposit.d.ts +42 -0
- package/dist/internal/funding/Deposit.d.ts.map +1 -1
- package/dist/internal/funding/Deposit.js +37 -5
- package/dist/internal/funding/Deposit.js.map +1 -1
- package/dist/internal/funding/Evidence.d.ts +1 -5
- package/dist/internal/funding/Evidence.d.ts.map +1 -1
- package/dist/internal/funding/Evidence.js +4 -3
- package/dist/internal/funding/Evidence.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +102 -0
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +5 -0
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/Reconciliation.d.ts +16 -2
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +257 -48
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- package/dist/internal/funding/SourceObservation.d.ts +108 -0
- package/dist/internal/funding/SourceObservation.d.ts.map +1 -0
- package/dist/internal/funding/SourceObservation.js +143 -0
- package/dist/internal/funding/SourceObservation.js.map +1 -0
- package/dist/internal/funding/Subsidy.d.ts +2 -2
- package/dist/internal/funding/Subsidy.d.ts.map +1 -1
- package/dist/internal/funding/Subsidy.js +1 -1
- package/dist/internal/funding/Subsidy.js.map +1 -1
- package/dist/internal/funding/index.d.ts +1 -0
- package/dist/internal/funding/index.d.ts.map +1 -1
- package/dist/internal/funding/index.js +1 -0
- package/dist/internal/funding/index.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts +43 -1
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +434 -27
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/package.json +3 -3
- package/src/ApiKey.test.ts +35 -0
- package/src/ApiKey.ts +27 -0
- package/src/ApiKeys.test.ts +29 -0
- package/src/ApiKeys.ts +11 -3
- package/src/App.test.ts +23 -5
- package/src/App.ts +76 -5
- package/src/Client.test-d.ts +171 -19
- package/src/admin/apps/api-keys.test.ts +25 -1
- package/src/admin/apps/api-keys.ts +6 -2
- package/src/admin/ui.generated.ts +67 -67
- package/src/analytics/Analytics.ts +4 -0
- package/src/apps/composition.test.ts +100 -2
- package/src/apps/data/App.test.ts +0 -12
- package/src/apps/data/App.ts +4 -8
- package/src/apps/data/routes/earn.test.ts +283 -0
- package/src/apps/data/routes/earn.ts +800 -3
- package/src/apps/data/routes/indexer.ts +2 -1
- package/src/apps/data/routes/rpc.ts +2 -0
- package/src/apps/data/routes/tokens.test.ts +6 -8
- package/src/apps/funding/Access.ts +26 -0
- package/src/apps/funding/App.test.ts +78 -0
- package/src/apps/funding/App.ts +63 -0
- package/src/apps/funding/Inventory.ts +30 -0
- package/src/apps/funding/ProviderAttempts.ts +26 -0
- package/src/apps/funding/index.ts +1 -0
- package/src/apps/funding/routes/chains.test.ts +78 -0
- package/src/apps/funding/routes/chains.ts +101 -0
- package/src/apps/funding/routes/deposit-addresses.test.ts +1628 -0
- package/src/apps/funding/routes/deposit-addresses.ts +1015 -0
- package/src/apps/funding/routes/providers.test.ts +165 -0
- package/src/apps/funding/routes/providers.ts +73 -0
- package/src/apps/funding/routes/quotes.test.ts +511 -0
- package/src/apps/funding/routes/quotes.ts +205 -0
- package/src/apps/funding/routes/transfers.test.ts +1255 -0
- package/src/apps/funding/routes/transfers.ts +704 -0
- package/src/apps/index.ts +1 -0
- package/src/apps/management/routes/api-keys.test.ts +98 -0
- package/src/apps/management/routes/api-keys.ts +96 -1
- package/src/apps/mcp.test.ts +17 -2
- package/src/apps/mcp.ts +18 -13
- package/src/apps/metadata.ts +12 -0
- package/src/apps/mpp/internal/Policy.test.ts +7 -1
- package/src/cloudflare.ts +279 -0
- package/src/db/Db.ts +62 -0
- package/src/db/Schema.ts +24 -0
- package/src/db/tables/fundingDepositAddresses.ts +28 -0
- package/src/db/tables/fundingDepositRequestObservations.test.ts +106 -0
- package/src/db/tables/fundingDepositRequestObservations.ts +99 -0
- package/src/db/tables/fundingDeposits.test.ts +138 -0
- package/src/db/tables/fundingDeposits.ts +145 -1
- package/src/internal/Auth.test.ts +177 -5
- package/src/internal/Auth.ts +48 -18
- package/src/internal/Cache.test.ts +37 -3
- package/src/internal/Cache.ts +1 -1
- package/src/internal/EdgeCache.test.ts +7 -7
- package/src/internal/EdgeCache.ts +13 -4
- package/src/internal/IpAllowlist.test.ts +52 -0
- package/src/internal/IpAllowlist.ts +39 -0
- package/src/internal/Log.test.ts +7 -1
- package/src/internal/OpenApi.ts +1 -1
- package/src/internal/Tidx.test.ts +28 -0
- package/src/internal/Tidx.ts +19 -3
- package/src/internal/Viem.test.ts +28 -0
- package/src/internal/Viem.ts +21 -5
- package/src/internal/funding/Deposit.ts +74 -5
- package/src/internal/funding/Evidence.test.ts +2 -2
- package/src/internal/funding/Evidence.ts +4 -9
- package/src/internal/funding/Provider.ts +115 -0
- package/src/internal/funding/Reconciliation.local.test.ts +7 -0
- package/src/internal/funding/Reconciliation.test.ts +1052 -35
- package/src/internal/funding/Reconciliation.ts +328 -56
- package/src/internal/funding/SourceObservation.test.ts +301 -0
- package/src/internal/funding/SourceObservation.ts +267 -0
- package/src/internal/funding/Subsidy.ts +2 -2
- package/src/internal/funding/index.test.ts +1 -0
- package/src/internal/funding/index.ts +1 -0
- package/src/internal/funding/providers/relay.test.ts +94 -4
- package/src/internal/funding/providers/relay.ts +528 -27
- package/dist/apps/data/routes/funding.d.ts.map +0 -1
- package/dist/apps/data/routes/funding.js +0 -1557
- package/dist/apps/data/routes/funding.js.map +0 -1
- package/src/apps/data/routes/funding.test.ts +0 -2994
- package/src/apps/data/routes/funding.ts +0 -1821
|
@@ -2,6 +2,7 @@ import { Hex } from 'ox'
|
|
|
2
2
|
|
|
3
3
|
import * as Db from '../../db/Db.js'
|
|
4
4
|
import * as FundingDepositAddresses from '../../db/tables/fundingDepositAddresses.js'
|
|
5
|
+
import * as FundingDepositRequestObservations from '../../db/tables/fundingDepositRequestObservations.js'
|
|
5
6
|
import * as FundingDeposits from '../../db/tables/fundingDeposits.js'
|
|
6
7
|
import type * as Metrics from '../../Metrics.js'
|
|
7
8
|
import * as Value from '../Value.js'
|
|
@@ -12,7 +13,12 @@ import * as Subsidy from './Subsidy.js'
|
|
|
12
13
|
const defaultBatchSize = 25
|
|
13
14
|
const defaultLeaseMs = 2 * 60_000
|
|
14
15
|
const defaultPollIntervalMs = 60_000
|
|
16
|
+
const followUpDelaySeconds = { delivery: 1, provider: 5, settlement: 2 } as const satisfies Record<
|
|
17
|
+
Provider.Webhook.receive.FollowUp['kind'],
|
|
18
|
+
number
|
|
19
|
+
>
|
|
15
20
|
const maximumPages = 100
|
|
21
|
+
const maximumFollowUpAttempts = 6
|
|
16
22
|
|
|
17
23
|
/** Verified token transfer returned by an authoritative chain reader. */
|
|
18
24
|
export type TransferEvidence = {
|
|
@@ -52,10 +58,11 @@ export function createTracker(options: createTracker.Options): createTracker.Tra
|
|
|
52
58
|
const leaseMs = options.leaseMs ?? defaultLeaseMs
|
|
53
59
|
const pollIntervalMs = options.pollIntervalMs ?? defaultPollIntervalMs
|
|
54
60
|
|
|
55
|
-
const record = (outcome: string, providerId: string) => {
|
|
61
|
+
const record = (outcome: string, providerId: string, trigger: Deposit.DetectionTrigger) => {
|
|
56
62
|
options.metrics?.count('funding_deposit_reconciliation_count', 1, {
|
|
57
63
|
outcome,
|
|
58
64
|
provider: providerId,
|
|
65
|
+
trigger,
|
|
59
66
|
})
|
|
60
67
|
}
|
|
61
68
|
|
|
@@ -63,6 +70,12 @@ export function createTracker(options: createTracker.Options): createTracker.Tra
|
|
|
63
70
|
async reconcile(message) {
|
|
64
71
|
const database = Db.get(options.db)
|
|
65
72
|
const startedAt = now()
|
|
73
|
+
// Legacy poll messages carry a lease version; legacy webhook messages do not.
|
|
74
|
+
const trigger = (() => {
|
|
75
|
+
if (message.trigger) return message.trigger
|
|
76
|
+
if (message.pollLeaseVersion !== undefined) return 'poll'
|
|
77
|
+
return 'webhook'
|
|
78
|
+
})()
|
|
66
79
|
const claimed = await claim(database, message, {
|
|
67
80
|
leaseUntil: new Date(startedAt.getTime() + leaseMs).toISOString(),
|
|
68
81
|
now: startedAt.toISOString(),
|
|
@@ -72,7 +85,7 @@ export function createTracker(options: createTracker.Options): createTracker.Tra
|
|
|
72
85
|
const provider = options.providers.find((candidate) => candidate.id === claimed.providerId)
|
|
73
86
|
if (!provider || !Provider.canListDepositAddressRequests(provider)) {
|
|
74
87
|
await fail(database, claimed, startedAt, pollIntervalMs)
|
|
75
|
-
record('provider_unavailable', claimed.providerId)
|
|
88
|
+
record('provider_unavailable', claimed.providerId, trigger)
|
|
76
89
|
return { type: 'provider_unavailable' }
|
|
77
90
|
}
|
|
78
91
|
|
|
@@ -84,24 +97,58 @@ export function createTracker(options: createTracker.Options): createTracker.Tra
|
|
|
84
97
|
requestId: message.requestId,
|
|
85
98
|
signal: options.signal,
|
|
86
99
|
})
|
|
100
|
+
if (trigger === 'poll')
|
|
101
|
+
await FundingDepositRequestObservations.observePoll(database, {
|
|
102
|
+
depositAddressId: claimed.id,
|
|
103
|
+
observedAt: now().toISOString(),
|
|
104
|
+
providerRequestIds: requests.requests.map((request) => request.id),
|
|
105
|
+
})
|
|
106
|
+
const webhookUpdateObserved = (() => {
|
|
107
|
+
const timing = message.webhookTiming
|
|
108
|
+
if (!timing) return true
|
|
109
|
+
if (!message.requestId) return false
|
|
110
|
+
// Relay can send a webhook before its requests API indexes the same update.
|
|
111
|
+
return requests.requests.some(
|
|
112
|
+
(request) =>
|
|
113
|
+
request.id === message.requestId && request.updatedAt >= timing.providerUpdatedAt,
|
|
114
|
+
)
|
|
115
|
+
})()
|
|
87
116
|
heartbeat.assert()
|
|
88
117
|
let created = 0
|
|
89
118
|
let updated = 0
|
|
90
|
-
const pendingRequestIds = new Set(
|
|
119
|
+
const pendingRequestIds = new Set([
|
|
120
|
+
...requests.pendingRequestIds,
|
|
121
|
+
...(message.requestId ? [message.requestId] : []),
|
|
122
|
+
])
|
|
123
|
+
let followUpKind: Provider.Webhook.receive.FollowUp['kind'] | undefined
|
|
91
124
|
for (const request of requests.requests) {
|
|
92
125
|
const result = await reconcileRequest(database, {
|
|
93
126
|
address: claimed,
|
|
94
127
|
metrics: options.metrics,
|
|
128
|
+
provider,
|
|
95
129
|
request,
|
|
130
|
+
signal: options.signal,
|
|
96
131
|
subsidies: options.subsidies,
|
|
132
|
+
trigger,
|
|
97
133
|
verifyTransfers: options.verifyTransfers,
|
|
98
134
|
})
|
|
99
135
|
heartbeat.assert()
|
|
100
136
|
created += result.created
|
|
101
137
|
updated += result.updated
|
|
102
138
|
if (result.terminal) pendingRequestIds.delete(request.id)
|
|
103
|
-
else
|
|
139
|
+
else {
|
|
140
|
+
pendingRequestIds.add(request.id)
|
|
141
|
+
followUpKind = result.followUp ?? followUpKind ?? 'provider'
|
|
142
|
+
}
|
|
104
143
|
}
|
|
144
|
+
const awaitsProvider = await FundingDeposits.hasUnattributed(database, claimed.id)
|
|
145
|
+
if (message.requestId && message.webhookTiming && !webhookUpdateObserved)
|
|
146
|
+
pendingRequestIds.add(message.requestId)
|
|
147
|
+
if (created > 0)
|
|
148
|
+
options.metrics?.count('funding_deposit_detection_count', created, {
|
|
149
|
+
provider: claimed.providerId,
|
|
150
|
+
trigger,
|
|
151
|
+
})
|
|
105
152
|
|
|
106
153
|
const leaseFailure = await heartbeat.stop()
|
|
107
154
|
if (leaseFailure !== undefined) throw leaseFailure
|
|
@@ -112,22 +159,65 @@ export function createTracker(options: createTracker.Options): createTracker.Tra
|
|
|
112
159
|
nextPollAt: new Date(completedAt.getTime() + pollIntervalMs).toISOString(),
|
|
113
160
|
pollLeaseVersion: claimed.pollLeaseVersion,
|
|
114
161
|
providerRequestIds: claimed.providerRequestIds,
|
|
115
|
-
providerState: reconciliationState(claimed.providerState,
|
|
116
|
-
...pendingRequestIds,
|
|
117
|
-
|
|
162
|
+
providerState: reconciliationState(claimed.providerState, {
|
|
163
|
+
pendingRequestIds: [...pendingRequestIds],
|
|
164
|
+
requests,
|
|
165
|
+
...(message.webhookTiming && message.requestId && webhookUpdateObserved
|
|
166
|
+
? {
|
|
167
|
+
webhookTiming: {
|
|
168
|
+
...message.webhookTiming,
|
|
169
|
+
reconciliationCompletedAt: completedAt.toISOString(),
|
|
170
|
+
requestId: message.requestId,
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
: {}),
|
|
174
|
+
}),
|
|
118
175
|
})
|
|
119
176
|
if (!stored) return { type: 'stale' }
|
|
120
|
-
|
|
177
|
+
if (pendingRequestIds.size > 0 || awaitsProvider) {
|
|
178
|
+
const followUp = nextFollowUp(message, followUpKind ?? 'provider')
|
|
179
|
+
if (followUp)
|
|
180
|
+
await options
|
|
181
|
+
.dispatch(
|
|
182
|
+
{
|
|
183
|
+
addressId: claimed.id,
|
|
184
|
+
followUp: followUp.state,
|
|
185
|
+
...(message.requestId ? { requestId: message.requestId } : {}),
|
|
186
|
+
trigger,
|
|
187
|
+
type: 'funding:deposit-address:reconcile',
|
|
188
|
+
...(message.webhookTiming && !webhookUpdateObserved
|
|
189
|
+
? { webhookTiming: message.webhookTiming }
|
|
190
|
+
: {}),
|
|
191
|
+
},
|
|
192
|
+
{ delaySeconds: followUp.delaySeconds },
|
|
193
|
+
)
|
|
194
|
+
.catch((cause) => {
|
|
195
|
+
record('follow_up_enqueue_failed', claimed.providerId, trigger)
|
|
196
|
+
throw cause
|
|
197
|
+
})
|
|
198
|
+
record(
|
|
199
|
+
followUp ? 'follow_up_enqueued' : 'follow_up_exhausted',
|
|
200
|
+
claimed.providerId,
|
|
201
|
+
trigger,
|
|
202
|
+
)
|
|
203
|
+
}
|
|
204
|
+
if (message.webhookTiming && webhookUpdateObserved)
|
|
205
|
+
recordWebhookLatency(options.metrics, {
|
|
206
|
+
completedAt,
|
|
207
|
+
providerId: claimed.providerId,
|
|
208
|
+
timing: message.webhookTiming,
|
|
209
|
+
})
|
|
210
|
+
record('completed', claimed.providerId, trigger)
|
|
121
211
|
options.metrics?.histogram(
|
|
122
212
|
'funding_deposit_reconciliation_duration_ms',
|
|
123
213
|
completedAt.getTime() - startedAt.getTime(),
|
|
124
|
-
{ provider: claimed.providerId },
|
|
214
|
+
{ provider: claimed.providerId, trigger },
|
|
125
215
|
)
|
|
126
216
|
return { created, type: 'completed', updated }
|
|
127
217
|
} catch (cause) {
|
|
128
218
|
const leaseFailure = await heartbeat.stop()
|
|
129
219
|
await fail(database, claimed, now(), pollIntervalMs)
|
|
130
|
-
record('failed', claimed.providerId)
|
|
220
|
+
record('failed', claimed.providerId, trigger)
|
|
131
221
|
throw leaseFailure ?? cause
|
|
132
222
|
} finally {
|
|
133
223
|
options.metrics?.flush()
|
|
@@ -159,13 +249,14 @@ export function createTracker(options: createTracker.Options): createTracker.Tra
|
|
|
159
249
|
await options.dispatch({
|
|
160
250
|
addressId: address.id,
|
|
161
251
|
pollLeaseVersion: address.pollLeaseVersion,
|
|
252
|
+
trigger: 'poll',
|
|
162
253
|
type: 'funding:deposit-address:reconcile',
|
|
163
254
|
})
|
|
164
|
-
record('enqueued', provider.id)
|
|
255
|
+
record('enqueued', provider.id, 'poll')
|
|
165
256
|
} catch {
|
|
166
257
|
failed += 1
|
|
167
258
|
await fail(database, address, startedAt, pollIntervalMs)
|
|
168
|
-
record('enqueue_failed', provider.id)
|
|
259
|
+
record('enqueue_failed', provider.id, 'poll')
|
|
169
260
|
}
|
|
170
261
|
}
|
|
171
262
|
if (claimed >= batchSize) break
|
|
@@ -184,7 +275,10 @@ export declare namespace createTracker {
|
|
|
184
275
|
/** Authoritative database or per-invocation database factory. */
|
|
185
276
|
db: Db.Source
|
|
186
277
|
/** Durable reconciliation queue transport. */
|
|
187
|
-
dispatch: (
|
|
278
|
+
dispatch: (
|
|
279
|
+
message: Provider.Webhook.receive.Dispatchable,
|
|
280
|
+
options?: createTracker.DispatchOptions,
|
|
281
|
+
) => Promise<void>
|
|
188
282
|
/** Duration of one reconciliation lease. */
|
|
189
283
|
leaseMs?: number | undefined
|
|
190
284
|
/** Optional bounded operational metrics sink. */
|
|
@@ -203,6 +297,12 @@ export declare namespace createTracker {
|
|
|
203
297
|
verifyTransfers: VerifyTransfers
|
|
204
298
|
}
|
|
205
299
|
|
|
300
|
+
/** Delivery options for one reconciliation queue message. */
|
|
301
|
+
type DispatchOptions = {
|
|
302
|
+
/** Delay before the queue makes the message available. */
|
|
303
|
+
delaySeconds: number
|
|
304
|
+
}
|
|
305
|
+
|
|
206
306
|
/** Result of reconciling one queue message. */
|
|
207
307
|
type ReconcileResult =
|
|
208
308
|
| { type: 'busy' | 'provider_unavailable' | 'stale' }
|
|
@@ -211,7 +311,7 @@ export declare namespace createTracker {
|
|
|
211
311
|
/** Scheduled and queue-driven reusable-address tracker. */
|
|
212
312
|
type Tracker = {
|
|
213
313
|
/** Reconciles one address from provider discovery and verified chain evidence. */
|
|
214
|
-
reconcile(message: Provider.Webhook.receive.
|
|
314
|
+
reconcile(message: Provider.Webhook.receive.Queued): Promise<ReconcileResult>
|
|
215
315
|
/** Claims and enqueues one bounded batch of due reusable addresses. */
|
|
216
316
|
tick(): Promise<{ claimed: number; failed: number }>
|
|
217
317
|
}
|
|
@@ -222,11 +322,7 @@ type ClaimOptions = {
|
|
|
222
322
|
now: string
|
|
223
323
|
}
|
|
224
324
|
|
|
225
|
-
async function claim(
|
|
226
|
-
db: Db.Db,
|
|
227
|
-
message: Provider.Webhook.receive.Dispatchable,
|
|
228
|
-
options: ClaimOptions,
|
|
229
|
-
) {
|
|
325
|
+
async function claim(db: Db.Db, message: Provider.Webhook.receive.Queued, options: ClaimOptions) {
|
|
230
326
|
if (message.pollLeaseVersion === undefined)
|
|
231
327
|
return FundingDepositAddresses.claim(db, {
|
|
232
328
|
id: message.addressId,
|
|
@@ -421,18 +517,42 @@ function readHighWater(state: FundingDepositAddresses.Record['providerState']):
|
|
|
421
517
|
}
|
|
422
518
|
}
|
|
423
519
|
|
|
520
|
+
type ReconciledWebhookTiming = Provider.Webhook.receive.WebhookTiming & {
|
|
521
|
+
/** When Tempo completed the webhook-triggered reconciliation. */
|
|
522
|
+
reconciliationCompletedAt: string
|
|
523
|
+
/** Provider request matched to the observed update. */
|
|
524
|
+
requestId: string
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
type ReconciliationStateOptions = {
|
|
528
|
+
/** Provider requests that still require reconciliation. */
|
|
529
|
+
pendingRequestIds: readonly string[]
|
|
530
|
+
/** Provider requests and high-water state observed in this pass. */
|
|
531
|
+
requests: Requests
|
|
532
|
+
/** Completed webhook timing retained for diagnostics. */
|
|
533
|
+
webhookTiming?: ReconciledWebhookTiming | undefined
|
|
534
|
+
}
|
|
535
|
+
|
|
424
536
|
function reconciliationState(
|
|
425
537
|
state: FundingDepositAddresses.Record['providerState'],
|
|
426
|
-
|
|
427
|
-
pendingRequestIds: readonly string[],
|
|
538
|
+
options: ReconciliationStateOptions,
|
|
428
539
|
) {
|
|
429
540
|
const current = state && typeof state === 'object' ? state : {}
|
|
541
|
+
const reconciliation = (() => {
|
|
542
|
+
const value = current['reconciliation']
|
|
543
|
+
if (value && typeof value === 'object') return value
|
|
544
|
+
return {}
|
|
545
|
+
})()
|
|
430
546
|
return {
|
|
431
547
|
...current,
|
|
432
548
|
reconciliation: {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
549
|
+
...reconciliation,
|
|
550
|
+
pendingRequestIds: options.pendingRequestIds,
|
|
551
|
+
requestIds: options.requests.highWaterRequestIds,
|
|
552
|
+
...(options.requests.highWaterUpdatedAt
|
|
553
|
+
? { updatedAt: options.requests.highWaterUpdatedAt }
|
|
554
|
+
: {}),
|
|
555
|
+
...(options.webhookTiming ? { webhookTiming: options.webhookTiming } : {}),
|
|
436
556
|
},
|
|
437
557
|
}
|
|
438
558
|
}
|
|
@@ -446,49 +566,120 @@ type ObservedSourceTransaction = {
|
|
|
446
566
|
type VerifiedDeposit = {
|
|
447
567
|
deposit: FundingDeposits.Record
|
|
448
568
|
required: bigint
|
|
569
|
+
source: TransferEvidence
|
|
449
570
|
sourceAmount: bigint
|
|
450
571
|
}
|
|
451
572
|
|
|
573
|
+
async function observeProviderDelivery(
|
|
574
|
+
options: reconcileRequest.Options,
|
|
575
|
+
verified: readonly VerifiedDeposit[],
|
|
576
|
+
) {
|
|
577
|
+
const [entry] = verified
|
|
578
|
+
if (
|
|
579
|
+
!entry ||
|
|
580
|
+
verified.length !== 1 ||
|
|
581
|
+
options.address.deliveryStrategy !== 'provider' ||
|
|
582
|
+
options.address.providerOutputToken.tokenKey !==
|
|
583
|
+
options.address.snapshot.destinationToken.tokenKey ||
|
|
584
|
+
(options.request.status === 'success' &&
|
|
585
|
+
options.request.destinationTransactionHashes.length > 0) ||
|
|
586
|
+
!options.provider ||
|
|
587
|
+
!Provider.canObserveDepositAddressRequest(options.provider)
|
|
588
|
+
)
|
|
589
|
+
return undefined
|
|
590
|
+
|
|
591
|
+
const result = await options.provider
|
|
592
|
+
.observeDepositAddressRequest(
|
|
593
|
+
{
|
|
594
|
+
address: options.address.address,
|
|
595
|
+
destinationChain: options.address.snapshot.destinationChain,
|
|
596
|
+
destinationToken: options.address.providerOutputToken,
|
|
597
|
+
providerState: entry.deposit.providerState ?? undefined,
|
|
598
|
+
recipient: options.address.recipient,
|
|
599
|
+
refundAddress: options.address.refundAddress,
|
|
600
|
+
request: options.request,
|
|
601
|
+
source: entry.source,
|
|
602
|
+
sourceChain: options.address.snapshot.sourceChain,
|
|
603
|
+
sourceToken: options.address.snapshot.sourceToken,
|
|
604
|
+
},
|
|
605
|
+
options.signal ?? new AbortController().signal,
|
|
606
|
+
)
|
|
607
|
+
.catch((cause) => {
|
|
608
|
+
if (options.signal?.aborted) throw cause
|
|
609
|
+
return undefined
|
|
610
|
+
})
|
|
611
|
+
options.metrics?.count('funding_deposit_delivery_observation_count', 1, {
|
|
612
|
+
outcome: result?.type ?? 'failed',
|
|
613
|
+
provider: options.address.providerId,
|
|
614
|
+
})
|
|
615
|
+
return result
|
|
616
|
+
}
|
|
617
|
+
|
|
452
618
|
/** Reconciles one provider request against verified chain evidence. */
|
|
453
619
|
export async function reconcileRequest(
|
|
454
620
|
db: Db.Db,
|
|
455
621
|
options: reconcileRequest.Options,
|
|
456
622
|
): Promise<reconcileRequest.ReturnType> {
|
|
623
|
+
const trigger = options.trigger ?? 'poll'
|
|
624
|
+
const observation = await FundingDepositRequestObservations.get(db, {
|
|
625
|
+
depositAddressId: options.address.id,
|
|
626
|
+
providerRequestId: options.request.id,
|
|
627
|
+
})
|
|
628
|
+
if (observation)
|
|
629
|
+
await FundingDeposits.recordRequestObservation(db, {
|
|
630
|
+
depositAddressId: observation.depositAddressId,
|
|
631
|
+
...(observation.pollObservedAt ? { pollObservedAt: observation.pollObservedAt } : {}),
|
|
632
|
+
providerRequestId: observation.providerRequestId,
|
|
633
|
+
...(observation.webhookReceivedAt
|
|
634
|
+
? { webhookReceivedAt: observation.webhookReceivedAt }
|
|
635
|
+
: {}),
|
|
636
|
+
})
|
|
457
637
|
let created = 0
|
|
638
|
+
let followUp: reconcileRequest.ReturnType['followUp']
|
|
458
639
|
const observed: ObservedSourceTransaction[] = []
|
|
459
640
|
// Persist provider observations first so transient chain failures do not hide acknowledged deposits.
|
|
460
641
|
for (const transactionHash of unique(options.request.sourceTransactionHashes)) {
|
|
461
|
-
|
|
642
|
+
const deposits = await FundingDeposits.withSourceTransaction(db, {
|
|
462
643
|
depositAddressId: options.address.id,
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
if (deposits.length === 0)
|
|
467
|
-
deposits = await FundingDeposits.listBySourceTransaction(db, {
|
|
468
|
-
depositAddressId: options.address.id,
|
|
469
|
-
sourceTransactionHash: transactionHash,
|
|
470
|
-
})
|
|
471
|
-
if (deposits.length === 0) {
|
|
472
|
-
deposits = [
|
|
473
|
-
await Deposit.create(db, {
|
|
474
|
-
now: new Date(options.request.createdAt),
|
|
644
|
+
fn: async (tx): Promise<FundingDeposits.Record[]> => {
|
|
645
|
+
let records = await FundingDeposits.listByProviderObservation(tx, {
|
|
646
|
+
depositAddressId: options.address.id,
|
|
475
647
|
providerRequestId: options.request.id,
|
|
476
|
-
snapshot: {
|
|
477
|
-
depositAddressId: options.address.id,
|
|
478
|
-
destinationChain: options.address.snapshot.destinationChain,
|
|
479
|
-
destinationToken: options.address.snapshot.destinationToken,
|
|
480
|
-
provider: options.address.snapshot.provider,
|
|
481
|
-
recipient: options.address.recipient,
|
|
482
|
-
refundAddress: options.address.refundAddress,
|
|
483
|
-
sourceChain: options.address.snapshot.sourceChain,
|
|
484
|
-
sourceToken: options.address.snapshot.sourceToken,
|
|
485
|
-
sourceTransactionHashes: [transactionHash],
|
|
486
|
-
},
|
|
487
648
|
sourceTransactionHash: transactionHash,
|
|
488
|
-
})
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
649
|
+
})
|
|
650
|
+
if (records.length === 0)
|
|
651
|
+
records = await FundingDeposits.listBySourceTransaction(tx, {
|
|
652
|
+
depositAddressId: options.address.id,
|
|
653
|
+
sourceTransactionHash: transactionHash,
|
|
654
|
+
})
|
|
655
|
+
if (records.length > 0) return records
|
|
656
|
+
created += 1
|
|
657
|
+
return [
|
|
658
|
+
await Deposit.create(tx, {
|
|
659
|
+
detectionTrigger: trigger,
|
|
660
|
+
now: new Date(options.request.createdAt),
|
|
661
|
+
...(observation?.pollObservedAt ? { pollObservedAt: observation.pollObservedAt } : {}),
|
|
662
|
+
providerRequestId: options.request.id,
|
|
663
|
+
snapshot: {
|
|
664
|
+
depositAddressId: options.address.id,
|
|
665
|
+
destinationChain: options.address.snapshot.destinationChain,
|
|
666
|
+
destinationToken: options.address.snapshot.destinationToken,
|
|
667
|
+
provider: options.address.snapshot.provider,
|
|
668
|
+
recipient: options.address.recipient,
|
|
669
|
+
refundAddress: options.address.refundAddress,
|
|
670
|
+
sourceChain: options.address.snapshot.sourceChain,
|
|
671
|
+
sourceToken: options.address.snapshot.sourceToken,
|
|
672
|
+
sourceTransactionHashes: [transactionHash],
|
|
673
|
+
},
|
|
674
|
+
sourceTransactionHash: transactionHash,
|
|
675
|
+
...(observation?.webhookReceivedAt
|
|
676
|
+
? { webhookReceivedAt: observation.webhookReceivedAt }
|
|
677
|
+
: {}),
|
|
678
|
+
}),
|
|
679
|
+
]
|
|
680
|
+
},
|
|
681
|
+
sourceTransactionHash: transactionHash,
|
|
682
|
+
})
|
|
492
683
|
observed.push({ deposits, transactionHash, transfers: [] })
|
|
493
684
|
}
|
|
494
685
|
|
|
@@ -539,7 +730,9 @@ export async function reconcileRequest(
|
|
|
539
730
|
)
|
|
540
731
|
if (!deposit) {
|
|
541
732
|
deposit = await Deposit.create(db, {
|
|
733
|
+
detectionTrigger: trigger,
|
|
542
734
|
now: new Date(options.request.createdAt),
|
|
735
|
+
...(observation?.pollObservedAt ? { pollObservedAt: observation.pollObservedAt } : {}),
|
|
543
736
|
providerRequestId: options.request.id,
|
|
544
737
|
providerTransferIndex,
|
|
545
738
|
snapshot: {
|
|
@@ -558,6 +751,9 @@ export async function reconcileRequest(
|
|
|
558
751
|
},
|
|
559
752
|
sourceTransactionHash: transfer.transactionHash,
|
|
560
753
|
sourceTransferIndex: transfer.transferIndex,
|
|
754
|
+
...(observation?.webhookReceivedAt
|
|
755
|
+
? { webhookReceivedAt: observation.webhookReceivedAt }
|
|
756
|
+
: {}),
|
|
561
757
|
})
|
|
562
758
|
source.deposits.push(deposit)
|
|
563
759
|
created += 1
|
|
@@ -566,7 +762,9 @@ export async function reconcileRequest(
|
|
|
566
762
|
const result = await Deposit.transition(db, {
|
|
567
763
|
expectedVersion: deposit.version,
|
|
568
764
|
id: deposit.id,
|
|
765
|
+
providerRequestId: options.request.id,
|
|
569
766
|
providerRequestIds: unique([...deposit.providerRequestIds, options.request.id]),
|
|
767
|
+
providerTransferIndex,
|
|
570
768
|
snapshot: {
|
|
571
769
|
...deposit.snapshot,
|
|
572
770
|
...(options.address.snapshot.subsidize ? { destinationAmountRequired } : {}),
|
|
@@ -581,14 +779,22 @@ export async function reconcileRequest(
|
|
|
581
779
|
updatedIds.add(deposit.id)
|
|
582
780
|
}
|
|
583
781
|
}
|
|
584
|
-
verified.push({ deposit, required, sourceAmount: BigInt(transfer.amount) })
|
|
782
|
+
verified.push({ deposit, required, source: transfer, sourceAmount: BigInt(transfer.amount) })
|
|
585
783
|
}
|
|
586
784
|
}
|
|
587
785
|
if (verified.length === 0) return { created, terminal: false, updated: updatedIds.size }
|
|
588
786
|
if (!sourceEvidenceComplete) return { created, terminal: false, updated: updatedIds.size }
|
|
589
787
|
|
|
788
|
+
const deliveryObservation = await observeProviderDelivery(options, verified)
|
|
789
|
+
if (deliveryObservation?.type === 'pending') followUp = 'delivery'
|
|
790
|
+
const destinationTransactionHashes = unique([
|
|
791
|
+
...options.request.destinationTransactionHashes,
|
|
792
|
+
...(deliveryObservation?.type === 'verified'
|
|
793
|
+
? deliveryObservation.destinationTransactionHashes
|
|
794
|
+
: []),
|
|
795
|
+
])
|
|
590
796
|
const destinationEvidence = await Promise.all(
|
|
591
|
-
|
|
797
|
+
destinationTransactionHashes.map((transactionHash) =>
|
|
592
798
|
options.verifyTransfers({
|
|
593
799
|
chain: options.address.snapshot.destinationChain,
|
|
594
800
|
recipient: options.address.recipient,
|
|
@@ -599,7 +805,8 @@ export async function reconcileRequest(
|
|
|
599
805
|
)
|
|
600
806
|
const destinationTransfers = destinationEvidence.flat()
|
|
601
807
|
const providerEvidenceComplete =
|
|
602
|
-
options.request.status === 'success' &&
|
|
808
|
+
(options.request.status === 'success' || deliveryObservation?.type === 'verified') &&
|
|
809
|
+
destinationEvidence.length > 0 &&
|
|
603
810
|
destinationEvidence.every((transfers) => transfers.length > 0)
|
|
604
811
|
const providerOutput = destinationTransfers.reduce(
|
|
605
812
|
(total, transfer) => total + BigInt(transfer.amount),
|
|
@@ -635,6 +842,7 @@ export async function reconcileRequest(
|
|
|
635
842
|
provider: options.address.providerId,
|
|
636
843
|
})
|
|
637
844
|
if (settlement.status === 'bridging' || settlement.status === 'settling') terminal = false
|
|
845
|
+
if (settlement.status === 'settling') followUp = 'settlement'
|
|
638
846
|
const destinationAmount = Value.tokenAmount({
|
|
639
847
|
baseUnits: delivered + settlement.delivered,
|
|
640
848
|
currency: options.address.snapshot.destinationToken.currency,
|
|
@@ -653,9 +861,18 @@ export async function reconcileRequest(
|
|
|
653
861
|
}
|
|
654
862
|
: {}),
|
|
655
863
|
providerRequestIds: unique([...deposit.providerRequestIds, options.request.id]),
|
|
864
|
+
...(deliveryObservation?.providerState
|
|
865
|
+
? {
|
|
866
|
+
providerState: {
|
|
867
|
+
...deposit.providerState,
|
|
868
|
+
...deliveryObservation.providerState,
|
|
869
|
+
},
|
|
870
|
+
}
|
|
871
|
+
: {}),
|
|
656
872
|
providerTransactionHashes: unique([
|
|
657
873
|
...deposit.providerTransactionHashes,
|
|
658
874
|
...options.request.destinationTransactionHashes,
|
|
875
|
+
...(deliveryObservation?.providerTransactionHashes ?? []),
|
|
659
876
|
]),
|
|
660
877
|
...(settlement.settlementTransaction
|
|
661
878
|
? { settlementTransaction: settlement.settlementTransaction }
|
|
@@ -697,7 +914,7 @@ export async function reconcileRequest(
|
|
|
697
914
|
}
|
|
698
915
|
}
|
|
699
916
|
}
|
|
700
|
-
return { created, terminal, updated: updatedIds.size }
|
|
917
|
+
return { created, ...(followUp ? { followUp } : {}), terminal, updated: updatedIds.size }
|
|
701
918
|
}
|
|
702
919
|
|
|
703
920
|
export declare namespace reconcileRequest {
|
|
@@ -707,10 +924,16 @@ export declare namespace reconcileRequest {
|
|
|
707
924
|
address: FundingDepositAddresses.Record
|
|
708
925
|
/** Optional bounded operational metrics sink. */
|
|
709
926
|
metrics?: Metrics.Metrics | undefined
|
|
927
|
+
/** Provider integration that discovered the request. */
|
|
928
|
+
provider?: Provider.Provider | undefined
|
|
710
929
|
/** Provider request containing discovered chain transactions. */
|
|
711
930
|
request: Provider.listDepositAddressRequests.ReturnType['requests'][number]
|
|
931
|
+
/** Abort signal forwarded to provider delivery observation. */
|
|
932
|
+
signal?: AbortSignal | undefined
|
|
712
933
|
/** Optional pathUSD-funded subsidy settler. */
|
|
713
934
|
subsidies?: Subsidy.Settler | undefined
|
|
935
|
+
/** Mechanism that requested reconciliation. */
|
|
936
|
+
trigger?: Deposit.DetectionTrigger | undefined
|
|
714
937
|
/** Authoritative source and destination transfer verifier. */
|
|
715
938
|
verifyTransfers: VerifyTransfers
|
|
716
939
|
}
|
|
@@ -719,6 +942,8 @@ export declare namespace reconcileRequest {
|
|
|
719
942
|
type ReturnType = {
|
|
720
943
|
/** Number of newly detected source transfers. */
|
|
721
944
|
created: number
|
|
945
|
+
/** Work that needs a bounded delayed follow-up. */
|
|
946
|
+
followUp?: 'delivery' | 'settlement' | undefined
|
|
722
947
|
/** Whether every deposit from this request reached a terminal state. */
|
|
723
948
|
terminal: boolean
|
|
724
949
|
/** Number of existing or new deposits materially changed. */
|
|
@@ -919,6 +1144,53 @@ function unique(values: readonly string[]) {
|
|
|
919
1144
|
return [...new Set(values)]
|
|
920
1145
|
}
|
|
921
1146
|
|
|
1147
|
+
function nextFollowUp(
|
|
1148
|
+
message: Provider.Webhook.receive.Queued,
|
|
1149
|
+
kind: Provider.Webhook.receive.FollowUp['kind'],
|
|
1150
|
+
) {
|
|
1151
|
+
const attempt = message.followUp?.kind === kind ? message.followUp.attempt + 1 : 0
|
|
1152
|
+
if (attempt >= maximumFollowUpAttempts) return undefined
|
|
1153
|
+
return { delaySeconds: followUpDelaySeconds[kind], state: { attempt, kind } }
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
type RecordWebhookLatencyOptions = {
|
|
1157
|
+
/** When Tempo completed the webhook-triggered reconciliation. */
|
|
1158
|
+
completedAt: Date
|
|
1159
|
+
/** Funding provider that sent the webhook. */
|
|
1160
|
+
providerId: string
|
|
1161
|
+
/** Authenticated timestamps carried from webhook receipt. */
|
|
1162
|
+
timing: Provider.Webhook.receive.WebhookTiming
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
function recordWebhookLatency(
|
|
1166
|
+
metrics: Metrics.Metrics | undefined,
|
|
1167
|
+
options: RecordWebhookLatencyOptions,
|
|
1168
|
+
) {
|
|
1169
|
+
if (!metrics) return
|
|
1170
|
+
const completedAt = options.completedAt.getTime()
|
|
1171
|
+
const enqueuedAt = Date.parse(options.timing.enqueuedAt)
|
|
1172
|
+
const providerUpdatedAt = Date.parse(options.timing.providerUpdatedAt)
|
|
1173
|
+
const receivedAt = Date.parse(options.timing.receivedAt)
|
|
1174
|
+
const sentAt = Date.parse(options.timing.sentAt)
|
|
1175
|
+
const segments = [
|
|
1176
|
+
{ from: providerUpdatedAt, segment: 'provider_to_sent', to: sentAt },
|
|
1177
|
+
{ from: sentAt, segment: 'sent_to_received', to: receivedAt },
|
|
1178
|
+
{ from: receivedAt, segment: 'received_to_enqueued', to: enqueuedAt },
|
|
1179
|
+
{ from: enqueuedAt, segment: 'enqueued_to_reconciled', to: completedAt },
|
|
1180
|
+
{ from: providerUpdatedAt, segment: 'provider_to_reconciled', to: completedAt },
|
|
1181
|
+
] as const
|
|
1182
|
+
for (const segment of segments)
|
|
1183
|
+
metrics.histogram(
|
|
1184
|
+
'funding_deposit_webhook_latency_ms',
|
|
1185
|
+
Math.max(0, segment.to - segment.from),
|
|
1186
|
+
{
|
|
1187
|
+
provider: options.providerId,
|
|
1188
|
+
segment: segment.segment,
|
|
1189
|
+
status: options.timing.providerStatus,
|
|
1190
|
+
},
|
|
1191
|
+
)
|
|
1192
|
+
}
|
|
1193
|
+
|
|
922
1194
|
/** Provider pagination exceeded the bounded reconciliation limit. */
|
|
923
1195
|
export class PaginationLimitError extends Error {
|
|
924
1196
|
override name = 'Funding.Reconciliation.PaginationLimitError'
|