@zerodev/smart-routing-address-react-ui 0.0.1

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 (100) hide show
  1. package/README.md +117 -0
  2. package/dist/_types/components/AddressDisplay/index.d.ts +43 -0
  3. package/dist/_types/components/AddressDisplay/index.d.ts.map +1 -0
  4. package/dist/_types/components/ErrorRetryCard/index.d.ts +21 -0
  5. package/dist/_types/components/ErrorRetryCard/index.d.ts.map +1 -0
  6. package/dist/_types/components/FeeBreakdown/feeInfo.d.ts +24 -0
  7. package/dist/_types/components/FeeBreakdown/feeInfo.d.ts.map +1 -0
  8. package/dist/_types/components/FeeBreakdown/index.d.ts +28 -0
  9. package/dist/_types/components/FeeBreakdown/index.d.ts.map +1 -0
  10. package/dist/_types/components/LoadingCard/index.d.ts +6 -0
  11. package/dist/_types/components/LoadingCard/index.d.ts.map +1 -0
  12. package/dist/_types/components/PendingDeposits/index.d.ts +14 -0
  13. package/dist/_types/components/PendingDeposits/index.d.ts.map +1 -0
  14. package/dist/_types/components/QrSheet/QrCode.d.ts +28 -0
  15. package/dist/_types/components/QrSheet/QrCode.d.ts.map +1 -0
  16. package/dist/_types/components/QrSheet/index.d.ts +16 -0
  17. package/dist/_types/components/QrSheet/index.d.ts.map +1 -0
  18. package/dist/_types/components/TxnItem/ExplorerLink.d.ts +16 -0
  19. package/dist/_types/components/TxnItem/ExplorerLink.d.ts.map +1 -0
  20. package/dist/_types/components/TxnItem/index.d.ts +19 -0
  21. package/dist/_types/components/TxnItem/index.d.ts.map +1 -0
  22. package/dist/_types/constants.d.ts +20 -0
  23. package/dist/_types/constants.d.ts.map +1 -0
  24. package/dist/_types/context/SmartRoutingAddressContext.d.ts +32 -0
  25. package/dist/_types/context/SmartRoutingAddressContext.d.ts.map +1 -0
  26. package/dist/_types/context/SmartRoutingAddressProvider.d.ts +13 -0
  27. package/dist/_types/context/SmartRoutingAddressProvider.d.ts.map +1 -0
  28. package/dist/_types/hooks/useCreateSmartRoutingAddress.d.ts +23 -0
  29. package/dist/_types/hooks/useCreateSmartRoutingAddress.d.ts.map +1 -0
  30. package/dist/_types/hooks/useDepositStatus.d.ts +25 -0
  31. package/dist/_types/hooks/useDepositStatus.d.ts.map +1 -0
  32. package/dist/_types/hooks/useNewDeposits.d.ts +8 -0
  33. package/dist/_types/hooks/useNewDeposits.d.ts.map +1 -0
  34. package/dist/_types/hooks/useProviderFees.d.ts +22 -0
  35. package/dist/_types/hooks/useProviderFees.d.ts.map +1 -0
  36. package/dist/_types/hooks/useSmartRoutingAddress.d.ts +20 -0
  37. package/dist/_types/hooks/useSmartRoutingAddress.d.ts.map +1 -0
  38. package/dist/_types/iconAssets.d.ts +7 -0
  39. package/dist/_types/iconAssets.d.ts.map +1 -0
  40. package/dist/_types/index.d.ts +14 -0
  41. package/dist/_types/index.d.ts.map +1 -0
  42. package/dist/_types/pages/Deposit.d.ts +12 -0
  43. package/dist/_types/pages/Deposit.d.ts.map +1 -0
  44. package/dist/_types/pages/PastDeposits.d.ts +14 -0
  45. package/dist/_types/pages/PastDeposits.d.ts.map +1 -0
  46. package/dist/_types/pages/TransactionDetails.d.ts +11 -0
  47. package/dist/_types/pages/TransactionDetails.d.ts.map +1 -0
  48. package/dist/_types/pages/index.d.ts +25 -0
  49. package/dist/_types/pages/index.d.ts.map +1 -0
  50. package/dist/_types/types.d.ts +75 -0
  51. package/dist/_types/types.d.ts.map +1 -0
  52. package/dist/_types/utils/chains.d.ts +8 -0
  53. package/dist/_types/utils/chains.d.ts.map +1 -0
  54. package/dist/_types/utils/config.d.ts +35 -0
  55. package/dist/_types/utils/config.d.ts.map +1 -0
  56. package/dist/_types/utils/deposits.d.ts +29 -0
  57. package/dist/_types/utils/deposits.d.ts.map +1 -0
  58. package/dist/_types/utils/fees.d.ts +14 -0
  59. package/dist/_types/utils/fees.d.ts.map +1 -0
  60. package/dist/_types/utils/format.d.ts +27 -0
  61. package/dist/_types/utils/format.d.ts.map +1 -0
  62. package/dist/_types/utils/providerFees.d.ts +103 -0
  63. package/dist/_types/utils/providerFees.d.ts.map +1 -0
  64. package/dist/index.cjs +2 -0
  65. package/dist/index.cjs.map +1 -0
  66. package/dist/index.mjs +2180 -0
  67. package/dist/index.mjs.map +1 -0
  68. package/dist/styles.css +2 -0
  69. package/package.json +91 -0
  70. package/src/components/AddressDisplay/index.tsx +108 -0
  71. package/src/components/ErrorRetryCard/index.tsx +68 -0
  72. package/src/components/FeeBreakdown/feeInfo.ts +25 -0
  73. package/src/components/FeeBreakdown/index.tsx +218 -0
  74. package/src/components/LoadingCard/index.tsx +27 -0
  75. package/src/components/PendingDeposits/index.tsx +152 -0
  76. package/src/components/QrSheet/QrCode.tsx +163 -0
  77. package/src/components/QrSheet/index.tsx +80 -0
  78. package/src/components/TxnItem/ExplorerLink.tsx +37 -0
  79. package/src/components/TxnItem/index.tsx +178 -0
  80. package/src/constants.ts +102 -0
  81. package/src/context/SmartRoutingAddressContext.ts +48 -0
  82. package/src/context/SmartRoutingAddressProvider.tsx +145 -0
  83. package/src/hooks/useCreateSmartRoutingAddress.ts +28 -0
  84. package/src/hooks/useDepositStatus.ts +110 -0
  85. package/src/hooks/useNewDeposits.ts +25 -0
  86. package/src/hooks/useProviderFees.ts +235 -0
  87. package/src/hooks/useSmartRoutingAddress.ts +25 -0
  88. package/src/iconAssets.ts +130 -0
  89. package/src/index.ts +27 -0
  90. package/src/pages/Deposit.tsx +605 -0
  91. package/src/pages/PastDeposits.tsx +232 -0
  92. package/src/pages/TransactionDetails.tsx +466 -0
  93. package/src/pages/index.tsx +165 -0
  94. package/src/types.ts +84 -0
  95. package/src/utils/chains.ts +26 -0
  96. package/src/utils/config.ts +142 -0
  97. package/src/utils/deposits.ts +106 -0
  98. package/src/utils/fees.ts +51 -0
  99. package/src/utils/format.ts +108 -0
  100. package/src/utils/providerFees.ts +301 -0
