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
@@ -1,59 +1,56 @@
1
- import { Address } from 'ox'
1
+ import { AbiFunction, Address, Hex, RpcTransport } from 'ox'
2
+ import { createClient, custom, fallback, publicActions } from 'viem'
2
3
  import * as z from 'zod/mini'
3
4
 
5
+ import * as Action from '../Action.js'
6
+ import type * as Catalog from '../Catalog.js'
4
7
  import * as FundingProvider from '../Provider.js'
5
8
 
6
- const defaultBaseUrl = 'https://transfer.layerzero-api.com/v1'
9
+ const approve = AbiFunction.from('function approve(address spender, uint256 amount)')
7
10
  const defaultFetch: FundingProvider.Fetch = (input, init) => globalThis.fetch(input, init)
8
- const sourceDetails = {
9
- STARGATE_V2_BUS: 'stargate:value-transfer-v1:bus',
10
- STARGATE_V2_TAXI: 'stargate:value-transfer-v1:taxi',
11
- } as const
12
-
13
- namespace schema {
14
- const DecimalString = z.string().check(z.regex(/^\d+(\.\d+)?$/))
15
- const IntegerString = z.string().check(z.regex(/^\d+$/))
16
-
17
- const Quote = z.object({
18
- dstAmount: IntegerString,
19
- dstAmountMin: IntegerString,
20
- duration: z.optional(
21
- z.object({
22
- estimated: z.nullable(DecimalString),
23
- }),
24
- ),
25
- expiresAt: z.optional(z.iso.datetime()),
26
- id: z.string().check(z.minLength(1)),
27
- routeSteps: z
28
- .array(
29
- z.object({
30
- srcChainKey: z.string(),
31
- type: z.string(),
32
- }),
33
- )
34
- .check(z.minLength(1)),
35
- srcAmount: IntegerString,
36
- })
11
+ const destinationChainId = 'eip155:4217'
12
+ const destinationEid = 30_410
13
+ const destinationToken = '0x20C000000000000000000000b9537d11c60E8b50'
14
+ const quoteOft = AbiFunction.from(
15
+ 'function quoteOFT((uint32 dstEid, bytes32 to, uint256 amountLD, uint256 minAmountLD, bytes extraOptions, bytes composeMsg, bytes oftCmd) sendParam) view returns ((uint256 minAmountLD, uint256 maxAmountLD) limit, (int256 feeAmountLD, string description)[] oftFeeDetails, (uint256 amountSentLD, uint256 amountReceivedLD) receipt)',
16
+ )
17
+ const quoteSend = AbiFunction.from(
18
+ 'function quoteSend((uint32 dstEid, bytes32 to, uint256 amountLD, uint256 minAmountLD, bytes extraOptions, bytes composeMsg, bytes oftCmd) sendParam, bool payInLzToken) view returns ((uint256 nativeFee, uint256 lzTokenFee) fee)',
19
+ )
20
+ const sendToken = AbiFunction.from(
21
+ 'function sendToken((uint32 dstEid, bytes32 to, uint256 amountLD, uint256 minAmountLD, bytes extraOptions, bytes composeMsg, bytes oftCmd) sendParam, (uint256 nativeFee, uint256 lzTokenFee) fee, address refundAddress) payable returns ((bytes32 guid, uint64 nonce, (uint256 nativeFee, uint256 lzTokenFee) fee) msgReceipt, (uint256 amountSentLD, uint256 amountReceivedLD) oftReceipt, (uint72 ticketId, bytes passengerBytes) ticket)',
22
+ )
23
+ const quoteTtlMs = 60_000
24
+ const zeroAddress = '0x0000000000000000000000000000000000000000'
37
25
 
38
- const Error = z.object({
39
- message: z.string(),
40
- status: z.union([z.string(), z.number()]),
26
+ const configuration = z
27
+ .strictObject({
28
+ poolAddress: z
29
+ .string()
30
+ .check(
31
+ z.regex(/^0x[0-9a-fA-F]{40}$/),
32
+ z.describe('Stargate pool contract on the source chain.'),
33
+ ),
41
34
  })
35
+ .check(z.describe('Stargate configuration for one funding route.'))
42
36
 
43
- export const Quotes = z.object({
44
- error: z.optional(z.nullable(Error)),
45
- quotes: z.array(Quote),
46
- })
37
+ type Deployment = {
38
+ pool: Address.Address
39
+ sourceToken: Address.Address
47
40
  }
48
41
 
49
- /** Creates a Stargate funding provider backed by LayerZero's `/quotes` endpoint. */
50
- export function stargate(options: stargate.Options) {
51
- if (options.apiKey.trim().length === 0) throw new FundingProvider.ProviderConfigurationError()
52
- if (!Address.validate(options.userAddress, { strict: false }))
53
- throw new FundingProvider.ProviderConfigurationError()
42
+ type SendParam = {
43
+ amountLD: bigint
44
+ composeMsg: Hex.Hex
45
+ dstEid: number
46
+ extraOptions: Hex.Hex
47
+ minAmountLD: bigint
48
+ oftCmd: Hex.Hex
49
+ to: Hex.Hex
50
+ }
54
51
 
55
- const endpoint = new URL(options.baseUrl ?? defaultBaseUrl)
56
- endpoint.pathname = `${endpoint.pathname.replace(/\/+$/, '')}/quotes`
52
+ /** Creates a direct Stargate V2 OFT funding provider. */
53
+ export function stargate(options: stargate.Options = {}) {
57
54
  const fetch = options.fetch ?? defaultFetch
58
55
  const now = options.now ?? (() => new Date())
59
56
 
@@ -61,124 +58,352 @@ export function stargate(options: stargate.Options) {
61
58
  id: 'stargate',
62
59
  name: 'Stargate',
63
60
  async getQuote({ candidate, route }, signal) {
64
- const response = await FundingProvider.requestJson(endpoint, {
65
- body: {
66
- amount: candidate.sourceAmount.amount,
67
- dstChainKey: route.destination.chain.slug,
68
- dstTokenAddress: candidate.destinationToken.address,
69
- dstWalletAddress: options.userAddress,
70
- options: { amountType: 'EXACT_SRC_AMOUNT' },
71
- srcChainKey: route.source.chain.slug,
72
- srcTokenAddress: candidate.sourceToken.address,
73
- srcWalletAddress: options.userAddress,
74
- },
75
- fetch,
76
- headers: { 'user-agent': 'tempo-api', 'x-api-key': options.apiKey },
77
- method: 'POST',
78
- signal,
61
+ const deployment = resolveDeployment({
62
+ configuration: candidate.configuration,
63
+ destinationChainId: route.destination.chain.id,
64
+ destinationTokenAddress: route.destination.address,
65
+ sourceChainId: route.source.chain.id,
66
+ sourceTokenAddress: route.source.address,
79
67
  })
80
- const parsed = schema.Quotes.safeParse(response)
81
- if (!parsed.success || parsed.data.error) throw new FundingProvider.ProviderPayloadError()
82
-
83
68
  const sampledAt = now()
84
- const selected = selectQuote(parsed.data.quotes, {
69
+ if (!deployment) return unavailable({ message: 'ROUTE_NOT_CONFIGURED', now: sampledAt })
70
+
71
+ const sendParam = createSendParam({
85
72
  amount: candidate.sourceAmount.amount,
86
- now: sampledAt,
87
- sourceChainKey: route.source.chain.slug,
73
+ recipient: zeroAddress,
74
+ })
75
+ const [limit, feeDetails, receipt] = await client({ fetch, route, signal }).readContract({
76
+ abi: [quoteOft],
77
+ address: deployment.pool,
78
+ args: [sendParam],
79
+ functionName: 'quoteOFT',
88
80
  })
89
- if (!selected)
90
- return FundingProvider.unavailableResult({
91
- detail: 'stargate:value-transfer-v1',
92
- message: 'NO_STARGATE_ROUTE',
93
- now: sampledAt,
94
- source: 'providerQuote',
95
- })
96
-
97
- const estimated = estimatedSeconds(selected.quote.duration?.estimated)
81
+ if (!isExactQuote({ amount: candidate.sourceAmount.amount, feeDetails, limit, receipt }))
82
+ return unavailable({ message: 'NO_EXACT_ROUTE', now: sampledAt })
83
+
98
84
  return {
99
- ...(selected.quote.expiresAt ? { expiresAt: selected.quote.expiresAt } : {}),
100
- destinationAmountMin: selected.quote.dstAmountMin,
101
- destinationAmount: selected.quote.dstAmount,
102
- quality: {
103
- ...(estimated === undefined ? {} : { estimatedSeconds: estimated }),
104
- liquiditySource: 'providerQuote',
105
- sourceDetail: sourceDetails[selected.type],
106
- tier: 'liquid',
107
- },
85
+ destinationAmountMin: receipt.amountReceivedLD.toString(),
86
+ destinationAmount: receipt.amountReceivedLD.toString(),
87
+ quality: quality(),
108
88
  sampledAt: sampledAt.toISOString(),
109
89
  status: 'available',
110
90
  }
111
91
  },
92
+ async prepareTransfer({ candidate, method, mode, recipient, sender }, signal) {
93
+ if (
94
+ method !== 'transaction' ||
95
+ mode !== 'exactSource' ||
96
+ !Address.validate(recipient, { strict: false }) ||
97
+ !Address.validate(sender, { strict: false })
98
+ )
99
+ throw new FundingProvider.ProviderConfigurationError()
100
+
101
+ const deployment = resolveDeployment({
102
+ configuration: candidate.configuration,
103
+ destinationChainId: candidate.destinationChain.id,
104
+ destinationTokenAddress: candidate.destinationToken.address,
105
+ sourceChainId: candidate.sourceChain.id,
106
+ sourceTokenAddress: candidate.sourceToken.address,
107
+ })
108
+ if (!deployment) throw new FundingProvider.ProviderUnavailableError()
109
+ const sendParam = createSendParam({ amount: candidate.sourceAmount.amount, recipient })
110
+ const rpc = client({ fetch, route: candidate.route, signal })
111
+ const [[limit, feeDetails, receipt], messagingFee] = await Promise.all([
112
+ rpc.readContract({
113
+ abi: [quoteOft],
114
+ address: deployment.pool,
115
+ args: [sendParam],
116
+ functionName: 'quoteOFT',
117
+ }),
118
+ rpc.readContract({
119
+ abi: [quoteSend],
120
+ account: sender as Address.Address,
121
+ address: deployment.pool,
122
+ args: [sendParam, false],
123
+ functionName: 'quoteSend',
124
+ }),
125
+ ])
126
+ if (!isExactQuote({ amount: candidate.sourceAmount.amount, feeDetails, limit, receipt }))
127
+ throw new FundingProvider.ProviderUnavailableError()
128
+ if (messagingFee.lzTokenFee !== 0n) throw new FundingProvider.ProviderPayloadError()
129
+
130
+ const action = {
131
+ calls: [
132
+ {
133
+ data: AbiFunction.encodeData(approve, [
134
+ deployment.pool,
135
+ BigInt(candidate.sourceAmount.amount),
136
+ ]),
137
+ to: deployment.sourceToken,
138
+ value: '0x0',
139
+ },
140
+ {
141
+ data: AbiFunction.encodeData(sendToken, [sendParam, messagingFee, sender]),
142
+ to: deployment.pool,
143
+ value: Hex.fromNumber(messagingFee.nativeFee),
144
+ },
145
+ ],
146
+ type: 'evm:calls' as const,
147
+ }
148
+ validateAction({
149
+ action,
150
+ configuration: candidate.configuration,
151
+ destinationTokenAddress: candidate.destinationToken.address,
152
+ recipient,
153
+ sender,
154
+ sourceAmount: candidate.sourceAmount.amount,
155
+ sourceChainId: candidate.sourceChain.id,
156
+ sourceTokenAddress: candidate.sourceToken.address,
157
+ })
158
+
159
+ const sampledAt = now()
160
+ return {
161
+ action,
162
+ destinationAmountMin: receipt.amountReceivedLD.toString(),
163
+ destinationAmount: receipt.amountReceivedLD.toString(),
164
+ expiresAt: new Date(sampledAt.getTime() + quoteTtlMs).toISOString(),
165
+ fees:
166
+ messagingFee.nativeFee === 0n
167
+ ? []
168
+ : [
169
+ {
170
+ amount: messagingFee.nativeFee.toString(),
171
+ side: 'source' as const,
172
+ token: nativeToken(candidate.sourceChain.id),
173
+ },
174
+ ],
175
+ quality: quality(),
176
+ sampledAt: sampledAt.toISOString(),
177
+ }
178
+ },
179
+ validateTransferAction({ action, candidate, recipient, sender }) {
180
+ return {
181
+ nativeValues: validateAction({
182
+ action,
183
+ configuration: candidate.configuration,
184
+ destinationTokenAddress: candidate.destinationToken.address,
185
+ recipient,
186
+ sender,
187
+ sourceAmount: candidate.sourceAmount.amount,
188
+ sourceChainId: candidate.sourceChain.id,
189
+ sourceTokenAddress: candidate.sourceToken.address,
190
+ }),
191
+ }
192
+ },
112
193
  })
113
194
  }
114
195
 
115
196
  export declare namespace stargate {
116
197
  /** Stargate provider options. */
117
198
  type Options = {
118
- /** LayerZero Value Transfer API key sent in the required request header. */
119
- apiKey: string
120
- /** Override for tests or LayerZero test environments. */
121
- baseUrl?: string | undefined
122
- /** Fetch implementation override for tests. */
199
+ /** Fetch implementation override for chain RPC requests. */
123
200
  fetch?: FundingProvider.Fetch | undefined
124
201
  /** Clock override for deterministic tests. */
125
202
  now?: (() => Date) | undefined
126
- /** Quote wallet address used as the LayerZero sender and recipient. */
127
- userAddress: string
128
203
  }
129
204
  }
130
205
 
131
- function selectQuote(
132
- quotes: z.output<typeof schema.Quotes>['quotes'],
133
- options: selectQuote.Options,
134
- ) {
135
- return quotes
136
- .flatMap((quote) => {
137
- const type = quote.routeSteps
138
- .filter((step) => step.srcChainKey === options.sourceChainKey)
139
- .map((step) => step.type)
140
- .find(isStargateType)
141
- if (!type) return []
142
- if (
143
- quote.expiresAt !== undefined &&
144
- new Date(quote.expiresAt).getTime() <= options.now.getTime()
145
- )
146
- return []
147
- if (
148
- quote.srcAmount !== options.amount ||
149
- BigInt(quote.dstAmount) <= 0n ||
150
- BigInt(quote.dstAmountMin) <= 0n ||
151
- BigInt(quote.dstAmountMin) > BigInt(quote.dstAmount)
152
- )
153
- throw new FundingProvider.ProviderPayloadError()
154
- return [{ quote, type }]
206
+ /** Validates one direct Stargate action and returns its exact native-value allowance. */
207
+ export function validateAction(options: validateAction.Options): readonly Action.NativeValue[] {
208
+ try {
209
+ const action = Action.schema.EvmCalls.parse(options.action)
210
+ const deployment = resolveDeployment({
211
+ configuration: options.configuration,
212
+ destinationChainId,
213
+ destinationTokenAddress: options.destinationTokenAddress,
214
+ sourceChainId: options.sourceChainId,
215
+ sourceTokenAddress: options.sourceTokenAddress,
155
216
  })
156
- .sort((a, b) => {
157
- const delta = BigInt(b.quote.dstAmountMin) - BigInt(a.quote.dstAmountMin)
158
- if (delta > 0n) return 1
159
- if (delta < 0n) return -1
160
- return 0
161
- })[0]
217
+ if (!deployment || action.calls.length !== 2) throw new FundingProvider.ProviderPayloadError()
218
+ const [approval, execution] = action.calls
219
+ if (
220
+ !Address.isEqual(approval!.to as Address.Address, deployment.sourceToken) ||
221
+ BigInt(approval!.value) !== 0n ||
222
+ !Address.isEqual(execution!.to as Address.Address, deployment.pool)
223
+ )
224
+ throw new FundingProvider.ProviderPayloadError()
225
+
226
+ const [spender, approvalAmount] = AbiFunction.decodeData(approve, approval!.data as Hex.Hex)
227
+ const [sendParam, messagingFee, refundAddress] = AbiFunction.decodeData(
228
+ sendToken,
229
+ execution!.data as Hex.Hex,
230
+ )
231
+ const nativeValue = BigInt(execution!.value)
232
+ if (
233
+ !Address.isEqual(spender, deployment.pool) ||
234
+ approvalAmount !== BigInt(options.sourceAmount) ||
235
+ sendParam.amountLD !== BigInt(options.sourceAmount) ||
236
+ sendParam.composeMsg !== '0x' ||
237
+ sendParam.dstEid !== destinationEid ||
238
+ sendParam.extraOptions !== '0x' ||
239
+ sendParam.minAmountLD !== BigInt(options.sourceAmount) ||
240
+ sendParam.oftCmd !== '0x' ||
241
+ !Hex.isEqual(sendParam.to, recipientBytes32(options.recipient)) ||
242
+ messagingFee.lzTokenFee !== 0n ||
243
+ messagingFee.nativeFee !== nativeValue ||
244
+ !Address.isEqual(refundAddress, options.sender as Address.Address)
245
+ )
246
+ throw new FundingProvider.ProviderPayloadError()
247
+ return [{ to: execution!.to, value: execution!.value }]
248
+ } catch (cause) {
249
+ if (cause instanceof FundingProvider.ProviderPayloadError) throw cause
250
+ throw new FundingProvider.ProviderPayloadError()
251
+ }
162
252
  }
163
253
 
164
- declare namespace selectQuote {
254
+ export declare namespace validateAction {
255
+ /** Expected route terms for one Stargate action. */
256
+ type Options = {
257
+ /** Prepared Stargate action. */
258
+ action: Action.EvmCalls
259
+ /** Stargate configuration for the selected route. */
260
+ configuration?: Catalog.Configuration | undefined
261
+ /** Curated Tempo destination token. */
262
+ destinationTokenAddress: string
263
+ /** Tempo beneficiary. */
264
+ recipient: string
265
+ /** Source account and LayerZero refund recipient. */
266
+ sender: string
267
+ /** Exact source amount in base units. */
268
+ sourceAmount: string
269
+ /** Source chain CAIP-2 id. */
270
+ sourceChainId: string
271
+ /** Curated source token. */
272
+ sourceTokenAddress: string
273
+ }
274
+ }
275
+
276
+ function client(options: client.Options) {
277
+ if (options.route.source.chain.rpcUrls.length === 0)
278
+ throw new FundingProvider.ProviderConfigurationError()
279
+ return createClient({
280
+ transport: fallback(
281
+ options.route.source.chain.rpcUrls.map((url) =>
282
+ custom(
283
+ RpcTransport.fromHttp(url, {
284
+ fetchFn: (input, init) => options.fetch(input, { ...init, signal: options.signal }),
285
+ }),
286
+ { retryCount: 0 },
287
+ ),
288
+ ),
289
+ ),
290
+ }).extend(publicActions)
291
+ }
292
+
293
+ declare namespace client {
294
+ type Options = {
295
+ fetch: FundingProvider.Fetch
296
+ route: FundingProvider.QuoteCandidate['route']
297
+ signal: AbortSignal
298
+ }
299
+ }
300
+
301
+ function createSendParam(options: createSendParam.Options): SendParam {
302
+ const amount = BigInt(options.amount)
303
+ return {
304
+ amountLD: amount,
305
+ composeMsg: '0x',
306
+ dstEid: destinationEid,
307
+ extraOptions: '0x',
308
+ minAmountLD: amount,
309
+ oftCmd: '0x',
310
+ to: recipientBytes32(options.recipient),
311
+ }
312
+ }
313
+
314
+ declare namespace createSendParam {
165
315
  type Options = {
166
316
  amount: string
167
- now: Date
168
- sourceChainKey: string
317
+ recipient: string
318
+ }
319
+ }
320
+
321
+ function isExactQuote(options: isExactQuote.Options) {
322
+ const amount = BigInt(options.amount)
323
+ return (
324
+ amount >= options.limit.minAmountLD &&
325
+ amount <= options.limit.maxAmountLD &&
326
+ options.feeDetails.every((fee) => fee.feeAmountLD >= 0n) &&
327
+ options.receipt.amountSentLD === amount &&
328
+ options.receipt.amountReceivedLD === amount
329
+ )
330
+ }
331
+
332
+ declare namespace isExactQuote {
333
+ type Options = {
334
+ amount: string
335
+ feeDetails: readonly { feeAmountLD: bigint }[]
336
+ limit: { maxAmountLD: bigint; minAmountLD: bigint }
337
+ receipt: { amountReceivedLD: bigint; amountSentLD: bigint }
169
338
  }
170
339
  }
171
340
 
172
- function isStargateType(type: string): type is keyof typeof sourceDetails {
173
- return Object.hasOwn(sourceDetails, type)
341
+ function nativeToken(sourceChainId: string) {
342
+ return {
343
+ address: '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
344
+ currency: 'ETH',
345
+ decimals: 18,
346
+ name: 'Ether',
347
+ standard: 'native',
348
+ symbol: 'ETH',
349
+ tokenKey: `${sourceChainId}/slip44:60`,
350
+ verified: true,
351
+ }
174
352
  }
175
353
 
176
- function estimatedSeconds(milliseconds: string | null | undefined) {
177
- if (milliseconds === undefined || milliseconds === null) return undefined
178
- const [whole = '0', fraction] = milliseconds.split('.')
179
- const roundedMilliseconds =
180
- BigInt(whole) + (fraction !== undefined && /[1-9]/.test(fraction) ? 1n : 0n)
181
- const seconds = (roundedMilliseconds + 999n) / 1_000n
182
- if (seconds > BigInt(Number.MAX_SAFE_INTEGER)) throw new FundingProvider.ProviderPayloadError()
183
- return Number(seconds)
354
+ function quality() {
355
+ return {
356
+ liquiditySource: 'onchain',
357
+ sourceDetail: 'stargate:oft-v2:taxi',
358
+ tier: 'liquid' as const,
359
+ }
360
+ }
361
+
362
+ function recipientBytes32(recipient: string) {
363
+ if (!Address.validate(recipient, { strict: false }))
364
+ throw new FundingProvider.ProviderConfigurationError()
365
+ return Hex.padLeft(recipient as Hex.Hex, 32)
366
+ }
367
+
368
+ function resolveDeployment(options: resolveDeployment.Options): Deployment | undefined {
369
+ const parsed = configuration.safeParse(options.configuration)
370
+ if (
371
+ !parsed.success ||
372
+ options.destinationChainId !== destinationChainId ||
373
+ !options.sourceChainId.startsWith('eip155:') ||
374
+ !Address.validate(options.destinationTokenAddress, { strict: false }) ||
375
+ !Address.validate(options.sourceTokenAddress, { strict: false }) ||
376
+ !Address.isEqual(options.destinationTokenAddress as Address.Address, destinationToken)
377
+ )
378
+ return undefined
379
+ return {
380
+ pool: Address.checksum(parsed.data.poolAddress),
381
+ sourceToken: Address.checksum(options.sourceTokenAddress),
382
+ }
383
+ }
384
+
385
+ declare namespace resolveDeployment {
386
+ type Options = {
387
+ configuration?: Catalog.Configuration | undefined
388
+ destinationChainId: string
389
+ destinationTokenAddress: string
390
+ sourceChainId: string
391
+ sourceTokenAddress: string
392
+ }
393
+ }
394
+
395
+ function unavailable(options: unavailable.Options) {
396
+ return FundingProvider.unavailableResult({
397
+ detail: 'stargate:oft-v2',
398
+ message: options.message,
399
+ now: options.now,
400
+ source: 'onchain',
401
+ })
402
+ }
403
+
404
+ declare namespace unavailable {
405
+ type Options = {
406
+ message: string
407
+ now: Date
408
+ }
184
409
  }
@@ -12,6 +12,7 @@ test('exports internal modules', () => {
12
12
  "Errors",
13
13
  "Exchange",
14
14
  "Fees",
15
+ "Fetch",
15
16
  "Funding",
16
17
  "Id",
17
18
  "Idempotency",
@@ -7,6 +7,7 @@ export * as EarnVaults from './EarnVaults.js'
7
7
  export * as Errors from './Errors.js'
8
8
  export * as Exchange from './exchange/index.js'
9
9
  export * as Fees from './Fees.js'
10
+ export * as Fetch from './Fetch.js'
10
11
  export * as Funding from './funding/index.js'
11
12
  export * as Id from './Id.js'
12
13
  export * as Idempotency from './Idempotency.js'