tapimo 0.10.2 → 0.11.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 +3412 -1968
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js.map +1 -1
- package/dist/Webhooks.d.ts +0 -75
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +1 -143
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/App.d.ts +74 -74
- package/dist/admin/apps/api-keys.d.ts +4 -4
- package/dist/admin/apps/earn-vaults.d.ts +1 -1
- package/dist/admin/apps/organizations.d.ts +3 -3
- package/dist/admin/apps/verified-tokens.d.ts +74 -74
- package/dist/apps/data/App.d.ts +3618 -2954
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +5 -2
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +1 -1
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +55 -27
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +343 -343
- package/dist/apps/data/routes/balances.d.ts +11 -11
- package/dist/apps/data/routes/blocks.d.ts +5 -5
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/earn.d.ts +4 -4
- package/dist/apps/data/routes/exchanges.d.ts +4 -4
- package/dist/apps/data/routes/fee-amm.d.ts +26 -26
- package/dist/apps/data/routes/indexer.d.ts +34 -34
- package/dist/apps/data/routes/receipts.d.ts +236 -236
- package/dist/apps/data/routes/tokenlist.d.ts +12 -12
- package/dist/apps/data/routes/tokens.d.ts +3 -3
- package/dist/apps/data/routes/transactions.d.ts +1 -1
- package/dist/apps/data/routes/transfers.d.ts +13 -13
- package/dist/apps/data/routes/valuation.d.ts +21 -21
- package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
- package/dist/apps/data/routes/webhooks.d.ts +1011 -151
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +124 -737
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +31 -31
- package/dist/apps/funding/routes/deposit-addresses.d.ts +2 -2
- package/dist/apps/funding/routes/providers.d.ts +13 -13
- package/dist/apps/funding/routes/transfers.d.ts +33 -33
- package/dist/apps/management/App.d.ts +793 -16
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +50 -50
- package/dist/apps/management/routes/faucet.d.ts +31 -31
- package/dist/apps/management/routes/invite-links.d.ts +42 -42
- package/dist/apps/management/routes/members.d.ts +79 -79
- package/dist/apps/management/routes/projects.d.ts +40 -40
- package/dist/apps/management/routes/usage.d.ts +80 -80
- package/dist/apps/management/routes/webhooks.d.ts +1031 -59
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/management/routes/webhooks.js +27 -4
- package/dist/apps/management/routes/webhooks.js.map +1 -1
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +21 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +7 -1
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +9 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/webhookQueueEvents.d.ts +3 -1
- package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -1
- package/dist/db/tables/webhookQueueEvents.js +4 -0
- package/dist/db/tables/webhookQueueEvents.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +33 -69
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +63 -100
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +35 -125
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +50 -121
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Schema.d.ts +3 -1
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +4 -2
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/WebhookTransfer.d.ts +1 -55
- package/dist/internal/WebhookTransfer.d.ts.map +1 -1
- package/dist/internal/WebhookTransfer.js +1 -239
- package/dist/internal/WebhookTransfer.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +83 -44
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +87 -73
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/DepositWebhook.d.ts +25 -0
- package/dist/internal/funding/DepositWebhook.d.ts.map +1 -0
- package/dist/internal/funding/DepositWebhook.js +74 -0
- package/dist/internal/funding/DepositWebhook.js.map +1 -0
- package/dist/internal/funding/Reconciliation.d.ts +6 -0
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +42 -10
- package/dist/internal/funding/Reconciliation.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/webhookDestinations/slack.d.ts.map +1 -1
- package/dist/internal/webhookDestinations/slack.js +16 -0
- package/dist/internal/webhookDestinations/slack.js.map +1 -1
- package/package.json +1 -1
- package/src/App.ts +3 -0
- package/src/Client.test-d.ts +22 -1
- package/src/Webhooks.ts +1 -220
- package/src/apps/data/App.ts +8 -2
- package/src/apps/data/routes/activities.test.ts +107 -11
- package/src/apps/data/routes/activities.ts +64 -32
- package/src/apps/data/routes/webhooks.test.ts +179 -1497
- package/src/apps/data/routes/webhooks.ts +150 -974
- package/src/apps/management/routes/webhooks.test.ts +79 -1
- package/src/apps/management/routes/webhooks.ts +40 -4
- package/src/db/Db.test.ts +1 -5
- package/src/db/Db.ts +23 -0
- package/src/db/Schema.ts +13 -0
- package/src/db/tables/organizations.test.ts +5 -2
- package/src/db/tables/webhookQueueEvents.ts +7 -1
- package/src/db/tables/webhookSubscriptions.ts +89 -138
- package/src/internal/MetricSink.test.ts +41 -373
- package/src/internal/MetricSink.ts +92 -273
- package/src/internal/Schema.test.ts +18 -0
- package/src/internal/Schema.ts +13 -2
- package/src/internal/WebhookTransfer.ts +1 -351
- package/src/internal/Webhooks.test.ts +102 -139
- package/src/internal/Webhooks.ts +175 -106
- package/src/internal/funding/DepositWebhook.test.ts +131 -0
- package/src/internal/funding/DepositWebhook.ts +101 -0
- package/src/internal/funding/Reconciliation.test.ts +14 -0
- package/src/internal/funding/Reconciliation.ts +161 -86
- package/src/internal/funding/index.test.ts +1 -0
- package/src/internal/funding/index.ts +1 -0
- package/src/internal/webhookDestinations/slack.ts +15 -0
- package/src/Webhooks.bench.ts +0 -714
- package/src/Webhooks.memory.bench.ts +0 -250
- package/src/Webhooks.test.ts +0 -146
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type * as Db from '../../db/Db.js'
|
|
2
|
+
import * as FundingDeposits from '../../db/tables/fundingDeposits.js'
|
|
3
|
+
import * as WebhookSubscriptions from '../../db/tables/webhookSubscriptions.js'
|
|
4
|
+
import * as Webhooks from '../Webhooks.js'
|
|
5
|
+
import * as Deposit from './Deposit.js'
|
|
6
|
+
|
|
7
|
+
/** Applies a deposit transition and stages every matching webhook obligation atomically. */
|
|
8
|
+
export async function transition(
|
|
9
|
+
db: Db.Db,
|
|
10
|
+
options: Deposit.transition.Options,
|
|
11
|
+
): Promise<transition.Result> {
|
|
12
|
+
return db.transaction(async (tx) => {
|
|
13
|
+
const result = await Deposit.transition(tx, options)
|
|
14
|
+
if (result.type !== 'applied') return { references: [], result }
|
|
15
|
+
const staged = await stage(tx, { records: [result.record] })
|
|
16
|
+
return { references: staged.references, result }
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export declare namespace transition {
|
|
21
|
+
/** Transition result plus compact references for atomically staged obligations. */
|
|
22
|
+
type Result = {
|
|
23
|
+
/** Queue references staged with the applied transition. */
|
|
24
|
+
references: Webhooks.QueueReference[]
|
|
25
|
+
/** Version-guarded deposit transition outcome. */
|
|
26
|
+
result: Deposit.transition.Result
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Stages durable webhook obligations for committed funding deposit versions. */
|
|
31
|
+
export async function stage(
|
|
32
|
+
db: Db.Db,
|
|
33
|
+
options: stage.Options,
|
|
34
|
+
): Promise<Webhooks.ensureQueueEvents.Result> {
|
|
35
|
+
const dispatchables: Webhooks.ensureQueueEvents.Dispatchable[] = []
|
|
36
|
+
const now = new Date().toISOString()
|
|
37
|
+
for (const record of unique(options.records)) {
|
|
38
|
+
const chainId = destinationChainId(record)
|
|
39
|
+
if (chainId === undefined) continue
|
|
40
|
+
const subscriptions = await WebhookSubscriptions.listActiveForFundingDeposit(db, {
|
|
41
|
+
chainId,
|
|
42
|
+
environment: record.environment,
|
|
43
|
+
now,
|
|
44
|
+
orgId: record.orgId,
|
|
45
|
+
...(record.projectId === null ? {} : { projectId: record.projectId }),
|
|
46
|
+
})
|
|
47
|
+
const data = Deposit.toPublic(record)
|
|
48
|
+
for (const subscription of subscriptions) {
|
|
49
|
+
if (!matches(subscription.filters, data)) continue
|
|
50
|
+
dispatchables.push({
|
|
51
|
+
envelope: Webhooks.buildKeyedEnvelope({
|
|
52
|
+
createdAt: new Date(record.updatedAt),
|
|
53
|
+
data,
|
|
54
|
+
key: `${record.id}:${record.version}`,
|
|
55
|
+
subscription,
|
|
56
|
+
}),
|
|
57
|
+
subscription,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return Webhooks.ensureQueueEvents(db, dispatchables)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export declare namespace stage {
|
|
65
|
+
/** Committed deposit changes eligible for customer webhook delivery. */
|
|
66
|
+
type Options = {
|
|
67
|
+
/** Latest committed funding deposit records. */
|
|
68
|
+
records: readonly FundingDeposits.Record[]
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function destinationChainId(record: FundingDeposits.Record): number | undefined {
|
|
73
|
+
const match = /^eip155:(\d+)$/.exec(record.snapshot.destinationChain.id)
|
|
74
|
+
if (!match) return undefined
|
|
75
|
+
const chainId = Number(match[1])
|
|
76
|
+
return Number.isSafeInteger(chainId) && chainId > 0 ? chainId : undefined
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function matches(filters: Record<string, unknown>, deposit: Deposit.Public): boolean {
|
|
80
|
+
if (
|
|
81
|
+
typeof filters['depositAddressId'] === 'string' &&
|
|
82
|
+
filters['depositAddressId'] !== deposit.depositAddressId
|
|
83
|
+
)
|
|
84
|
+
return false
|
|
85
|
+
if (
|
|
86
|
+
typeof filters['recipient'] === 'string' &&
|
|
87
|
+
filters['recipient'].toLowerCase() !== deposit.recipient.toLowerCase()
|
|
88
|
+
)
|
|
89
|
+
return false
|
|
90
|
+
if (typeof filters['status'] === 'string' && filters['status'] !== deposit.status) return false
|
|
91
|
+
return true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function unique(records: readonly FundingDeposits.Record[]): FundingDeposits.Record[] {
|
|
95
|
+
const byId = new Map<string, FundingDeposits.Record>()
|
|
96
|
+
for (const record of records) {
|
|
97
|
+
const current = byId.get(record.id)
|
|
98
|
+
if (!current || current.version < record.version) byId.set(record.id, record)
|
|
99
|
+
}
|
|
100
|
+
return [...byId.values()]
|
|
101
|
+
}
|
|
@@ -770,9 +770,11 @@ describe('createTracker', () => {
|
|
|
770
770
|
).requests[0]
|
|
771
771
|
if (!request) throw new Error('Expected a Relay refund request.')
|
|
772
772
|
const verifyTransfers = Evidence.createVerifier({ db })
|
|
773
|
+
const depositUpdateVersions: number[] = []
|
|
773
774
|
const first = await Reconciliation.reconcileRequest(db, {
|
|
774
775
|
address,
|
|
775
776
|
metrics,
|
|
777
|
+
onDeposit: (deposit) => depositUpdateVersions.push(deposit.version),
|
|
776
778
|
request,
|
|
777
779
|
verifyTransfers,
|
|
778
780
|
})
|
|
@@ -784,12 +786,14 @@ describe('createTracker', () => {
|
|
|
784
786
|
const second = await Reconciliation.reconcileRequest(db, {
|
|
785
787
|
address,
|
|
786
788
|
metrics,
|
|
789
|
+
onDeposit: (deposit) => depositUpdateVersions.push(deposit.version),
|
|
787
790
|
request,
|
|
788
791
|
verifyTransfers,
|
|
789
792
|
})
|
|
790
793
|
const duplicate = await Reconciliation.reconcileRequest(db, {
|
|
791
794
|
address,
|
|
792
795
|
metrics,
|
|
796
|
+
onDeposit: (deposit) => depositUpdateVersions.push(deposit.version),
|
|
793
797
|
request,
|
|
794
798
|
verifyTransfers,
|
|
795
799
|
})
|
|
@@ -797,6 +801,9 @@ describe('createTracker', () => {
|
|
|
797
801
|
depositAddressId: address.id,
|
|
798
802
|
sourceTransactionHash,
|
|
799
803
|
})
|
|
804
|
+
if (!firstRecord || !record) throw new Error('Expected reconciled funding deposits.')
|
|
805
|
+
|
|
806
|
+
expect(depositUpdateVersions).toEqual([2, firstRecord.version, 4, record.version])
|
|
800
807
|
|
|
801
808
|
expect({
|
|
802
809
|
counts,
|
|
@@ -1944,9 +1951,14 @@ describe('createTracker', () => {
|
|
|
1944
1951
|
routes: [],
|
|
1945
1952
|
tokens: [],
|
|
1946
1953
|
})
|
|
1954
|
+
const dispatchedEventIds: string[][] = []
|
|
1947
1955
|
const tracker = Reconciliation.createTracker({
|
|
1948
1956
|
db,
|
|
1949
1957
|
dispatch: async () => undefined,
|
|
1958
|
+
dispatchDepositUpdates: async (references) => {
|
|
1959
|
+
dispatchedEventIds.push(references.map((reference) => reference.eventId))
|
|
1960
|
+
return references.length
|
|
1961
|
+
},
|
|
1950
1962
|
providers: [Relay.relay({ apiKey: 'relay-api-key', baseUrl: server.url })],
|
|
1951
1963
|
verifyTransfers: Evidence.createVerifier({ db }),
|
|
1952
1964
|
})
|
|
@@ -1970,6 +1982,8 @@ describe('createTracker', () => {
|
|
|
1970
1982
|
{ limit: 10 },
|
|
1971
1983
|
)
|
|
1972
1984
|
|
|
1985
|
+
expect(dispatchedEventIds).toEqual([])
|
|
1986
|
+
|
|
1973
1987
|
expect({ failures, records: records.map(Deposit.toPublic) }).toMatchInlineSnapshot(
|
|
1974
1988
|
{
|
|
1975
1989
|
records: [
|
|
@@ -6,7 +6,9 @@ import * as FundingDepositRequestObservations from '../../db/tables/fundingDepos
|
|
|
6
6
|
import * as FundingDeposits from '../../db/tables/fundingDeposits.js'
|
|
7
7
|
import type * as Metrics from '../../Metrics.js'
|
|
8
8
|
import * as Value from '../Value.js'
|
|
9
|
+
import type * as Webhooks from '../Webhooks.js'
|
|
9
10
|
import * as Deposit from './Deposit.js'
|
|
11
|
+
import * as DepositWebhook from './DepositWebhook.js'
|
|
10
12
|
import * as Provider from './Provider.js'
|
|
11
13
|
import * as Subsidy from './Subsidy.js'
|
|
12
14
|
|
|
@@ -119,6 +121,17 @@ export function createTracker(options: createTracker.Options): createTracker.Tra
|
|
|
119
121
|
})()
|
|
120
122
|
heartbeat.assert()
|
|
121
123
|
let created = 0
|
|
124
|
+
const depositWebhookReferences = new Map<string, Webhooks.QueueReference>()
|
|
125
|
+
const onDeposit: NonNullable<reconcileRequest.Options['onDeposit']> = (
|
|
126
|
+
_deposit,
|
|
127
|
+
references,
|
|
128
|
+
) => {
|
|
129
|
+
for (const reference of references)
|
|
130
|
+
depositWebhookReferences.set(
|
|
131
|
+
`${reference.subscriptionId}:${reference.eventId}`,
|
|
132
|
+
reference,
|
|
133
|
+
)
|
|
134
|
+
}
|
|
122
135
|
let updated = 0
|
|
123
136
|
const pendingRequestIds = new Set([
|
|
124
137
|
...requests.pendingRequestIds,
|
|
@@ -129,6 +142,7 @@ export function createTracker(options: createTracker.Options): createTracker.Tra
|
|
|
129
142
|
const result = await reconcileRequest(database, {
|
|
130
143
|
address: claimed,
|
|
131
144
|
metrics: options.metrics,
|
|
145
|
+
onDeposit,
|
|
132
146
|
provider,
|
|
133
147
|
request,
|
|
134
148
|
signal: options.signal,
|
|
@@ -148,6 +162,7 @@ export function createTracker(options: createTracker.Options): createTracker.Tra
|
|
|
148
162
|
const unattributed = await reconcileUnattributed(database, {
|
|
149
163
|
address: claimed,
|
|
150
164
|
metrics: options.metrics,
|
|
165
|
+
onDeposit,
|
|
151
166
|
provider,
|
|
152
167
|
signal: options.signal,
|
|
153
168
|
subsidies: options.subsidies,
|
|
@@ -166,6 +181,24 @@ export function createTracker(options: createTracker.Options): createTracker.Tra
|
|
|
166
181
|
trigger,
|
|
167
182
|
})
|
|
168
183
|
|
|
184
|
+
if (options.dispatchDepositUpdates && depositWebhookReferences.size > 0) {
|
|
185
|
+
const references = [...depositWebhookReferences.values()]
|
|
186
|
+
await options.dispatchDepositUpdates(references).then(
|
|
187
|
+
(count) =>
|
|
188
|
+
options.metrics?.count('funding_deposit_webhook_enqueue_count', count, {
|
|
189
|
+
outcome: 'succeeded',
|
|
190
|
+
provider: claimed.providerId,
|
|
191
|
+
}),
|
|
192
|
+
(cause) => {
|
|
193
|
+
options.metrics?.count('funding_deposit_webhook_enqueue_count', references.length, {
|
|
194
|
+
outcome: 'failed',
|
|
195
|
+
provider: claimed.providerId,
|
|
196
|
+
})
|
|
197
|
+
throw cause
|
|
198
|
+
},
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
|
|
169
202
|
const leaseFailure = await heartbeat.stop()
|
|
170
203
|
if (leaseFailure !== undefined) throw leaseFailure
|
|
171
204
|
const completedAt = now()
|
|
@@ -295,6 +328,10 @@ export declare namespace createTracker {
|
|
|
295
328
|
message: Provider.Webhook.receive.Dispatchable,
|
|
296
329
|
options?: createTracker.DispatchOptions,
|
|
297
330
|
) => Promise<void>
|
|
331
|
+
/** Stages and dispatches committed funding deposit webhook updates. */
|
|
332
|
+
dispatchDepositUpdates?:
|
|
333
|
+
| ((references: readonly Webhooks.QueueReference[]) => Promise<number>)
|
|
334
|
+
| undefined
|
|
298
335
|
/** Duration of one reconciliation lease. */
|
|
299
336
|
leaseMs?: number | undefined
|
|
300
337
|
/** Optional bounded operational metrics sink. */
|
|
@@ -589,6 +626,7 @@ type VerifiedDeposit = {
|
|
|
589
626
|
type ReconcileDeliveryOptions = {
|
|
590
627
|
address: FundingDepositAddresses.Record
|
|
591
628
|
metrics?: Metrics.Metrics | undefined
|
|
629
|
+
onDeposit?: reconcileRequest.Options['onDeposit'] | undefined
|
|
592
630
|
provider?: Provider.Provider | undefined
|
|
593
631
|
request?: Provider.listDepositAddressRequests.ReturnType['requests'][number] | undefined
|
|
594
632
|
signal?: AbortSignal | undefined
|
|
@@ -690,7 +728,11 @@ async function reconcileUnattributed(db: Db.Db, options: reconcileUnattributed.O
|
|
|
690
728
|
if (!delivery.terminal) terminal = false
|
|
691
729
|
for (const id of delivery.updatedIds) updatedIds.add(id)
|
|
692
730
|
}
|
|
693
|
-
return {
|
|
731
|
+
return {
|
|
732
|
+
...(followUp ? { followUp } : {}),
|
|
733
|
+
terminal,
|
|
734
|
+
updatedIds,
|
|
735
|
+
}
|
|
694
736
|
}
|
|
695
737
|
|
|
696
738
|
declare namespace reconcileUnattributed {
|
|
@@ -843,21 +885,25 @@ export async function reconcileRequest(
|
|
|
843
885
|
deposit.providerRequestId === null ||
|
|
844
886
|
!deposit.providerRequestIds.includes(options.request.id)
|
|
845
887
|
) {
|
|
846
|
-
const result = await
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
888
|
+
const result = await transitionDeposit(
|
|
889
|
+
db,
|
|
890
|
+
{
|
|
891
|
+
expectedVersion: deposit.version,
|
|
892
|
+
id: deposit.id,
|
|
893
|
+
providerRequestId: options.request.id,
|
|
894
|
+
providerRequestIds: unique([...deposit.providerRequestIds, options.request.id]),
|
|
895
|
+
providerTransferIndex,
|
|
896
|
+
snapshot: {
|
|
897
|
+
...deposit.snapshot,
|
|
898
|
+
...(options.address.snapshot.subsidize ? { destinationAmountRequired } : {}),
|
|
899
|
+
...(transfer.sender ? { sender: transfer.sender } : {}),
|
|
900
|
+
sourceAmount,
|
|
901
|
+
},
|
|
902
|
+
sourceTransferIndex: transfer.transferIndex,
|
|
903
|
+
status: deposit.status === 'detected' ? 'bridging' : deposit.status,
|
|
857
904
|
},
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
})
|
|
905
|
+
options.onDeposit,
|
|
906
|
+
)
|
|
861
907
|
if (result.type === 'applied') {
|
|
862
908
|
deposit = result.record
|
|
863
909
|
updatedIds.add(deposit.id)
|
|
@@ -867,7 +913,9 @@ export async function reconcileRequest(
|
|
|
867
913
|
}
|
|
868
914
|
}
|
|
869
915
|
if (verified.length === 0) return { created, terminal: false, updated: updatedIds.size }
|
|
870
|
-
if (!sourceEvidenceComplete)
|
|
916
|
+
if (!sourceEvidenceComplete) {
|
|
917
|
+
return { created, terminal: false, updated: updatedIds.size }
|
|
918
|
+
}
|
|
871
919
|
const recovery = await reconcileProviderRecovery(db, { ...options, verified })
|
|
872
920
|
if (recovery) {
|
|
873
921
|
for (const id of recovery.updatedIds) updatedIds.add(id)
|
|
@@ -895,6 +943,10 @@ export declare namespace reconcileRequest {
|
|
|
895
943
|
address: FundingDepositAddresses.Record
|
|
896
944
|
/** Optional bounded operational metrics sink. */
|
|
897
945
|
metrics?: Metrics.Metrics | undefined
|
|
946
|
+
/** Observes the latest committed form of each verified deposit. */
|
|
947
|
+
onDeposit?:
|
|
948
|
+
| ((deposit: FundingDeposits.Record, references: readonly Webhooks.QueueReference[]) => void)
|
|
949
|
+
| undefined
|
|
898
950
|
/** Provider integration that discovered the request. */
|
|
899
951
|
provider?: Provider.Provider | undefined
|
|
900
952
|
/** Provider request containing discovered chain transactions. */
|
|
@@ -922,6 +974,17 @@ export declare namespace reconcileRequest {
|
|
|
922
974
|
}
|
|
923
975
|
}
|
|
924
976
|
|
|
977
|
+
async function transitionDeposit(
|
|
978
|
+
db: Db.Db,
|
|
979
|
+
options: Deposit.transition.Options,
|
|
980
|
+
onDeposit: reconcileRequest.Options['onDeposit'],
|
|
981
|
+
): Promise<Deposit.transition.Result> {
|
|
982
|
+
const committed = await DepositWebhook.transition(db, options)
|
|
983
|
+
if (committed.result.type === 'applied')
|
|
984
|
+
onDeposit?.(committed.result.record, committed.references)
|
|
985
|
+
return committed.result
|
|
986
|
+
}
|
|
987
|
+
|
|
925
988
|
async function reconcileProviderRecovery(db: Db.Db, options: ReconcileDeliveryOptions) {
|
|
926
989
|
const failed = (providerFailureStatuses as readonly string[]).includes(
|
|
927
990
|
options.request?.status ?? '',
|
|
@@ -991,15 +1054,19 @@ async function reconcileProviderRecovery(db: Db.Db, options: ReconcileDeliveryOp
|
|
|
991
1054
|
if (Deposit.isTerminal(entry.deposit.status)) continue
|
|
992
1055
|
let deposit = entry.deposit
|
|
993
1056
|
if (deposit.status !== 'refunding') {
|
|
994
|
-
const result = await
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1057
|
+
const result = await transitionDeposit(
|
|
1058
|
+
db,
|
|
1059
|
+
{
|
|
1060
|
+
expectedVersion: deposit.version,
|
|
1061
|
+
id: deposit.id,
|
|
1062
|
+
providerTransactionHashes: unique([
|
|
1063
|
+
...deposit.providerTransactionHashes,
|
|
1064
|
+
...providerRefundTransactionHashes,
|
|
1065
|
+
]),
|
|
1066
|
+
status: 'refunding',
|
|
1067
|
+
},
|
|
1068
|
+
options.onDeposit,
|
|
1069
|
+
)
|
|
1003
1070
|
if (result.type === 'applied') {
|
|
1004
1071
|
deposit = result.record
|
|
1005
1072
|
updatedIds.add(deposit.id)
|
|
@@ -1011,20 +1078,24 @@ async function reconcileProviderRecovery(db: Db.Db, options: ReconcileDeliveryOp
|
|
|
1011
1078
|
terminal = false
|
|
1012
1079
|
continue
|
|
1013
1080
|
}
|
|
1014
|
-
const result = await
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1081
|
+
const result = await transitionDeposit(
|
|
1082
|
+
db,
|
|
1083
|
+
{
|
|
1084
|
+
expectedVersion: deposit.version,
|
|
1085
|
+
id: deposit.id,
|
|
1086
|
+
snapshot: {
|
|
1087
|
+
...deposit.snapshot,
|
|
1088
|
+
refundAmount: Value.tokenAmount({
|
|
1089
|
+
baseUnits: amount,
|
|
1090
|
+
currency: options.address.snapshot.sourceToken.currency,
|
|
1091
|
+
decimals: options.address.snapshot.sourceToken.decimals,
|
|
1092
|
+
}),
|
|
1093
|
+
refundTransactionHashes,
|
|
1094
|
+
},
|
|
1095
|
+
status: 'refunded',
|
|
1025
1096
|
},
|
|
1026
|
-
|
|
1027
|
-
|
|
1097
|
+
options.onDeposit,
|
|
1098
|
+
)
|
|
1028
1099
|
if (result.type === 'applied') updatedIds.add(deposit.id)
|
|
1029
1100
|
else if (result.type !== 'stale' || !Deposit.isTerminal(result.current.status)) terminal = false
|
|
1030
1101
|
}
|
|
@@ -1116,57 +1187,61 @@ async function reconcileDelivery(db: Db.Db, options: ReconcileDeliveryOptions) {
|
|
|
1116
1187
|
currency: options.address.snapshot.destinationToken.currency,
|
|
1117
1188
|
decimals: options.address.snapshot.destinationToken.decimals,
|
|
1118
1189
|
})
|
|
1119
|
-
const result = await
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
:
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
...deposit.providerTransactionHashes,
|
|
1144
|
-
...providerTransactionHashes,
|
|
1145
|
-
...(deliveryObservation?.providerTransactionHashes ?? []),
|
|
1146
|
-
]),
|
|
1147
|
-
...(settlement.settlementTransaction
|
|
1148
|
-
? { settlementTransaction: settlement.settlementTransaction }
|
|
1149
|
-
: {}),
|
|
1150
|
-
...(settlement.settlementTransactionHash
|
|
1151
|
-
? { settlementTransactionHash: settlement.settlementTransactionHash }
|
|
1152
|
-
: {}),
|
|
1153
|
-
snapshot: {
|
|
1154
|
-
...deposit.snapshot,
|
|
1155
|
-
...(delivered + settlement.delivered > 0n ? { destinationAmount } : {}),
|
|
1156
|
-
...(settlement.status === 'completed' && !options.address.snapshot.subsidize
|
|
1157
|
-
? { destinationAmountRequired: destinationAmount }
|
|
1190
|
+
const result = await transitionDeposit(
|
|
1191
|
+
db,
|
|
1192
|
+
{
|
|
1193
|
+
expectedVersion: deposit.version,
|
|
1194
|
+
id: deposit.id,
|
|
1195
|
+
...(delivered > 0n
|
|
1196
|
+
? {
|
|
1197
|
+
providerOutputAmount: Value.tokenAmount({
|
|
1198
|
+
baseUnits: delivered,
|
|
1199
|
+
currency: options.address.providerOutputToken.currency,
|
|
1200
|
+
decimals: options.address.providerOutputToken.decimals,
|
|
1201
|
+
}),
|
|
1202
|
+
}
|
|
1203
|
+
: {}),
|
|
1204
|
+
providerRequestIds: options.request
|
|
1205
|
+
? unique([...deposit.providerRequestIds, options.request.id])
|
|
1206
|
+
: deposit.providerRequestIds,
|
|
1207
|
+
...(deliveryObservation?.providerState
|
|
1208
|
+
? {
|
|
1209
|
+
providerState: {
|
|
1210
|
+
...deposit.providerState,
|
|
1211
|
+
...deliveryObservation.providerState,
|
|
1212
|
+
},
|
|
1213
|
+
}
|
|
1158
1214
|
: {}),
|
|
1159
|
-
|
|
1160
|
-
...
|
|
1161
|
-
...
|
|
1162
|
-
...
|
|
1215
|
+
providerTransactionHashes: unique([
|
|
1216
|
+
...deposit.providerTransactionHashes,
|
|
1217
|
+
...providerTransactionHashes,
|
|
1218
|
+
...(deliveryObservation?.providerTransactionHashes ?? []),
|
|
1163
1219
|
]),
|
|
1220
|
+
...(settlement.settlementTransaction
|
|
1221
|
+
? { settlementTransaction: settlement.settlementTransaction }
|
|
1222
|
+
: {}),
|
|
1223
|
+
...(settlement.settlementTransactionHash
|
|
1224
|
+
? { settlementTransactionHash: settlement.settlementTransactionHash }
|
|
1225
|
+
: {}),
|
|
1226
|
+
snapshot: {
|
|
1227
|
+
...deposit.snapshot,
|
|
1228
|
+
...(delivered + settlement.delivered > 0n ? { destinationAmount } : {}),
|
|
1229
|
+
...(settlement.status === 'completed' && !options.address.snapshot.subsidize
|
|
1230
|
+
? { destinationAmountRequired: destinationAmount }
|
|
1231
|
+
: {}),
|
|
1232
|
+
destinationTransactionHashes: unique([
|
|
1233
|
+
...(deposit.snapshot.destinationTransactionHashes ?? []),
|
|
1234
|
+
...destinationTransfers.map((evidence) => evidence.transactionHash),
|
|
1235
|
+
...settlement.destinationTransactionHashes,
|
|
1236
|
+
]),
|
|
1237
|
+
},
|
|
1238
|
+
status: settlement.status,
|
|
1239
|
+
...(settlement.statusReason ? { statusReason: settlement.statusReason } : {}),
|
|
1240
|
+
...(settlement.subsidyAmount ? { subsidyAmount: settlement.subsidyAmount } : {}),
|
|
1241
|
+
...(settlement.tempoGasPaid ? { tempoGasPaid: settlement.tempoGasPaid } : {}),
|
|
1164
1242
|
},
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
...(settlement.subsidyAmount ? { subsidyAmount: settlement.subsidyAmount } : {}),
|
|
1168
|
-
...(settlement.tempoGasPaid ? { tempoGasPaid: settlement.tempoGasPaid } : {}),
|
|
1169
|
-
})
|
|
1243
|
+
options.onDeposit,
|
|
1244
|
+
)
|
|
1170
1245
|
if (result.type === 'applied') {
|
|
1171
1246
|
updatedIds.add(deposit.id)
|
|
1172
1247
|
if (settlement.broadcast && options.subsidies) {
|
|
@@ -3,6 +3,7 @@ export * as Chain from './Chain.js'
|
|
|
3
3
|
export * as Catalog from './Catalog.js'
|
|
4
4
|
export * as Deposit from './Deposit.js'
|
|
5
5
|
export * as DepositAddress from './DepositAddress.js'
|
|
6
|
+
export * as DepositWebhook from './DepositWebhook.js'
|
|
6
7
|
export * as Evidence from './Evidence.js'
|
|
7
8
|
export * as Provider from './Provider.js'
|
|
8
9
|
export * as Reconciliation from './Reconciliation.js'
|
|
@@ -41,6 +41,7 @@ type Block =
|
|
|
41
41
|
/** Per-event emoji + friendly header name; also the notification fallback lead. */
|
|
42
42
|
const friendlyName: Record<Webhooks.Envelope['type'], string> = {
|
|
43
43
|
'block:created': ':link: Block created',
|
|
44
|
+
'funding:deposit.updated': ':inbox_tray: Funding deposit updated',
|
|
44
45
|
'log:emitted': ':label: Log emitted',
|
|
45
46
|
ping: ':bell: Webhook test ping',
|
|
46
47
|
'token:transfer': ':money_with_wings: Token transfer',
|
|
@@ -150,9 +151,23 @@ function renderEvent(
|
|
|
150
151
|
if (type === 'transaction:included') return transactionLayout(data, base)
|
|
151
152
|
if (type === 'log:emitted') return logLayout(data, base)
|
|
152
153
|
if (type === 'block:created') return blockLayout(data, base)
|
|
154
|
+
if (type === 'funding:deposit.updated') return fundingDepositLayout(data)
|
|
153
155
|
return pingLayout()
|
|
154
156
|
}
|
|
155
157
|
|
|
158
|
+
function fundingDepositLayout(data: Record<string, unknown>): Layout {
|
|
159
|
+
const fields: Field[] = []
|
|
160
|
+
field(fields, 'Deposit', escape(str(data['id']) ?? 'unknown'))
|
|
161
|
+
field(fields, 'Status', escape(str(data['status']) ?? 'unknown'))
|
|
162
|
+
const recipient = str(data['recipient'])
|
|
163
|
+
if (recipient !== undefined) field(fields, 'Recipient', escape(recipient))
|
|
164
|
+
return {
|
|
165
|
+
fields,
|
|
166
|
+
header: friendlyName['funding:deposit.updated'],
|
|
167
|
+
text: `Funding deposit updated · ${str(data['status']) ?? 'unknown'}`,
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
156
171
|
function transferLayout(data: Record<string, unknown>, base: string | undefined): Layout {
|
|
157
172
|
const amount = str(data['amount'])
|
|
158
173
|
const token =
|