tapimo 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/dist/App.d.ts +2095 -2111
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +0 -1
  4. package/dist/App.js.map +1 -1
  5. package/dist/admin/apps/verified-tokens.d.ts +166 -166
  6. package/dist/apps/data/App.d.ts +4084 -3845
  7. package/dist/apps/data/App.d.ts.map +1 -1
  8. package/dist/apps/data/routes/activities.d.ts +270 -270
  9. package/dist/apps/data/routes/addresses.d.ts +248 -17
  10. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  11. package/dist/apps/data/routes/balances.d.ts +272 -2
  12. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  13. package/dist/apps/data/routes/balances.js +114 -2
  14. package/dist/apps/data/routes/balances.js.map +1 -1
  15. package/dist/apps/data/routes/blocks.d.ts +123 -123
  16. package/dist/apps/data/routes/earn.d.ts +65 -3
  17. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  18. package/dist/apps/data/routes/earn.js +259 -14
  19. package/dist/apps/data/routes/earn.js.map +1 -1
  20. package/dist/apps/data/routes/rpc.d.ts +31 -31
  21. package/dist/apps/data/routes/tokens.d.ts +454 -454
  22. package/dist/apps/data/routes/transfers.d.ts +81 -81
  23. package/dist/apps/funding/App.d.ts +40 -299
  24. package/dist/apps/funding/App.d.ts.map +1 -1
  25. package/dist/apps/funding/App.js +20 -5
  26. package/dist/apps/funding/App.js.map +1 -1
  27. package/dist/apps/funding/routes/deposit-addresses.d.ts +43 -434
  28. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  29. package/dist/apps/funding/routes/deposit-addresses.js +21 -103
  30. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  31. package/dist/apps/funding/routes/quotes.d.ts +89 -89
  32. package/dist/apps/funding/routes/transfers.js +5 -5
  33. package/dist/apps/funding/routes/transfers.js.map +1 -1
  34. package/dist/apps/management/routes/invitations.d.ts +134 -134
  35. package/dist/apps/management/routes/orgs.d.ts +98 -98
  36. package/dist/apps/management/routes/verified-token-requests.d.ts +83 -83
  37. package/dist/apps/mpp/App.d.ts +44 -44
  38. package/dist/db/Db.d.ts.map +1 -1
  39. package/dist/db/Db.js +8 -0
  40. package/dist/db/Db.js.map +1 -1
  41. package/dist/db/tables/fundingDeposits.d.ts +18 -1
  42. package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
  43. package/dist/db/tables/fundingDeposits.js +46 -2
  44. package/dist/db/tables/fundingDeposits.js.map +1 -1
  45. package/dist/internal/Auth.d.ts +4 -0
  46. package/dist/internal/Auth.d.ts.map +1 -1
  47. package/dist/internal/Auth.js +11 -2
  48. package/dist/internal/Auth.js.map +1 -1
  49. package/dist/internal/funding/Provider.d.ts +6 -6
  50. package/dist/internal/funding/Provider.d.ts.map +1 -1
  51. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  52. package/dist/internal/funding/Reconciliation.js +101 -16
  53. package/dist/internal/funding/Reconciliation.js.map +1 -1
  54. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  55. package/dist/internal/funding/providers/relay.js +6 -2
  56. package/dist/internal/funding/providers/relay.js.map +1 -1
  57. package/package.json +1 -1
  58. package/src/App.test.ts +32 -0
  59. package/src/App.ts +4 -1
  60. package/src/Client.test-d.ts +56 -33
  61. package/src/apps/composition.test.ts +39 -10
  62. package/src/apps/data/routes/balances.test.ts +42 -0
  63. package/src/apps/data/routes/balances.ts +218 -85
  64. package/src/apps/data/routes/earn.test.ts +89 -9
  65. package/src/apps/data/routes/earn.ts +355 -14
  66. package/src/apps/funding/App.ts +20 -5
  67. package/src/apps/funding/routes/deposit-addresses.test.ts +43 -22
  68. package/src/apps/funding/routes/deposit-addresses.ts +34 -117
  69. package/src/apps/funding/routes/transfers.test.ts +6 -25
  70. package/src/apps/funding/routes/transfers.ts +5 -5
  71. package/src/db/Db.ts +9 -0
  72. package/src/db/tables/fundingDeposits.test.ts +101 -0
  73. package/src/db/tables/fundingDeposits.ts +71 -2
  74. package/src/internal/Auth.test.ts +28 -0
  75. package/src/internal/Auth.ts +15 -1
  76. package/src/internal/funding/Provider.ts +4 -4
  77. package/src/internal/funding/Reconciliation.test.ts +292 -54
  78. package/src/internal/funding/Reconciliation.ts +160 -53
  79. package/src/internal/funding/SourceObservation.test.ts +2 -2
  80. package/src/internal/funding/providers/relay.ts +7 -2
