tapimo 0.13.0 → 0.14.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 (230) hide show
  1. package/dist/App.d.ts +6064 -4829
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +1 -0
  4. package/dist/App.js.map +1 -1
  5. package/dist/Webhooks.js +2 -2
  6. package/dist/Webhooks.js.map +1 -1
  7. package/dist/admin/App.d.ts +1008 -917
  8. package/dist/admin/App.d.ts.map +1 -1
  9. package/dist/admin/apps/earn-vaults.d.ts +48 -0
  10. package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
  11. package/dist/admin/apps/earn-vaults.js +2 -1
  12. package/dist/admin/apps/earn-vaults.js.map +1 -1
  13. package/dist/admin/apps/organizations.d.ts +92 -3
  14. package/dist/admin/apps/organizations.d.ts.map +1 -1
  15. package/dist/admin/apps/organizations.js +69 -7
  16. package/dist/admin/apps/organizations.js.map +1 -1
  17. package/dist/admin/apps/verified-tokens.d.ts +74 -74
  18. package/dist/admin/ui.generated.d.ts +1 -1
  19. package/dist/admin/ui.generated.js +69 -69
  20. package/dist/admin/ui.generated.js.map +1 -1
  21. package/dist/apps/Zones.d.ts +12 -12
  22. package/dist/apps/Zones.js +3 -3
  23. package/dist/apps/data/App.d.ts +2909 -2523
  24. package/dist/apps/data/App.d.ts.map +1 -1
  25. package/dist/apps/data/App.js +11 -6
  26. package/dist/apps/data/App.js.map +1 -1
  27. package/dist/apps/data/routes/addresses.d.ts +22 -22
  28. package/dist/apps/data/routes/earn.d.ts +183 -7
  29. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  30. package/dist/apps/data/routes/earn.js +434 -75
  31. package/dist/apps/data/routes/earn.js.map +1 -1
  32. package/dist/apps/data/routes/exchanges.d.ts +15 -0
  33. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  34. package/dist/apps/data/routes/exchanges.js +180 -114
  35. package/dist/apps/data/routes/exchanges.js.map +1 -1
  36. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  37. package/dist/apps/data/routes/fee-amm.js +18 -19
  38. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  39. package/dist/apps/data/routes/tokens.d.ts +93 -93
  40. package/dist/apps/data/routes/transfers.d.ts +12 -12
  41. package/dist/apps/data/routes/valuation.d.ts +30 -30
  42. package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
  43. package/dist/apps/data/routes/webhooks.d.ts +805 -102
  44. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  45. package/dist/apps/data/routes/webhooks.js +47 -5
  46. package/dist/apps/data/routes/webhooks.js.map +1 -1
  47. package/dist/apps/funding/App.d.ts +456 -45
  48. package/dist/apps/funding/App.d.ts.map +1 -1
  49. package/dist/apps/funding/App.js +1 -1
  50. package/dist/apps/funding/App.js.map +1 -1
  51. package/dist/apps/funding/routes/deposit-addresses.d.ts +17 -17
  52. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  53. package/dist/apps/funding/routes/deposit-addresses.js +1 -1
  54. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  55. package/dist/apps/funding/routes/transfers.d.ts +654 -73
  56. package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
  57. package/dist/apps/funding/routes/transfers.js +211 -5
  58. package/dist/apps/funding/routes/transfers.js.map +1 -1
  59. package/dist/apps/management/App.d.ts +831 -387
  60. package/dist/apps/management/App.d.ts.map +1 -1
  61. package/dist/apps/management/Billing.d.ts +1 -1
  62. package/dist/apps/management/Billing.d.ts.map +1 -1
  63. package/dist/apps/management/Billing.js +17 -1
  64. package/dist/apps/management/Billing.js.map +1 -1
  65. package/dist/apps/management/routes/api-keys.d.ts +76 -76
  66. package/dist/apps/management/routes/billing.d.ts +76 -76
  67. package/dist/apps/management/routes/faucet.d.ts +12 -12
  68. package/dist/apps/management/routes/invitations.d.ts +24 -24
  69. package/dist/apps/management/routes/invite-links.d.ts +46 -46
  70. package/dist/apps/management/routes/members.d.ts +46 -46
  71. package/dist/apps/management/routes/orgs.d.ts +20 -20
  72. package/dist/apps/management/routes/projects.d.ts +44 -44
  73. package/dist/apps/management/routes/usage.d.ts +32 -32
  74. package/dist/apps/management/routes/verified-token-requests.d.ts +16 -16
  75. package/dist/apps/management/routes/webhooks.d.ts +707 -57
  76. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  77. package/dist/apps/management/routes/webhooks.js +21 -21
  78. package/dist/apps/management/routes/webhooks.js.map +1 -1
  79. package/dist/apps/relay/App.d.ts.map +1 -1
  80. package/dist/apps/relay/App.js +115 -23
  81. package/dist/apps/relay/App.js.map +1 -1
  82. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  83. package/dist/apps/relay/Sponsorships.js +27 -1
  84. package/dist/apps/relay/Sponsorships.js.map +1 -1
  85. package/dist/db/Db.d.ts.map +1 -1
  86. package/dist/db/Db.js +22 -0
  87. package/dist/db/Db.js.map +1 -1
  88. package/dist/db/Schema.d.ts +8 -3
  89. package/dist/db/Schema.d.ts.map +1 -1
  90. package/dist/db/Schema.js +16 -1
  91. package/dist/db/Schema.js.map +1 -1
  92. package/dist/db/tables/fundingTransferTransactions.d.ts +38 -0
  93. package/dist/db/tables/fundingTransferTransactions.d.ts.map +1 -1
  94. package/dist/db/tables/fundingTransferTransactions.js +44 -3
  95. package/dist/db/tables/fundingTransferTransactions.js.map +1 -1
  96. package/dist/db/tables/fundingTransfers.d.ts +17 -0
  97. package/dist/db/tables/fundingTransfers.d.ts.map +1 -1
  98. package/dist/db/tables/fundingTransfers.js +21 -0
  99. package/dist/db/tables/fundingTransfers.js.map +1 -1
  100. package/dist/db/tables/organizations.d.ts +24 -0
  101. package/dist/db/tables/organizations.d.ts.map +1 -1
  102. package/dist/db/tables/organizations.js +27 -0
  103. package/dist/db/tables/organizations.js.map +1 -1
  104. package/dist/db/tables/projects.d.ts +25 -1
  105. package/dist/db/tables/projects.d.ts.map +1 -1
  106. package/dist/db/tables/projects.js +26 -0
  107. package/dist/db/tables/projects.js.map +1 -1
  108. package/dist/db/tables/sponsoredTransactions.d.ts +44 -0
  109. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  110. package/dist/db/tables/sponsoredTransactions.js +146 -13
  111. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  112. package/dist/db/tables/webhookSubscriptions.d.ts +10 -6
  113. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  114. package/dist/db/tables/webhookSubscriptions.js +9 -6
  115. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  116. package/dist/internal/EarnVaults.js +5 -5
  117. package/dist/internal/EarnVaults.js.map +1 -1
  118. package/dist/internal/Log.d.ts +1 -1
  119. package/dist/internal/Log.d.ts.map +1 -1
  120. package/dist/internal/Log.js.map +1 -1
  121. package/dist/internal/Viem.d.ts +16 -1
  122. package/dist/internal/Viem.d.ts.map +1 -1
  123. package/dist/internal/Viem.js +12 -2
  124. package/dist/internal/Viem.js.map +1 -1
  125. package/dist/internal/funding/DepositWebhook.d.ts.map +1 -1
  126. package/dist/internal/funding/DepositWebhook.js +2 -1
  127. package/dist/internal/funding/DepositWebhook.js.map +1 -1
  128. package/dist/internal/funding/Evidence.d.ts.map +1 -1
  129. package/dist/internal/funding/Evidence.js +2 -10
  130. package/dist/internal/funding/Evidence.js.map +1 -1
  131. package/dist/internal/funding/Provider.d.ts +40 -0
  132. package/dist/internal/funding/Provider.d.ts.map +1 -1
  133. package/dist/internal/funding/Provider.js +5 -0
  134. package/dist/internal/funding/Provider.js.map +1 -1
  135. package/dist/internal/funding/SourceObservation.d.ts.map +1 -1
  136. package/dist/internal/funding/SourceObservation.js +5 -7
  137. package/dist/internal/funding/SourceObservation.js.map +1 -1
  138. package/dist/internal/funding/Transfer.d.ts +101 -6
  139. package/dist/internal/funding/Transfer.d.ts.map +1 -1
  140. package/dist/internal/funding/Transfer.js +162 -34
  141. package/dist/internal/funding/Transfer.js.map +1 -1
  142. package/dist/internal/funding/TransferReconciliation.d.ts +52 -0
  143. package/dist/internal/funding/TransferReconciliation.d.ts.map +1 -0
  144. package/dist/internal/funding/TransferReconciliation.js +192 -0
  145. package/dist/internal/funding/TransferReconciliation.js.map +1 -0
  146. package/dist/internal/funding/TransferWebhook.d.ts +21 -0
  147. package/dist/internal/funding/TransferWebhook.d.ts.map +1 -0
  148. package/dist/internal/funding/TransferWebhook.js +43 -0
  149. package/dist/internal/funding/TransferWebhook.js.map +1 -0
  150. package/dist/internal/funding/index.d.ts +1 -0
  151. package/dist/internal/funding/index.d.ts.map +1 -1
  152. package/dist/internal/funding/index.js +1 -0
  153. package/dist/internal/funding/index.js.map +1 -1
  154. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  155. package/dist/internal/funding/providers/relay.js +3 -12
  156. package/dist/internal/funding/providers/relay.js.map +1 -1
  157. package/dist/internal/funding/providers/stargate.d.ts +99 -0
  158. package/dist/internal/funding/providers/stargate.d.ts.map +1 -1
  159. package/dist/internal/funding/providers/stargate.js +337 -35
  160. package/dist/internal/funding/providers/stargate.js.map +1 -1
  161. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
  162. package/dist/internal/webhookDestinations/slack.js +16 -0
  163. package/dist/internal/webhookDestinations/slack.js.map +1 -1
  164. package/package.json +1 -1
  165. package/src/App.ts +1 -0
  166. package/src/Client.test-d.ts +83 -2
  167. package/src/Webhooks.ts +2 -2
  168. package/src/admin/apps/earn-vaults.ts +2 -1
  169. package/src/admin/apps/organizations.test.ts +53 -1
  170. package/src/admin/apps/organizations.ts +90 -5
  171. package/src/admin/ui/src/lib/organizations.test.ts +112 -1
  172. package/src/admin/ui/src/lib/organizations.ts +55 -0
  173. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +116 -0
  174. package/src/admin/ui.generated.ts +69 -69
  175. package/src/apps/Zones.test.ts +3 -3
  176. package/src/apps/Zones.ts +3 -3
  177. package/src/apps/composition.test.ts +1 -0
  178. package/src/apps/data/App.ts +15 -6
  179. package/src/apps/data/routes/earn.test.ts +369 -14
  180. package/src/apps/data/routes/earn.ts +548 -79
  181. package/src/apps/data/routes/exchanges.test.ts +740 -15
  182. package/src/apps/data/routes/exchanges.ts +954 -753
  183. package/src/apps/data/routes/fee-amm.ts +17 -18
  184. package/src/apps/data/routes/webhooks.test.ts +21 -5
  185. package/src/apps/data/routes/webhooks.ts +63 -8
  186. package/src/apps/funding/App.ts +12 -2
  187. package/src/apps/funding/routes/deposit-addresses.test.ts +4 -4
  188. package/src/apps/funding/routes/deposit-addresses.ts +2 -2
  189. package/src/apps/funding/routes/transfers.test.ts +510 -19
  190. package/src/apps/funding/routes/transfers.ts +271 -5
  191. package/src/apps/management/Billing.ts +18 -5
  192. package/src/apps/management/routes/webhooks.test.ts +3 -3
  193. package/src/apps/management/routes/webhooks.ts +24 -25
  194. package/src/apps/relay/App.test.ts +212 -1
  195. package/src/apps/relay/App.ts +158 -29
  196. package/src/apps/relay/Sponsorships.ts +24 -1
  197. package/src/db/Db.ts +35 -0
  198. package/src/db/Schema.ts +17 -2
  199. package/src/db/tables/fundingTransferTransactions.test.ts +43 -0
  200. package/src/db/tables/fundingTransferTransactions.ts +79 -3
  201. package/src/db/tables/fundingTransfers.test.ts +26 -0
  202. package/src/db/tables/fundingTransfers.ts +36 -0
  203. package/src/db/tables/organizations.test.ts +41 -0
  204. package/src/db/tables/organizations.ts +49 -0
  205. package/src/db/tables/projects.test.ts +55 -0
  206. package/src/db/tables/projects.ts +46 -1
  207. package/src/db/tables/sponsoredTransactions.test.ts +338 -0
  208. package/src/db/tables/sponsoredTransactions.ts +197 -13
  209. package/src/db/tables/webhookSubscriptions.ts +18 -11
  210. package/src/internal/EarnVaults.ts +5 -5
  211. package/src/internal/Log.test-d.ts +2 -0
  212. package/src/internal/Log.ts +2 -0
  213. package/src/internal/Viem.ts +39 -3
  214. package/src/internal/Webhooks.test.ts +45 -0
  215. package/src/internal/funding/DepositWebhook.ts +2 -1
  216. package/src/internal/funding/Evidence.ts +2 -13
  217. package/src/internal/funding/Provider.ts +43 -0
  218. package/src/internal/funding/SourceObservation.ts +5 -9
  219. package/src/internal/funding/Transfer.test.ts +155 -0
  220. package/src/internal/funding/Transfer.ts +254 -37
  221. package/src/internal/funding/TransferReconciliation.test.ts +147 -0
  222. package/src/internal/funding/TransferReconciliation.ts +264 -0
  223. package/src/internal/funding/TransferWebhook.test.ts +128 -0
  224. package/src/internal/funding/TransferWebhook.ts +67 -0
  225. package/src/internal/funding/index.test.ts +1 -0
  226. package/src/internal/funding/index.ts +1 -0
  227. package/src/internal/funding/providers/relay.ts +3 -15
  228. package/src/internal/funding/providers/stargate.test.ts +169 -4
  229. package/src/internal/funding/providers/stargate.ts +498 -51
  230. package/src/internal/webhookDestinations/slack.ts +15 -0
