|
6 | 6 | } from '$lib/canisters/bridge.svelte' |
7 | 7 | import ArrowLeftRightLine from '$lib/icons/arrow-left-right-line.svelte' |
8 | 8 | import ArrowRightUpLine from '$lib/icons/arrow-right-up-line.svelte' |
| 9 | + import RefreshLine from '$lib/icons/refresh-line.svelte' |
9 | 10 | import { authStore } from '$lib/stores/auth.svelte' |
10 | 11 | import { toastRun } from '$lib/stores/toast.svelte' |
11 | 12 | import type { Chain } from '$lib/types/bridge' |
|
16 | 17 | } from '$lib/utils/helper' |
17 | 18 | import { type TokenInfo } from '$lib/utils/token' |
18 | 19 | import { tick } from 'svelte' |
| 20 | + import Spinner from '../ui/Spinner.svelte' |
19 | 21 | import TextClipboardButton from '../ui/TextClipboardButton.svelte' |
20 | 22 | import NetworkSelector from './ChainSelector.svelte' |
21 | 23 | import PrimaryButton from './PrimaryButton.svelte' |
|
145 | 147 | fromAmount = undefined |
146 | 148 | error = null |
147 | 149 | bridgingProgress = null |
| 150 | + refreshMyTokenInfo() |
148 | 151 | } |
149 | 152 |
|
150 | 153 | function validateSendAmount(event: Event) { |
|
210 | 213 | } |
211 | 214 | } |
212 | 215 |
|
213 | | - async function refreshMyTokenInfo() { |
| 216 | + async function refreshMyTokenInfo(all: boolean = false) { |
214 | 217 | await tick() |
215 | 218 |
|
216 | 219 | if ( |
|
229 | 232 |
|
230 | 233 | try { |
231 | 234 | isLoading = true |
| 235 | +
|
| 236 | + if (all) { |
| 237 | + await mainBridge.refreshState() |
| 238 | + if (selectedBridge !== mainBridge) { |
| 239 | + await selectedBridge.refreshState() |
| 240 | + } |
| 241 | +
|
| 242 | + const subBridges = await mainBridge.loadSubBridges() |
| 243 | + bridges = [mainBridge, ...subBridges] |
| 244 | + supportTokens = bridges.map((b) => b.token!) |
| 245 | + } |
| 246 | +
|
232 | 247 | const icp = await selectedBridge.loadICPTokenAPI() |
233 | 248 | switch (fromChain.name) { |
234 | 249 | case 'ICP': |
|
269 | 284 | if (bridge) { |
270 | 285 | selectedBridge = bridge |
271 | 286 | } |
| 287 | + refreshMyTokenInfo() |
272 | 288 | } |
273 | 289 |
|
274 | 290 | async function onSwapChains() { |
|
329 | 345 | localStorage.setItem('defaultToken', selectedBridge.token.symbol) |
330 | 346 | localStorage.setItem('defaultFrom', fromChain.name) |
331 | 347 | localStorage.setItem('defaultTo', toChain.name) |
| 348 | +
|
| 349 | + refreshMyTokenInfo() |
| 350 | + setTimeout(() => { |
| 351 | + refreshMyTokenInfo() |
| 352 | + }, 5000) |
332 | 353 | } catch (err) { |
333 | 354 | isBridging = false |
334 | 355 | throw err |
|
341 | 362 | class="space-y-6 rounded-xl border border-white/10 bg-[#131721]/80 p-6 pb-10 text-white/90 shadow-2xl backdrop-blur" |
342 | 363 | > |
343 | 364 | {#key bridgeCanister} |
| 365 | + <button |
| 366 | + title="Refresh state" |
| 367 | + class="absolute top-2 right-4 rounded-full bg-white/10 p-1 text-white/60 hover:bg-white/20 hover:text-white/80" |
| 368 | + onclick={() => refreshMyTokenInfo(true)} |
| 369 | + disabled={isLoading} |
| 370 | + > |
| 371 | + {#if isLoading} |
| 372 | + <Spinner class="size-5 text-white" /> |
| 373 | + {:else} |
| 374 | + <span class=" *:size-5"><RefreshLine /></span> |
| 375 | + {/if} |
| 376 | + </button> |
344 | 377 | {#if myIcpAddress && myEvmAddress} |
345 | | - <div class="mb-3 flex flex-col gap-1 text-sm text-white/90"> |
| 378 | + <div class="mb-3 flex flex-col gap-1 text-sm text-white/80"> |
346 | 379 | <p class="mb-1 text-white/60">Your address</p> |
347 | 380 | <p class="flex items-center gap-1"> |
348 | 381 | {#key myIcpAddress} |
349 | 382 | <span>ICP: {pruneAddress(myIcpAddress, true)}</span> |
350 | | - <TextClipboardButton value={myIcpAddress} class="*:size-5" /> |
| 383 | + <TextClipboardButton |
| 384 | + value={myIcpAddress} |
| 385 | + class="text-white/60 *:size-5 hover:text-white/80" |
| 386 | + /> |
351 | 387 | {/key} |
352 | 388 | </p> |
353 | 389 | <p class="flex items-center gap-1"> |
354 | 390 | {#key myEvmAddress} |
355 | 391 | <span>EVM: {pruneAddress(myEvmAddress, true)}</span> |
356 | | - <TextClipboardButton value={myEvmAddress} class="*:size-5" /> |
| 392 | + <TextClipboardButton |
| 393 | + value={myEvmAddress} |
| 394 | + class="text-white/60 *:size-5 hover:text-white/80" |
| 395 | + /> |
357 | 396 | {/key} |
358 | 397 | </p> |
359 | 398 | </div> |
|
367 | 406 | {onSelectToken} |
368 | 407 | tokens={supportTokens} |
369 | 408 | /> |
370 | | - {#if selectedToken} |
| 409 | + {#if selectedBridge} |
371 | 410 | <a |
372 | 411 | class="flex items-center gap-1 text-sm font-medium text-white/60" |
373 | 412 | title="View token ledger canister" |
374 | | - href="https://dashboard.internetcomputer.org/canister/{selectedToken.canisterId}" |
| 413 | + href="https://dashboard.internetcomputer.org/canister/{selectedBridge.canisterId.toText()}" |
375 | 414 | target="_blank" |
376 | 415 | > |
377 | | - <span>{pruneCanister(selectedToken.canisterId)}</span> |
| 416 | + <span>Bridge</span> |
| 417 | + <span>{pruneCanister(selectedBridge.canisterId.toText())}</span> |
378 | 418 | <span class="*:size-4"><ArrowRightUpLine /></span> |
379 | 419 | </a> |
380 | 420 | {/if} |
|
384 | 424 | <div class="grid grid-cols-[1fr_32px_1fr] items-center gap-0"> |
385 | 425 | <!-- From Section --> |
386 | 426 | <div class=""> |
387 | | - <p class="mb-1 text-sm text-white/60">From</p> |
| 427 | + <p class="mb-1 flex items-center gap-2 text-sm text-white/60"> |
| 428 | + <span>From</span> |
| 429 | + {#if selectedBridge && fromChain} |
| 430 | + {@const [token, tokenUrl] = selectedBridge.getTokenUrl( |
| 431 | + fromChain.name |
| 432 | + )} |
| 433 | + {#if token && tokenUrl} |
| 434 | + <a |
| 435 | + class="flex items-center gap-1 text-sm font-medium text-white/60" |
| 436 | + title="View {token} info" |
| 437 | + href={tokenUrl} |
| 438 | + target="_blank" |
| 439 | + > |
| 440 | + <span>{pruneCanister(token, false)}</span> |
| 441 | + <span class="*:size-4"><ArrowRightUpLine /></span> |
| 442 | + </a> |
| 443 | + {/if} |
| 444 | + {/if} |
| 445 | + </p> |
388 | 446 | <NetworkSelector |
389 | 447 | disabled={isLoading || isBridging} |
390 | 448 | selectedChain={fromChain} |
|
410 | 468 |
|
411 | 469 | <!-- To Section --> |
412 | 470 | <div class="relative"> |
413 | | - <p class="mb-1 text-sm text-white/60">To</p> |
| 471 | + <p class="mb-1 flex items-center gap-2 text-sm text-white/60"> |
| 472 | + <span>To</span> |
| 473 | + {#if selectedBridge && toChain} |
| 474 | + {@const [token, tokenUrl] = selectedBridge.getTokenUrl( |
| 475 | + toChain.name |
| 476 | + )} |
| 477 | + {#if token && tokenUrl} |
| 478 | + <a |
| 479 | + class="flex items-center gap-1 text-sm font-medium text-white/60" |
| 480 | + title="View {token} info" |
| 481 | + href={tokenUrl} |
| 482 | + target="_blank" |
| 483 | + > |
| 484 | + <span>{pruneCanister(token, false)}</span> |
| 485 | + <span class="*:size-4"><ArrowRightUpLine /></span> |
| 486 | + </a> |
| 487 | + {/if} |
| 488 | + {/if}</p |
| 489 | + > |
414 | 490 | <NetworkSelector |
415 | 491 | disabled={isLoading || isBridging} |
416 | 492 | selectedChain={toChain} |
|
440 | 516 | placeholder="0.0" |
441 | 517 | data-1p-ignore |
442 | 518 | autocomplete="off" |
443 | | - class="w-full flex-1 rounded-xl border border-white/10 bg-white/10 p-2 text-left font-mono text-xl leading-8 ring-0 transition-all duration-200 outline-none placeholder:text-gray-500 invalid:border-red-400 focus:bg-white/20" |
| 519 | + class="w-full flex-1 rounded-xl border border-white/10 bg-white/10 p-2 text-left font-mono text-xl leading-8 ring-0 transition-all duration-200 outline-none placeholder:text-gray-500 invalid:border-red-400 focus:bg-white/20 disabled:cursor-not-allowed" |
444 | 520 | /> |
445 | 521 | {#if selectedBridge} |
446 | 522 | {@const token_bridge_fee = selectedBridge.state?.token_bridge_fee || 0n} |
|
478 | 554 | placeholder={pruneAddress(toAddress) || '0x...'} |
479 | 555 | data-1p-ignore |
480 | 556 | autocomplete="off" |
481 | | - class="mb-1 w-full min-w-0 flex-1 rounded-xl border border-white/10 bg-white/10 p-2 text-left leading-8 ring-0 transition-all duration-200 outline-none placeholder:text-gray-500 invalid:border-red-400 focus:bg-white/20" |
| 557 | + class="mb-1 w-full min-w-0 flex-1 rounded-xl border border-white/10 bg-white/10 p-2 text-left leading-8 ring-0 transition-all duration-200 outline-none placeholder:text-gray-500 invalid:border-red-400 focus:bg-white/20 disabled:cursor-not-allowed" |
482 | 558 | /> |
| 559 | + {#if selectedBridge && !error && fromAmount! > 0} |
| 560 | + {@const token_bridge_fee = selectedBridge.state?.token_bridge_fee || 0n} |
| 561 | + {@const amount = selectedBridge.parseAmount(fromAmount!)} |
| 562 | + <div class="mt-1 text-sm text-green-500"> |
| 563 | + <span |
| 564 | + >You receive: {selectedBridge.displayAmount( |
| 565 | + amount - token_bridge_fee |
| 566 | + )}</span |
| 567 | + > |
| 568 | + </div> |
| 569 | + {/if} |
483 | 570 | {#if thirdAddress} |
484 | 571 | <label |
485 | 572 | class="flex items-center text-sm font-medium text-white/60 rtl:text-right" |
486 | 573 | ><input |
487 | 574 | type="checkbox" |
488 | 575 | name="confirmAddress" |
| 576 | + disabled={isLoading || isBridging} |
489 | 577 | bind:checked={confirmAddress} |
490 | | - class="text-primary-600 me-2 size-4 flex-shrink-0 rounded-sm border-gray-300 bg-gray-100 ring-0" |
| 578 | + class="text-primary-600 me-2 size-4 flex-shrink-0 rounded-sm border-gray-300 bg-gray-100 ring-0 disabled:cursor-not-allowed" |
491 | 579 | /> |
492 | 580 | I confirmed the address is correct and not an exchange or contract address. |
493 | 581 | Any tokens sent to an incorrect address will be unrecoverable.</label |
|
0 commit comments