@@ -0,0 +1,301 @@
1
+ import { formatUnits } from 'viem'
2
+ import { z } from 'zod/mini'
3
+ import type { EstimatedFeeData } from '../types'
4
+ import { formatDisplayAmount, STABLE_SYMBOLS } from './format'
5
+
6
+ /**
7
+ * Standardised fee breakdown shared across routing providers. Each provider
8
+ * (Across, Relay, …) exposes fees in its own shape; the parsers below map
9
+ * them onto this single model so the fees view can render any of them
10
+ * identically.
11
+ *
12
+ * A leg is either a `rate` (proportional to the deposit, e.g. capital / LP)
13
+ * or `flat` (a fixed cost, e.g. destination gas). Because the deposit amount
14
+ * is not known up front, each leg is shown only in its amount-invariant
15
+ * form: a percentage for rates, a fixed USD value for flat costs.
16
+ */
17
+ export type FeeKind = 'rate' | 'flat'
18
+
19
+ export type FeeLine = {
20
+ key: string
21
+ label: string
22
+ kind: FeeKind
23
+ /** Right-aligned accent: gas pump for gas legs, $ disc otherwise */
24
+ accent: 'gas' | 'usd'
25
+ /** Proportional rate as a percent (e.g. 0.01 for 0.01%); rate legs only */
26
+ pct: number | null
27
+ /** USD value at the quoted amount; the display value for flat legs */
28
+ usd: number | null
29
+ /** Raw fallback text (e.g. a token amount) when neither pct nor usd applies */
30
+ text?: string
31
+ /** True when the leg is waived (shown as "Sponsored") */
32
+ sponsored?: boolean
33
+ }
34
+
35
+ export type FeeBreakdown = {
36
+ /** Routing provider, or null when no itemised provider data is available */
37
+ provider: string | null
38
+ lines: FeeLine[]
39
+ /** Combined proportional rate as a percent, e.g. 0.013 for "0.013%" */
40
+ ratePct: number | null
41
+ /** Combined flat (fixed) cost in USD */
42
+ flatUsd: number | null
43
+ /** Full cost in USD at the quoted amount — used to compare routes */
44
+ quotedTotalUsd: number | null
45
+ /** Headline fallback when USD isn't derivable (e.g. "0.00002 ETH") */
46
+ totalText: string | null
47
+ /** Estimated fill time in seconds, when the provider reports it */
48
+ fillTimeSec: number | null
49
+ }
50
+
51
+ function sum(values: (number | null)[]): number | null {
52
+ return values.reduce<number | null>(
53
+ (total, v) => (v === null ? total : (total ?? 0) + v),
54
+ null,
55
+ )
56
+ }
57
+
58
+ // ---------------------------------------------------------------------------
59
+ // Across — GET https://app.across.to/api/suggested-fees
60
+ // Fees are nested { pct, total } objects. `pct` is an 18-decimal fraction
61
+ // (1e18 = 100%); `total` is in input-token units. Capital and LP fees are
62
+ // true rates; the relayer gas fee is a fixed destination-gas cost (its pct
63
+ // only looks like a rate because it is gas/amount). totalRelayFee = sum of
64
+ // the three legs.
65
+ //
66
+ // Schemas validate the response at the fetch boundary — the API is external,
67
+ // so a shape change (added required field, renamed leg, changed type) must
68
+ // surface as a failed parse, not silently corrupted numbers. Unknown keys
69
+ // are stripped (Zod default); we only assert what we actually consume.
70
+ // ---------------------------------------------------------------------------
71
+
72
+ const AcrossLegSchema = z.object({
73
+ pct: z.string(),
74
+ total: z.string(),
75
+ })
76
+
77
+ export const AcrossSuggestedFeesSchema = z.object({
78
+ totalRelayFee: AcrossLegSchema,
79
+ relayerCapitalFee: AcrossLegSchema,
80
+ relayerGasFee: AcrossLegSchema,
81
+ lpFee: AcrossLegSchema,
82
+ estimatedFillTimeSec: z.optional(z.number()),
83
+ })
84
+
85
+ type AcrossLeg = z.infer<typeof AcrossLegSchema>
86
+ export type AcrossSuggestedFees = z.infer<typeof AcrossSuggestedFeesSchema>
87
+
88
+ const E18 = 10n ** 18n
89
+
90
+ /** 18-decimal Across fraction → percent (e.g. "150298578165425" → 0.0150) */
91
+ function acrossPct(raw: string): number | null {
92
+ try {
93
+ // percent = raw/1e18 * 100, kept to 4-decimal percent precision via
94
+ // BigInt before Number conversion (raw can exceed 2^53).
95
+ return Number((BigInt(raw) * 1_000_000n) / E18) / 10_000
96
+ } catch {
97
+ return null
98
+ }
99
+ }
100
+
101
+ /** Across totals are in input-token units; USD only when the token is ~$1 */
102
+ function tokenUsd(
103
+ total: string,
104
+ decimals: number,
105
+ symbol: string,
106
+ ): number | null {
107
+ if (!STABLE_SYMBOLS.has(symbol.toUpperCase())) return null
108
+ try {
109
+ return Number(formatUnits(BigInt(total), decimals))
110
+ } catch {
111
+ return null
112
+ }
113
+ }
114
+
115
+ export function parseAcrossFees(
116
+ res: AcrossSuggestedFees,
117
+ symbol: string,
118
+ decimals: number,
119
+ ): FeeBreakdown {
120
+ const leg = (
121
+ key: string,
122
+ label: string,
123
+ kind: FeeKind,
124
+ accent: FeeLine['accent'],
125
+ data: AcrossLeg,
126
+ ): FeeLine => ({
127
+ key,
128
+ label,
129
+ kind,
130
+ accent,
131
+ pct: acrossPct(data.pct),
132
+ usd: tokenUsd(data.total, decimals, symbol),
133
+ })
134
+
135
+ const lines: FeeLine[] = [
136
+ leg('capital', 'Capital Fee', 'rate', 'usd', res.relayerCapitalFee),
137
+ leg('destGas', 'Destination Gas', 'flat', 'gas', res.relayerGasFee),
138
+ leg('lp', 'LP Fee', 'rate', 'usd', res.lpFee),
139
+ ]
140
+
141
+ return {
142
+ provider: 'Across',
143
+ lines,
144
+ ratePct: sum(lines.filter((l) => l.kind === 'rate').map((l) => l.pct)),
145
+ flatUsd: sum(lines.filter((l) => l.kind === 'flat').map((l) => l.usd)),
146
+ quotedTotalUsd: tokenUsd(res.totalRelayFee.total, decimals, symbol),
147
+ totalText: null,
148
+ fillTimeSec:
149
+ typeof res.estimatedFillTimeSec === 'number'
150
+ ? res.estimatedFillTimeSec
151
+ : null,
152
+ }
153
+ }
154
+
155
+ // ---------------------------------------------------------------------------
156
+ // Relay — POST https://api.relay.link/quote
157
+ // Each fee carries `amountUsd` only (no rate), so every leg is treated as a
158
+ // fixed cost. `relayer` is the full relayer fee (relayerGas + relayerService);
159
+ // `gas` is the user's origin-chain gas.
160
+ // ---------------------------------------------------------------------------
161
+
162
+ const RelayFeeSchema = z.object({
163
+ amountUsd: z.optional(z.string()),
164
+ })
165
+
166
+ export const RelayQuoteSchema = z.object({
167
+ fees: z.optional(
168
+ z.object({
169
+ gas: z.optional(RelayFeeSchema),
170
+ relayer: z.optional(RelayFeeSchema),
171
+ relayerGas: z.optional(RelayFeeSchema),
172
+ relayerService: z.optional(RelayFeeSchema),
173
+ app: z.optional(RelayFeeSchema),
174
+ }),
175
+ ),
176
+ details: z.optional(
177
+ z.object({
178
+ timeEstimate: z.optional(z.number()),
179
+ }),
180
+ ),
181
+ timeEstimate: z.optional(z.number()),
182
+ })
183
+
184
+ type RelayFee = z.infer<typeof RelayFeeSchema>
185
+ export type RelayQuote = z.infer<typeof RelayQuoteSchema>
186
+
187
+ function usdOf(fee?: RelayFee): number | null {
188
+ if (!fee?.amountUsd) return null
189
+ const value = Number(fee.amountUsd)
190
+ return Number.isFinite(value) ? value : null
191
+ }
192
+
193
+ export function parseRelayFees(res: RelayQuote): FeeBreakdown {
194
+ const fees = res.fees ?? {}
195
+ const lines: FeeLine[] = []
196
+ const push = (
197
+ key: string,
198
+ label: string,
199
+ accent: FeeLine['accent'],
200
+ fee?: RelayFee,
201
+ ) => {
202
+ const usd = usdOf(fee)
203
+ if (usd === null) return
204
+ // Drop zero-value optional legs (e.g. app fee)
205
+ if (usd === 0 && (key === 'app' || key === 'originGas')) return
206
+ // Relay exposes USD only, so every leg is shown as a fixed cost
207
+ lines.push({ key, label, kind: 'flat', accent, pct: null, usd })
208
+ }
209
+
210
+ push('service', 'Service Fee', 'usd', fees.relayerService)
211
+ push('destGas', 'Destination Gas', 'gas', fees.relayerGas)
212
+ push('originGas', 'Origin Gas', 'gas', fees.gas)
213
+ push('app', 'App Fee', 'usd', fees.app)
214
+
215
+ const totalUsd = usdOf(fees.relayer)
216
+ return {
217
+ provider: 'Relay',
218
+ lines,
219
+ ratePct: null,
220
+ flatUsd: sum(lines.map((l) => l.usd)),
221
+ quotedTotalUsd: totalUsd,
222
+ totalText: null,
223
+ fillTimeSec: res.details?.timeEstimate ?? res.timeEstimate ?? null,
224
+ }
225
+ }
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Combine SRA fee + provider legs into the final breakdown
229
+ // ---------------------------------------------------------------------------
230
+
231
+ /**
232
+ * Present the SRA fee as the actual all-in charge, with the provider's
233
+ * bridge legs as a breakdown *of* it (not added on top — the SRA server fee
234
+ * already covers bridging + execution). The execution portion is derived as
235
+ * `SRA fee − bridge cost` so the legs sum back to the SRA fee. Without
236
+ * provider data, the whole SRA fee is shown as a single execution line.
237
+ */
238
+ export function buildFeeBreakdown(
239
+ feeData: EstimatedFeeData,
240
+ symbol: string,
241
+ provider: FeeBreakdown | null = null,
242
+ ): FeeBreakdown {
243
+ // The SRA fee is the all-in amount ZeroDev charges (bridging + execution)
244
+ const sraUsd = feeData.isSponsored
245
+ ? 0
246
+ : tokenUsd(feeData.fee, feeData.decimal, symbol)
247
+ const bridgeUsd = provider?.quotedTotalUsd ?? null
248
+ // Token-unit fallback for non-stablecoins where USD isn't derivable
249
+ const feeToken = `${formatDisplayAmount(feeData.fee, feeData.decimal, 'up')} ${symbol}`
250
+
251
+ // Execution = the part of the SRA fee not explained by the bridge legs
252
+ const executionUsd = feeData.isSponsored
253
+ ? 0
254
+ : sraUsd === null
255
+ ? null
256
+ : bridgeUsd === null
257
+ ? sraUsd
258
+ : Math.max(0, sraUsd - bridgeUsd)
259
+
260
+ const showTokenFallback = !feeData.isSponsored && sraUsd === null
261
+ const executionLine: FeeLine = {
262
+ key: 'execution',
263
+ label: 'Execution Fee',
264
+ kind: 'flat',
265
+ accent: 'usd',
266
+ pct: null,
267
+ usd: executionUsd,
268
+ // Show the SRA fee in token units when we can't price it in USD
269
+ ...(showTokenFallback && { text: feeToken }),
270
+ sponsored: feeData.isSponsored,
271
+ }
272
+
273
+ const lines = [executionLine, ...(provider?.lines ?? [])]
274
+
275
+ return {
276
+ provider: provider?.provider ?? null,
277
+ lines,
278
+ ratePct: sum(lines.filter((l) => l.kind === 'rate').map((l) => l.pct)),
279
+ flatUsd: sum(lines.filter((l) => l.kind === 'flat').map((l) => l.usd)),
280
+ // The headline charge is the SRA fee itself, never the sum on top of it
281
+ quotedTotalUsd: feeData.isSponsored ? 0 : sraUsd,
282
+ // Headline fallback for non-stable tokens (e.g. "0.00002 ETH")
283
+ totalText: !feeData.isSponsored && sraUsd === null ? feeToken : null,
284
+ fillTimeSec: provider?.fillTimeSec ?? null,
285
+ }
286
+ }
287
+
288
+ /** Format a percent number for display, trimming to a sensible precision */
289
+ export function formatFeePct(pct: number): string {
290
+ const digits = pct >= 1 ? 2 : pct >= 0.1 ? 2 : 3
291
+ return `${pct.toFixed(digits)}%`
292
+ }
293
+
294
+ /** Format a USD number for display */
295
+ export function formatFeeUsd(usd: number): string {
296
+ const digits = usd > 0 && usd < 0.01 ? 4 : 2
297
+ return `$${usd.toLocaleString('en-US', {
298
+ minimumFractionDigits: 2,
299
+ maximumFractionDigits: digits,
300
+ })}`
301
+ }