@@ -13,20 +13,33 @@ import * as Runtime from '../../../../test/runtime.js'
13
13
  import * as Exchanges from './exchanges.js'
14
14
 
15
15
  type JsonSchema = {
16
+ $ref?: string
16
17
  anyOf?: readonly JsonSchema[]
18
+ description?: string
19
+ enum?: readonly string[]
17
20
  examples?: readonly unknown[]
21
+ format?: string
22
+ items?: JsonSchema
18
23
  properties?: Record<string, JsonSchema>
19
24
  required?: readonly string[]
20
25
  }
26
+ type OpenApiResponse = {
27
+ $ref?: string
28
+ content?: Record<string, { schema?: JsonSchema }>
29
+ }
30
+ type OpenApiOperation = {
31
+ operationId?: string
32
+ requestBody?: { content?: Record<string, { schema?: JsonSchema }> }
33
+ responses?: Record<string, OpenApiResponse>
34
+ }
21
35
  type OrdersResponse = z.output<typeof Exchanges.schema.getOrders.Response>
22
36
  type OpenApiDocument = {
37
+ components: { schemas: Record<string, JsonSchema> }
23
38
  paths: Record<
24
39
  string,
25
40
  {
26
- post?: {
27
- requestBody?: { content?: Record<string, { schema?: JsonSchema }> }
28
- responses?: Record<string, { content?: Record<string, { schema?: JsonSchema }> }>
29
- }
41
+ get?: OpenApiOperation
42
+ post?: OpenApiOperation
30
43
  }
31
44
  >
32
45
  }
