tapimo 0.12.0 → 0.13.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 (257) hide show
  1. package/dist/App.d.ts +5561 -5026
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +38 -3
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +2 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +17 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/admin/App.d.ts +123 -74
  10. package/dist/admin/App.d.ts.map +1 -1
  11. package/dist/admin/apps/api-keys.d.ts +80 -0
  12. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  13. package/dist/admin/apps/api-keys.js +54 -0
  14. package/dist/admin/apps/api-keys.js.map +1 -1
  15. package/dist/admin/apps/earn-vaults.d.ts +15 -15
  16. package/dist/admin/apps/earn-vaults.js +1 -1
  17. package/dist/admin/apps/verified-tokens.d.ts +81 -81
  18. package/dist/admin/ui.generated.d.ts +1 -1
  19. package/dist/admin/ui.generated.d.ts.map +1 -1
  20. package/dist/admin/ui.generated.js +151 -151
  21. package/dist/admin/ui.generated.js.map +1 -1
  22. package/dist/apps/Zones.d.ts +16 -16
  23. package/dist/apps/Zones.js +4 -4
  24. package/dist/apps/data/App.js +18 -18
  25. package/dist/apps/data/App.js.map +1 -1
  26. package/dist/apps/data/ZoneSelection.d.ts +8 -8
  27. package/dist/apps/data/ZoneSelection.d.ts.map +1 -1
  28. package/dist/apps/data/ZoneSelection.js +1 -1
  29. package/dist/apps/data/ZoneSelection.js.map +1 -1
  30. package/dist/apps/data/routes/activities.d.ts +588 -584
  31. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  32. package/dist/apps/data/routes/activities.js +1561 -1239
  33. package/dist/apps/data/routes/activities.js.map +1 -1
  34. package/dist/apps/data/routes/balances.d.ts +26 -26
  35. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  36. package/dist/apps/data/routes/balances.js +43 -16
  37. package/dist/apps/data/routes/balances.js.map +1 -1
  38. package/dist/apps/data/routes/blocks.d.ts +44 -44
  39. package/dist/apps/data/routes/blocks.d.ts.map +1 -1
  40. package/dist/apps/data/routes/blocks.js +21 -11
  41. package/dist/apps/data/routes/blocks.js.map +1 -1
  42. package/dist/apps/data/routes/coingecko.d.ts +3 -3
  43. package/dist/apps/data/routes/earn.d.ts +18 -18
  44. package/dist/apps/data/routes/earn.js +13 -13
  45. package/dist/apps/data/routes/exchanges.d.ts +145 -145
  46. package/dist/apps/data/routes/fee-amm.d.ts +2 -2
  47. package/dist/apps/data/routes/receipts.d.ts +155 -55
  48. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  49. package/dist/apps/data/routes/receipts.js +28 -10
  50. package/dist/apps/data/routes/receipts.js.map +1 -1
  51. package/dist/apps/data/routes/tokenlist.d.ts +13 -13
  52. package/dist/apps/data/routes/tokens.d.ts +98 -98
  53. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  54. package/dist/apps/data/routes/tokens.js +51 -20
  55. package/dist/apps/data/routes/tokens.js.map +1 -1
  56. package/dist/apps/data/routes/transactions.d.ts +7 -2
  57. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  58. package/dist/apps/data/routes/transactions.js +37 -17
  59. package/dist/apps/data/routes/transactions.js.map +1 -1
  60. package/dist/apps/data/routes/transfers.d.ts +1 -1
  61. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  62. package/dist/apps/data/routes/transfers.js +18 -12
  63. package/dist/apps/data/routes/transfers.js.map +1 -1
  64. package/dist/apps/data/routes/valuation.d.ts +1 -1
  65. package/dist/apps/data/routes/valuation.d.ts.map +1 -1
  66. package/dist/apps/data/routes/valuation.js +12 -7
  67. package/dist/apps/data/routes/valuation.js.map +1 -1
  68. package/dist/apps/data/routes/verified-tokens.d.ts +3 -3
  69. package/dist/apps/data/routes/webhooks.d.ts +11 -11
  70. package/dist/apps/data/routes/zones.d.ts +14 -14
  71. package/dist/apps/data/routes/zones.js +1 -1
  72. package/dist/apps/funding/App.d.ts +11 -0
  73. package/dist/apps/funding/App.d.ts.map +1 -1
  74. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  75. package/dist/apps/funding/routes/deposit-addresses.js +16 -11
  76. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  77. package/dist/apps/funding/routes/transfers.d.ts +60 -48
  78. package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
  79. package/dist/apps/funding/routes/transfers.js +27 -19
  80. package/dist/apps/funding/routes/transfers.js.map +1 -1
  81. package/dist/apps/management/App.d.ts +4487 -3963
  82. package/dist/apps/management/App.d.ts.map +1 -1
  83. package/dist/apps/management/routes/api-keys.d.ts +710 -159
  84. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  85. package/dist/apps/management/routes/api-keys.js +236 -4
  86. package/dist/apps/management/routes/api-keys.js.map +1 -1
  87. package/dist/apps/management/routes/faucet.d.ts +6 -6
  88. package/dist/apps/management/routes/invite-links.d.ts +42 -42
  89. package/dist/apps/management/routes/members.d.ts +30 -30
  90. package/dist/apps/management/routes/projects.d.ts +40 -40
  91. package/dist/apps/management/routes/usage.d.ts +24 -24
  92. package/dist/apps/management/routes/verified-token-requests.d.ts +1 -1
  93. package/dist/apps/management/routes/webhooks.d.ts +55 -55
  94. package/dist/db/Db.d.ts.map +1 -1
  95. package/dist/db/Db.js +12 -1
  96. package/dist/db/Db.js.map +1 -1
  97. package/dist/db/Schema.d.ts +3 -2
  98. package/dist/db/Schema.d.ts.map +1 -1
  99. package/dist/db/Schema.js +5 -2
  100. package/dist/db/Schema.js.map +1 -1
  101. package/dist/db/tables/fundingCatalog.d.ts +4 -2
  102. package/dist/db/tables/fundingCatalog.d.ts.map +1 -1
  103. package/dist/db/tables/fundingCatalog.js +5 -1
  104. package/dist/db/tables/fundingCatalog.js.map +1 -1
  105. package/dist/handlers/internal/utils.d.ts.map +1 -1
  106. package/dist/handlers/internal/utils.js +9 -0
  107. package/dist/handlers/internal/utils.js.map +1 -1
  108. package/dist/internal/Auth.d.ts +3 -1
  109. package/dist/internal/Auth.d.ts.map +1 -1
  110. package/dist/internal/Auth.js +6 -3
  111. package/dist/internal/Auth.js.map +1 -1
  112. package/dist/internal/EarnVaults.d.ts +3 -3
  113. package/dist/internal/EarnVaults.js +3 -3
  114. package/dist/internal/Fetch.d.ts +23 -0
  115. package/dist/internal/Fetch.d.ts.map +1 -0
  116. package/dist/internal/Fetch.js +26 -0
  117. package/dist/internal/Fetch.js.map +1 -0
  118. package/dist/internal/Log.d.ts +3 -1
  119. package/dist/internal/Log.d.ts.map +1 -1
  120. package/dist/internal/Log.js +16 -3
  121. package/dist/internal/Log.js.map +1 -1
  122. package/dist/internal/OpenApi.d.ts +5 -3
  123. package/dist/internal/OpenApi.d.ts.map +1 -1
  124. package/dist/internal/OpenApi.js +13 -3
  125. package/dist/internal/OpenApi.js.map +1 -1
  126. package/dist/internal/Response.d.ts +1 -1
  127. package/dist/internal/Response.d.ts.map +1 -1
  128. package/dist/internal/Schema.d.ts +6 -3
  129. package/dist/internal/Schema.d.ts.map +1 -1
  130. package/dist/internal/Schema.js +8 -2
  131. package/dist/internal/Schema.js.map +1 -1
  132. package/dist/internal/Store.d.ts +16 -3
  133. package/dist/internal/Store.d.ts.map +1 -1
  134. package/dist/internal/Store.js +82 -16
  135. package/dist/internal/Store.js.map +1 -1
  136. package/dist/internal/Tidx.d.ts.map +1 -1
  137. package/dist/internal/Tidx.js +7 -2
  138. package/dist/internal/Tidx.js.map +1 -1
  139. package/dist/internal/Timeout.d.ts +36 -0
  140. package/dist/internal/Timeout.d.ts.map +1 -0
  141. package/dist/internal/Timeout.js +37 -0
  142. package/dist/internal/Timeout.js.map +1 -0
  143. package/dist/internal/Timing.d.ts +9 -0
  144. package/dist/internal/Timing.d.ts.map +1 -1
  145. package/dist/internal/Timing.js +7 -0
  146. package/dist/internal/Timing.js.map +1 -1
  147. package/dist/internal/WebhookDestination.d.ts.map +1 -1
  148. package/dist/internal/WebhookDestination.js +3 -0
  149. package/dist/internal/WebhookDestination.js.map +1 -1
  150. package/dist/internal/funding/Action.d.ts +9 -0
  151. package/dist/internal/funding/Action.d.ts.map +1 -1
  152. package/dist/internal/funding/Action.js +16 -8
  153. package/dist/internal/funding/Action.js.map +1 -1
  154. package/dist/internal/funding/Catalog.d.ts +9 -2
  155. package/dist/internal/funding/Catalog.d.ts.map +1 -1
  156. package/dist/internal/funding/Catalog.js +19 -8
  157. package/dist/internal/funding/Catalog.js.map +1 -1
  158. package/dist/internal/funding/Provider.d.ts +68 -13
  159. package/dist/internal/funding/Provider.d.ts.map +1 -1
  160. package/dist/internal/funding/Provider.js +29 -8
  161. package/dist/internal/funding/Provider.js.map +1 -1
  162. package/dist/internal/funding/Transfer.d.ts +13 -12
  163. package/dist/internal/funding/Transfer.d.ts.map +1 -1
  164. package/dist/internal/funding/Transfer.js +23 -21
  165. package/dist/internal/funding/Transfer.js.map +1 -1
  166. package/dist/internal/funding/providers/stargate.d.ts +28 -9
  167. package/dist/internal/funding/providers/stargate.d.ts.map +1 -1
  168. package/dist/internal/funding/providers/stargate.js +251 -117
  169. package/dist/internal/funding/providers/stargate.js.map +1 -1
  170. package/dist/internal/index.d.ts +1 -0
  171. package/dist/internal/index.d.ts.map +1 -1
  172. package/dist/internal/index.js +1 -0
  173. package/dist/internal/index.js.map +1 -1
  174. package/package.json +2 -2
  175. package/src/App.test.ts +100 -0
  176. package/src/App.ts +42 -3
  177. package/src/Client.test-d.ts +46 -0
  178. package/src/Scope.test.ts +20 -0
  179. package/src/Scope.ts +16 -0
  180. package/src/admin/apps/api-keys.test.ts +75 -0
  181. package/src/admin/apps/api-keys.ts +64 -0
  182. package/src/admin/apps/earn-vaults.test.ts +1 -1
  183. package/src/admin/apps/earn-vaults.ts +1 -1
  184. package/src/admin/ui/src/lib/api-keys.ts +19 -0
  185. package/src/admin/ui/src/routes/api-keys.tsx +89 -2
  186. package/src/admin/ui.generated.ts +189 -151
  187. package/src/apps/Zones.test.ts +4 -4
  188. package/src/apps/Zones.ts +4 -4
  189. package/src/apps/composition.test.ts +1 -1
  190. package/src/apps/data/App.test.ts +41 -0
  191. package/src/apps/data/App.ts +18 -18
  192. package/src/apps/data/ZoneSelection.test.ts +2 -2
  193. package/src/apps/data/ZoneSelection.ts +8 -8
  194. package/src/apps/data/routes/activities.test.ts +1526 -12
  195. package/src/apps/data/routes/activities.ts +1774 -1356
  196. package/src/apps/data/routes/balances.ts +133 -97
  197. package/src/apps/data/routes/blocks.test.ts +533 -0
  198. package/src/apps/data/routes/blocks.ts +35 -16
  199. package/src/apps/data/routes/earn.test.ts +4 -2
  200. package/src/apps/data/routes/earn.ts +13 -13
  201. package/src/apps/data/routes/earn.zone-access.test.ts +6 -6
  202. package/src/apps/data/routes/receipts.ts +47 -16
  203. package/src/apps/data/routes/tokens.test.ts +578 -0
  204. package/src/apps/data/routes/tokens.ts +283 -225
  205. package/src/apps/data/routes/transactions.ts +68 -29
  206. package/src/apps/data/routes/transfers.test.ts +421 -0
  207. package/src/apps/data/routes/transfers.ts +45 -27
  208. package/src/apps/data/routes/valuation.ts +94 -78
  209. package/src/apps/data/routes/zones.test.ts +2 -2
  210. package/src/apps/data/routes/zones.ts +1 -1
  211. package/src/apps/funding/routes/deposit-addresses.test.ts +25 -3
  212. package/src/apps/funding/routes/deposit-addresses.ts +17 -12
  213. package/src/apps/funding/routes/quotes.test.ts +5 -9
  214. package/src/apps/funding/routes/transfers.test.ts +395 -8
  215. package/src/apps/funding/routes/transfers.ts +76 -59
  216. package/src/apps/management/routes/api-keys.test.ts +220 -0
  217. package/src/apps/management/routes/api-keys.ts +280 -3
  218. package/src/db/Db.ts +13 -1
  219. package/src/db/Schema.ts +5 -2
  220. package/src/db/tables/earnVaults.test.ts +9 -9
  221. package/src/db/tables/fundingCatalog.test.ts +16 -2
  222. package/src/db/tables/fundingCatalog.ts +9 -3
  223. package/src/handlers/internal/utils.test.ts +53 -0
  224. package/src/handlers/internal/utils.ts +15 -0
  225. package/src/internal/Auth.ts +12 -4
  226. package/src/internal/EarnVaults.capabilities.test.ts +2 -2
  227. package/src/internal/EarnVaults.test.ts +1 -1
  228. package/src/internal/EarnVaults.ts +3 -3
  229. package/src/internal/Fetch.test.ts +46 -0
  230. package/src/internal/Fetch.ts +56 -0
  231. package/src/internal/Log.test.ts +21 -0
  232. package/src/internal/Log.ts +20 -5
  233. package/src/internal/OpenApi.ts +14 -4
  234. package/src/internal/Response.ts +1 -1
  235. package/src/internal/Schema.test.ts +2 -0
  236. package/src/internal/Schema.ts +12 -2
  237. package/src/internal/Store.test.ts +178 -0
  238. package/src/internal/Store.ts +104 -18
  239. package/src/internal/Tidx.test.ts +2 -1
  240. package/src/internal/Tidx.ts +7 -2
  241. package/src/internal/Timeout.test.ts +35 -0
  242. package/src/internal/Timeout.ts +73 -0
  243. package/src/internal/Timing.ts +16 -0
  244. package/src/internal/WebhookDestination.ts +3 -0
  245. package/src/internal/funding/Action.test.ts +41 -1
  246. package/src/internal/funding/Action.ts +69 -41
  247. package/src/internal/funding/Catalog.test.ts +34 -0
  248. package/src/internal/funding/Catalog.ts +28 -11
  249. package/src/internal/funding/Provider.test.ts +73 -0
  250. package/src/internal/funding/Provider.ts +82 -28
  251. package/src/internal/funding/Transfer.ts +133 -110
  252. package/src/internal/funding/providers/index.test.ts +4 -167
  253. package/src/internal/funding/providers/relay.test.ts +2 -2
  254. package/src/internal/funding/providers/stargate.test.ts +163 -0
  255. package/src/internal/funding/providers/stargate.ts +359 -134
  256. package/src/internal/index.test.ts +1 -0
  257. package/src/internal/index.ts +1 -0
