@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.
- package/README.md +117 -0
- package/dist/_types/components/AddressDisplay/index.d.ts +43 -0
- package/dist/_types/components/AddressDisplay/index.d.ts.map +1 -0
- package/dist/_types/components/ErrorRetryCard/index.d.ts +21 -0
- package/dist/_types/components/ErrorRetryCard/index.d.ts.map +1 -0
- package/dist/_types/components/FeeBreakdown/feeInfo.d.ts +24 -0
- package/dist/_types/components/FeeBreakdown/feeInfo.d.ts.map +1 -0
- package/dist/_types/components/FeeBreakdown/index.d.ts +28 -0
- package/dist/_types/components/FeeBreakdown/index.d.ts.map +1 -0
- package/dist/_types/components/LoadingCard/index.d.ts +6 -0
- package/dist/_types/components/LoadingCard/index.d.ts.map +1 -0
- package/dist/_types/components/PendingDeposits/index.d.ts +14 -0
- package/dist/_types/components/PendingDeposits/index.d.ts.map +1 -0
- package/dist/_types/components/QrSheet/QrCode.d.ts +28 -0
- package/dist/_types/components/QrSheet/QrCode.d.ts.map +1 -0
- package/dist/_types/components/QrSheet/index.d.ts +16 -0
- package/dist/_types/components/QrSheet/index.d.ts.map +1 -0
- package/dist/_types/components/TxnItem/ExplorerLink.d.ts +16 -0
- package/dist/_types/components/TxnItem/ExplorerLink.d.ts.map +1 -0
- package/dist/_types/components/TxnItem/index.d.ts +19 -0
- package/dist/_types/components/TxnItem/index.d.ts.map +1 -0
- package/dist/_types/constants.d.ts +20 -0
- package/dist/_types/constants.d.ts.map +1 -0
- package/dist/_types/context/SmartRoutingAddressContext.d.ts +32 -0
- package/dist/_types/context/SmartRoutingAddressContext.d.ts.map +1 -0
- package/dist/_types/context/SmartRoutingAddressProvider.d.ts +13 -0
- package/dist/_types/context/SmartRoutingAddressProvider.d.ts.map +1 -0
- package/dist/_types/hooks/useCreateSmartRoutingAddress.d.ts +23 -0
- package/dist/_types/hooks/useCreateSmartRoutingAddress.d.ts.map +1 -0
- package/dist/_types/hooks/useDepositStatus.d.ts +25 -0
- package/dist/_types/hooks/useDepositStatus.d.ts.map +1 -0
- package/dist/_types/hooks/useNewDeposits.d.ts +8 -0
- package/dist/_types/hooks/useNewDeposits.d.ts.map +1 -0
- package/dist/_types/hooks/useProviderFees.d.ts +22 -0
- package/dist/_types/hooks/useProviderFees.d.ts.map +1 -0
- package/dist/_types/hooks/useSmartRoutingAddress.d.ts +20 -0
- package/dist/_types/hooks/useSmartRoutingAddress.d.ts.map +1 -0
- package/dist/_types/iconAssets.d.ts +7 -0
- package/dist/_types/iconAssets.d.ts.map +1 -0
- package/dist/_types/index.d.ts +14 -0
- package/dist/_types/index.d.ts.map +1 -0
- package/dist/_types/pages/Deposit.d.ts +12 -0
- package/dist/_types/pages/Deposit.d.ts.map +1 -0
- package/dist/_types/pages/PastDeposits.d.ts +14 -0
- package/dist/_types/pages/PastDeposits.d.ts.map +1 -0
- package/dist/_types/pages/TransactionDetails.d.ts +11 -0
- package/dist/_types/pages/TransactionDetails.d.ts.map +1 -0
- package/dist/_types/pages/index.d.ts +25 -0
- package/dist/_types/pages/index.d.ts.map +1 -0
- package/dist/_types/types.d.ts +75 -0
- package/dist/_types/types.d.ts.map +1 -0
- package/dist/_types/utils/chains.d.ts +8 -0
- package/dist/_types/utils/chains.d.ts.map +1 -0
- package/dist/_types/utils/config.d.ts +35 -0
- package/dist/_types/utils/config.d.ts.map +1 -0
- package/dist/_types/utils/deposits.d.ts +29 -0
- package/dist/_types/utils/deposits.d.ts.map +1 -0
- package/dist/_types/utils/fees.d.ts +14 -0
- package/dist/_types/utils/fees.d.ts.map +1 -0
- package/dist/_types/utils/format.d.ts +27 -0
- package/dist/_types/utils/format.d.ts.map +1 -0
- package/dist/_types/utils/providerFees.d.ts +103 -0
- package/dist/_types/utils/providerFees.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +2180 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +2 -0
- package/package.json +91 -0
- package/src/components/AddressDisplay/index.tsx +108 -0
- package/src/components/ErrorRetryCard/index.tsx +68 -0
- package/src/components/FeeBreakdown/feeInfo.ts +25 -0
- package/src/components/FeeBreakdown/index.tsx +218 -0
- package/src/components/LoadingCard/index.tsx +27 -0
- package/src/components/PendingDeposits/index.tsx +152 -0
- package/src/components/QrSheet/QrCode.tsx +163 -0
- package/src/components/QrSheet/index.tsx +80 -0
- package/src/components/TxnItem/ExplorerLink.tsx +37 -0
- package/src/components/TxnItem/index.tsx +178 -0
- package/src/constants.ts +102 -0
- package/src/context/SmartRoutingAddressContext.ts +48 -0
- package/src/context/SmartRoutingAddressProvider.tsx +145 -0
- package/src/hooks/useCreateSmartRoutingAddress.ts +28 -0
- package/src/hooks/useDepositStatus.ts +110 -0
- package/src/hooks/useNewDeposits.ts +25 -0
- package/src/hooks/useProviderFees.ts +235 -0
- package/src/hooks/useSmartRoutingAddress.ts +25 -0
- package/src/iconAssets.ts +130 -0
- package/src/index.ts +27 -0
- package/src/pages/Deposit.tsx +605 -0
- package/src/pages/PastDeposits.tsx +232 -0
- package/src/pages/TransactionDetails.tsx +466 -0
- package/src/pages/index.tsx +165 -0
- package/src/types.ts +84 -0
- package/src/utils/chains.ts +26 -0
- package/src/utils/config.ts +142 -0
- package/src/utils/deposits.ts +106 -0
- package/src/utils/fees.ts +51 -0
- package/src/utils/format.ts +108 -0
- 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
|
+
}
|