@@ -42,6 +55,704 @@ const fixed = FxOracle.from({
42
55
  }),
43
56
  })
44
57
 
58
+ function resolveSchema(spec: OpenApiDocument, schema: JsonSchema | undefined): JsonSchema {
59
+ if (!schema) throw new Error('Missing OpenAPI schema')
60
+ if (!schema.$ref) return schema
61
+ const name = schema.$ref.split('/').at(-1)
62
+ if (!name) throw new Error(`Invalid OpenAPI component ref ${schema.$ref}`)
63
+ return component(spec, name)
64
+ }
65
+
66
+ function component(spec: OpenApiDocument, name: string): JsonSchema {
67
+ const schema = spec.components.schemas[name]
68
+ if (!schema) throw new Error(`Missing OpenAPI component ${name}`)
69
+ return schema
70
+ }
71
+
72
+ describe('OpenAPI', () => {
73
+ test('publishes generator-ready exchange contracts', async () => {
74
+ const app = TestApp.create({ auth: false })
75
+ const spec = (await (await app.request('/openapi.json')).json()) as OpenApiDocument
76
+ const operations = {
77
+ createQuote: spec.paths['/v1/exchange/quotes']?.post,
78
+ finalizeQuote: spec.paths['/v1/exchange/quotes/execute']?.post,
79
+ getDepth: spec.paths['/v1/exchange/pairs/{base}/depth']?.get,
80
+ getOhlc: spec.paths['/v1/exchange/pairs/{base}/ohlc']?.get,
81
+ getOrder: spec.paths['/v1/exchange/orders/{orderId}']?.get,
82
+ getOrderFills: spec.paths['/v1/exchange/orders/{orderId}/fills']?.get,
83
+ getOrders: spec.paths['/v1/exchange/orders']?.get,
84
+ getPair: spec.paths['/v1/exchange/pairs/{base}']?.get,
85
+ getPairs: spec.paths['/v1/exchange/pairs']?.get,
86
+ getSwaps: spec.paths['/v1/exchange/swaps']?.get,
87
+ }
88
+ if (Object.values(operations).some((operation) => !operation))
89
+ throw new Error('Missing exchange OpenAPI operation')
90
+
91
+ const summarizeError = (response: OpenApiResponse | undefined) => {
92
+ if (!response) return undefined
93
+ if (response.$ref) return { $ref: response.$ref }
94
+ const schema = response.content?.['application/json']?.schema
95
+ return resolveSchema(spec, schema).properties?.['error']?.properties?.['code']?.enum
96
+ }
97
+ const summarize = (operation: OpenApiOperation | undefined) => {
98
+ if (!operation) throw new Error('Missing exchange OpenAPI operation')
99
+ return {
100
+ errors: Object.fromEntries(
101
+ [400, 401, 402, 403, 404, 429, 500, 502, 504]
102
+ .filter((status) => operation.responses?.[status])
103
+ .map((status) => [status, summarizeError(operation.responses?.[status])]),
104
+ ),
105
+ operationId: operation.operationId,
106
+ request: operation.requestBody?.content?.['application/json']?.schema,
107
+ response: operation.responses?.['200']?.content?.['application/json']?.schema,
108
+ }
109
+ }
110
+ const components = {
111
+ depth: component(spec, 'ExchangePairDepth'),
112
+ ohlc: component(spec, 'ExchangePairOhlc'),
113
+ ohlcBucket: component(spec, 'ExchangeOhlcBucket'),
114
+ order: component(spec, 'ExchangeRestingOrder'),
115
+ orderFill: component(spec, 'ExchangeOrderFill'),
116
+ orderFillList: component(spec, 'ExchangeOrderFillList'),
117
+ orderList: component(spec, 'ExchangeOrderList'),
118
+ pair: component(spec, 'ExchangePair'),
119
+ pairList: component(spec, 'ExchangePairList'),
120
+ quote: component(spec, 'ExchangeQuote'),
121
+ quoteApproval: component(spec, 'ExchangeQuoteExactSourceApprovalRequired'),
122
+ quoteReady: component(spec, 'ExchangeQuoteExactSourceReady'),
123
+ quoteSignature: component(spec, 'ExchangeQuoteExactSourceSignatureRequired'),
124
+ swap: component(spec, 'ExchangeSwap'),
125
+ swapList: component(spec, 'ExchangeSwapList'),
126
+ }
127
+ const quote = components.quote
128
+ const quoteVariants = quote.anyOf ?? []
129
+ const exchangeComponentNames = Object.keys(spec.components.schemas)
130
+ .filter((name) => name.includes('Exchange'))
131
+ .sort()
132
+
133
+ expect({
134
+ components: exchangeComponentNames,
135
+ formats: {
136
+ ohlc: components.ohlcBucket.properties?.['timestamp']?.format,
137
+ order: components.order.properties?.['placedAt']?.format,
138
+ orderFill: components.orderFill.properties?.['filledAt']?.format,
139
+ pair: components.pair.properties?.['timestamp']?.format,
140
+ swap: components.swap.properties?.['filledAt']?.format,
141
+ },
142
+ models: {
143
+ depthLevels: components.depth.properties?.['asks']?.items,
144
+ ohlcBuckets: components.ohlc.properties?.['data']?.items,
145
+ orderFills: components.orderFillList.properties?.['data']?.items,
146
+ orderFillsMeta: components.orderFillList.properties?.['meta'],
147
+ orders: components.orderList.properties?.['data']?.items,
148
+ ordersMeta: components.orderList.properties?.['meta'],
149
+ pairDetail: operations.getPair?.responses?.['200']?.content?.['application/json']?.schema,
150
+ pairs: components.pairList.properties?.['data']?.items,
151
+ pairsMeta: components.pairList.properties?.['meta'],
152
+ quoteApproval: components.quoteApproval.properties?.['approval'],
153
+ quoteAmount: components.quoteSignature.properties?.['sourceAmount'],
154
+ quoteMeta: components.quoteSignature.properties?.['meta'],
155
+ quoteToken: components.quoteSignature.properties?.['sourceToken'],
156
+ quoteTransaction: components.quoteReady.properties?.['transaction'],
157
+ quoteTypedData: components.quoteSignature.properties?.['typedData'],
158
+ quoteVariants: {
159
+ count: quoteVariants.length,
160
+ referenced: quoteVariants.every((variant) => variant.$ref),
161
+ schemas: quoteVariants,
162
+ },
163
+ swapFills: components.swap.properties?.['fills']?.items,
164
+ swaps: components.swapList.properties?.['data']?.items,
165
+ swapsMeta: components.swapList.properties?.['meta'],
166
+ },
167
+ operations: Object.fromEntries(
168
+ Object.entries(operations).map(([name, operation]) => [name, summarize(operation)]),
169
+ ),
170
+ undescribedComponents: exchangeComponentNames.filter(
171
+ (name) => !component(spec, name).description,
172
+ ),
173
+ }).toMatchInlineSnapshot(`
174
+ {
175
+ "components": [
176
+ "CreateExchangeQuoteRequest",
177
+ "ExchangeDepthLevel",
178
+ "ExchangeOhlcBucket",
179
+ "ExchangeOrder",
180
+ "ExchangeOrderFill",
181
+ "ExchangeOrderFillList",
182
+ "ExchangeOrderFillListMeta",
183
+ "ExchangeOrderList",
184
+ "ExchangeOrderListMeta",
185
+ "ExchangeOrderPair",
186
+ "ExchangePair",
187
+ "ExchangePairDepth",
188
+ "ExchangePairList",
189
+ "ExchangePairListMeta",
190
+ "ExchangePairOhlc",
191
+ "ExchangePairToken",
192
+ "ExchangeQuote",
193
+ "ExchangeQuoteApproval",
194
+ "ExchangeQuoteCall",
195
+ "ExchangeQuoteDestinationToken",
196
+ "ExchangeQuoteExactDestinationApprovalRequired",
197
+ "ExchangeQuoteExactDestinationReady",
198
+ "ExchangeQuoteExactDestinationSignatureRequired",
199
+ "ExchangeQuoteExactSourceApprovalRequired",
200
+ "ExchangeQuoteExactSourceReady",
201
+ "ExchangeQuoteExactSourceSignatureRequired",
202
+ "ExchangeQuoteMeta",
203
+ "ExchangeQuoteSourceToken",
204
+ "ExchangeQuoteTransaction",
205
+ "ExchangeQuoteTypedData",
206
+ "ExchangeRestingOrder",
207
+ "ExchangeSwap",
208
+ "ExchangeSwapFill",
209
+ "ExchangeSwapList",
210
+ "ExchangeSwapListMeta",
211
+ "ExchangeSwapToken",
212
+ "ExchangeValuedAmount",
213
+ "FinalizeExchangeQuoteRequest",
214
+ "FinalizedExchangeQuote",
215
+ ],
216
+ "formats": {
217
+ "ohlc": "date-time",
218
+ "order": "date-time",
219
+ "orderFill": "date-time",
220
+ "pair": "date-time",
221
+ "swap": "date-time",
222
+ },
223
+ "models": {
224
+ "depthLevels": {
225
+ "$ref": "#/components/schemas/ExchangeDepthLevel",
226
+ },
227
+ "ohlcBuckets": {
228
+ "$ref": "#/components/schemas/ExchangeOhlcBucket",
229
+ },
230
+ "orderFills": {
231
+ "$ref": "#/components/schemas/ExchangeOrderFill",
232
+ },
233
+ "orderFillsMeta": {
234
+ "$ref": "#/components/schemas/ExchangeOrderFillListMeta",
235
+ },
236
+ "orders": {
237
+ "$ref": "#/components/schemas/ExchangeRestingOrder",
238
+ },
239
+ "ordersMeta": {
240
+ "$ref": "#/components/schemas/ExchangeOrderListMeta",
241
+ },
242
+ "pairDetail": {
243
+ "$ref": "#/components/schemas/ExchangePair",
244
+ },
245
+ "pairs": {
246
+ "$ref": "#/components/schemas/ExchangePair",
247
+ },
248
+ "pairsMeta": {
249
+ "$ref": "#/components/schemas/ExchangePairListMeta",
250
+ },
251
+ "quoteAmount": {
252
+ "$ref": "#/components/schemas/ExchangeValuedAmount",
253
+ },
254
+ "quoteApproval": {
255
+ "$ref": "#/components/schemas/ExchangeQuoteApproval",
256
+ },
257
+ "quoteMeta": {
258
+ "$ref": "#/components/schemas/ExchangeQuoteMeta",
259
+ },
260
+ "quoteToken": {
261
+ "$ref": "#/components/schemas/ExchangeQuoteSourceToken",
262
+ },
263
+ "quoteTransaction": {
264
+ "$ref": "#/components/schemas/ExchangeQuoteTransaction",
265
+ },
266
+ "quoteTypedData": {
267
+ "$ref": "#/components/schemas/ExchangeQuoteTypedData",
268
+ },
269
+ "quoteVariants": {
270
+ "count": 6,
271
+ "referenced": true,
272
+ "schemas": [
273
+ {
274
+ "$ref": "#/components/schemas/ExchangeQuoteExactSourceApprovalRequired",
275
+ },
276
+ {
277
+ "$ref": "#/components/schemas/ExchangeQuoteExactSourceReady",
278
+ },
279
+ {
280
+ "$ref": "#/components/schemas/ExchangeQuoteExactSourceSignatureRequired",
281
+ },
282
+ {
283
+ "$ref": "#/components/schemas/ExchangeQuoteExactDestinationApprovalRequired",
284
+ },
285
+ {
286
+ "$ref": "#/components/schemas/ExchangeQuoteExactDestinationReady",
287
+ },
288
+ {
289
+ "$ref": "#/components/schemas/ExchangeQuoteExactDestinationSignatureRequired",
290
+ },
291
+ ],
292
+ },
293
+ "swapFills": {
294
+ "$ref": "#/components/schemas/ExchangeSwapFill",
295
+ },
296
+ "swaps": {
297
+ "$ref": "#/components/schemas/ExchangeSwap",
298
+ },
299
+ "swapsMeta": {
300
+ "$ref": "#/components/schemas/ExchangeSwapListMeta",
301
+ },
302
+ },
303
+ "operations": {
304
+ "createQuote": {
305
+ "errors": {
306
+ "400": [
307
+ "api_key_malformed",
308
+ "body_invalid",
309
+ "chain_id_invalid",
310
+ "chain_id_unsupported",
311
+ "query_invalid",
312
+ "quote_amount_out_of_range",
313
+ ],
314
+ "401": [
315
+ "api_key_invalid",
316
+ "api_key_missing",
317
+ "unauthorized",
318
+ ],
319
+ "402": {
320
+ "$ref": "#/components/responses/PaymentRequired",
321
+ },
322
+ "403": [
323
+ "api_key_forbidden",
324
+ "api_key_ip_forbidden",
325
+ "forbidden",
326
+ ],
327
+ "404": [
328
+ "quote_not_available",
329
+ ],
330
+ "429": {
331
+ "$ref": "#/components/responses/RateLimited",
332
+ },
333
+ "500": {
334
+ "$ref": "#/components/responses/InternalError",
335
+ },
336
+ "502": [
337
+ "upstream_error",
338
+ ],
339
+ "504": {
340
+ "$ref": "#/components/responses/RequestTimeout",
341
+ },
342
+ },
343
+ "operationId": "createExchangeQuote",
344
+ "request": {
345
+ "$ref": "#/components/schemas/CreateExchangeQuoteRequest",
346
+ },
347
+ "response": {
348
+ "$ref": "#/components/schemas/ExchangeQuote",
349
+ },
350
+ },
351
+ "finalizeQuote": {
352
+ "errors": {
353
+ "400": [
354
+ "api_key_malformed",
355
+ "body_invalid",
356
+ "chain_id_invalid",
357
+ "chain_id_unsupported",
358
+ "query_invalid",
359
+ "swap_continuation_invalid",
360
+ "swap_provider_invalid",
361
+ ],
362
+ "401": [
363
+ "api_key_invalid",
364
+ "api_key_missing",
365
+ "unauthorized",
366
+ ],
367
+ "402": {
368
+ "$ref": "#/components/responses/PaymentRequired",
369
+ },
370
+ "403": [
371
+ "api_key_forbidden",
372
+ "api_key_ip_forbidden",
373
+ "forbidden",
374
+ ],
375
+ "429": {
376
+ "$ref": "#/components/responses/RateLimited",
377
+ },
378
+ "500": {
379
+ "$ref": "#/components/responses/InternalError",
380
+ },
381
+ "502": [
382
+ "upstream_error",
383
+ ],
384
+ "504": {
385
+ "$ref": "#/components/responses/RequestTimeout",
386
+ },
387
+ },
388
+ "operationId": "finalizeExchangeQuote",
389
+ "request": {
390
+ "$ref": "#/components/schemas/FinalizeExchangeQuoteRequest",
391
+ },
392
+ "response": {
393
+ "$ref": "#/components/schemas/FinalizedExchangeQuote",
394
+ },
395
+ },
396
+ "getDepth": {
397
+ "errors": {
398
+ "400": [
399
+ "api_key_malformed",
400
+ "chain_id_invalid",
401
+ "chain_id_unsupported",
402
+ "pair_invalid",
403
+ "query_invalid",
404
+ ],
405
+ "401": [
406
+ "api_key_invalid",
407
+ "api_key_missing",
408
+ "unauthorized",
409
+ ],
410
+ "402": {
411
+ "$ref": "#/components/responses/PaymentRequired",
412
+ },
413
+ "403": [
414
+ "api_key_forbidden",
415
+ "api_key_ip_forbidden",
416
+ "forbidden",
417
+ ],
418
+ "404": [
419
+ "pair_not_found",
420
+ ],
421
+ "429": {
422
+ "$ref": "#/components/responses/RateLimited",
423
+ },
424
+ "500": {
425
+ "$ref": "#/components/responses/InternalError",
426
+ },
427
+ "502": [
428
+ "upstream_error",
429
+ ],
430
+ "504": {
431
+ "$ref": "#/components/responses/RequestTimeout",
432
+ },
433
+ },
434
+ "operationId": "getPairDepth",
435
+ "request": undefined,
436
+ "response": {
437
+ "$ref": "#/components/schemas/ExchangePairDepth",
438
+ },
439
+ },
440
+ "getOhlc": {
441
+ "errors": {
442
+ "400": [
443
+ "api_key_malformed",
444
+ "chain_id_invalid",
445
+ "chain_id_unsupported",
446
+ "pair_invalid",
447
+ "query_invalid",
448
+ ],
449
+ "401": [
450
+ "api_key_invalid",
451
+ "api_key_missing",
452
+ "unauthorized",
453
+ ],
454
+ "402": {
455
+ "$ref": "#/components/responses/PaymentRequired",
456
+ },
457
+ "403": [
458
+ "api_key_forbidden",
459
+ "api_key_ip_forbidden",
460
+ "forbidden",
461
+ ],
462
+ "404": [
463
+ "pair_not_found",
464
+ ],
465
+ "429": {
466
+ "$ref": "#/components/responses/RateLimited",
467
+ },
468
+ "500": {
469
+ "$ref": "#/components/responses/InternalError",
470
+ },
471
+ "502": [
472
+ "upstream_error",
473
+ ],
474
+ "504": {
475
+ "$ref": "#/components/responses/RequestTimeout",
476
+ },
477
+ },
478
+ "operationId": "getPairOhlc",
479
+ "request": undefined,
480
+ "response": {
481
+ "$ref": "#/components/schemas/ExchangePairOhlc",
482
+ },
483
+ },
484
+ "getOrder": {
485
+ "errors": {
486
+ "400": [
487
+ "api_key_malformed",
488
+ "chain_id_invalid",
489
+ "chain_id_unsupported",
490
+ "order_invalid",
491
+ "query_invalid",
492
+ ],
493
+ "401": [
494
+ "api_key_invalid",
495
+ "api_key_missing",
496
+ "unauthorized",
497
+ ],
498
+ "402": {
499
+ "$ref": "#/components/responses/PaymentRequired",
500
+ },
501
+ "403": [
502
+ "api_key_forbidden",
503
+ "api_key_ip_forbidden",
504
+ "forbidden",
505
+ ],
506
+ "404": [
507
+ "order_not_found",
508
+ ],
509
+ "429": {
510
+ "$ref": "#/components/responses/RateLimited",
511
+ },
512
+ "500": {
513
+ "$ref": "#/components/responses/InternalError",
514
+ },
515
+ "502": [
516
+ "upstream_error",
517
+ ],
518
+ "504": {
519
+ "$ref": "#/components/responses/RequestTimeout",
520
+ },
521
+ },
522
+ "operationId": "getOrder",
523
+ "request": undefined,
524
+ "response": {
525
+ "$ref": "#/components/schemas/ExchangeOrder",
526
+ },
527
+ },
528
+ "getOrderFills": {
529
+ "errors": {
530
+ "400": [
531
+ "api_key_malformed",
532
+ "chain_id_invalid",
533
+ "chain_id_unsupported",
534
+ "order_invalid",
535
+ "query_invalid",
536
+ ],
537
+ "401": [
538
+ "api_key_invalid",
539
+ "api_key_missing",
540
+ "unauthorized",
541
+ ],
542
+ "402": {
543
+ "$ref": "#/components/responses/PaymentRequired",
544
+ },
545
+ "403": [
546
+ "api_key_forbidden",
547
+ "api_key_ip_forbidden",
548
+ "forbidden",
549
+ ],
550
+ "429": {
551
+ "$ref": "#/components/responses/RateLimited",
552
+ },
553
+ "500": {
554
+ "$ref": "#/components/responses/InternalError",
555
+ },
556
+ "502": [
557
+ "upstream_error",
558
+ ],
559
+ "504": {
560
+ "$ref": "#/components/responses/RequestTimeout",
561
+ },
562
+ },
563
+ "operationId": "getOrderFills",
564
+ "request": undefined,
565
+ "response": {
566
+ "$ref": "#/components/schemas/ExchangeOrderFillList",
567
+ },
568
+ },
569
+ "getOrders": {
570
+ "errors": {
571
+ "400": [
572
+ "api_key_malformed",
573
+ "chain_id_invalid",
574
+ "chain_id_unsupported",
575
+ "query_invalid",
576
+ ],
577
+ "401": [
578
+ "api_key_invalid",
579
+ "api_key_missing",
580
+ "unauthorized",
581
+ ],
582
+ "402": {
583
+ "$ref": "#/components/responses/PaymentRequired",
584
+ },
585
+ "403": [
586
+ "api_key_forbidden",
587
+ "api_key_ip_forbidden",
588
+ "forbidden",
589
+ ],
590
+ "404": [
591
+ "pair_not_found",
592
+ ],
593
+ "429": {
594
+ "$ref": "#/components/responses/RateLimited",
595
+ },
596
+ "500": {
597
+ "$ref": "#/components/responses/InternalError",
598
+ },
599
+ "502": [
600
+ "upstream_error",
601
+ ],
602
+ "504": {
603
+ "$ref": "#/components/responses/RequestTimeout",
604
+ },
605
+ },
606
+ "operationId": "getOrders",
607
+ "request": undefined,
608
+ "response": {
609
+ "$ref": "#/components/schemas/ExchangeOrderList",
610
+ },
611
+ },
612
+ "getPair": {
613
+ "errors": {
614
+ "400": [
615
+ "api_key_malformed",
616
+ "chain_id_invalid",
617
+ "chain_id_unsupported",
618
+ "pair_invalid",
619
+ "query_invalid",
620
+ ],
621
+ "401": [
622
+ "api_key_invalid",
623
+ "api_key_missing",
624
+ "unauthorized",
625
+ ],
626
+ "402": {
627
+ "$ref": "#/components/responses/PaymentRequired",
628
+ },
629
+ "403": [
630
+ "api_key_forbidden",
631
+ "api_key_ip_forbidden",
632
+ "forbidden",
633
+ ],
634
+ "404": [
635
+ "pair_not_found",
636
+ ],
637
+ "429": {
638
+ "$ref": "#/components/responses/RateLimited",
639
+ },
640
+ "500": {
641
+ "$ref": "#/components/responses/InternalError",
642
+ },
643
+ "502": [
644
+ "upstream_error",
645
+ ],
646
+ "504": {
647
+ "$ref": "#/components/responses/RequestTimeout",
648
+ },
649
+ },
650
+ "operationId": "getPair",
651
+ "request": undefined,
652
+ "response": {
653
+ "$ref": "#/components/schemas/ExchangePair",
654
+ },
655
+ },
656
+ "getPairs": {
657
+ "errors": {
658
+ "400": [
659
+ "api_key_malformed",
660
+ "chain_id_invalid",
661
+ "chain_id_unsupported",
662
+ "query_invalid",
663
+ ],
664
+ "401": [
665
+ "api_key_invalid",
666
+ "api_key_missing",
667
+ "unauthorized",
668
+ ],
669
+ "402": {
670
+ "$ref": "#/components/responses/PaymentRequired",
671
+ },
672
+ "403": [
673
+ "api_key_forbidden",
674
+ "api_key_ip_forbidden",
675
+ "forbidden",
676
+ ],
677
+ "429": {
678
+ "$ref": "#/components/responses/RateLimited",
679
+ },
680
+ "500": {
681
+ "$ref": "#/components/responses/InternalError",
682
+ },
683
+ "502": [
684
+ "upstream_error",
685
+ ],
686
+ "504": {
687
+ "$ref": "#/components/responses/RequestTimeout",
688
+ },
689
+ },
690
+ "operationId": "getPairs",
691
+ "request": undefined,
692
+ "response": {
693
+ "$ref": "#/components/schemas/ExchangePairList",
694
+ },
695
+ },
696
+ "getSwaps": {
697
+ "errors": {
698
+ "400": [
699
+ "api_key_malformed",
700
+ "chain_id_invalid",
701
+ "chain_id_unsupported",
702
+ "query_invalid",
703
+ ],
704
+ "401": [
705
+ "api_key_invalid",
706
+ "api_key_missing",
707
+ "unauthorized",
708
+ ],
709
+ "402": {
710
+ "$ref": "#/components/responses/PaymentRequired",
711
+ },
712
+ "403": [
713
+ "api_key_forbidden",
714
+ "api_key_ip_forbidden",
715
+ "forbidden",
716
+ ],
717
+ "429": {
718
+ "$ref": "#/components/responses/RateLimited",
719
+ },
720
+ "500": {
721
+ "$ref": "#/components/responses/InternalError",
722
+ },
723
+ "502": [
724
+ "upstream_error",
725
+ ],
726
+ "504": {
727
+ "$ref": "#/components/responses/RequestTimeout",
728
+ },
729
+ },
730
+ "operationId": "getSwaps",
731
+ "request": undefined,
732
+ "response": {
733
+ "$ref": "#/components/schemas/ExchangeSwapList",
734
+ },
735
+ },
736
+ },
737
+ "undescribedComponents": [],
738
+ }
739
+ `)
740
+ })
741
+
742
+ test('returns exact chain and pair validation errors', async () => {
743
+ const app = TestApp.create({ auth: false })
744
+ const invalidChain = await app.request('/v1/exchange/pairs?chainId=abc')
745
+ const unsupportedChain = await app.request('/v1/exchange/pairs?chainId=999999')
746
+ const invalidPair = await app.request('/v1/exchange/pairs/not-an-address')
747
+
748
+ expect([
749
+ (await TestApp.json(invalidChain, Schema.ErrorResponse)).error.code,
750
+ (await TestApp.json(unsupportedChain, Schema.ErrorResponse)).error.code,
751
+ (await TestApp.json(invalidPair, Schema.ErrorResponse)).error.code,
752
+ ]).toStrictEqual(['chain_id_invalid', 'chain_id_unsupported', 'pair_invalid'])
753
+ })
754
+ })
755
+
45
756
  describe('schema.createQuote.TypedData', () => {
46
757
  test('accepts provider-specific EIP-712 types', () => {
47
758
  expect(
@@ -949,7 +1660,10 @@ describe('POST and QUERY /exchange/quotes', () => {
949
1660
  const operation = spec.paths['/v1/exchange/quotes']?.post
950
1661
  if (!operation) throw new Error('Missing create quote OpenAPI operation')
951
1662
 
952
- const requestSchema = operation.requestBody?.content?.['application/json']?.schema
1663
+ const requestSchema = resolveSchema(
1664
+ spec,
1665
+ operation.requestBody?.content?.['application/json']?.schema,
1666
+ )
953
1667
  const requestExample = Object.fromEntries(
954
1668
  ['account', 'amount', 'destinationToken', 'mode', 'slippageBps', 'sourceToken'].map(
955
1669
  (property) => [property, requestSchema?.properties?.[property]?.examples?.[0]],
@@ -965,16 +1679,20 @@ describe('POST and QUERY /exchange/quotes', () => {
965
1679
  expect(requestExample['account']).toMatch(/^0x[\da-f]{40}$/)
966
1680
  expect(Exchanges.schema.createQuote.Request.safeParse(requestExample).success).toBe(true)
967
1681
 
968
- const responseSchema = operation.responses?.['200']?.content?.['application/json']?.schema
1682
+ const responseSchema = resolveSchema(
1683
+ spec,
1684
+ operation.responses?.['200']?.content?.['application/json']?.schema,
1685
+ )
969
1686
  const variants = responseSchema?.anyOf
970
1687
  if (!variants) throw new Error('Missing create quote response variants')
971
1688
  for (const [variants_amount, names] of [
972
1689
  [variants.slice(0, 3), ['destinationAmount', 'destinationAmountMin', 'sourceAmount']],
973
1690
  [variants.slice(3), ['destinationAmount', 'sourceAmount', 'sourceAmountMax']],
974
1691
  ] as const)
975
- for (const variant of variants_amount)
1692
+ for (const variant_ref of variants_amount) {
1693
+ const variant = resolveSchema(spec, variant_ref)
976
1694
  for (const name of names) {
977
- const amount = variant?.properties?.[name]
1695
+ const amount = resolveSchema(spec, variant.properties?.[name])
978
1696
  expect(amount?.required).toEqual(['baseUnits', 'currency', 'decimals', 'formatted'])
979
1697
  expect(
980
1698
  Object.fromEntries(
@@ -985,10 +1703,12 @@ describe('POST and QUERY /exchange/quotes', () => {
985
1703
  ),
986
1704
  ).toEqual({ baseUnits: '1000000', currency: 'USD', decimals: 6, formatted: '1' })
987
1705
  }
1706
+ }
988
1707
 
989
- for (const variant of variants)
1708
+ for (const variant_ref of variants) {
1709
+ const variant = resolveSchema(spec, variant_ref)
990
1710
  for (const name of ['destinationToken', 'sourceToken']) {
991
- const token = variant.properties?.[name]
1711
+ const token = resolveSchema(spec, variant.properties?.[name])
992
1712
  expect(token?.required).toEqual(['address', 'currency', 'decimals', 'name', 'symbol'])
993
1713
  expect(token?.properties).not.toHaveProperty('amount')
994
1714
  expect(token?.properties).not.toHaveProperty('maximumAmount')
@@ -996,9 +1716,13 @@ describe('POST and QUERY /exchange/quotes', () => {
996
1716
  expect(token?.required).not.toContain('logoUri')
997
1717
  expect(token?.required).not.toContain('verified')
998
1718
  }
1719
+ }
999
1720
 
1000
- const callsExample = variants.find((variant) => variant.properties?.['transaction'])
1001
- ?.properties?.['transaction']?.properties?.['calls']?.examples?.[0]
1721
+ const ready = variants
1722
+ .map((variant) => resolveSchema(spec, variant))
1723
+ .find((variant) => variant.properties?.['transaction'])
1724
+ const transaction = resolveSchema(spec, ready?.properties?.['transaction'])
1725
+ const callsExample = transaction.properties?.['calls']?.examples?.[0]
1002
1726
  if (!Array.isArray(callsExample)) throw new Error('Missing create quote call examples')
1003
1727
  const calls = callsExample.map((call) => Exchanges.schema.createQuote.Call.parse(call))
1004
1728
  expect(decodeFunctionData({ abi: Abis.tip20, data: calls[0]!.data })).toEqual({
@@ -1015,9 +1739,10 @@ describe('POST and QUERY /exchange/quotes', () => {
1015
1739
  functionName: 'swapExactAmountIn',
1016
1740
  })
1017
1741
 
1018
- const typedData = variants.find((variant) => variant.properties?.['typedData'])?.properties?.[
1019
- 'typedData'
1020
- ]
1742
+ const signatureRequired = variants
1743
+ .map((variant) => resolveSchema(spec, variant))
1744
+ .find((variant) => variant.properties?.['typedData'])
1745
+ const typedData = resolveSchema(spec, signatureRequired?.properties?.['typedData'])
1021
1746
  expect(typedData?.properties?.['primaryType']?.examples).toEqual(['PermitSingle'])
1022
1747
  expect(typedData?.properties?.['domain']?.examples?.[0]).toMatchObject({
1023
1748
  chainId: 4217,