tapimo 0.7.2 → 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/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 +20117 -19183
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +61 -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 +11 -11
- 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 +11696 -13937
- 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 +271 -271
- package/dist/apps/data/routes/addresses.d.ts +398 -167
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +278 -8
- 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/coingecko.d.ts +170 -170
- package/dist/apps/data/routes/earn.d.ts +498 -6
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +876 -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 +31 -31
- 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 +449 -449
- package/dist/apps/data/routes/transactions.d.ts +2 -2
- package/dist/apps/data/routes/transfers.d.ts +81 -81
- 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} +1640 -3217
- package/dist/apps/funding/App.d.ts.map +1 -0
- package/dist/apps/funding/App.js +61 -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 +1997 -0
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -0
- package/dist/apps/funding/routes/deposit-addresses.js +778 -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 +122 -122
- 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 +99 -99
- 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 +77 -77
- 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 +44 -44
- 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 +45 -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 +74 -1
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +126 -0
- package/dist/db/tables/fundingDeposits.js.map +1 -1
- package/dist/internal/Auth.d.ts +15 -11
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +52 -17
- 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 +348 -54
- 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 +438 -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 +55 -5
- package/src/App.ts +79 -5
- package/src/Client.test-d.ts +203 -28
- 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 +130 -3
- package/src/apps/data/App.test.ts +0 -12
- package/src/apps/data/App.ts +4 -8
- 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 +363 -0
- package/src/apps/data/routes/earn.ts +1141 -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 +78 -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 +1649 -0
- package/src/apps/funding/routes/deposit-addresses.ts +932 -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 +1236 -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 +71 -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 +239 -0
- package/src/db/tables/fundingDeposits.ts +214 -1
- package/src/internal/Auth.test.ts +205 -5
- package/src/internal/Auth.ts +63 -19
- 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 +1364 -109
- package/src/internal/funding/Reconciliation.ts +467 -88
- 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 +533 -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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Hex } from 'ox';
|
|
2
2
|
import * as Db from '../../db/Db.js';
|
|
3
3
|
import * as FundingDepositAddresses from '../../db/tables/fundingDepositAddresses.js';
|
|
4
|
+
import * as FundingDepositRequestObservations from '../../db/tables/fundingDepositRequestObservations.js';
|
|
4
5
|
import * as FundingDeposits from '../../db/tables/fundingDeposits.js';
|
|
5
6
|
import * as Value from '../Value.js';
|
|
6
7
|
import * as Deposit from './Deposit.js';
|
|
@@ -9,23 +10,36 @@ import * as Subsidy from './Subsidy.js';
|
|
|
9
10
|
const defaultBatchSize = 25;
|
|
10
11
|
const defaultLeaseMs = 2 * 60_000;
|
|
11
12
|
const defaultPollIntervalMs = 60_000;
|
|
13
|
+
const chainProviderFollowUpDelaySeconds = 1;
|
|
14
|
+
const chainProviderMaximumFollowUpAttempts = 30;
|
|
15
|
+
const followUpDelaySeconds = { delivery: 1, provider: 5, settlement: 2 };
|
|
12
16
|
const maximumPages = 100;
|
|
17
|
+
const maximumFollowUpAttempts = 6;
|
|
13
18
|
/** Creates a reusable-address reconciliation tracker. */
|
|
14
19
|
export function createTracker(options) {
|
|
15
20
|
const now = options.now ?? (() => new Date());
|
|
16
21
|
const batchSize = options.batchSize ?? defaultBatchSize;
|
|
17
22
|
const leaseMs = options.leaseMs ?? defaultLeaseMs;
|
|
18
23
|
const pollIntervalMs = options.pollIntervalMs ?? defaultPollIntervalMs;
|
|
19
|
-
const record = (outcome, providerId) => {
|
|
24
|
+
const record = (outcome, providerId, trigger) => {
|
|
20
25
|
options.metrics?.count('funding_deposit_reconciliation_count', 1, {
|
|
21
26
|
outcome,
|
|
22
27
|
provider: providerId,
|
|
28
|
+
trigger,
|
|
23
29
|
});
|
|
24
30
|
};
|
|
25
31
|
return {
|
|
26
32
|
async reconcile(message) {
|
|
27
33
|
const database = Db.get(options.db);
|
|
28
34
|
const startedAt = now();
|
|
35
|
+
// Legacy poll messages carry a lease version; legacy webhook messages do not.
|
|
36
|
+
const trigger = (() => {
|
|
37
|
+
if (message.trigger)
|
|
38
|
+
return message.trigger;
|
|
39
|
+
if (message.pollLeaseVersion !== undefined)
|
|
40
|
+
return 'poll';
|
|
41
|
+
return 'webhook';
|
|
42
|
+
})();
|
|
29
43
|
const claimed = await claim(database, message, {
|
|
30
44
|
leaseUntil: new Date(startedAt.getTime() + leaseMs).toISOString(),
|
|
31
45
|
now: startedAt.toISOString(),
|
|
@@ -35,7 +49,7 @@ export function createTracker(options) {
|
|
|
35
49
|
const provider = options.providers.find((candidate) => candidate.id === claimed.providerId);
|
|
36
50
|
if (!provider || !Provider.canListDepositAddressRequests(provider)) {
|
|
37
51
|
await fail(database, claimed, startedAt, pollIntervalMs);
|
|
38
|
-
record('provider_unavailable', claimed.providerId);
|
|
52
|
+
record('provider_unavailable', claimed.providerId, trigger);
|
|
39
53
|
return { type: 'provider_unavailable' };
|
|
40
54
|
}
|
|
41
55
|
const heartbeat = maintainLease(database, claimed, { leaseMs, now });
|
|
@@ -46,16 +60,38 @@ export function createTracker(options) {
|
|
|
46
60
|
requestId: message.requestId,
|
|
47
61
|
signal: options.signal,
|
|
48
62
|
});
|
|
63
|
+
if (trigger === 'poll')
|
|
64
|
+
await FundingDepositRequestObservations.observePoll(database, {
|
|
65
|
+
depositAddressId: claimed.id,
|
|
66
|
+
observedAt: now().toISOString(),
|
|
67
|
+
providerRequestIds: requests.requests.map((request) => request.id),
|
|
68
|
+
});
|
|
69
|
+
const webhookUpdateObserved = (() => {
|
|
70
|
+
const timing = message.webhookTiming;
|
|
71
|
+
if (!timing)
|
|
72
|
+
return true;
|
|
73
|
+
if (!message.requestId)
|
|
74
|
+
return false;
|
|
75
|
+
// Relay can send a webhook before its requests API indexes the same update.
|
|
76
|
+
return requests.requests.some((request) => request.id === message.requestId && request.updatedAt >= timing.providerUpdatedAt);
|
|
77
|
+
})();
|
|
49
78
|
heartbeat.assert();
|
|
50
79
|
let created = 0;
|
|
51
80
|
let updated = 0;
|
|
52
|
-
const pendingRequestIds = new Set(
|
|
81
|
+
const pendingRequestIds = new Set([
|
|
82
|
+
...requests.pendingRequestIds,
|
|
83
|
+
...(message.requestId ? [message.requestId] : []),
|
|
84
|
+
]);
|
|
85
|
+
let followUpKind;
|
|
53
86
|
for (const request of requests.requests) {
|
|
54
87
|
const result = await reconcileRequest(database, {
|
|
55
88
|
address: claimed,
|
|
56
89
|
metrics: options.metrics,
|
|
90
|
+
provider,
|
|
57
91
|
request,
|
|
92
|
+
signal: options.signal,
|
|
58
93
|
subsidies: options.subsidies,
|
|
94
|
+
trigger,
|
|
59
95
|
verifyTransfers: options.verifyTransfers,
|
|
60
96
|
});
|
|
61
97
|
heartbeat.assert();
|
|
@@ -63,9 +99,31 @@ export function createTracker(options) {
|
|
|
63
99
|
updated += result.updated;
|
|
64
100
|
if (result.terminal)
|
|
65
101
|
pendingRequestIds.delete(request.id);
|
|
66
|
-
else
|
|
102
|
+
else {
|
|
67
103
|
pendingRequestIds.add(request.id);
|
|
104
|
+
followUpKind = result.followUp ?? followUpKind ?? 'provider';
|
|
105
|
+
}
|
|
68
106
|
}
|
|
107
|
+
const unattributed = await reconcileUnattributed(database, {
|
|
108
|
+
address: claimed,
|
|
109
|
+
metrics: options.metrics,
|
|
110
|
+
provider,
|
|
111
|
+
signal: options.signal,
|
|
112
|
+
subsidies: options.subsidies,
|
|
113
|
+
verifyTransfers: options.verifyTransfers,
|
|
114
|
+
});
|
|
115
|
+
heartbeat.assert();
|
|
116
|
+
updated += unattributed.updatedIds.size;
|
|
117
|
+
if (!unattributed.terminal)
|
|
118
|
+
followUpKind = unattributed.followUp ?? followUpKind ?? 'provider';
|
|
119
|
+
const awaitsProvider = await FundingDeposits.hasUnattributed(database, claimed.id);
|
|
120
|
+
if (message.requestId && message.webhookTiming && !webhookUpdateObserved)
|
|
121
|
+
pendingRequestIds.add(message.requestId);
|
|
122
|
+
if (created > 0)
|
|
123
|
+
options.metrics?.count('funding_deposit_detection_count', created, {
|
|
124
|
+
provider: claimed.providerId,
|
|
125
|
+
trigger,
|
|
126
|
+
});
|
|
69
127
|
const leaseFailure = await heartbeat.stop();
|
|
70
128
|
if (leaseFailure !== undefined)
|
|
71
129
|
throw leaseFailure;
|
|
@@ -76,20 +134,56 @@ export function createTracker(options) {
|
|
|
76
134
|
nextPollAt: new Date(completedAt.getTime() + pollIntervalMs).toISOString(),
|
|
77
135
|
pollLeaseVersion: claimed.pollLeaseVersion,
|
|
78
136
|
providerRequestIds: claimed.providerRequestIds,
|
|
79
|
-
providerState: reconciliationState(claimed.providerState,
|
|
80
|
-
...pendingRequestIds,
|
|
81
|
-
|
|
137
|
+
providerState: reconciliationState(claimed.providerState, {
|
|
138
|
+
pendingRequestIds: [...pendingRequestIds],
|
|
139
|
+
requests,
|
|
140
|
+
...(message.webhookTiming && message.requestId && webhookUpdateObserved
|
|
141
|
+
? {
|
|
142
|
+
webhookTiming: {
|
|
143
|
+
...message.webhookTiming,
|
|
144
|
+
reconciliationCompletedAt: completedAt.toISOString(),
|
|
145
|
+
requestId: message.requestId,
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
: {}),
|
|
149
|
+
}),
|
|
82
150
|
});
|
|
83
151
|
if (!stored)
|
|
84
152
|
return { type: 'stale' };
|
|
85
|
-
|
|
86
|
-
|
|
153
|
+
if (pendingRequestIds.size > 0 || awaitsProvider) {
|
|
154
|
+
const followUp = nextFollowUp(message, followUpKind ?? 'provider');
|
|
155
|
+
if (followUp)
|
|
156
|
+
await options
|
|
157
|
+
.dispatch({
|
|
158
|
+
addressId: claimed.id,
|
|
159
|
+
followUp: followUp.state,
|
|
160
|
+
...(message.requestId ? { requestId: message.requestId } : {}),
|
|
161
|
+
trigger,
|
|
162
|
+
type: 'funding:deposit-address:reconcile',
|
|
163
|
+
...(message.webhookTiming && !webhookUpdateObserved
|
|
164
|
+
? { webhookTiming: message.webhookTiming }
|
|
165
|
+
: {}),
|
|
166
|
+
}, { delaySeconds: followUp.delaySeconds })
|
|
167
|
+
.catch((cause) => {
|
|
168
|
+
record('follow_up_enqueue_failed', claimed.providerId, trigger);
|
|
169
|
+
throw cause;
|
|
170
|
+
});
|
|
171
|
+
record(followUp ? 'follow_up_enqueued' : 'follow_up_exhausted', claimed.providerId, trigger);
|
|
172
|
+
}
|
|
173
|
+
if (message.webhookTiming && webhookUpdateObserved)
|
|
174
|
+
recordWebhookLatency(options.metrics, {
|
|
175
|
+
completedAt,
|
|
176
|
+
providerId: claimed.providerId,
|
|
177
|
+
timing: message.webhookTiming,
|
|
178
|
+
});
|
|
179
|
+
record('completed', claimed.providerId, trigger);
|
|
180
|
+
options.metrics?.histogram('funding_deposit_reconciliation_duration_ms', completedAt.getTime() - startedAt.getTime(), { provider: claimed.providerId, trigger });
|
|
87
181
|
return { created, type: 'completed', updated };
|
|
88
182
|
}
|
|
89
183
|
catch (cause) {
|
|
90
184
|
const leaseFailure = await heartbeat.stop();
|
|
91
185
|
await fail(database, claimed, now(), pollIntervalMs);
|
|
92
|
-
record('failed', claimed.providerId);
|
|
186
|
+
record('failed', claimed.providerId, trigger);
|
|
93
187
|
throw leaseFailure ?? cause;
|
|
94
188
|
}
|
|
95
189
|
finally {
|
|
@@ -118,14 +212,15 @@ export function createTracker(options) {
|
|
|
118
212
|
await options.dispatch({
|
|
119
213
|
addressId: address.id,
|
|
120
214
|
pollLeaseVersion: address.pollLeaseVersion,
|
|
215
|
+
trigger: 'poll',
|
|
121
216
|
type: 'funding:deposit-address:reconcile',
|
|
122
217
|
});
|
|
123
|
-
record('enqueued', provider.id);
|
|
218
|
+
record('enqueued', provider.id, 'poll');
|
|
124
219
|
}
|
|
125
220
|
catch {
|
|
126
221
|
failed += 1;
|
|
127
222
|
await fail(database, address, startedAt, pollIntervalMs);
|
|
128
|
-
record('enqueue_failed', provider.id);
|
|
223
|
+
record('enqueue_failed', provider.id, 'poll');
|
|
129
224
|
}
|
|
130
225
|
}
|
|
131
226
|
if (claimed >= batchSize)
|
|
@@ -264,54 +359,170 @@ function readHighWater(state) {
|
|
|
264
359
|
: {}),
|
|
265
360
|
};
|
|
266
361
|
}
|
|
267
|
-
function reconciliationState(state,
|
|
362
|
+
function reconciliationState(state, options) {
|
|
268
363
|
const current = state && typeof state === 'object' ? state : {};
|
|
364
|
+
const reconciliation = (() => {
|
|
365
|
+
const value = current['reconciliation'];
|
|
366
|
+
if (value && typeof value === 'object')
|
|
367
|
+
return value;
|
|
368
|
+
return {};
|
|
369
|
+
})();
|
|
269
370
|
return {
|
|
270
371
|
...current,
|
|
271
372
|
reconciliation: {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
373
|
+
...reconciliation,
|
|
374
|
+
pendingRequestIds: options.pendingRequestIds,
|
|
375
|
+
requestIds: options.requests.highWaterRequestIds,
|
|
376
|
+
...(options.requests.highWaterUpdatedAt
|
|
377
|
+
? { updatedAt: options.requests.highWaterUpdatedAt }
|
|
378
|
+
: {}),
|
|
379
|
+
...(options.webhookTiming ? { webhookTiming: options.webhookTiming } : {}),
|
|
275
380
|
},
|
|
276
381
|
};
|
|
277
382
|
}
|
|
383
|
+
async function observeProviderDelivery(options) {
|
|
384
|
+
const { verified } = options;
|
|
385
|
+
const [entry] = verified;
|
|
386
|
+
if (!entry ||
|
|
387
|
+
verified.length !== 1 ||
|
|
388
|
+
options.address.deliveryStrategy !== 'provider' ||
|
|
389
|
+
options.address.providerOutputToken.tokenKey !==
|
|
390
|
+
options.address.snapshot.destinationToken.tokenKey ||
|
|
391
|
+
(options.request?.status === 'success' &&
|
|
392
|
+
options.request.destinationTransactionHashes.length > 0) ||
|
|
393
|
+
!options.provider ||
|
|
394
|
+
!Provider.canObserveDepositAddressRequest(options.provider))
|
|
395
|
+
return undefined;
|
|
396
|
+
const result = await options.provider
|
|
397
|
+
.observeDepositAddressRequest({
|
|
398
|
+
address: options.address.address,
|
|
399
|
+
destinationChain: options.address.snapshot.destinationChain,
|
|
400
|
+
destinationToken: options.address.providerOutputToken,
|
|
401
|
+
providerState: entry.deposit.providerState ?? undefined,
|
|
402
|
+
recipient: options.address.recipient,
|
|
403
|
+
refundAddress: options.address.refundAddress,
|
|
404
|
+
...(options.request ? { request: options.request } : {}),
|
|
405
|
+
source: entry.source,
|
|
406
|
+
sourceChain: options.address.snapshot.sourceChain,
|
|
407
|
+
sourceToken: options.address.snapshot.sourceToken,
|
|
408
|
+
}, options.signal ?? new AbortController().signal)
|
|
409
|
+
.catch((cause) => {
|
|
410
|
+
if (options.signal?.aborted)
|
|
411
|
+
throw cause;
|
|
412
|
+
return undefined;
|
|
413
|
+
});
|
|
414
|
+
options.metrics?.count('funding_deposit_delivery_observation_count', 1, {
|
|
415
|
+
outcome: result?.type ?? 'failed',
|
|
416
|
+
provider: options.address.providerId,
|
|
417
|
+
});
|
|
418
|
+
return result;
|
|
419
|
+
}
|
|
420
|
+
async function reconcileUnattributed(db, options) {
|
|
421
|
+
const deposits = await FundingDeposits.listUnattributed(db, options.address.id);
|
|
422
|
+
if (deposits.length === 0)
|
|
423
|
+
return { terminal: true, updatedIds: new Set() };
|
|
424
|
+
const transfersByTransaction = new Map(await Promise.all(unique(deposits.map((deposit) => deposit.sourceTransactionHash)).map(async (transactionHash) => [
|
|
425
|
+
transactionHash,
|
|
426
|
+
await options.verifyTransfers({
|
|
427
|
+
chain: options.address.snapshot.sourceChain,
|
|
428
|
+
recipient: options.address.address,
|
|
429
|
+
token: options.address.snapshot.sourceToken,
|
|
430
|
+
transactionHash,
|
|
431
|
+
}),
|
|
432
|
+
])));
|
|
433
|
+
let followUp;
|
|
434
|
+
let terminal = true;
|
|
435
|
+
const updatedIds = new Set();
|
|
436
|
+
for (const deposit of deposits) {
|
|
437
|
+
const source = transfersByTransaction
|
|
438
|
+
.get(deposit.sourceTransactionHash)
|
|
439
|
+
?.find((transfer) => transfer.transferIndex === deposit.sourceTransferIndex);
|
|
440
|
+
if (!source) {
|
|
441
|
+
terminal = false;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
const delivery = await reconcileDelivery(db, {
|
|
445
|
+
...options,
|
|
446
|
+
verified: [
|
|
447
|
+
{
|
|
448
|
+
deposit,
|
|
449
|
+
required: destinationBaseUnits(BigInt(source.amount), options.address.snapshot.sourceToken.decimals, options.address.snapshot.destinationToken.decimals),
|
|
450
|
+
source,
|
|
451
|
+
sourceAmount: BigInt(source.amount),
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
});
|
|
455
|
+
if (delivery.followUp === 'delivery' || !followUp)
|
|
456
|
+
followUp = delivery.followUp;
|
|
457
|
+
if (!delivery.terminal)
|
|
458
|
+
terminal = false;
|
|
459
|
+
for (const id of delivery.updatedIds)
|
|
460
|
+
updatedIds.add(id);
|
|
461
|
+
}
|
|
462
|
+
return { ...(followUp ? { followUp } : {}), terminal, updatedIds };
|
|
463
|
+
}
|
|
278
464
|
/** Reconciles one provider request against verified chain evidence. */
|
|
279
465
|
export async function reconcileRequest(db, options) {
|
|
466
|
+
const trigger = options.trigger ?? 'poll';
|
|
467
|
+
const observation = await FundingDepositRequestObservations.get(db, {
|
|
468
|
+
depositAddressId: options.address.id,
|
|
469
|
+
providerRequestId: options.request.id,
|
|
470
|
+
});
|
|
471
|
+
if (observation)
|
|
472
|
+
await FundingDeposits.recordRequestObservation(db, {
|
|
473
|
+
depositAddressId: observation.depositAddressId,
|
|
474
|
+
...(observation.pollObservedAt ? { pollObservedAt: observation.pollObservedAt } : {}),
|
|
475
|
+
providerRequestId: observation.providerRequestId,
|
|
476
|
+
...(observation.webhookReceivedAt
|
|
477
|
+
? { webhookReceivedAt: observation.webhookReceivedAt }
|
|
478
|
+
: {}),
|
|
479
|
+
});
|
|
280
480
|
let created = 0;
|
|
281
481
|
const observed = [];
|
|
282
482
|
// Persist provider observations first so transient chain failures do not hide acknowledged deposits.
|
|
283
483
|
for (const transactionHash of unique(options.request.sourceTransactionHashes)) {
|
|
284
|
-
|
|
484
|
+
const deposits = await FundingDeposits.withSourceTransaction(db, {
|
|
285
485
|
depositAddressId: options.address.id,
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (deposits.length === 0)
|
|
290
|
-
deposits = await FundingDeposits.listBySourceTransaction(db, {
|
|
291
|
-
depositAddressId: options.address.id,
|
|
292
|
-
sourceTransactionHash: transactionHash,
|
|
293
|
-
});
|
|
294
|
-
if (deposits.length === 0) {
|
|
295
|
-
deposits = [
|
|
296
|
-
await Deposit.create(db, {
|
|
297
|
-
now: new Date(options.request.createdAt),
|
|
486
|
+
fn: async (tx) => {
|
|
487
|
+
let records = await FundingDeposits.listByProviderObservation(tx, {
|
|
488
|
+
depositAddressId: options.address.id,
|
|
298
489
|
providerRequestId: options.request.id,
|
|
299
|
-
snapshot: {
|
|
300
|
-
depositAddressId: options.address.id,
|
|
301
|
-
destinationChain: options.address.snapshot.destinationChain,
|
|
302
|
-
destinationToken: options.address.snapshot.destinationToken,
|
|
303
|
-
provider: options.address.snapshot.provider,
|
|
304
|
-
recipient: options.address.recipient,
|
|
305
|
-
refundAddress: options.address.refundAddress,
|
|
306
|
-
sourceChain: options.address.snapshot.sourceChain,
|
|
307
|
-
sourceToken: options.address.snapshot.sourceToken,
|
|
308
|
-
sourceTransactionHashes: [transactionHash],
|
|
309
|
-
},
|
|
310
490
|
sourceTransactionHash: transactionHash,
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
491
|
+
});
|
|
492
|
+
if (records.length === 0)
|
|
493
|
+
records = await FundingDeposits.listBySourceTransaction(tx, {
|
|
494
|
+
depositAddressId: options.address.id,
|
|
495
|
+
sourceTransactionHash: transactionHash,
|
|
496
|
+
});
|
|
497
|
+
if (records.length > 0)
|
|
498
|
+
return records;
|
|
499
|
+
created += 1;
|
|
500
|
+
return [
|
|
501
|
+
await Deposit.create(tx, {
|
|
502
|
+
detectionTrigger: trigger,
|
|
503
|
+
now: new Date(options.request.createdAt),
|
|
504
|
+
...(observation?.pollObservedAt ? { pollObservedAt: observation.pollObservedAt } : {}),
|
|
505
|
+
providerRequestId: options.request.id,
|
|
506
|
+
snapshot: {
|
|
507
|
+
depositAddressId: options.address.id,
|
|
508
|
+
destinationChain: options.address.snapshot.destinationChain,
|
|
509
|
+
destinationToken: options.address.snapshot.destinationToken,
|
|
510
|
+
provider: options.address.snapshot.provider,
|
|
511
|
+
recipient: options.address.recipient,
|
|
512
|
+
refundAddress: options.address.refundAddress,
|
|
513
|
+
sourceChain: options.address.snapshot.sourceChain,
|
|
514
|
+
sourceToken: options.address.snapshot.sourceToken,
|
|
515
|
+
sourceTransactionHashes: [transactionHash],
|
|
516
|
+
},
|
|
517
|
+
sourceTransactionHash: transactionHash,
|
|
518
|
+
...(observation?.webhookReceivedAt
|
|
519
|
+
? { webhookReceivedAt: observation.webhookReceivedAt }
|
|
520
|
+
: {}),
|
|
521
|
+
}),
|
|
522
|
+
];
|
|
523
|
+
},
|
|
524
|
+
sourceTransactionHash: transactionHash,
|
|
525
|
+
});
|
|
315
526
|
observed.push({ deposits, transactionHash, transfers: [] });
|
|
316
527
|
}
|
|
317
528
|
const sources = await Promise.all(observed.map(async (source) => ({
|
|
@@ -324,7 +535,6 @@ export async function reconcileRequest(db, options) {
|
|
|
324
535
|
}),
|
|
325
536
|
})));
|
|
326
537
|
const sourceEvidenceComplete = sources.length > 0 && sources.every((source) => source.transfers.length > 0);
|
|
327
|
-
let terminal = sourceEvidenceComplete;
|
|
328
538
|
const updatedIds = new Set();
|
|
329
539
|
const verified = [];
|
|
330
540
|
for (const source of sources) {
|
|
@@ -347,7 +557,9 @@ export async function reconcileRequest(db, options) {
|
|
|
347
557
|
: record.providerTransferIndex === providerTransferIndex);
|
|
348
558
|
if (!deposit) {
|
|
349
559
|
deposit = await Deposit.create(db, {
|
|
560
|
+
detectionTrigger: trigger,
|
|
350
561
|
now: new Date(options.request.createdAt),
|
|
562
|
+
...(observation?.pollObservedAt ? { pollObservedAt: observation.pollObservedAt } : {}),
|
|
351
563
|
providerRequestId: options.request.id,
|
|
352
564
|
providerTransferIndex,
|
|
353
565
|
snapshot: {
|
|
@@ -366,15 +578,22 @@ export async function reconcileRequest(db, options) {
|
|
|
366
578
|
},
|
|
367
579
|
sourceTransactionHash: transfer.transactionHash,
|
|
368
580
|
sourceTransferIndex: transfer.transferIndex,
|
|
581
|
+
...(observation?.webhookReceivedAt
|
|
582
|
+
? { webhookReceivedAt: observation.webhookReceivedAt }
|
|
583
|
+
: {}),
|
|
369
584
|
});
|
|
370
585
|
source.deposits.push(deposit);
|
|
371
586
|
created += 1;
|
|
372
587
|
}
|
|
373
|
-
if (!Deposit.isTerminal(deposit.status)
|
|
588
|
+
if (!Deposit.isTerminal(deposit.status) ||
|
|
589
|
+
deposit.providerRequestId === null ||
|
|
590
|
+
!deposit.providerRequestIds.includes(options.request.id)) {
|
|
374
591
|
const result = await Deposit.transition(db, {
|
|
375
592
|
expectedVersion: deposit.version,
|
|
376
593
|
id: deposit.id,
|
|
594
|
+
providerRequestId: options.request.id,
|
|
377
595
|
providerRequestIds: unique([...deposit.providerRequestIds, options.request.id]),
|
|
596
|
+
providerTransferIndex,
|
|
378
597
|
snapshot: {
|
|
379
598
|
...deposit.snapshot,
|
|
380
599
|
...(options.address.snapshot.subsidize ? { destinationAmountRequired } : {}),
|
|
@@ -389,27 +608,54 @@ export async function reconcileRequest(db, options) {
|
|
|
389
608
|
updatedIds.add(deposit.id);
|
|
390
609
|
}
|
|
391
610
|
}
|
|
392
|
-
verified.push({ deposit, required, sourceAmount: BigInt(transfer.amount) });
|
|
611
|
+
verified.push({ deposit, required, source: transfer, sourceAmount: BigInt(transfer.amount) });
|
|
393
612
|
}
|
|
394
613
|
}
|
|
395
614
|
if (verified.length === 0)
|
|
396
615
|
return { created, terminal: false, updated: updatedIds.size };
|
|
397
616
|
if (!sourceEvidenceComplete)
|
|
398
617
|
return { created, terminal: false, updated: updatedIds.size };
|
|
399
|
-
const
|
|
618
|
+
const delivery = await reconcileDelivery(db, { ...options, verified });
|
|
619
|
+
for (const id of delivery.updatedIds)
|
|
620
|
+
updatedIds.add(id);
|
|
621
|
+
return {
|
|
622
|
+
created,
|
|
623
|
+
...(delivery.followUp ? { followUp: delivery.followUp } : {}),
|
|
624
|
+
terminal: delivery.terminal,
|
|
625
|
+
updated: updatedIds.size,
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
async function reconcileDelivery(db, options) {
|
|
629
|
+
let followUp;
|
|
630
|
+
let terminal = true;
|
|
631
|
+
const updatedIds = new Set();
|
|
632
|
+
if (options.verified.every((entry) => Deposit.isTerminal(entry.deposit.status)))
|
|
633
|
+
return { terminal, updatedIds };
|
|
634
|
+
const deliveryObservation = await observeProviderDelivery(options);
|
|
635
|
+
if (deliveryObservation?.type === 'pending')
|
|
636
|
+
followUp = 'delivery';
|
|
637
|
+
const providerTransactionHashes = options.request?.destinationTransactionHashes ?? [];
|
|
638
|
+
const destinationTransactionHashes = unique([
|
|
639
|
+
...providerTransactionHashes,
|
|
640
|
+
...(deliveryObservation?.type === 'verified'
|
|
641
|
+
? deliveryObservation.destinationTransactionHashes
|
|
642
|
+
: []),
|
|
643
|
+
]);
|
|
644
|
+
const destinationEvidence = await Promise.all(destinationTransactionHashes.map((transactionHash) => options.verifyTransfers({
|
|
400
645
|
chain: options.address.snapshot.destinationChain,
|
|
401
646
|
recipient: options.address.recipient,
|
|
402
647
|
token: options.address.providerOutputToken,
|
|
403
648
|
transactionHash,
|
|
404
649
|
})));
|
|
405
650
|
const destinationTransfers = destinationEvidence.flat();
|
|
406
|
-
const providerEvidenceComplete = options.request
|
|
651
|
+
const providerEvidenceComplete = (options.request?.status === 'success' || deliveryObservation?.type === 'verified') &&
|
|
652
|
+
destinationEvidence.length > 0 &&
|
|
407
653
|
destinationEvidence.every((transfers) => transfers.length > 0);
|
|
408
654
|
const providerOutput = destinationTransfers.reduce((total, transfer) => total + BigInt(transfer.amount), 0n);
|
|
409
|
-
const totalRequired = verified.reduce((total, entry) => total + entry.required, 0n);
|
|
655
|
+
const totalRequired = options.verified.reduce((total, entry) => total + entry.required, 0n);
|
|
410
656
|
let allocated = 0n;
|
|
411
657
|
let cumulativeRequired = 0n;
|
|
412
|
-
for (const entry of verified) {
|
|
658
|
+
for (const entry of options.verified) {
|
|
413
659
|
const { required, sourceAmount } = entry;
|
|
414
660
|
cumulativeRequired += required;
|
|
415
661
|
const nextAllocated = totalRequired === 0n ? 0n : (providerOutput * cumulativeRequired) / totalRequired;
|
|
@@ -435,6 +681,8 @@ export async function reconcileRequest(db, options) {
|
|
|
435
681
|
});
|
|
436
682
|
if (settlement.status === 'bridging' || settlement.status === 'settling')
|
|
437
683
|
terminal = false;
|
|
684
|
+
if (settlement.status === 'settling')
|
|
685
|
+
followUp = 'settlement';
|
|
438
686
|
const destinationAmount = Value.tokenAmount({
|
|
439
687
|
baseUnits: delivered + settlement.delivered,
|
|
440
688
|
currency: options.address.snapshot.destinationToken.currency,
|
|
@@ -452,10 +700,21 @@ export async function reconcileRequest(db, options) {
|
|
|
452
700
|
}),
|
|
453
701
|
}
|
|
454
702
|
: {}),
|
|
455
|
-
providerRequestIds:
|
|
703
|
+
providerRequestIds: options.request
|
|
704
|
+
? unique([...deposit.providerRequestIds, options.request.id])
|
|
705
|
+
: deposit.providerRequestIds,
|
|
706
|
+
...(deliveryObservation?.providerState
|
|
707
|
+
? {
|
|
708
|
+
providerState: {
|
|
709
|
+
...deposit.providerState,
|
|
710
|
+
...deliveryObservation.providerState,
|
|
711
|
+
},
|
|
712
|
+
}
|
|
713
|
+
: {}),
|
|
456
714
|
providerTransactionHashes: unique([
|
|
457
715
|
...deposit.providerTransactionHashes,
|
|
458
|
-
...
|
|
716
|
+
...providerTransactionHashes,
|
|
717
|
+
...(deliveryObservation?.providerTransactionHashes ?? []),
|
|
459
718
|
]),
|
|
460
719
|
...(settlement.settlementTransaction
|
|
461
720
|
? { settlementTransaction: settlement.settlementTransaction }
|
|
@@ -497,7 +756,7 @@ export async function reconcileRequest(db, options) {
|
|
|
497
756
|
}
|
|
498
757
|
}
|
|
499
758
|
}
|
|
500
|
-
return {
|
|
759
|
+
return { ...(followUp ? { followUp } : {}), terminal, updatedIds };
|
|
501
760
|
}
|
|
502
761
|
async function reconcileSubsidy(options) {
|
|
503
762
|
const empty = { delivered: 0n, destinationTransactionHashes: [] };
|
|
@@ -665,6 +924,41 @@ function destinationBaseUnits(amount, sourceDecimals, destinationDecimals) {
|
|
|
665
924
|
function unique(values) {
|
|
666
925
|
return [...new Set(values)];
|
|
667
926
|
}
|
|
927
|
+
function nextFollowUp(message, kind) {
|
|
928
|
+
const attempt = message.followUp?.kind === kind ? message.followUp.attempt + 1 : 0;
|
|
929
|
+
const chainProvider = kind === 'provider' && message.trigger === 'chain';
|
|
930
|
+
const maximumAttempts = chainProvider
|
|
931
|
+
? chainProviderMaximumFollowUpAttempts
|
|
932
|
+
: maximumFollowUpAttempts;
|
|
933
|
+
if (attempt >= maximumAttempts)
|
|
934
|
+
return undefined;
|
|
935
|
+
return {
|
|
936
|
+
delaySeconds: chainProvider ? chainProviderFollowUpDelaySeconds : followUpDelaySeconds[kind],
|
|
937
|
+
state: { attempt, kind },
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
function recordWebhookLatency(metrics, options) {
|
|
941
|
+
if (!metrics)
|
|
942
|
+
return;
|
|
943
|
+
const completedAt = options.completedAt.getTime();
|
|
944
|
+
const enqueuedAt = Date.parse(options.timing.enqueuedAt);
|
|
945
|
+
const providerUpdatedAt = Date.parse(options.timing.providerUpdatedAt);
|
|
946
|
+
const receivedAt = Date.parse(options.timing.receivedAt);
|
|
947
|
+
const sentAt = Date.parse(options.timing.sentAt);
|
|
948
|
+
const segments = [
|
|
949
|
+
{ from: providerUpdatedAt, segment: 'provider_to_sent', to: sentAt },
|
|
950
|
+
{ from: sentAt, segment: 'sent_to_received', to: receivedAt },
|
|
951
|
+
{ from: receivedAt, segment: 'received_to_enqueued', to: enqueuedAt },
|
|
952
|
+
{ from: enqueuedAt, segment: 'enqueued_to_reconciled', to: completedAt },
|
|
953
|
+
{ from: providerUpdatedAt, segment: 'provider_to_reconciled', to: completedAt },
|
|
954
|
+
];
|
|
955
|
+
for (const segment of segments)
|
|
956
|
+
metrics.histogram('funding_deposit_webhook_latency_ms', Math.max(0, segment.to - segment.from), {
|
|
957
|
+
provider: options.providerId,
|
|
958
|
+
segment: segment.segment,
|
|
959
|
+
status: options.timing.providerStatus,
|
|
960
|
+
});
|
|
961
|
+
}
|
|
668
962
|
/** Provider pagination exceeded the bounded reconciliation limit. */
|
|
669
963
|
export class PaginationLimitError extends Error {
|
|
670
964
|
name = 'Funding.Reconciliation.PaginationLimitError';
|