|
6 | 6 | import { |
7 | 7 | robsonApi, |
8 | 8 | type FundingQuote, |
| 9 | + type FundingRecoverSpotUsdtResponse, |
9 | 10 | type FundingSagaSummary, |
10 | 11 | type ApiError, |
11 | 12 | } from '$api/robson'; |
|
25 | 26 | let submitting = $state(false); |
26 | 27 | let tick = $state(Date.now()); |
27 | 28 | let tickTimer: ReturnType<typeof setInterval> | null = null; |
| 29 | + let recoveryPreview = $state<FundingRecoverSpotUsdtResponse | null>(null); |
| 30 | + let recoveryResult = $state<FundingRecoverSpotUsdtResponse | null>(null); |
| 31 | + let recoveryError = $state<string | null>(null); |
| 32 | + let recoveryConfirmInput = $state(''); |
| 33 | + let recoverySubmitting = $state(false); |
28 | 34 |
|
29 | 35 | let keyword = $derived($_('funding.confirmKeyword') ?? 'CONVERTER E MOVER'); |
30 | 36 | let canConfirm = $derived(confirmInput === keyword && !submitting); |
| 37 | + const recoveryConfirmPhrase = 'TRANSFER_SPOT_USDT_TO_FUTURES'; |
| 38 | + let recoveryCanExecute = $derived( |
| 39 | + Boolean(recoveryPreview) && |
| 40 | + recoveryConfirmInput === recoveryConfirmPhrase && |
| 41 | + !recoverySubmitting, |
| 42 | + ); |
31 | 43 |
|
32 | 44 | let expiresInMs = $derived.by(() => { |
33 | 45 | if (!quote) return 0; |
|
131 | 143 | return $_(`funding.state.${state}`) ?? state; |
132 | 144 | } |
133 | 145 |
|
| 146 | + function fmtRecovery(v: string | null | undefined): string { |
| 147 | + if (!v) return '—'; |
| 148 | + return `${Number(v).toFixed(8)} USDT`; |
| 149 | + } |
| 150 | +
|
| 151 | + async function previewSpotRecovery() { |
| 152 | + recoveryError = null; |
| 153 | + recoveryResult = null; |
| 154 | + recoveryConfirmInput = ''; |
| 155 | + recoverySubmitting = true; |
| 156 | + try { |
| 157 | + recoveryPreview = await robsonApi.recoverSpotUsdtToFutures({ |
| 158 | + asset: 'USDT', |
| 159 | + dry_run: true, |
| 160 | + }); |
| 161 | + } catch (e) { |
| 162 | + recoveryPreview = null; |
| 163 | + recoveryError = |
| 164 | + e instanceof Error ? e.message : 'Recovery preview failed'; |
| 165 | + } finally { |
| 166 | + recoverySubmitting = false; |
| 167 | + } |
| 168 | + } |
| 169 | +
|
| 170 | + async function executeSpotRecovery() { |
| 171 | + if (!recoveryPreview || !recoveryCanExecute) return; |
| 172 | + recoveryError = null; |
| 173 | + recoverySubmitting = true; |
| 174 | + try { |
| 175 | + recoveryResult = await robsonApi.recoverSpotUsdtToFutures({ |
| 176 | + asset: 'USDT', |
| 177 | + amount: recoveryPreview.amount, |
| 178 | + execute: true, |
| 179 | + dry_run: false, |
| 180 | + confirm: recoveryConfirmPhrase, |
| 181 | + correlation_id: recoveryPreview.correlation_id, |
| 182 | + }); |
| 183 | + recoveryPreview = recoveryResult; |
| 184 | + recoveryConfirmInput = ''; |
| 185 | + void loadRecent(); |
| 186 | + } catch (e) { |
| 187 | + recoveryError = |
| 188 | + e instanceof Error ? e.message : 'Recovery execution failed'; |
| 189 | + } finally { |
| 190 | + recoverySubmitting = false; |
| 191 | + } |
| 192 | + } |
| 193 | +
|
134 | 194 | $effect(() => { |
135 | 195 | void loadRecent(); |
136 | 196 | return () => { |
|
283 | 343 | </Stack> |
284 | 344 | </Card> |
285 | 345 |
|
| 346 | + <Card padding={6}> |
| 347 | + <Stack gap={4}> |
| 348 | + <div class="eyebrow">RECOVERY</div> |
| 349 | + <h2>Transferir USDT livre da Spot para Futures</h2> |
| 350 | + <p class="recovery-copy"> |
| 351 | + Recupera funding parcialmente executado sem nova conversão, sem compra e |
| 352 | + sem trade. O preview é obrigatório antes da execução. |
| 353 | + </p> |
| 354 | + |
| 355 | + <Row gap={3} justify="start"> |
| 356 | + <button |
| 357 | + class="btn-primary" |
| 358 | + disabled={recoverySubmitting} |
| 359 | + onclick={previewSpotRecovery} |
| 360 | + > |
| 361 | + {#if recoverySubmitting} |
| 362 | + Consultando... |
| 363 | + {:else} |
| 364 | + Preview Spot → Futures |
| 365 | + {/if} |
| 366 | + </button> |
| 367 | + </Row> |
| 368 | + |
| 369 | + {#if recoveryError} |
| 370 | + <p class="err-text">{recoveryError}</p> |
| 371 | + {/if} |
| 372 | + |
| 373 | + {#if recoveryPreview} |
| 374 | + <div class="quote-block"> |
| 375 | + <Stack gap={3}> |
| 376 | + <Row gap={4} justify="between" align="baseline"> |
| 377 | + <span class="eyebrow">Spot USDT antes</span> |
| 378 | + <span class="mono" |
| 379 | + >{fmtRecovery(recoveryPreview.spot_usdt_before)}</span |
| 380 | + > |
| 381 | + </Row> |
| 382 | + <Row gap={4} justify="between" align="baseline"> |
| 383 | + <span class="eyebrow">Futures wallet antes</span> |
| 384 | + <span class="mono" |
| 385 | + >{fmtRecovery(recoveryPreview.futures_usdt_wallet_before)}</span |
| 386 | + > |
| 387 | + </Row> |
| 388 | + <Row gap={4} justify="between" align="baseline"> |
| 389 | + <span class="eyebrow">Amount proposto</span> |
| 390 | + <span class="mono">{fmtRecovery(recoveryPreview.amount)}</span> |
| 391 | + </Row> |
| 392 | + <Row gap={4} justify="between" align="baseline"> |
| 393 | + <span class="eyebrow">Spot esperado depois</span> |
| 394 | + <span class="mono" |
| 395 | + >{fmtRecovery(recoveryPreview.spot_usdt_after_expected)}</span |
| 396 | + > |
| 397 | + </Row> |
| 398 | + <Row gap={4} justify="between" align="baseline"> |
| 399 | + <span class="eyebrow">Futures esperado depois</span> |
| 400 | + <span class="mono" |
| 401 | + >{fmtRecovery( |
| 402 | + recoveryPreview.futures_usdt_wallet_after_expected, |
| 403 | + )}</span |
| 404 | + > |
| 405 | + </Row> |
| 406 | + <Row gap={4} justify="between" align="baseline"> |
| 407 | + <span class="eyebrow">Correlation id</span> |
| 408 | + <span class="mono dim">{recoveryPreview.correlation_id}</span> |
| 409 | + </Row> |
| 410 | + {#if recoveryResult} |
| 411 | + <Row gap={4} justify="between" align="baseline"> |
| 412 | + <span class="eyebrow">Transfer id</span> |
| 413 | + <span class="mono">{recoveryResult.transfer_id ?? '—'}</span> |
| 414 | + </Row> |
| 415 | + <Row gap={4} justify="between" align="baseline"> |
| 416 | + <span class="eyebrow">Spot atual</span> |
| 417 | + <span class="mono" |
| 418 | + >{fmtRecovery(recoveryResult.spot_usdt_after_actual)}</span |
| 419 | + > |
| 420 | + </Row> |
| 421 | + <Row gap={4} justify="between" align="baseline"> |
| 422 | + <span class="eyebrow">Futures atual</span> |
| 423 | + <span class="mono" |
| 424 | + >{fmtRecovery( |
| 425 | + recoveryResult.futures_usdt_wallet_after_actual, |
| 426 | + )}</span |
| 427 | + > |
| 428 | + </Row> |
| 429 | + {/if} |
| 430 | + </Stack> |
| 431 | + </div> |
| 432 | + |
| 433 | + <Stack gap={2}> |
| 434 | + <label class="eyebrow" for="recovery-confirm-input"> |
| 435 | + Digite exatamente: {recoveryConfirmPhrase} |
| 436 | + </label> |
| 437 | + <input |
| 438 | + id="recovery-confirm-input" |
| 439 | + type="text" |
| 440 | + bind:value={recoveryConfirmInput} |
| 441 | + autocomplete="off" |
| 442 | + spellcheck="false" |
| 443 | + class="confirm-input" |
| 444 | + disabled={recoverySubmitting} |
| 445 | + /> |
| 446 | + </Stack> |
| 447 | + |
| 448 | + <Row gap={3} justify="end"> |
| 449 | + <button |
| 450 | + class="btn-confirm" |
| 451 | + class:ready={recoveryCanExecute} |
| 452 | + disabled={!recoveryCanExecute} |
| 453 | + onclick={executeSpotRecovery} |
| 454 | + > |
| 455 | + Transferir USDT Spot → Futures |
| 456 | + </button> |
| 457 | + </Row> |
| 458 | + {/if} |
| 459 | + </Stack> |
| 460 | + </Card> |
| 461 | + |
286 | 462 | {#if recent.length > 0} |
287 | 463 | <section> |
288 | 464 | <Stack gap={4}> |
|
328 | 504 | font-weight: 300; |
329 | 505 | letter-spacing: var(--track-tight); |
330 | 506 | } |
| 507 | + h2 { |
| 508 | + font-size: var(--text-xl); |
| 509 | + font-weight: 400; |
| 510 | + letter-spacing: var(--track-tight); |
| 511 | + } |
331 | 512 | .eyebrow { |
332 | 513 | font-family: var(--font-mono); |
333 | 514 | font-size: var(--text-xs); |
|
342 | 523 | border: 1px solid var(--border); |
343 | 524 | border-radius: var(--radius-sm); |
344 | 525 | } |
| 526 | + .recovery-copy { |
| 527 | + color: var(--fg-2); |
| 528 | + font-size: var(--text-sm); |
| 529 | + line-height: 1.5; |
| 530 | + } |
345 | 531 | .items { |
346 | 532 | display: flex; |
347 | 533 | flex-direction: column; |
|
0 commit comments