Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/lib/target-tick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { fetchTickInfo } from '@/lib/network-stats'

const isRequestedTargetTickExpired = (
requestedTargetTick: bigint | number | undefined,
currentTick: number | undefined,
): boolean => {
if (
requestedTargetTick === undefined ||
typeof currentTick !== 'number' ||
!Number.isInteger(currentTick)
) {
return false
}

if (typeof requestedTargetTick === 'number' && !Number.isInteger(requestedTargetTick)) {
return false
}

const targetTick =
typeof requestedTargetTick === 'bigint' ? requestedTargetTick : BigInt(requestedTargetTick)

return targetTick < BigInt(currentTick)
}

export const isRequestedTargetTickExpiredNow = async (
requestedTargetTick: bigint | number | undefined,
): Promise<boolean> => {
if (requestedTargetTick === undefined) return false
try {
const latestTickInfo = await fetchTickInfo()
return isRequestedTargetTickExpired(requestedTargetTick, latestTickInfo.tickInfo?.tick)
} catch {
return false
}
}
49 changes: 49 additions & 0 deletions src/lib/transaction-submission-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { isRequestedTargetTickExpiredNow } from '@/lib/target-tick'

type RequestedTargetTick = bigint | number | undefined
const TARGET_TICK_EXPIRED_ERROR_CODE = 'tx_target_tick_expired'

type TransactionSubmissionErrorMessages = {
generic: string
targetTickExpired: string
networkError: string
broadcastFailed: string
}

const getErrorCode = (error: unknown): string | undefined => {
if (!error || typeof error !== 'object' || !('code' in error)) return undefined
const { code } = error as { code?: unknown }
return typeof code === 'string' ? code : undefined
}

