tapimo 0.8.0 → 0.10.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 +7410 -7613
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +0 -1
- package/dist/App.js.map +1 -1
- package/dist/Webhooks.d.ts +21 -20
- package/dist/Webhooks.d.ts.map +1 -1
- package/dist/Webhooks.js +66 -23
- package/dist/Webhooks.js.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +74 -74
- package/dist/apps/Zones.d.ts +34 -22
- package/dist/apps/Zones.d.ts.map +1 -1
- package/dist/apps/Zones.js +9 -8
- package/dist/apps/Zones.js.map +1 -1
- package/dist/apps/data/App.d.ts +3120 -2782
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +4 -5
- package/dist/apps/data/FxOracle.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.js +19 -4
- package/dist/apps/data/FxOracle.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +32 -27
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +283 -52
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +291 -21
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +232 -66
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +4 -4
- package/dist/apps/data/routes/coingecko.d.ts +54 -54
- package/dist/apps/data/routes/earn.d.ts +642 -126
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +627 -58
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +120 -120
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +114 -118
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +68 -47
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +2 -0
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +59 -51
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +25 -23
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +40 -18
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +618 -105
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +21 -21
- package/dist/apps/data/routes/valuation.d.ts.map +1 -1
- package/dist/apps/data/routes/valuation.js +2 -2
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +37 -37
- package/dist/apps/data/routes/webhooks.d.ts +102 -505
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +484 -342
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/data/routes/zones.d.ts +12 -12
- package/dist/apps/data/routes/zones.d.ts.map +1 -1
- package/dist/apps/data/routes/zones.js +3 -1
- package/dist/apps/data/routes/zones.js.map +1 -1
- package/dist/apps/funding/App.d.ts +127 -386
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/App.js +20 -5
- package/dist/apps/funding/App.js.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts +141 -532
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +21 -103
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +4 -4
- package/dist/apps/funding/routes/transfers.js +5 -5
- package/dist/apps/funding/routes/transfers.js.map +1 -1
- package/dist/apps/management/App.d.ts +429 -727
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +24 -24
- package/dist/apps/management/routes/billing.d.ts +72 -72
- package/dist/apps/management/routes/faucet.d.ts +6 -6
- package/dist/apps/management/routes/invite-links.d.ts +38 -38
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/members.d.ts +30 -30
- package/dist/apps/management/routes/projects.d.ts +20 -20
- package/dist/apps/management/routes/usage.d.ts +24 -24
- package/dist/apps/management/routes/webhooks.d.ts +158 -573
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/mpp/App.d.ts +12 -0
- package/dist/apps/mpp/App.d.ts.map +1 -1
- package/dist/apps/mpp/Mpp.d.ts +3 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -1
- package/dist/apps/mpp/Mpp.js +3 -0
- package/dist/apps/mpp/Mpp.js.map +1 -1
- package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
- package/dist/apps/mpp/internal/Adapter.js +9 -2
- package/dist/apps/mpp/internal/Adapter.js.map +1 -1
- package/dist/db/Db.d.ts +2 -2
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +25 -15
- package/dist/db/Db.js.map +1 -1
- package/dist/db/tables/fundingDeposits.d.ts +18 -1
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +46 -2
- package/dist/db/tables/fundingDeposits.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +4 -1
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/webhookSubscriptions.d.ts +56 -3
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +76 -3
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +4 -0
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +11 -2
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/MetricSink.d.ts +112 -3
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +116 -3
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Schema.js +1 -1
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Tidx.d.ts +1 -3
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +1 -3
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +2 -1
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/WebhookDestination.d.ts +2 -0
- package/dist/internal/WebhookDestination.d.ts.map +1 -1
- package/dist/internal/WebhookDestination.js +11 -0
- package/dist/internal/WebhookDestination.js.map +1 -1
- package/dist/internal/WebhookTransfer.d.ts +55 -0
- package/dist/internal/WebhookTransfer.d.ts.map +1 -0
- package/dist/internal/WebhookTransfer.js +296 -0
- package/dist/internal/WebhookTransfer.js.map +1 -0
- package/dist/internal/Webhooks.d.ts +37 -0
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +87 -13
- package/dist/internal/Webhooks.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +10 -6
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +11 -7
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +229 -16
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts +3 -0
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +18 -3
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/package.json +3 -3
- package/src/App.test.ts +32 -0
- package/src/App.ts +4 -1
- package/src/Client.test-d.ts +116 -38
- package/src/Webhooks.bench.ts +714 -0
- package/src/Webhooks.memory.bench.ts +250 -0
- package/src/Webhooks.test.ts +77 -6
- package/src/Webhooks.ts +83 -29
- package/src/apps/Zones.test.ts +5 -4
- package/src/apps/Zones.ts +9 -8
- package/src/apps/composition.test.ts +39 -10
- package/src/apps/data/FxOracle.test.ts +18 -0
- package/src/apps/data/FxOracle.ts +28 -5
- package/src/apps/data/routes/activities.test.ts +19 -0
- package/src/apps/data/routes/activities.ts +34 -30
- package/src/apps/data/routes/balances.test.ts +161 -10
- package/src/apps/data/routes/balances.ts +339 -147
- package/src/apps/data/routes/blocks.test.ts +12 -6
- package/src/apps/data/routes/earn.test.ts +286 -9
- package/src/apps/data/routes/earn.ts +841 -58
- package/src/apps/data/routes/exchanges.test.ts +7 -6
- package/src/apps/data/routes/exchanges.ts +124 -117
- package/src/apps/data/routes/receipts.test.ts +24 -7
- package/src/apps/data/routes/receipts.ts +67 -50
- package/src/apps/data/routes/tokens.test.ts +51 -0
- package/src/apps/data/routes/transactions.test.ts +42 -0
- package/src/apps/data/routes/transactions.ts +44 -39
- package/src/apps/data/routes/transfers.test.ts +402 -24
- package/src/apps/data/routes/transfers.ts +708 -101
- package/src/apps/data/routes/valuation.test.ts +26 -0
- package/src/apps/data/routes/valuation.ts +6 -2
- package/src/apps/data/routes/webhooks.test.ts +414 -130
- package/src/apps/data/routes/webhooks.ts +603 -419
- package/src/apps/data/routes/zones.ts +6 -1
- package/src/apps/funding/App.ts +20 -5
- package/src/apps/funding/routes/deposit-addresses.test.ts +43 -22
- package/src/apps/funding/routes/deposit-addresses.ts +34 -117
- package/src/apps/funding/routes/quotes.test.ts +1 -1
- package/src/apps/funding/routes/transfers.test.ts +6 -25
- package/src/apps/funding/routes/transfers.ts +5 -5
- package/src/apps/mpp/Mpp.test.ts +13 -1
- package/src/apps/mpp/Mpp.ts +8 -0
- package/src/apps/mpp/internal/Adapter.ts +10 -2
- package/src/db/Db.test.ts +40 -6
- package/src/db/Db.ts +42 -19
- package/src/db/tables/fundingDeposits.test.ts +101 -0
- package/src/db/tables/fundingDeposits.ts +71 -2
- package/src/db/tables/sponsoredTransactions.ts +9 -2
- package/src/db/tables/webhookSubscriptions.ts +116 -4
- package/src/internal/Auth.test.ts +28 -0
- package/src/internal/Auth.ts +15 -1
- package/src/internal/EarnVaults.test.ts +2 -2
- package/src/internal/MetricSink.test.ts +518 -0
- package/src/internal/MetricSink.ts +266 -9
- package/src/internal/Schema.ts +1 -1
- package/src/internal/Tidx.test.ts +4 -4
- package/src/internal/Tidx.ts +1 -3
- package/src/internal/Viem.test.ts +8 -0
- package/src/internal/Viem.ts +2 -1
- package/src/internal/WebhookDestination.test.ts +20 -0
- package/src/internal/WebhookDestination.ts +10 -0
- package/src/internal/WebhookTransfer.ts +426 -0
- package/src/internal/Webhooks.test.ts +137 -0
- package/src/internal/Webhooks.ts +131 -17
- package/src/internal/funding/Provider.test.ts +47 -7
- package/src/internal/funding/Provider.ts +16 -11
- package/src/internal/funding/Reconciliation.local.test.ts +3 -1
- package/src/internal/funding/Reconciliation.test.ts +1532 -54
- package/src/internal/funding/Reconciliation.ts +296 -53
- package/src/internal/funding/SourceObservation.test.ts +3 -2
- package/src/internal/funding/providers/relay.test.ts +1 -4
- package/src/internal/funding/providers/relay.ts +23 -3
|
@@ -13,12 +13,16 @@ import * as Subsidy from './Subsidy.js'
|
|
|
13
13
|
const defaultBatchSize = 25
|
|
14
14
|
const defaultLeaseMs = 2 * 60_000
|
|
15
15
|
const defaultPollIntervalMs = 60_000
|
|
16
|
+
const chainProviderFollowUpDelaySeconds = 1
|
|
17
|
+
const chainProviderMaximumFollowUpAttempts = 30
|
|
16
18
|
const followUpDelaySeconds = { delivery: 1, provider: 5, settlement: 2 } as const satisfies Record<
|
|
17
19
|
Provider.Webhook.receive.FollowUp['kind'],
|
|
18
20
|
number
|
|
19
21
|
>
|
|
20
22
|
const maximumPages = 100
|
|
21
23
|
const maximumFollowUpAttempts = 6
|
|
24
|
+
const providerFailureStatuses = ['failure'] as const
|
|
25
|
+
const providerRefundStatuses = ['refund', 'refunded'] as const
|
|
22
26
|
|
|
23
27
|
/** Verified token transfer returned by an authoritative chain reader. */
|
|
24
28
|
export type TransferEvidence = {
|
|
@@ -141,6 +145,18 @@ export function createTracker(options: createTracker.Options): createTracker.Tra
|
|
|
141
145
|
followUpKind = result.followUp ?? followUpKind ?? 'provider'
|
|
142
146
|
}
|
|
143
147
|
}
|
|
148
|
+
const unattributed = await reconcileUnattributed(database, {
|
|
149
|
+
address: claimed,
|
|
150
|
+
metrics: options.metrics,
|
|
151
|
+
provider,
|
|
152
|
+
signal: options.signal,
|
|
153
|
+
subsidies: options.subsidies,
|
|
154
|
+
verifyTransfers: options.verifyTransfers,
|
|
155
|
+
})
|
|
156
|
+
heartbeat.assert()
|
|
157
|
+
updated += unattributed.updatedIds.size
|
|
158
|
+
if (!unattributed.terminal)
|
|
159
|
+
followUpKind = unattributed.followUp ?? followUpKind ?? 'provider'
|
|
144
160
|
const awaitsProvider = await FundingDeposits.hasUnattributed(database, claimed.id)
|
|
145
161
|
if (message.requestId && message.webhookTiming && !webhookUpdateObserved)
|
|
146
162
|
pendingRequestIds.add(message.requestId)
|
|
@@ -570,10 +586,19 @@ type VerifiedDeposit = {
|
|
|
570
586
|
sourceAmount: bigint
|
|
571
587
|
}
|
|
572
588
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
589
|
+
type ReconcileDeliveryOptions = {
|
|
590
|
+
address: FundingDepositAddresses.Record
|
|
591
|
+
metrics?: Metrics.Metrics | undefined
|
|
592
|
+
provider?: Provider.Provider | undefined
|
|
593
|
+
request?: Provider.listDepositAddressRequests.ReturnType['requests'][number] | undefined
|
|
594
|
+
signal?: AbortSignal | undefined
|
|
595
|
+
subsidies?: Subsidy.Settler | undefined
|
|
596
|
+
verified: readonly VerifiedDeposit[]
|
|
597
|
+
verifyTransfers: VerifyTransfers
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async function observeProviderDelivery(options: ReconcileDeliveryOptions) {
|
|
601
|
+
const { verified } = options
|
|
577
602
|
const [entry] = verified
|
|
578
603
|
if (
|
|
579
604
|
!entry ||
|
|
@@ -581,7 +606,7 @@ async function observeProviderDelivery(
|
|
|
581
606
|
options.address.deliveryStrategy !== 'provider' ||
|
|
582
607
|
options.address.providerOutputToken.tokenKey !==
|
|
583
608
|
options.address.snapshot.destinationToken.tokenKey ||
|
|
584
|
-
(options.request
|
|
609
|
+
(options.request?.status === 'success' &&
|
|
585
610
|
options.request.destinationTransactionHashes.length > 0) ||
|
|
586
611
|
!options.provider ||
|
|
587
612
|
!Provider.canObserveDepositAddressRequest(options.provider)
|
|
@@ -597,7 +622,7 @@ async function observeProviderDelivery(
|
|
|
597
622
|
providerState: entry.deposit.providerState ?? undefined,
|
|
598
623
|
recipient: options.address.recipient,
|
|
599
624
|
refundAddress: options.address.refundAddress,
|
|
600
|
-
request: options.request,
|
|
625
|
+
...(options.request ? { request: options.request } : {}),
|
|
601
626
|
source: entry.source,
|
|
602
627
|
sourceChain: options.address.snapshot.sourceChain,
|
|
603
628
|
sourceToken: options.address.snapshot.sourceToken,
|
|
@@ -615,6 +640,63 @@ async function observeProviderDelivery(
|
|
|
615
640
|
return result
|
|
616
641
|
}
|
|
617
642
|
|
|
643
|
+
async function reconcileUnattributed(db: Db.Db, options: reconcileUnattributed.Options) {
|
|
644
|
+
const deposits = await FundingDeposits.listUnattributed(db, options.address.id)
|
|
645
|
+
if (deposits.length === 0) return { terminal: true, updatedIds: new Set<string>() }
|
|
646
|
+
|
|
647
|
+
const transfersByTransaction = new Map(
|
|
648
|
+
await Promise.all(
|
|
649
|
+
unique(deposits.map((deposit) => deposit.sourceTransactionHash)).map(
|
|
650
|
+
async (transactionHash) =>
|
|
651
|
+
[
|
|
652
|
+
transactionHash,
|
|
653
|
+
await options.verifyTransfers({
|
|
654
|
+
chain: options.address.snapshot.sourceChain,
|
|
655
|
+
recipient: options.address.address,
|
|
656
|
+
token: options.address.snapshot.sourceToken,
|
|
657
|
+
transactionHash,
|
|
658
|
+
}),
|
|
659
|
+
] as const,
|
|
660
|
+
),
|
|
661
|
+
),
|
|
662
|
+
)
|
|
663
|
+
let followUp: reconcileRequest.ReturnType['followUp']
|
|
664
|
+
let terminal = true
|
|
665
|
+
const updatedIds = new Set<string>()
|
|
666
|
+
for (const deposit of deposits) {
|
|
667
|
+
const source = transfersByTransaction
|
|
668
|
+
.get(deposit.sourceTransactionHash)
|
|
669
|
+
?.find((transfer) => transfer.transferIndex === deposit.sourceTransferIndex)
|
|
670
|
+
if (!source) {
|
|
671
|
+
terminal = false
|
|
672
|
+
continue
|
|
673
|
+
}
|
|
674
|
+
const delivery = await reconcileDelivery(db, {
|
|
675
|
+
...options,
|
|
676
|
+
verified: [
|
|
677
|
+
{
|
|
678
|
+
deposit,
|
|
679
|
+
required: destinationBaseUnits(
|
|
680
|
+
BigInt(source.amount),
|
|
681
|
+
options.address.snapshot.sourceToken.decimals,
|
|
682
|
+
options.address.snapshot.destinationToken.decimals,
|
|
683
|
+
),
|
|
684
|
+
source,
|
|
685
|
+
sourceAmount: BigInt(source.amount),
|
|
686
|
+
},
|
|
687
|
+
],
|
|
688
|
+
})
|
|
689
|
+
if (delivery.followUp === 'delivery' || !followUp) followUp = delivery.followUp
|
|
690
|
+
if (!delivery.terminal) terminal = false
|
|
691
|
+
for (const id of delivery.updatedIds) updatedIds.add(id)
|
|
692
|
+
}
|
|
693
|
+
return { ...(followUp ? { followUp } : {}), terminal, updatedIds }
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
declare namespace reconcileUnattributed {
|
|
697
|
+
type Options = Omit<ReconcileDeliveryOptions, 'request' | 'verified'>
|
|
698
|
+
}
|
|
699
|
+
|
|
618
700
|
/** Reconciles one provider request against verified chain evidence. */
|
|
619
701
|
export async function reconcileRequest(
|
|
620
702
|
db: Db.Db,
|
|
@@ -635,7 +717,6 @@ export async function reconcileRequest(
|
|
|
635
717
|
: {}),
|
|
636
718
|
})
|
|
637
719
|
let created = 0
|
|
638
|
-
let followUp: reconcileRequest.ReturnType['followUp']
|
|
639
720
|
const observed: ObservedSourceTransaction[] = []
|
|
640
721
|
// Persist provider observations first so transient chain failures do not hide acknowledged deposits.
|
|
641
722
|
for (const transactionHash of unique(options.request.sourceTransactionHashes)) {
|
|
@@ -699,7 +780,6 @@ export async function reconcileRequest(
|
|
|
699
780
|
|
|
700
781
|
const sourceEvidenceComplete =
|
|
701
782
|
sources.length > 0 && sources.every((source) => source.transfers.length > 0)
|
|
702
|
-
let terminal = sourceEvidenceComplete
|
|
703
783
|
const updatedIds = new Set<string>()
|
|
704
784
|
const verified: VerifiedDeposit[] = []
|
|
705
785
|
for (const source of sources) {
|
|
@@ -758,7 +838,11 @@ export async function reconcileRequest(
|
|
|
758
838
|
source.deposits.push(deposit)
|
|
759
839
|
created += 1
|
|
760
840
|
}
|
|
761
|
-
if (
|
|
841
|
+
if (
|
|
842
|
+
!Deposit.isTerminal(deposit.status) ||
|
|
843
|
+
deposit.providerRequestId === null ||
|
|
844
|
+
!deposit.providerRequestIds.includes(options.request.id)
|
|
845
|
+
) {
|
|
762
846
|
const result = await Deposit.transition(db, {
|
|
763
847
|
expectedVersion: deposit.version,
|
|
764
848
|
id: deposit.id,
|
|
@@ -784,11 +868,195 @@ export async function reconcileRequest(
|
|
|
784
868
|
}
|
|
785
869
|
if (verified.length === 0) return { created, terminal: false, updated: updatedIds.size }
|
|
786
870
|
if (!sourceEvidenceComplete) return { created, terminal: false, updated: updatedIds.size }
|
|
871
|
+
const recovery = await reconcileProviderRecovery(db, { ...options, verified })
|
|
872
|
+
if (recovery) {
|
|
873
|
+
for (const id of recovery.updatedIds) updatedIds.add(id)
|
|
874
|
+
return {
|
|
875
|
+
created,
|
|
876
|
+
...(recovery.followUp ? { followUp: recovery.followUp } : {}),
|
|
877
|
+
terminal: recovery.terminal,
|
|
878
|
+
updated: updatedIds.size,
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
const delivery = await reconcileDelivery(db, { ...options, verified })
|
|
882
|
+
for (const id of delivery.updatedIds) updatedIds.add(id)
|
|
883
|
+
return {
|
|
884
|
+
created,
|
|
885
|
+
...(delivery.followUp ? { followUp: delivery.followUp } : {}),
|
|
886
|
+
terminal: delivery.terminal,
|
|
887
|
+
updated: updatedIds.size,
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
export declare namespace reconcileRequest {
|
|
892
|
+
/** Provider request, address, and settlement dependencies. */
|
|
893
|
+
type Options = {
|
|
894
|
+
/** Reusable deposit address attributed by the provider. */
|
|
895
|
+
address: FundingDepositAddresses.Record
|
|
896
|
+
/** Optional bounded operational metrics sink. */
|
|
897
|
+
metrics?: Metrics.Metrics | undefined
|
|
898
|
+
/** Provider integration that discovered the request. */
|
|
899
|
+
provider?: Provider.Provider | undefined
|
|
900
|
+
/** Provider request containing discovered chain transactions. */
|
|
901
|
+
request: Provider.listDepositAddressRequests.ReturnType['requests'][number]
|
|
902
|
+
/** Abort signal forwarded to provider delivery observation. */
|
|
903
|
+
signal?: AbortSignal | undefined
|
|
904
|
+
/** Optional pathUSD-funded subsidy settler. */
|
|
905
|
+
subsidies?: Subsidy.Settler | undefined
|
|
906
|
+
/** Mechanism that requested reconciliation. */
|
|
907
|
+
trigger?: Deposit.DetectionTrigger | undefined
|
|
908
|
+
/** Authoritative source and destination transfer verifier. */
|
|
909
|
+
verifyTransfers: VerifyTransfers
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/** Material changes produced by one provider request. */
|
|
913
|
+
type ReturnType = {
|
|
914
|
+
/** Number of newly detected source transfers. */
|
|
915
|
+
created: number
|
|
916
|
+
/** Work that needs a bounded delayed follow-up. */
|
|
917
|
+
followUp?: 'delivery' | 'settlement' | undefined
|
|
918
|
+
/** Whether every deposit from this request reached a terminal state. */
|
|
919
|
+
terminal: boolean
|
|
920
|
+
/** Number of existing or new deposits materially changed. */
|
|
921
|
+
updated: number
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
async function reconcileProviderRecovery(db: Db.Db, options: ReconcileDeliveryOptions) {
|
|
926
|
+
const failed = (providerFailureStatuses as readonly string[]).includes(
|
|
927
|
+
options.request?.status ?? '',
|
|
928
|
+
)
|
|
929
|
+
const refunded = (providerRefundStatuses as readonly string[]).includes(
|
|
930
|
+
options.request?.status ?? '',
|
|
931
|
+
)
|
|
932
|
+
if (!refunded && !failed) return undefined
|
|
933
|
+
if (failed) {
|
|
934
|
+
const destinationEvidence = await Promise.all(
|
|
935
|
+
unique(options.request?.destinationTransactionHashes ?? []).map((transactionHash) =>
|
|
936
|
+
options.verifyTransfers({
|
|
937
|
+
chain: options.address.snapshot.destinationChain,
|
|
938
|
+
recipient: options.address.recipient,
|
|
939
|
+
token: options.address.providerOutputToken,
|
|
940
|
+
transactionHash,
|
|
941
|
+
}),
|
|
942
|
+
),
|
|
943
|
+
)
|
|
944
|
+
if (destinationEvidence.some((transfers) => transfers.length > 0)) return undefined
|
|
945
|
+
}
|
|
787
946
|
|
|
788
|
-
const
|
|
947
|
+
const updatedIds = new Set<string>()
|
|
948
|
+
if (options.verified.every((entry) => Deposit.isTerminal(entry.deposit.status)))
|
|
949
|
+
return { terminal: true, updatedIds }
|
|
950
|
+
|
|
951
|
+
// Relay auto-refunds may include an internal settlement transaction; only matching source-token transfers become public evidence.
|
|
952
|
+
const providerRefundTransactionHashes = unique(options.request?.refundTransactionHashes ?? [])
|
|
953
|
+
const refundEvidence = await Promise.all(
|
|
954
|
+
providerRefundTransactionHashes.map((transactionHash) =>
|
|
955
|
+
options.verifyTransfers({
|
|
956
|
+
chain: options.address.snapshot.sourceChain,
|
|
957
|
+
recipient: options.address.refundAddress,
|
|
958
|
+
token: options.address.snapshot.sourceToken,
|
|
959
|
+
transactionHash,
|
|
960
|
+
}),
|
|
961
|
+
),
|
|
962
|
+
)
|
|
963
|
+
const refundTransfers = refundEvidence.flat()
|
|
964
|
+
const refundOutput = refundTransfers.reduce(
|
|
965
|
+
(total, transfer) => total + BigInt(transfer.amount),
|
|
966
|
+
0n,
|
|
967
|
+
)
|
|
968
|
+
const refundTransactionHashes = unique(
|
|
969
|
+
refundTransfers.map((transfer) => transfer.transactionHash),
|
|
970
|
+
)
|
|
971
|
+
// Relay can include internal transactions, so its reported amount proves when all matching refund transfers are visible.
|
|
972
|
+
const expectedRefundOutput = options.request?.refundAmountExpected
|
|
973
|
+
? BigInt(options.request.refundAmountExpected)
|
|
974
|
+
: undefined
|
|
975
|
+
const refundEvidenceComplete = (() => {
|
|
976
|
+
if (refundOutput === 0n) return false
|
|
977
|
+
if (expectedRefundOutput !== undefined) return refundOutput === expectedRefundOutput
|
|
978
|
+
return refundEvidence.length > 0 && refundEvidence.every((transfers) => transfers.length > 0)
|
|
979
|
+
})()
|
|
980
|
+
const totalSource = options.verified.reduce((total, entry) => total + entry.sourceAmount, 0n)
|
|
981
|
+
let allocated = 0n
|
|
982
|
+
let cumulativeSource = 0n
|
|
983
|
+
let terminal = refundEvidenceComplete
|
|
984
|
+
|
|
985
|
+
// Include terminal rows in proportional allocation so retries preserve the original split without reallocating their verified refunds.
|
|
986
|
+
for (const entry of options.verified) {
|
|
987
|
+
cumulativeSource += entry.sourceAmount
|
|
988
|
+
const nextAllocated = totalSource === 0n ? 0n : (refundOutput * cumulativeSource) / totalSource
|
|
989
|
+
const amount = nextAllocated - allocated
|
|
990
|
+
allocated = nextAllocated
|
|
991
|
+
if (Deposit.isTerminal(entry.deposit.status)) continue
|
|
992
|
+
let deposit = entry.deposit
|
|
993
|
+
if (deposit.status !== 'refunding') {
|
|
994
|
+
const result = await Deposit.transition(db, {
|
|
995
|
+
expectedVersion: deposit.version,
|
|
996
|
+
id: deposit.id,
|
|
997
|
+
providerTransactionHashes: unique([
|
|
998
|
+
...deposit.providerTransactionHashes,
|
|
999
|
+
...providerRefundTransactionHashes,
|
|
1000
|
+
]),
|
|
1001
|
+
status: 'refunding',
|
|
1002
|
+
})
|
|
1003
|
+
if (result.type === 'applied') {
|
|
1004
|
+
deposit = result.record
|
|
1005
|
+
updatedIds.add(deposit.id)
|
|
1006
|
+
} else if (result.type === 'stale') deposit = result.current
|
|
1007
|
+
}
|
|
1008
|
+
if (!refundEvidenceComplete) continue
|
|
1009
|
+
if (Deposit.isTerminal(deposit.status)) continue
|
|
1010
|
+
if (deposit.status !== 'refunding') {
|
|
1011
|
+
terminal = false
|
|
1012
|
+
continue
|
|
1013
|
+
}
|
|
1014
|
+
const result = await Deposit.transition(db, {
|
|
1015
|
+
expectedVersion: deposit.version,
|
|
1016
|
+
id: deposit.id,
|
|
1017
|
+
snapshot: {
|
|
1018
|
+
...deposit.snapshot,
|
|
1019
|
+
refundAmount: Value.tokenAmount({
|
|
1020
|
+
baseUnits: amount,
|
|
1021
|
+
currency: options.address.snapshot.sourceToken.currency,
|
|
1022
|
+
decimals: options.address.snapshot.sourceToken.decimals,
|
|
1023
|
+
}),
|
|
1024
|
+
refundTransactionHashes,
|
|
1025
|
+
},
|
|
1026
|
+
status: 'refunded',
|
|
1027
|
+
})
|
|
1028
|
+
if (result.type === 'applied') updatedIds.add(deposit.id)
|
|
1029
|
+
else if (result.type !== 'stale' || !Deposit.isTerminal(result.current.status)) terminal = false
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
const outcome = (() => {
|
|
1033
|
+
if (refundEvidenceComplete) return 'verified'
|
|
1034
|
+
// Relay failure can be followed by an automatic refund; durable exhaustion owns escalation to action-required.
|
|
1035
|
+
if (failed) return 'provider_failed'
|
|
1036
|
+
return 'pending'
|
|
1037
|
+
})()
|
|
1038
|
+
options.metrics?.count('funding_deposit_refund_count', 1, {
|
|
1039
|
+
outcome,
|
|
1040
|
+
provider: options.address.providerId,
|
|
1041
|
+
})
|
|
1042
|
+
return {
|
|
1043
|
+
...(!refundEvidenceComplete ? { followUp: 'delivery' as const } : {}),
|
|
1044
|
+
terminal,
|
|
1045
|
+
updatedIds,
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
async function reconcileDelivery(db: Db.Db, options: ReconcileDeliveryOptions) {
|
|
1050
|
+
let followUp: reconcileRequest.ReturnType['followUp']
|
|
1051
|
+
let terminal = true
|
|
1052
|
+
const updatedIds = new Set<string>()
|
|
1053
|
+
if (options.verified.every((entry) => Deposit.isTerminal(entry.deposit.status)))
|
|
1054
|
+
return { terminal, updatedIds }
|
|
1055
|
+
const deliveryObservation = await observeProviderDelivery(options)
|
|
789
1056
|
if (deliveryObservation?.type === 'pending') followUp = 'delivery'
|
|
1057
|
+
const providerTransactionHashes = options.request?.destinationTransactionHashes ?? []
|
|
790
1058
|
const destinationTransactionHashes = unique([
|
|
791
|
-
...
|
|
1059
|
+
...providerTransactionHashes,
|
|
792
1060
|
...(deliveryObservation?.type === 'verified'
|
|
793
1061
|
? deliveryObservation.destinationTransactionHashes
|
|
794
1062
|
: []),
|
|
@@ -805,18 +1073,18 @@ export async function reconcileRequest(
|
|
|
805
1073
|
)
|
|
806
1074
|
const destinationTransfers = destinationEvidence.flat()
|
|
807
1075
|
const providerEvidenceComplete =
|
|
808
|
-
(options.request
|
|
1076
|
+
(options.request?.status === 'success' || deliveryObservation?.type === 'verified') &&
|
|
809
1077
|
destinationEvidence.length > 0 &&
|
|
810
1078
|
destinationEvidence.every((transfers) => transfers.length > 0)
|
|
811
1079
|
const providerOutput = destinationTransfers.reduce(
|
|
812
1080
|
(total, transfer) => total + BigInt(transfer.amount),
|
|
813
1081
|
0n,
|
|
814
1082
|
)
|
|
815
|
-
const totalRequired = verified.reduce((total, entry) => total + entry.required, 0n)
|
|
1083
|
+
const totalRequired = options.verified.reduce((total, entry) => total + entry.required, 0n)
|
|
816
1084
|
let allocated = 0n
|
|
817
1085
|
let cumulativeRequired = 0n
|
|
818
1086
|
|
|
819
|
-
for (const entry of verified) {
|
|
1087
|
+
for (const entry of options.verified) {
|
|
820
1088
|
const { required, sourceAmount } = entry
|
|
821
1089
|
cumulativeRequired += required
|
|
822
1090
|
const nextAllocated =
|
|
@@ -860,7 +1128,9 @@ export async function reconcileRequest(
|
|
|
860
1128
|
}),
|
|
861
1129
|
}
|
|
862
1130
|
: {}),
|
|
863
|
-
providerRequestIds:
|
|
1131
|
+
providerRequestIds: options.request
|
|
1132
|
+
? unique([...deposit.providerRequestIds, options.request.id])
|
|
1133
|
+
: deposit.providerRequestIds,
|
|
864
1134
|
...(deliveryObservation?.providerState
|
|
865
1135
|
? {
|
|
866
1136
|
providerState: {
|
|
@@ -871,7 +1141,7 @@ export async function reconcileRequest(
|
|
|
871
1141
|
: {}),
|
|
872
1142
|
providerTransactionHashes: unique([
|
|
873
1143
|
...deposit.providerTransactionHashes,
|
|
874
|
-
...
|
|
1144
|
+
...providerTransactionHashes,
|
|
875
1145
|
...(deliveryObservation?.providerTransactionHashes ?? []),
|
|
876
1146
|
]),
|
|
877
1147
|
...(settlement.settlementTransaction
|
|
@@ -914,41 +1184,7 @@ export async function reconcileRequest(
|
|
|
914
1184
|
}
|
|
915
1185
|
}
|
|
916
1186
|
}
|
|
917
|
-
return {
|
|
918
|
-
}
|
|
919
|
-
|
|
920
|
-
export declare namespace reconcileRequest {
|
|
921
|
-
/** Provider request, address, and settlement dependencies. */
|
|
922
|
-
type Options = {
|
|
923
|
-
/** Reusable deposit address attributed by the provider. */
|
|
924
|
-
address: FundingDepositAddresses.Record
|
|
925
|
-
/** Optional bounded operational metrics sink. */
|
|
926
|
-
metrics?: Metrics.Metrics | undefined
|
|
927
|
-
/** Provider integration that discovered the request. */
|
|
928
|
-
provider?: Provider.Provider | undefined
|
|
929
|
-
/** Provider request containing discovered chain transactions. */
|
|
930
|
-
request: Provider.listDepositAddressRequests.ReturnType['requests'][number]
|
|
931
|
-
/** Abort signal forwarded to provider delivery observation. */
|
|
932
|
-
signal?: AbortSignal | undefined
|
|
933
|
-
/** Optional pathUSD-funded subsidy settler. */
|
|
934
|
-
subsidies?: Subsidy.Settler | undefined
|
|
935
|
-
/** Mechanism that requested reconciliation. */
|
|
936
|
-
trigger?: Deposit.DetectionTrigger | undefined
|
|
937
|
-
/** Authoritative source and destination transfer verifier. */
|
|
938
|
-
verifyTransfers: VerifyTransfers
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
/** Material changes produced by one provider request. */
|
|
942
|
-
type ReturnType = {
|
|
943
|
-
/** Number of newly detected source transfers. */
|
|
944
|
-
created: number
|
|
945
|
-
/** Work that needs a bounded delayed follow-up. */
|
|
946
|
-
followUp?: 'delivery' | 'settlement' | undefined
|
|
947
|
-
/** Whether every deposit from this request reached a terminal state. */
|
|
948
|
-
terminal: boolean
|
|
949
|
-
/** Number of existing or new deposits materially changed. */
|
|
950
|
-
updated: number
|
|
951
|
-
}
|
|
1187
|
+
return { ...(followUp ? { followUp } : {}), terminal, updatedIds }
|
|
952
1188
|
}
|
|
953
1189
|
|
|
954
1190
|
type ReconcileSubsidyOptions = {
|
|
@@ -1149,8 +1385,15 @@ function nextFollowUp(
|
|
|
1149
1385
|
kind: Provider.Webhook.receive.FollowUp['kind'],
|
|
1150
1386
|
) {
|
|
1151
1387
|
const attempt = message.followUp?.kind === kind ? message.followUp.attempt + 1 : 0
|
|
1152
|
-
|
|
1153
|
-
|
|
1388
|
+
const chainProvider = kind === 'provider' && message.trigger === 'chain'
|
|
1389
|
+
const maximumAttempts = chainProvider
|
|
1390
|
+
? chainProviderMaximumFollowUpAttempts
|
|
1391
|
+
: maximumFollowUpAttempts
|
|
1392
|
+
if (attempt >= maximumAttempts) return undefined
|
|
1393
|
+
return {
|
|
1394
|
+
delaySeconds: chainProvider ? chainProviderFollowUpDelaySeconds : followUpDelaySeconds[kind],
|
|
1395
|
+
state: { attempt, kind },
|
|
1396
|
+
}
|
|
1154
1397
|
}
|
|
1155
1398
|
|
|
1156
1399
|
type RecordWebhookLatencyOptions = {
|
|
@@ -188,6 +188,7 @@ describe('observe', () => {
|
|
|
188
188
|
createdAt: '2026-01-01T00:00:02.000Z',
|
|
189
189
|
destinationTransactionHashes: [],
|
|
190
190
|
id: 'relay-source-request',
|
|
191
|
+
refundTransactionHashes: [],
|
|
191
192
|
sourceTransactionHashes: [sourceTransactionHash],
|
|
192
193
|
status: 'pending',
|
|
193
194
|
updatedAt: '2026-01-01T00:00:03.000Z',
|
|
@@ -230,7 +231,7 @@ describe('observe', () => {
|
|
|
230
231
|
})(),
|
|
231
232
|
}).toMatchInlineSnapshot(
|
|
232
233
|
{
|
|
233
|
-
dispatches: [[{ addressId: expect.any(String) }, { delaySeconds:
|
|
234
|
+
dispatches: [[{ addressId: expect.any(String) }, { delaySeconds: 1 }]],
|
|
234
235
|
},
|
|
235
236
|
`
|
|
236
237
|
{
|
|
@@ -246,7 +247,7 @@ describe('observe', () => {
|
|
|
246
247
|
"type": "funding:deposit-address:reconcile",
|
|
247
248
|
},
|
|
248
249
|
{
|
|
249
|
-
"delaySeconds":
|
|
250
|
+
"delaySeconds": 1,
|
|
250
251
|
},
|
|
251
252
|
],
|
|
252
253
|
],
|
|
@@ -151,11 +151,8 @@ describe.skipIf(!apiKey)('relay.prepareTransfer (live)', () => {
|
|
|
151
151
|
expect(result.correlation.requestId).toMatch(/^0x[0-9a-f]{64}$/)
|
|
152
152
|
expect(result.correlation.checkEndpoint).toContain('/intents/status/v3')
|
|
153
153
|
expect(BigInt(result.destinationAmount) > 0n).toBe(true)
|
|
154
|
+
expect(BigInt(result.destinationAmountMin) > 0n).toBe(true)
|
|
154
155
|
expect(BigInt(result.destinationAmountMin) <= BigInt(result.destinationAmount)).toBe(true)
|
|
155
|
-
// The forwarded slippage bound holds on the quoted minimum.
|
|
156
|
-
expect(
|
|
157
|
-
BigInt(result.destinationAmountMin) >= (BigInt(result.destinationAmount) * 9_700n) / 10_000n,
|
|
158
|
-
).toBe(true)
|
|
159
156
|
// Relay returns no quote validity window; the fan-out applies the default.
|
|
160
157
|
expect(result.expiresAt).toBeUndefined()
|
|
161
158
|
expect(Number.isNaN(Date.parse(result.sampledAt))).toBe(false)
|
|
@@ -17,6 +17,7 @@ const defaultBaseUrl = 'https://api.relay.link'
|
|
|
17
17
|
const defaultFetch: FundingProvider.Fetch = (input, init) => globalThis.fetch(input, init)
|
|
18
18
|
const deliveryBackfillBlocks = 256n
|
|
19
19
|
const relayChainsCacheTtlMs = 60_000
|
|
20
|
+
const relayRefundStatuses = ['refund', 'refunded'] as const
|
|
20
21
|
const sourceSweepForwardBlocks = 256n
|
|
21
22
|
const depositErc20FullAllowance = AbiFunction.from(
|
|
22
23
|
'function depositErc20(address depositor,address token,bytes32 id)',
|
|
@@ -46,6 +47,7 @@ const webhookStatuses = [
|
|
|
46
47
|
'fallback',
|
|
47
48
|
'pending',
|
|
48
49
|
'refund',
|
|
50
|
+
'refunded',
|
|
49
51
|
'submitted',
|
|
50
52
|
'success',
|
|
51
53
|
'waiting',
|
|
@@ -196,6 +198,13 @@ namespace schema {
|
|
|
196
198
|
),
|
|
197
199
|
),
|
|
198
200
|
id: z.string().check(z.minLength(1)),
|
|
201
|
+
refundCurrencyData: z.optional(
|
|
202
|
+
z.nullable(
|
|
203
|
+
z.object({
|
|
204
|
+
amount: z.string().check(z.regex(/^\d+$/)),
|
|
205
|
+
}),
|
|
206
|
+
),
|
|
207
|
+
),
|
|
199
208
|
protocol: z.optional(
|
|
200
209
|
z.nullable(
|
|
201
210
|
z.object({
|
|
@@ -481,6 +490,8 @@ export function relay(options: relay.Options = {}): FundingProvider.Provider<'re
|
|
|
481
490
|
return {
|
|
482
491
|
...(parsed.continuation ? { continuation: parsed.continuation } : {}),
|
|
483
492
|
requests: parsed.requests.map((request) => {
|
|
493
|
+
const outputTransactionHashes = request.data?.outTxs?.map((tx) => tx.txHash) ?? []
|
|
494
|
+
const refunded = (relayRefundStatuses as readonly string[]).includes(request.status)
|
|
484
495
|
const correlation = (() => {
|
|
485
496
|
const { protocol } = request
|
|
486
497
|
if (!protocol) return undefined
|
|
@@ -499,9 +510,13 @@ export function relay(options: relay.Options = {}): FundingProvider.Provider<'re
|
|
|
499
510
|
})()
|
|
500
511
|
return {
|
|
501
512
|
createdAt: new Date(request.createdAt).toISOString(),
|
|
502
|
-
destinationTransactionHashes:
|
|
513
|
+
destinationTransactionHashes: refunded ? [] : outputTransactionHashes,
|
|
503
514
|
id: request.id,
|
|
504
515
|
...(correlation ? { providerState: correlation } : {}),
|
|
516
|
+
...(refunded && request.refundCurrencyData
|
|
517
|
+
? { refundAmountExpected: request.refundCurrencyData.amount }
|
|
518
|
+
: {}),
|
|
519
|
+
refundTransactionHashes: refunded ? outputTransactionHashes : [],
|
|
505
520
|
sourceTransactionHashes: request.depositAddress?.depositTxHash
|
|
506
521
|
? [request.depositAddress.depositTxHash]
|
|
507
522
|
: [],
|
|
@@ -709,7 +724,10 @@ function createDepositAddressObserver(
|
|
|
709
724
|
}
|
|
710
725
|
|
|
711
726
|
return async (parameters, signal) => {
|
|
712
|
-
if (
|
|
727
|
+
if (
|
|
728
|
+
parameters.request &&
|
|
729
|
+
!(deliveryObservationStatuses as readonly string[]).includes(parameters.request.status)
|
|
730
|
+
)
|
|
713
731
|
return { type: 'unsupported' }
|
|
714
732
|
if (parameters.sourceChain.kind !== 'evm' || parameters.destinationChain.kind !== 'evm')
|
|
715
733
|
return { type: 'unsupported' }
|
|
@@ -739,7 +757,7 @@ function createDepositAddressObserver(
|
|
|
739
757
|
)
|
|
740
758
|
return { type: 'unsupported' }
|
|
741
759
|
|
|
742
|
-
const parsedCorrelation = schema.RequestCorrelation.safeParse(parameters.request
|
|
760
|
+
const parsedCorrelation = schema.RequestCorrelation.safeParse(parameters.request?.providerState)
|
|
743
761
|
const correlation = parsedCorrelation.success ? parsedCorrelation.data.relay : undefined
|
|
744
762
|
if (
|
|
745
763
|
(correlation?.depository &&
|
|
@@ -854,6 +872,8 @@ function createDepositAddressObserver(
|
|
|
854
872
|
}
|
|
855
873
|
const destinationTransactionHashes = (() => {
|
|
856
874
|
if (correlatedTransactionHashes.length > 0) return correlatedTransactionHashes
|
|
875
|
+
// Provider-independent completion requires the Relay order id to bind both chain legs.
|
|
876
|
+
if (!parameters.request) return []
|
|
857
877
|
if (uncorrelatedTransactionHashes.length === 1) return uncorrelatedTransactionHashes
|
|
858
878
|
return []
|
|
859
879
|
})()
|