@@ -13,6 +13,8 @@ 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
@@ -141,6 +143,18 @@ export function createTracker(options: createTracker.Options): createTracker.Tra
141
143
  followUpKind = result.followUp ?? followUpKind ?? 'provider'
142
144
  }
143
145
  }
146
+ const unattributed = await reconcileUnattributed(database, {
147
+ address: claimed,
148
+ metrics: options.metrics,
149
+ provider,
150
+ signal: options.signal,
151
+ subsidies: options.subsidies,
152
+ verifyTransfers: options.verifyTransfers,
153
+ })
154
+ heartbeat.assert()
155
+ updated += unattributed.updatedIds.size
156
+ if (!unattributed.terminal)
157
+ followUpKind = unattributed.followUp ?? followUpKind ?? 'provider'
144
158
  const awaitsProvider = await FundingDeposits.hasUnattributed(database, claimed.id)
145
159
  if (message.requestId && message.webhookTiming && !webhookUpdateObserved)
146
160
  pendingRequestIds.add(message.requestId)
@@ -570,10 +584,19 @@ type VerifiedDeposit = {
570
584
  sourceAmount: bigint
571
585
  }
572
586
 
573
- async function observeProviderDelivery(
574
- options: reconcileRequest.Options,
575
- verified: readonly VerifiedDeposit[],
576
- ) {
587
+ type ReconcileDeliveryOptions = {
588
+ address: FundingDepositAddresses.Record
589
+ metrics?: Metrics.Metrics | undefined
590
+ provider?: Provider.Provider | undefined
591
+ request?: Provider.listDepositAddressRequests.ReturnType['requests'][number] | undefined
592
+ signal?: AbortSignal | undefined
593
+ subsidies?: Subsidy.Settler | undefined
594
+ verified: readonly VerifiedDeposit[]
595
+ verifyTransfers: VerifyTransfers
596
+ }
597
+
598
+ async function observeProviderDelivery(options: ReconcileDeliveryOptions) {
599
+ const { verified } = options
577
600
  const [entry] = verified
578
601
  if (
579
602
  !entry ||
@@ -581,7 +604,7 @@ async function observeProviderDelivery(
581
604
  options.address.deliveryStrategy !== 'provider' ||
582
605
  options.address.providerOutputToken.tokenKey !==
583
606
  options.address.snapshot.destinationToken.tokenKey ||
584
- (options.request.status === 'success' &&
607
+ (options.request?.status === 'success' &&
585
608
  options.request.destinationTransactionHashes.length > 0) ||
586
609
  !options.provider ||
587
610
  !Provider.canObserveDepositAddressRequest(options.provider)
@@ -597,7 +620,7 @@ async function observeProviderDelivery(
597
620
  providerState: entry.deposit.providerState ?? undefined,
598
621
  recipient: options.address.recipient,
599
622
  refundAddress: options.address.refundAddress,
600
- request: options.request,
623
+ ...(options.request ? { request: options.request } : {}),
601
624
  source: entry.source,
602
625
  sourceChain: options.address.snapshot.sourceChain,
603
626
  sourceToken: options.address.snapshot.sourceToken,
@@ -615,6 +638,63 @@ async function observeProviderDelivery(
615
638
  return result
616
639
  }
617
640
 
641
+ async function reconcileUnattributed(db: Db.Db, options: reconcileUnattributed.Options) {
642
+ const deposits = await FundingDeposits.listUnattributed(db, options.address.id)
643
+ if (deposits.length === 0) return { terminal: true, updatedIds: new Set<string>() }
644
+
645
+ const transfersByTransaction = new Map(
646
+ await Promise.all(
647
+ unique(deposits.map((deposit) => deposit.sourceTransactionHash)).map(
648
+ async (transactionHash) =>
649
+ [
650
+ transactionHash,
651
+ await options.verifyTransfers({
652
+ chain: options.address.snapshot.sourceChain,
653
+ recipient: options.address.address,
654
+ token: options.address.snapshot.sourceToken,
655
+ transactionHash,
656
+ }),
657
+ ] as const,
658
+ ),
659
+ ),
660
+ )
661
+ let followUp: reconcileRequest.ReturnType['followUp']
662
+ let terminal = true
663
+ const updatedIds = new Set<string>()
664
+ for (const deposit of deposits) {
665
+ const source = transfersByTransaction
666
+ .get(deposit.sourceTransactionHash)
667
+ ?.find((transfer) => transfer.transferIndex === deposit.sourceTransferIndex)
668
+ if (!source) {
669
+ terminal = false
670
+ continue
671
+ }
672
+ const delivery = await reconcileDelivery(db, {
673
+ ...options,
674
+ verified: [
675
+ {
676
+ deposit,
677
+ required: destinationBaseUnits(
678
+ BigInt(source.amount),
679
+ options.address.snapshot.sourceToken.decimals,
680
+ options.address.snapshot.destinationToken.decimals,
681
+ ),
682
+ source,
683
+ sourceAmount: BigInt(source.amount),
684
+ },
685
+ ],
686
+ })
687
+ if (delivery.followUp === 'delivery' || !followUp) followUp = delivery.followUp
688
+ if (!delivery.terminal) terminal = false
689
+ for (const id of delivery.updatedIds) updatedIds.add(id)
690
+ }
691
+ return { ...(followUp ? { followUp } : {}), terminal, updatedIds }
692
+ }
693
+
694
+ declare namespace reconcileUnattributed {
695
+ type Options = Omit<ReconcileDeliveryOptions, 'request' | 'verified'>
696
+ }
697
+
618
698
  /** Reconciles one provider request against verified chain evidence. */
619
699
  export async function reconcileRequest(
620
700
  db: Db.Db,
@@ -635,7 +715,6 @@ export async function reconcileRequest(
635
715
  : {}),
636
716
  })
637
717
  let created = 0
638
- let followUp: reconcileRequest.ReturnType['followUp']
639
718
  const observed: ObservedSourceTransaction[] = []
640
719
  // Persist provider observations first so transient chain failures do not hide acknowledged deposits.
641
720
  for (const transactionHash of unique(options.request.sourceTransactionHashes)) {
@@ -699,7 +778,6 @@ export async function reconcileRequest(
699
778
 
700
779
  const sourceEvidenceComplete =
701
780
  sources.length > 0 && sources.every((source) => source.transfers.length > 0)
702
- let terminal = sourceEvidenceComplete
703
781
  const updatedIds = new Set<string>()
704
782
  const verified: VerifiedDeposit[] = []
705
783
  for (const source of sources) {
@@ -758,7 +836,11 @@ export async function reconcileRequest(
758
836
  source.deposits.push(deposit)
759
837
  created += 1
760
838
  }
761
- if (!Deposit.isTerminal(deposit.status)) {
839
+ if (
840
+ !Deposit.isTerminal(deposit.status) ||
841
+ deposit.providerRequestId === null ||
842
+ !deposit.providerRequestIds.includes(options.request.id)
843
+ ) {
762
844
  const result = await Deposit.transition(db, {
763
845
  expectedVersion: deposit.version,
764
846
  id: deposit.id,
@@ -784,11 +866,61 @@ export async function reconcileRequest(
784
866
  }
785
867
  if (verified.length === 0) return { created, terminal: false, updated: updatedIds.size }
786
868
  if (!sourceEvidenceComplete) return { created, terminal: false, updated: updatedIds.size }
869
+ const delivery = await reconcileDelivery(db, { ...options, verified })
870
+ for (const id of delivery.updatedIds) updatedIds.add(id)
871
+ return {
872
+ created,
873
+ ...(delivery.followUp ? { followUp: delivery.followUp } : {}),
874
+ terminal: delivery.terminal,
875
+ updated: updatedIds.size,
876
+ }
877
+ }
878
+
879
+ export declare namespace reconcileRequest {
880
+ /** Provider request, address, and settlement dependencies. */
881
+ type Options = {
882
+ /** Reusable deposit address attributed by the provider. */
883
+ address: FundingDepositAddresses.Record
884
+ /** Optional bounded operational metrics sink. */
885
+ metrics?: Metrics.Metrics | undefined
886
+ /** Provider integration that discovered the request. */
887
+ provider?: Provider.Provider | undefined
888
+ /** Provider request containing discovered chain transactions. */
889
+ request: Provider.listDepositAddressRequests.ReturnType['requests'][number]
890
+ /** Abort signal forwarded to provider delivery observation. */
891
+ signal?: AbortSignal | undefined
892
+ /** Optional pathUSD-funded subsidy settler. */
893
+ subsidies?: Subsidy.Settler | undefined
894
+ /** Mechanism that requested reconciliation. */
895
+ trigger?: Deposit.DetectionTrigger | undefined
896
+ /** Authoritative source and destination transfer verifier. */
897
+ verifyTransfers: VerifyTransfers
898
+ }
787
899
 
788
- const deliveryObservation = await observeProviderDelivery(options, verified)
900
+ /** Material changes produced by one provider request. */
901
+ type ReturnType = {
902
+ /** Number of newly detected source transfers. */
903
+ created: number
904
+ /** Work that needs a bounded delayed follow-up. */
905
+ followUp?: 'delivery' | 'settlement' | undefined
906
+ /** Whether every deposit from this request reached a terminal state. */
907
+ terminal: boolean
908
+ /** Number of existing or new deposits materially changed. */
909
+ updated: number
910
+ }
911
+ }
912
+
913
+ async function reconcileDelivery(db: Db.Db, options: ReconcileDeliveryOptions) {
914
+ let followUp: reconcileRequest.ReturnType['followUp']
915
+ let terminal = true
916
+ const updatedIds = new Set<string>()
917
+ if (options.verified.every((entry) => Deposit.isTerminal(entry.deposit.status)))
918
+ return { terminal, updatedIds }
919
+ const deliveryObservation = await observeProviderDelivery(options)
789
920
  if (deliveryObservation?.type === 'pending') followUp = 'delivery'
921
+ const providerTransactionHashes = options.request?.destinationTransactionHashes ?? []
790
922
  const destinationTransactionHashes = unique([
791
- ...options.request.destinationTransactionHashes,
923
+ ...providerTransactionHashes,
792
924
  ...(deliveryObservation?.type === 'verified'
793
925
  ? deliveryObservation.destinationTransactionHashes
794
926
  : []),
@@ -805,18 +937,18 @@ export async function reconcileRequest(
805
937
  )
806
938
  const destinationTransfers = destinationEvidence.flat()
807
939
  const providerEvidenceComplete =
808
- (options.request.status === 'success' || deliveryObservation?.type === 'verified') &&
940
+ (options.request?.status === 'success' || deliveryObservation?.type === 'verified') &&
809
941
  destinationEvidence.length > 0 &&
810
942
  destinationEvidence.every((transfers) => transfers.length > 0)
811
943
  const providerOutput = destinationTransfers.reduce(
812
944
  (total, transfer) => total + BigInt(transfer.amount),
813
945
  0n,
814
946
  )
815
- const totalRequired = verified.reduce((total, entry) => total + entry.required, 0n)
947
+ const totalRequired = options.verified.reduce((total, entry) => total + entry.required, 0n)
816
948
  let allocated = 0n
817
949
  let cumulativeRequired = 0n
818
950
 
819
- for (const entry of verified) {
951
+ for (const entry of options.verified) {
820
952
  const { required, sourceAmount } = entry
821
953
  cumulativeRequired += required
822
954
  const nextAllocated =
@@ -860,7 +992,9 @@ export async function reconcileRequest(
860
992
  }),
861
993
  }
862
994
  : {}),
863
- providerRequestIds: unique([...deposit.providerRequestIds, options.request.id]),
995
+ providerRequestIds: options.request
996
+ ? unique([...deposit.providerRequestIds, options.request.id])
997
+ : deposit.providerRequestIds,
864
998
  ...(deliveryObservation?.providerState
865
999
  ? {
866
1000
  providerState: {
@@ -871,7 +1005,7 @@ export async function reconcileRequest(
871
1005
  : {}),
872
1006
  providerTransactionHashes: unique([
873
1007
  ...deposit.providerTransactionHashes,
874
- ...options.request.destinationTransactionHashes,
1008
+ ...providerTransactionHashes,
875
1009
  ...(deliveryObservation?.providerTransactionHashes ?? []),
876
1010
  ]),
877
1011
  ...(settlement.settlementTransaction
@@ -914,41 +1048,7 @@ export async function reconcileRequest(
914
1048
  }
915
1049
  }
916
1050
  }
917
- return { created, ...(followUp ? { followUp } : {}), terminal, updated: updatedIds.size }
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
- }
1051
+ return { ...(followUp ? { followUp } : {}), terminal, updatedIds }
952
1052
  }
953
1053
 
954
1054
  type ReconcileSubsidyOptions = {
@@ -1149,8 +1249,15 @@ function nextFollowUp(
1149
1249
  kind: Provider.Webhook.receive.FollowUp['kind'],
1150
1250
  ) {
1151
1251
  const attempt = message.followUp?.kind === kind ? message.followUp.attempt + 1 : 0
1152
- if (attempt >= maximumFollowUpAttempts) return undefined
1153
- return { delaySeconds: followUpDelaySeconds[kind], state: { attempt, kind } }
1252
+ const chainProvider = kind === 'provider' && message.trigger === 'chain'
1253
+ const maximumAttempts = chainProvider
1254
+ ? chainProviderMaximumFollowUpAttempts
1255
+ : maximumFollowUpAttempts
1256
+ if (attempt >= maximumAttempts) return undefined
1257
+ return {
1258
+ delaySeconds: chainProvider ? chainProviderFollowUpDelaySeconds : followUpDelaySeconds[kind],
1259
+ state: { attempt, kind },
1260
+ }
1154
1261
  }
1155
1262
 
1156
1263
  type RecordWebhookLatencyOptions = {
@@ -230,7 +230,7 @@ describe('observe', () => {
230
230
  })(),
231
231
  }).toMatchInlineSnapshot(
232
232
  {
233
- dispatches: [[{ addressId: expect.any(String) }, { delaySeconds: 5 }]],
233
+ dispatches: [[{ addressId: expect.any(String) }, { delaySeconds: 1 }]],
234
234
  },
235
235
  `
236
236
  {
@@ -246,7 +246,7 @@ describe('observe', () => {
246
246
  "type": "funding:deposit-address:reconcile",
247
247
  },
248
248
  {
249
- "delaySeconds": 5,
249
+ "delaySeconds": 1,
250
250
  },
251
251
  ],
252
252
  ],
@@ -709,7 +709,10 @@ function createDepositAddressObserver(
709
709
  }
710
710
 
711
711
  return async (parameters, signal) => {
712
- if (!(deliveryObservationStatuses as readonly string[]).includes(parameters.request.status))
712
+ if (
713
+ parameters.request &&
714
+ !(deliveryObservationStatuses as readonly string[]).includes(parameters.request.status)
715
+ )
713
716
  return { type: 'unsupported' }
714
717
  if (parameters.sourceChain.kind !== 'evm' || parameters.destinationChain.kind !== 'evm')
715
718
  return { type: 'unsupported' }
@@ -739,7 +742,7 @@ function createDepositAddressObserver(
739
742
  )
740
743
  return { type: 'unsupported' }
741
744
 
742
- const parsedCorrelation = schema.RequestCorrelation.safeParse(parameters.request.providerState)
745
+ const parsedCorrelation = schema.RequestCorrelation.safeParse(parameters.request?.providerState)
743
746
  const correlation = parsedCorrelation.success ? parsedCorrelation.data.relay : undefined
744
747
  if (
745
748
  (correlation?.depository &&
@@ -854,6 +857,8 @@ function createDepositAddressObserver(
854
857
  }
855
858
  const destinationTransactionHashes = (() => {
856
859
  if (correlatedTransactionHashes.length > 0) return correlatedTransactionHashes
860
+ // Provider-independent completion requires the Relay order id to bind both chain legs.
861
+ if (!parameters.request) return []
857
862
  if (uncorrelatedTransactionHashes.length === 1) return uncorrelatedTransactionHashes
858
863
  return []
859
864
  })()