export const resolveTransactionSubmissionErrorMessage = async (
error: unknown,
requestedTargetTick: RequestedTargetTick,
messages: TransactionSubmissionErrorMessages,
options?: { allowTickExpiryHeuristic?: boolean },
): Promise<string> => {
if (getErrorCode(error) === TARGET_TICK_EXPIRED_ERROR_CODE) {
return messages.targetTickExpired
}

if (!(error instanceof Error)) return messages.generic

const errorMessage = error.message.toLowerCase()

if (errorMessage.includes('network') || errorMessage.includes('fetch')) {
return messages.networkError
}

if (errorMessage.includes('broadcast')) {
return messages.broadcastFailed
}

if (
options?.allowTickExpiryHeuristic &&
(await isRequestedTargetTickExpiredNow(requestedTargetTick))
) {
return messages.targetTickExpired
}

return error.message
}
2 changes: 2 additions & 0 deletions src/locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@
},
"errors": {
"networkError": "Netzwerkfehler. Bitte erneut versuchen.",
"targetTickExpired": "Der Ziel-Tick ist bereits abgelaufen. Bitte einen höheren Ziel-Tick verwenden.",
"broadcastFailed": "Transaktion konnte nicht gesendet werden.",
"watchOnly": "Watch-Only-Konten können keine Überweisungen senden.",
"generic": "Überweisung fehlgeschlagen. Bitte erneut versuchen."
Expand Down Expand Up @@ -413,6 +414,7 @@
},
"errors": {
"networkError": "Netzwerkfehler. Bitte erneut versuchen.",
"targetTickExpired": "Der Ziel-Tick ist bereits abgelaufen. Bitte einen höheren Ziel-Tick verwenden.",
"broadcastFailed": "Transaktion konnte nicht gesendet werden.",
"watchOnly": "Watch-Only-Konten können keine Rechte übertragen.",
"generic": "Rechteübertragung fehlgeschlagen. Bitte erneut versuchen."
Expand Down
2 changes: 2 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@
},
"errors": {
"networkError": "Network error. Please retry.",
"targetTickExpired": "Target tick has already passed. Please use a higher target tick.",
"broadcastFailed": "Failed to broadcast transaction.",
"watchOnly": "Watch-only accounts cannot send transfers.",
"generic": "Transfer failed. Please retry."
Expand Down Expand Up @@ -413,6 +414,7 @@
},
"errors": {
"networkError": "Network error. Please retry.",
"targetTickExpired": "Target tick has already passed. Please use a higher target tick.",
"broadcastFailed": "Failed to broadcast transaction.",
"watchOnly": "Watch-only accounts cannot transfer rights.",
"generic": "Transfer rights failed. Please retry."
Expand Down
2 changes: 2 additions & 0 deletions src/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@
},
"errors": {
"networkError": "Error de red. Intenta de nuevo.",
"targetTickExpired": "El tick objetivo ya pasó. Usa un tick objetivo más alto.",
"broadcastFailed": "Fallo al transmitir la transacción.",
"watchOnly": "Las cuentas de solo lectura no pueden enviar transferencias.",
"generic": "Transferencia fallida. Intenta de nuevo."
Expand Down Expand Up @@ -413,6 +414,7 @@
},
"errors": {
"networkError": "Error de red. Intenta de nuevo.",
"targetTickExpired": "El tick objetivo ya pasó. Usa un tick objetivo más alto.",
"broadcastFailed": "Fallo al transmitir la transacción.",
"watchOnly": "Las cuentas de solo lectura no pueden transferir derechos.",
"generic": "Transferencia de derechos fallida. Intenta de nuevo."
Expand Down
2 changes: 2 additions & 0 deletions src/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@
},
"errors": {
"networkError": "Erreur réseau. Veuillez réessayer.",
"targetTickExpired": "Le tick cible est déjà passé. Veuillez utiliser un tick cible plus élevé.",
"broadcastFailed": "Échec de la diffusion de la transaction.",
"watchOnly": "Les comptes en lecture seule ne peuvent pas envoyer de transferts.",
"generic": "Échec du transfert. Veuillez réessayer."
Expand Down Expand Up @@ -413,6 +414,7 @@
},
"errors": {
"networkError": "Erreur réseau. Veuillez réessayer.",
"targetTickExpired": "Le tick cible est déjà passé. Veuillez utiliser un tick cible plus élevé.",
"broadcastFailed": "Échec de la diffusion de la transaction.",
"watchOnly": "Les comptes en lecture seule ne peuvent pas transférer de droits.",
"generic": "Échec du transfert de droits. Veuillez réessayer."
Expand Down
2 changes: 2 additions & 0 deletions src/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@
},
"errors": {
"networkError": "Ошибка сети. Попробуйте снова.",
"targetTickExpired": "Целевой тик уже прошел. Укажите более высокий целевой тик.",
"broadcastFailed": "Не удалось отправить транзакцию в сеть.",
"watchOnly": "Аккаунты только для просмотра не могут отправлять переводы.",
"generic": "Перевод не удался. Попробуйте снова."
Expand Down Expand Up @@ -413,6 +414,7 @@
},
"errors": {
"networkError": "Ошибка сети. Попробуйте снова.",
"targetTickExpired": "Целевой тик уже прошел. Укажите более высокий целевой тик.",
"broadcastFailed": "Не удалось отправить транзакцию в сеть.",
"watchOnly": "Аккаунты только для просмотра не могут передавать права.",
"generic": "Передача прав не удалась. Попробуйте снова."
Expand Down
2 changes: 2 additions & 0 deletions src/locales/tr.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@
},
"errors": {
"networkError": "Ağ hatası. Lütfen tekrar deneyin.",
"targetTickExpired": "Hedef tick zaten geçti. Lütfen daha yüksek bir hedef tick kullanın.",
"broadcastFailed": "İşlem yayınlanamadı.",
"watchOnly": "Yalnızca izleme hesapları transfer gönderemez.",
"generic": "Transfer başarısız. Lütfen tekrar deneyin."
Expand Down Expand Up @@ -413,6 +414,7 @@
},
"errors": {
"networkError": "Ağ hatası. Lütfen tekrar deneyin.",
"targetTickExpired": "Hedef tick zaten geçti. Lütfen daha yüksek bir hedef tick kullanın.",
"broadcastFailed": "İşlem yayınlanamadı.",
"watchOnly": "Yalnızca izleme hesapları hak transferi yapamaz.",
"generic": "Hak transferi başarısız. Lütfen tekrar deneyin."
Expand Down
2 changes: 2 additions & 0 deletions src/locales/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@
},
"errors": {
"networkError": "Lỗi mạng. Vui lòng thử lại.",
"targetTickExpired": "Tick mục tiêu đã qua. Vui lòng dùng tick mục tiêu cao hơn.",
"broadcastFailed": "Không thể truyền giao dịch.",
"watchOnly": "Tài khoản chỉ xem không thể gửi chuyển khoản.",
"generic": "Chuyển khoản thất bại. Vui lòng thử lại."
Expand Down Expand Up @@ -413,6 +414,7 @@
},
"errors": {
"networkError": "Lỗi mạng. Vui lòng thử lại.",
"targetTickExpired": "Tick mục tiêu đã qua. Vui lòng dùng tick mục tiêu cao hơn.",
"broadcastFailed": "Không thể truyền giao dịch.",
"watchOnly": "Tài khoản chỉ xem không thể chuyển quyền.",
"generic": "Chuyển quyền thất bại. Vui lòng thử lại."
Expand Down
2 changes: 2 additions & 0 deletions src/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@
},
"errors": {
"networkError": "网络错误,请重试。",
"targetTickExpired": "目标 Tick 已经过期。请使用更高的目标 Tick。",
"broadcastFailed": "交易广播失败。",
"watchOnly": "仅观察账户无法发送转账。",
"generic": "转账失败,请重试。"
Expand Down Expand Up @@ -413,6 +414,7 @@
},
"errors": {
"networkError": "网络错误,请重试。",
"targetTickExpired": "目标 Tick 已经过期。请使用更高的目标 Tick。",
"broadcastFailed": "交易广播失败。",
"watchOnly": "仅观察账户无法转移权限。",
"generic": "权限转移失败,请重试。"
Expand Down
29 changes: 16 additions & 13 deletions src/pages/transfer-rights.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
import { addPendingTransaction, PENDING_SETTLED_EVENT } from '@/lib/pending-transactions'
import { isWalletLocked } from '@/lib/lock'
import { useTickInfo, fetchTickInfo } from '@/lib/network-stats'
import { resolveTransactionSubmissionErrorMessage } from '@/lib/transaction-submission-errors'
import {
compareBigIntDesc,
formatBalance,
Expand Down Expand Up @@ -324,15 +325,15 @@ const TransferRights = () => {

setSending(true)
setErrorMessage('')
let requestedTargetTick: bigint | number | undefined
let reachedSubmitStage = false

try {
const parsedShares = parseAmount(shares)
if (!parsedShares) {
throw new Error(t('transferRights.validation.sharesInvalid'))
}

let requestedTargetTick: bigint | number | undefined

const freshTickInfo = await fetchTickInfo()
const sendCurrentTick = freshTickInfo.tickInfo?.tick

Expand Down Expand Up @@ -381,6 +382,8 @@ const TransferRights = () => {
)
}

reachedSubmitStage = true

const result = await sdk.transactions.send({
fromSeed: seed,
toIdentity: sourceContract.contractAddress,
Expand Down Expand Up @@ -415,17 +418,17 @@ const TransferRights = () => {

navigate('/')
} catch (error) {
let message = t('transferRights.errors.generic')

if (error instanceof Error) {
if (error.message.includes('network') || error.message.includes('fetch')) {
message = t('transferRights.errors.networkError')
} else if (error.message.includes('broadcast')) {
message = t('transferRights.errors.broadcastFailed')
} else {
message = error.message
}
}
const message = await resolveTransactionSubmissionErrorMessage(
error,
requestedTargetTick,
{
generic: t('transferRights.errors.generic'),
targetTickExpired: t('transferRights.errors.targetTickExpired'),
networkError: t('transferRights.errors.networkError'),
broadcastFailed: t('transferRights.errors.broadcastFailed'),
},
{ allowTickExpiryHeuristic: reachedSubmitStage },
)

seedRef.current = null
setErrorMessage(message)
Expand Down
29 changes: 16 additions & 13 deletions src/pages/transfer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
import { addPendingTransaction, PENDING_SETTLED_EVENT } from '@/lib/pending-transactions'
import { isWalletLocked } from '@/lib/lock'
import { useLatestStats, useTickInfo, fetchTickInfo } from '@/lib/network-stats'
import { resolveTransactionSubmissionErrorMessage } from '@/lib/transaction-submission-errors'
import ConfirmationDrawer from '@/components/pages/transfer/confirmation-drawer'
import TransferForm from '@/components/pages/transfer/transfer-form'
import type { FormErrors } from '@/components/pages/transfer/types'
Expand Down Expand Up @@ -204,6 +205,8 @@ const Transfer = () => {

setSending(true)
setErrorMessage('')
let requestedTargetTick: bigint | number | undefined
let reachedSubmitStage = false

try {
const parsedAmount = parseAmount(amount)
Expand All @@ -212,9 +215,7 @@ const Transfer = () => {
}

let result: { txId: string; targetTick: bigint }
let requestedTargetTick: bigint | number | undefined

// Fetch fresh tick info at send time
const freshTickInfo = await fetchTickInfo()
const sendCurrentTick = freshTickInfo.tickInfo?.tick

Expand Down Expand Up @@ -247,6 +248,8 @@ const Transfer = () => {
throw new Error(t('transfer.errors.networkError'))
}

reachedSubmitStage = true

if (selectedAsset) {
const payload = buildAssetTransferPayload(
selectedAsset.issuerIdentity,
Expand Down Expand Up @@ -298,17 +301,17 @@ const Transfer = () => {

navigate('/')
} catch (error) {
let message = t('transfer.errors.generic')

if (error instanceof Error) {
if (error.message.includes('network') || error.message.includes('fetch')) {
message = t('transfer.errors.networkError')
} else if (error.message.includes('broadcast')) {
message = t('transfer.errors.broadcastFailed')
} else {
message = error.message
}
}
const message = await resolveTransactionSubmissionErrorMessage(
error,
requestedTargetTick,
{
generic: t('transfer.errors.generic'),
targetTickExpired: t('transfer.errors.targetTickExpired'),
networkError: t('transfer.errors.networkError'),
broadcastFailed: t('transfer.errors.broadcastFailed'),
},
{ allowTickExpiryHeuristic: reachedSubmitStage },
)

seedRef.current = null
setErrorMessage(message)
Expand Down
Loading