@@ -16,6 +16,8 @@ import * as Idempotency from '../../../internal/Idempotency.js'
16
16
 
17
17
  import * as Relay from '../../../internal/funding/providers/relay.js'
18
18
 
19
+ import * as Stargate from '../../../internal/funding/providers/stargate.js'
20
+
19
21
  import * as TestApp from '../../../../test/App.js'
20
22
 
21
23
  import * as TestFunding from '../../../../test/Funding.js'
@@ -29,8 +31,14 @@ const executableProviders = [
29
31
  {
30
32
  create: (_sender: string) =>
31
33
  Relay.relay(process.env.RELAY_API_KEY ? { apiKey: process.env.RELAY_API_KEY } : {}),
34
+ destinationToken: 'pathusd',
32
35
  id: 'relay',
33
36
  },
37
+ {
38
+ create: (_sender: string) => Stargate.stargate(),
39
+ destinationToken: 'usdce',
40
+ id: 'stargate',
41
+ },
34
42
  ] as const
35
43
 
36
44
  beforeAll(() => TestFunding.publish(db))
@@ -39,6 +47,11 @@ function create(options: TestApp.create.Options = {}) {
39
47
  return TestApp.create({ ...options, db })
40
48
  }
41
49
 
50
+ type ContractResponse = {
51
+ $ref?: string | undefined
52
+ content?: Record<string, { schema: unknown }> | undefined
53
+ }
54
+
42
55
  type JsonSchema = {
43
56
  $ref?: string | undefined
44
57
  additionalProperties?: boolean | undefined
@@ -377,6 +390,69 @@ describe('POST /funding/transfers', () => {
377
390
  expect(row.providerState).toEqual({ requestId: `0x${'ab'.repeat(32)}` })
378
391
  })
379
392
 
393
+ test('creates a transfer with the requested provider', async () => {
394
+ const app = transfersApp({ providers: [provider()] })
395
+ const response = await postTransfer(app, {
396
+ body: { ...createBody, provider: 'relay' },
397
+ idempotencyKey: nextIdempotencyKey(),
398
+ })
399
+
400
+ expect(response.status).toBe(200)
401
+ const body = await TestApp.json(response, Funding.schema.createFundingTransfer.Response)
402
+ expect(body.provider.id).toBe('relay')
403
+ })
404
+
405
+ test('rejects an unsupported provider', async () => {
406
+ const app = transfersApp({ providers: [provider()] })
407
+ const response = await postTransfer(app, {
408
+ body: { ...createBody, provider: 'bridge' },
409
+ idempotencyKey: nextIdempotencyKey(),
410
+ })
411
+
412
+ expect(response.status).toBe(400)
413
+ expect(((await response.json()) as { error: { code: string } }).error.code).toBe('body_invalid')
414
+ })
415
+
416
+ test('preserves a provider-specified native fee token', async () => {
417
+ const app = transfersApp({
418
+ providers: [
419
+ provider(async () => ({
420
+ ...providerResponse(),
421
+ fees: [
422
+ {
423
+ amount: '1000000000000000',
424
+ side: 'source',
425
+ token: {
426
+ address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
427
+ currency: 'ETH',
428
+ decimals: 18,
429
+ name: 'Ether',
430
+ standard: 'native',
431
+ symbol: 'ETH',
432
+ tokenKey: 'eip155:8453/slip44:60',
433
+ verified: true,
434
+ },
435
+ },
436
+ ],
437
+ })),
438
+ ],
439
+ })
440
+ const response = await postTransfer(app, {
441
+ body: { ...createBody, destinationToken: 'pathusd', sourceToken: 'usdc' },
442
+ idempotencyKey: nextIdempotencyKey(),
443
+ })
444
+ expect(response.status).toBe(200)
445
+ const body = await TestApp.json(response, Funding.schema.createFundingTransfer.Response)
446
+
447
+ expect(body.fees).toMatchObject([
448
+ {
449
+ amount: { baseUnits: '1000000000000000', formatted: '0.001' },
450
+ side: 'source',
451
+ token: { standard: 'native', symbol: 'ETH' },
452
+ },
453
+ ])
454
+ })
455
+
380
456
  test('resolves token identifiers through the composed route', async () => {
381
457
  const app = transfersApp({ providers: [provider()] })
382
458
  const identifiers = [
@@ -608,7 +684,7 @@ describe.skipIf(!Anvil.available)('POST /funding/transfers (forked localnet)', (
608
684
  (entry) =>
609
685
  entry.route.source.chain.id === `eip155:${base.id}` &&
610
686
  entry.route.source.symbol === 'USDC' &&
611
- entry.route.destination.slug === 'pathusd',
687
+ entry.route.destination.slug === executable.destinationToken,
612
688
  )!
613
689
  const usdc = route.route.source
614
690
  const amount = Value.from('10', usdc.decimals)
@@ -627,7 +703,12 @@ describe.skipIf(!Anvil.available)('POST /funding/transfers (forked localnet)', (
627
703
  function createTransfer(idempotencyKey: string) {
628
704
  const app = transfersApp({ providers: [executable.create(sender.address)] })
629
705
  return postTransfer(app, {
630
- body: { ...createBody, amount: amount.toString(), sender: sender.address },
706
+ body: {
707
+ ...createBody,
708
+ amount: amount.toString(),
709
+ destinationToken: executable.destinationToken,
710
+ sender: sender.address,
711
+ },
631
712
  idempotencyKey,
632
713
  })
633
714
  }
@@ -647,6 +728,14 @@ describe.skipIf(!Anvil.available)('POST /funding/transfers (forked localnet)', (
647
728
  expect(transfer.action.type).toBe('evm:calls')
648
729
  if (transfer.action.type !== 'evm:calls') return
649
730
  expect(transfer.action.calls.length).toBeGreaterThan(0)
731
+ if (executable.id === 'stargate')
732
+ expect(transfer.fees).toMatchObject([
733
+ {
734
+ amount: { baseUnits: BigInt(transfer.action.calls.at(-1)!.value).toString() },
735
+ side: 'source',
736
+ token: { standard: 'native', symbol: 'ETH' },
737
+ },
738
+ ])
650
739
 
651
740
  const receipts = []
652
741
  for (const call of transfer.action.calls) {
@@ -756,24 +845,318 @@ describe('POST /funding/transfers/{destination}', () => {
756
845
  })
757
846
 
758
847
  describe('funding transfers OpenAPI', () => {
848
+ test('publishes generator-ready transfer contracts', async () => {
849
+ const spec = await (await create({ auth: false }).request('/openapi.json')).json()
850
+ const detailPath = Object.keys(spec.paths).find((path) =>
851
+ path.includes('/v1/funding/transfers/{'),
852
+ )!
853
+ const operations = {
854
+ create: spec.paths['/v1/funding/transfers'].post,
855
+ createVault: spec.paths['/v1/funding/transfers/vault'].post,
856
+ createZone: spec.paths['/v1/funding/transfers/zone'].post,
857
+ get: spec.paths[detailPath].get,
858
+ list: spec.paths['/v1/funding/transfers'].get,
859
+ }
860
+ const summarize = (operation: (typeof operations)[keyof typeof operations]) => ({
861
+ errors: Object.fromEntries(
862
+ (Object.entries(operation.responses) as [string, ContractResponse][])
863
+ .filter(([status]) => Number(status) >= 400)
864
+ .map(([status, response]) => [
865
+ status,
866
+ response.$ref ?? response.content?.['application/json']?.schema,
867
+ ]),
868
+ ),
869
+ operationId: operation.operationId,
870
+ request: operation.requestBody?.content['application/json'].schema,
871
+ response: operation.responses[200]?.content?.['application/json']?.schema,
872
+ })
873
+
874
+ expect({
875
+ components: [
876
+ 'CreateFundingTransferRequest',
877
+ 'CreateFundingTransferVaultRequest',
878
+ 'CreateFundingTransferZoneRequest',
879
+ 'CreatedFundingTransfer',
880
+ 'FundingAction',
881
+ 'FundingActionCall',
882
+ 'FundingEvmCallsAction',
883
+ 'FundingTransfer',
884
+ 'FundingTransferFee',
885
+ 'FundingTransferList',
886
+ 'FundingTransferQuote',
887
+ 'FundingTransferStatusReason',
888
+ ].filter((name) => spec.components.schemas[name]),
889
+ formats: {
890
+ createdAt: spec.components.schemas.FundingTransfer.properties.createdAt.format,
891
+ quoteExpiresAt: spec.components.schemas.FundingTransferQuote.properties.expiresAt.format,
892
+ quoteSampledAt: spec.components.schemas.FundingTransferQuote.properties.sampledAt.format,
893
+ updatedAt: spec.components.schemas.FundingTransfer.properties.updatedAt.format,
894
+ },
895
+ models: {
896
+ action: spec.components.schemas.FundingAction.oneOf,
897
+ createdAction: spec.components.schemas.CreatedFundingTransfer.properties.action,
898
+ evmCall: spec.components.schemas.FundingEvmCallsAction.properties.calls.items,
899
+ feeToken: spec.components.schemas.FundingTransferFee.properties.token,
900
+ list: spec.components.schemas.FundingTransferList.properties.data.items,
901
+ quote: spec.components.schemas.FundingTransfer.properties.quote,
902
+ statusReason: spec.components.schemas.FundingTransfer.properties.statusReason,
903
+ },
904
+ operations: {
905
+ create: summarize(operations.create),
906
+ createVault: summarize(operations.createVault),
907
+ createZone: summarize(operations.createZone),
908
+ get: summarize(operations.get),
909
+ list: summarize(operations.list),
910
+ },
911
+ operationErrorCodes: {
912
+ createInvalid:
913
+ spec.components.schemas.ApiKeyMalformedOrBodyInvalidOrIdempotencyKeyRequiredError
914
+ .properties.error.properties.code.enum,
915
+ idempotencyConflict:
916
+ spec.components.schemas.IdempotencyConflictOrIdempotencyInProgressError.properties.error
917
+ .properties.code.enum,
918
+ notImplemented:
919
+ spec.components.schemas.NotImplemented501Error.properties.error.properties.code.enum,
920
+ parametersInvalid:
921
+ spec.components.schemas.ApiKeyMalformedOrParamInvalidError.properties.error.properties
922
+ .code.enum,
923
+ queryInvalid:
924
+ spec.components.schemas.ApiKeyMalformedOrQueryInvalidError.properties.error.properties
925
+ .code.enum,
926
+ quoteNotAvailable:
927
+ spec.components.schemas.QuoteNotAvailableError.properties.error.properties.code.enum,
928
+ transferNotFound:
929
+ spec.components.schemas.FundingTransferNotFoundError.properties.error.properties.code
930
+ .enum,
931
+ },
932
+ }).toMatchInlineSnapshot(`
933
+ {
934
+ "components": [
935
+ "CreateFundingTransferRequest",
936
+ "CreateFundingTransferVaultRequest",
937
+ "CreateFundingTransferZoneRequest",
938
+ "CreatedFundingTransfer",
939
+ "FundingAction",
940
+ "FundingActionCall",
941
+ "FundingEvmCallsAction",
942
+ "FundingTransfer",
943
+ "FundingTransferFee",
944
+ "FundingTransferList",
945
+ "FundingTransferQuote",
946
+ "FundingTransferStatusReason",
947
+ ],
948
+ "formats": {
949
+ "createdAt": "date-time",
950
+ "quoteExpiresAt": "date-time",
951
+ "quoteSampledAt": "date-time",
952
+ "updatedAt": "date-time",
953
+ },
954
+ "models": {
955
+ "action": [
956
+ {
957
+ "$ref": "#/components/schemas/FundingEvmCallsAction",
958
+ },
959
+ ],
960
+ "createdAction": {
961
+ "$ref": "#/components/schemas/FundingAction",
962
+ },
963
+ "evmCall": {
964
+ "$ref": "#/components/schemas/FundingActionCall",
965
+ },
966
+ "feeToken": {
967
+ "$ref": "#/components/schemas/FundingToken",
968
+ },
969
+ "list": {
970
+ "$ref": "#/components/schemas/FundingTransfer",
971
+ },
972
+ "quote": {
973
+ "$ref": "#/components/schemas/FundingTransferQuote",
974
+ },
975
+ "statusReason": {
976
+ "$ref": "#/components/schemas/FundingTransferStatusReason",
977
+ },
978
+ },
979
+ "operationErrorCodes": {
980
+ "createInvalid": [
981
+ "api_key_malformed",
982
+ "body_invalid",
983
+ "idempotency_key_required",
984
+ ],
985
+ "idempotencyConflict": [
986
+ "idempotency_conflict",
987
+ "idempotency_in_progress",
988
+ ],
989
+ "notImplemented": [
990
+ "not_implemented",
991
+ ],
992
+ "parametersInvalid": [
993
+ "api_key_malformed",
994
+ "param_invalid",
995
+ ],
996
+ "queryInvalid": [
997
+ "api_key_malformed",
998
+ "query_invalid",
999
+ ],
1000
+ "quoteNotAvailable": [
1001
+ "quote_not_available",
1002
+ ],
1003
+ "transferNotFound": [
1004
+ "funding_transfer_not_found",
1005
+ ],
1006
+ },
1007
+ "operations": {
1008
+ "create": {
1009
+ "errors": {
1010
+ "400": {
1011
+ "$ref": "#/components/schemas/ApiKeyMalformedOrBodyInvalidOrIdempotencyKeyRequiredError",
1012
+ },
1013
+ "401": {
1014
+ "$ref": "#/components/schemas/AuthenticationError",
1015
+ },
1016
+ "403": {
1017
+ "$ref": "#/components/schemas/ForbiddenError",
1018
+ },
1019
+ "404": {
1020
+ "$ref": "#/components/schemas/QuoteNotAvailableError",
1021
+ },
1022
+ "409": {
1023
+ "$ref": "#/components/schemas/IdempotencyConflictOrIdempotencyInProgressError",
1024
+ },
1025
+ "429": "#/components/responses/RateLimited",
1026
+ "500": "#/components/responses/InternalError",
1027
+ "502": {
1028
+ "$ref": "#/components/schemas/UpstreamError",
1029
+ },
1030
+ "504": "#/components/responses/RequestTimeout",
1031
+ },
1032
+ "operationId": "createFundingTransfer",
1033
+ "request": {
1034
+ "$ref": "#/components/schemas/CreateFundingTransferRequest",
1035
+ },
1036
+ "response": {
1037
+ "$ref": "#/components/schemas/CreatedFundingTransfer",
1038
+ },
1039
+ },
1040
+ "createVault": {
1041
+ "errors": {
1042
+ "400": {
1043
+ "$ref": "#/components/schemas/ApiKeyMalformedOrBodyInvalidOrIdempotencyKeyRequiredError",
1044
+ },
1045
+ "401": {
1046
+ "$ref": "#/components/schemas/AuthenticationError",
1047
+ },
1048
+ "403": {
1049
+ "$ref": "#/components/schemas/ForbiddenError",
1050
+ },
1051
+ "429": "#/components/responses/RateLimited",
1052
+ "500": "#/components/responses/InternalError",
1053
+ "501": {
1054
+ "$ref": "#/components/schemas/NotImplemented501Error",
1055
+ },
1056
+ "504": "#/components/responses/RequestTimeout",
1057
+ },
1058
+ "operationId": "createFundingTransferVault",
1059
+ "request": {
1060
+ "$ref": "#/components/schemas/CreateFundingTransferVaultRequest",
1061
+ },
1062
+ "response": undefined,
1063
+ },
1064
+ "createZone": {
1065
+ "errors": {
1066
+ "400": {
1067
+ "$ref": "#/components/schemas/ApiKeyMalformedOrBodyInvalidOrIdempotencyKeyRequiredError",
1068
+ },
1069
+ "401": {
1070
+ "$ref": "#/components/schemas/AuthenticationError",
1071
+ },
1072
+ "403": {
1073
+ "$ref": "#/components/schemas/ForbiddenError",
1074
+ },
1075
+ "429": "#/components/responses/RateLimited",
1076
+ "500": "#/components/responses/InternalError",
1077
+ "501": {
1078
+ "$ref": "#/components/schemas/NotImplemented501Error",
1079
+ },
1080
+ "504": "#/components/responses/RequestTimeout",
1081
+ },
1082
+ "operationId": "createFundingTransferZone",
1083
+ "request": {
1084
+ "$ref": "#/components/schemas/CreateFundingTransferZoneRequest",
1085
+ },
1086
+ "response": undefined,
1087
+ },
1088
+ "get": {
1089
+ "errors": {
1090
+ "400": {
1091
+ "$ref": "#/components/schemas/ApiKeyMalformedOrParamInvalidError",
1092
+ },
1093
+ "401": {
1094
+ "$ref": "#/components/schemas/AuthenticationError",
1095
+ },
1096
+ "403": {
1097
+ "$ref": "#/components/schemas/ForbiddenError",
1098
+ },
1099
+ "404": {
1100
+ "$ref": "#/components/schemas/FundingTransferNotFoundError",
1101
+ },
1102
+ "429": "#/components/responses/RateLimited",
1103
+ "500": "#/components/responses/InternalError",
1104
+ "502": {
1105
+ "$ref": "#/components/schemas/UpstreamError",
1106
+ },
1107
+ "504": "#/components/responses/RequestTimeout",
1108
+ },
1109
+ "operationId": "getFundingTransfer",
1110
+ "request": undefined,
1111
+ "response": {
1112
+ "$ref": "#/components/schemas/FundingTransfer",
1113
+ },
1114
+ },
1115
+ "list": {
1116
+ "errors": {
1117
+ "400": {
1118
+ "$ref": "#/components/schemas/ApiKeyMalformedOrQueryInvalidError",
1119
+ },
1120
+ "401": {
1121
+ "$ref": "#/components/schemas/AuthenticationError",
1122
+ },
1123
+ "403": {
1124
+ "$ref": "#/components/schemas/ForbiddenError",
1125
+ },
1126
+ "429": "#/components/responses/RateLimited",
1127
+ "500": "#/components/responses/InternalError",
1128
+ "502": {
1129
+ "$ref": "#/components/schemas/UpstreamError",
1130
+ },
1131
+ "504": "#/components/responses/RequestTimeout",
1132
+ },
1133
+ "operationId": "listFundingTransfers",
1134
+ "request": undefined,
1135
+ "response": {
1136
+ "$ref": "#/components/schemas/FundingTransferList",
1137
+ },
1138
+ },
1139
+ },
1140
+ }
1141
+ `)
1142
+ })
1143
+
759
1144
  test('publishes an executable account transfer example', async () => {
760
1145
  const app = create({ auth: false })
761
- type TokenSchema = { examples: string[] }
762
1146
  type TransferExample = { destinationToken: { address: string } }
763
1147
  type Operation = {
764
1148
  requestBody: {
765
- content: Record<string, { schema: { properties: { destinationToken: TokenSchema } } }>
1149
+ content: Record<string, { schema: JsonSchema }>
766
1150
  }
767
1151
  responses: Record<string, { content: Record<string, { example: TransferExample }> }>
768
1152
  }
769
- type Spec = { paths: Record<string, { post: Operation }> }
1153
+ type Spec = ComponentSpec & { paths: Record<string, { post: Operation }> }
770
1154
  const spec = (await (await app.request('/openapi.json')).json()) as Spec
771
1155
  const operation = spec.paths['/v1/funding/transfers']!.post
1156
+ const request = dereference(spec, operation.requestBody.content['application/json']!.schema)
772
1157
 
773
1158
  expect({
774
- request:
775
- operation.requestBody.content['application/json']!.schema.properties.destinationToken
776
- .examples,
1159
+ request: request.properties!['destinationToken']!.examples,
777
1160
  response:
778
1161
  operation.responses['200']!.content['application/json']!.example.destinationToken.address,
779
1162
  }).toEqual({
@@ -1002,6 +1385,7 @@ describe('funding transfers OpenAPI', () => {
1002
1385
  "amount",
1003
1386
  "destinationToken",
1004
1387
  "mode",
1388
+ "provider",
1005
1389
  "recipient",
1006
1390
  "sender",
1007
1391
  "slippageBps",
@@ -1031,6 +1415,7 @@ describe('funding transfers OpenAPI', () => {
1031
1415
  "429",
1032
1416
  "500",
1033
1417
  "502",
1418
+ "504",
1034
1419
  ],
1035
1420
  "vault": {
1036
1421
  "additionalProperties": false,
@@ -1052,6 +1437,7 @@ describe('funding transfers OpenAPI', () => {
1052
1437
  "429",
1053
1438
  "500",
1054
1439
  "501",
1440
+ "504",
1055
1441
  ],
1056
1442
  "zone": {
1057
1443
  "additionalProperties": false,
@@ -1075,6 +1461,7 @@ describe('funding transfers OpenAPI', () => {
1075
1461
  "429",
1076
1462
  "500",
1077
1463
  "501",
1464
+ "504",
1078
1465
  ],
1079
1466
  }
1080
1467
  `)
@@ -36,15 +36,20 @@ export namespace schema {
36
36
  .check(z.describe('Query parameters for listing funding transfers.'))
37
37
 
38
38
  /** Paginated funding transfer list. */
39
- export const Response = z
40
- .object({
41
- data: z.array(Transfer).check(z.describe('Funding transfers on this page, newest first.')),
42
- meta: z
43
- .optional(Schema.CountMeta)
44
- .check(z.describe('Extra response metadata requested with `include`.')),
45
- nextCursor: Schema.NextCursor,
46
- })
47
- .check(z.describe('A page of funding transfers.'))
39
+ export const Response = OpenApi.component(
40
+ z
41
+ .object({
42
+ data: z
43
+ .array(Transfer)
44
+ .check(z.describe('Funding transfers on this page, newest first.')),
45
+ meta: z
46
+ .optional(Schema.CountMeta)
47
+ .check(z.describe('Extra response metadata requested with `include`.')),
48
+ nextCursor: Schema.NextCursor,
49
+ })
50
+ .check(z.describe('A page of funding transfers.')),
51
+ 'FundingTransferList',
52
+ )
48
53
  }
49
54
 
50
55
  /** Schemas for the createFundingTransfer operation. */
@@ -82,18 +87,21 @@ export namespace schema {
82
87
  /** Schemas for the createFundingTransferVault operation. */
83
88
  export namespace createFundingTransferVault {
84
89
  /** Funding transfer creation request for an Earn vault destination. */
85
- export const Body = z
86
- .strictObject({
87
- ...destinationCreateFields,
88
- vaultAddress: z
89
- .string()
90
- .check(
91
- z.regex(/^0x[0-9a-fA-F]{40}$/),
92
- z.describe('Tempo Earn vault contract address.'),
93
- z.meta({ examples: [`0x${'f4'.repeat(20)}`] }),
94
- ),
95
- })
96
- .check(z.describe('Vault funding transfer creation request.'))
90
+ export const Body = OpenApi.component(
91
+ z
92
+ .strictObject({
93
+ ...destinationCreateFields,
94
+ vaultAddress: z
95
+ .string()
96
+ .check(
97
+ z.regex(/^0x[0-9a-fA-F]{40}$/),
98
+ z.describe('Tempo Earn vault contract address.'),
99
+ z.meta({ examples: [`0x${'f4'.repeat(20)}`] }),
100
+ ),
101
+ })
102
+ .check(z.describe('Vault funding transfer creation request.')),
103
+ 'CreateFundingTransferVaultRequest',
104
+ )
97
105
 
98
106
  /** Required headers for vault funding transfer creation. */
99
107
  export const Headers = createFundingTransfer.Headers
@@ -102,32 +110,35 @@ export namespace schema {
102
110
  /** Schemas for the createFundingTransferZone operation. */
103
111
  export namespace createFundingTransferZone {
104
112
  /** Funding transfer creation request for a Zone destination. */
105
- export const Body = z
106
- .strictObject({
107
- amount: destinationCreateFields.amount,
108
- destinationChain: z
109
- .string()
110
- .check(
111
- z.regex(/^eip155:\d+$/),
112
- z.describe('Configured Zone CAIP-2 id.'),
113
- z.meta({ examples: ['eip155:1424310001'] }),
114
- ),
115
- destinationToken: FundingTransfer.schema.CreateRequest.shape.destinationToken,
116
- mode: destinationCreateFields.mode,
117
- recipient: destinationCreateFields.recipient,
118
- recipientFallback: z
119
- .string()
120
- .check(
121
- z.regex(/^0x[0-9a-fA-F]{40}$/),
122
- z.describe('Public Tempo recipient used if the Zone credit cannot complete.'),
123
- z.meta({ examples: [`0x${'22'.repeat(20)}`] }),
124
- ),
125
- sender: destinationCreateFields.sender,
126
- slippageBps: destinationCreateFields.slippageBps,
127
- sourceChain: destinationCreateFields.sourceChain,
128
- sourceToken: destinationCreateFields.sourceToken,
129
- })
130
- .check(z.describe('Zone funding transfer creation request.'))
113
+ export const Body = OpenApi.component(
114
+ z
115
+ .strictObject({
116
+ amount: destinationCreateFields.amount,
117
+ destinationChain: z
118
+ .string()
119
+ .check(
120
+ z.regex(/^eip155:\d+$/),
121
+ z.describe('Configured Zone CAIP-2 id.'),
122
+ z.meta({ examples: ['eip155:1424310003'] }),
123
+ ),
124
+ destinationToken: FundingTransfer.schema.CreateRequest.shape.destinationToken,
125
+ mode: destinationCreateFields.mode,
126
+ recipient: destinationCreateFields.recipient,
127
+ recipientFallback: z
128
+ .string()
129
+ .check(
130
+ z.regex(/^0x[0-9a-fA-F]{40}$/),
131
+ z.describe('Public Tempo recipient used if the Zone credit cannot complete.'),
132
+ z.meta({ examples: [`0x${'22'.repeat(20)}`] }),
133
+ ),
134
+ sender: destinationCreateFields.sender,
135
+ slippageBps: destinationCreateFields.slippageBps,
136
+ sourceChain: destinationCreateFields.sourceChain,
137
+ sourceToken: destinationCreateFields.sourceToken,
138
+ })
139
+ .check(z.describe('Zone funding transfer creation request.')),
140
+ 'CreateFundingTransferZoneRequest',
141
+ )
131
142
 
132
143
  /** Required headers for Zone funding transfer creation. */
133
144
  export const Headers = createFundingTransfer.Headers
@@ -516,6 +527,7 @@ export function transfers() {
516
527
  method: 'transaction',
517
528
  mode: body.mode,
518
529
  onAttempt: (attempt) => attempts.push(attempt),
530
+ provider: body.provider,
519
531
  providers,
520
532
  recipient: body.recipient,
521
533
  sender: body.sender,
@@ -532,8 +544,6 @@ export function transfers() {
532
544
  }
533
545
 
534
546
  const { candidate, result } = selected
535
- const feeToken = (side: 'destination' | 'source') =>
536
- side === 'source' ? candidate.sourceToken : candidate.destinationToken
537
547
  const snapshot: FundingTransfer.Snapshot = {
538
548
  destinationAmount: Value.tokenAmount({
539
549
  baseUnits: result.destinationAmount,
@@ -547,16 +557,21 @@ export function transfers() {
547
557
  }),
548
558
  destinationChain: candidate.destinationChain,
549
559
  destinationToken: candidate.destinationToken,
550
- fees: result.fees.map((fee) => ({
551
- amount: Value.tokenAmount({
552
- baseUnits: fee.amount,
553
- currency: feeToken(fee.side).currency,
554
- decimals: feeToken(fee.side).decimals,
555
- }),
556
- side: fee.side,
557
- token: feeToken(fee.side),
558
- type: 'provider',
559
- })),
560
+ fees: result.fees.map((fee) => {
561
+ const token =
562
+ fee.token ??
563
+ (fee.side === 'source' ? candidate.sourceToken : candidate.destinationToken)
564
+ return {
565
+ amount: Value.tokenAmount({
566
+ baseUnits: fee.amount,
567
+ currency: token.currency,
568
+ decimals: token.decimals,
569
+ }),
570
+ side: fee.side,
571
+ token,
572
+ type: 'provider',
573
+ }
574
+ }),
560
575
  method: 'transaction',
561
576
  mode: body.mode,
562
577
  provider: candidate.provider,
@@ -604,6 +619,8 @@ export function transfers() {
604
619
  await release()
605
620
  if (cause instanceof FundingProvider.InvalidAmountError)
606
621
  return Response.error(c, { code: 'body_invalid', message: 'Invalid `amount`.', status: 400 }) // prettier-ignore
622
+ if (cause instanceof FundingProvider.UnsupportedProviderError)
623
+ return Response.error(c, { code: 'body_invalid', message: 'Unsupported `provider`.', status: 400 }) // prettier-ignore
607
624
  if (cause instanceof FundingProvider.UnsupportedSourceChainError)
608
625
  return quoteNotAvailable(c)
609
626
  if (cause instanceof FundingProvider.ProviderQuoteError) {