Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ class FioDomainRegisterSelectWallet extends React.PureComponent<Props, LocalStat
const styles = getStyles(theme)
const detailsText = sprintf(lstrings.fio_domain_wallet_selection_text, config.appName, loading ? '-' : activationCost)

let paymentWalletBody = lstrings.choose_your_wallet
let paymentWalletBody: string = lstrings.choose_your_wallet
if (paymentWallet != null && paymentWallet.id !== '') {
const wallet = account.currencyWallets[paymentWallet.id]
paymentWalletBody = `${getWalletName(wallet)} (${wallet.currencyInfo.currencyCode})`
Expand Down
2 changes: 1 addition & 1 deletion src/components/scenes/SettingsScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const SettingsScene = (props: Props) => {
const [defaultLogLevel, setDefaultLogLevel] = React.useState<EdgeLogType | 'silent'>(logSettings.defaultLogLevel)
const [disableAnim, setDisableAnim] = useState<boolean>(getDeviceSettings().disableAnimations)
const [forceLightAccountCreate, setForceLightAccountCreate] = useState<boolean>(getDeviceSettings().forceLightAccountCreate)
const [touchIdText, setTouchIdText] = React.useState(lstrings.settings_button_use_touchID)
const [touchIdText, setTouchIdText] = React.useState<string>(lstrings.settings_button_use_touchID)

const iconSize = theme.rem(1.25)
const isLightAccount = username == null
Expand Down
3 changes: 3 additions & 0 deletions src/components/scenes/Staking/StakeModifyScene.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
StakePosition
} from '../../../plugins/stake-plugins/types'
import { getExchangeDenomByCurrencyCode } from '../../../selectors/DenominationSelectors'
import { HumanFriendlyError } from '../../../types/HumanFriendlyError'
import { useSelector } from '../../../types/reactRedux'
import { EdgeSceneProps } from '../../../types/routerTypes'
import { getCurrencyIconUris } from '../../../util/CdnUris'
Expand Down Expand Up @@ -169,6 +170,8 @@ const StakeModifySceneComponent = (props: Props) => {
setErrorMessage(errMessage)
} else if (err instanceof InsufficientFundsError) {
setErrorMessage(lstrings.exchange_insufficient_funds_title)
} else if (err instanceof HumanFriendlyError) {
setErrorMessage(err.message)
} else {
showError(err)
setErrorMessage(lstrings.unknown_error_occurred_fragment)
Expand Down
2 changes: 1 addition & 1 deletion src/components/themed/FioRequestRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ class FioRequestRowComponent extends React.PureComponent<Props> {
const styles = getStyles(theme)

let statusStyle = styles.requestPartialConfirmation
let label = lstrings.fragment_wallet_unconfirmed
let label: string = lstrings.fragment_wallet_unconfirmed
if (status === 'sent_to_blockchain') {
statusStyle = styles.requestDetailsReceivedTx
label = lstrings.fragment_request_subtitle
Expand Down
12 changes: 4 additions & 8 deletions src/locales/en_US.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ const strings = {
error_paymentprotocol_no_payment_option: 'No currencies available for this Payment Protocol invoice. Accepted currencies: %s',
error_paymentprotocol_tx_verification_failed: 'Payment Protocol transaction verification mismatch',
error_spend_amount_less_then_min_s: 'Spend amount is less than minimum of %s',
error_amount_too_low_to_stake_s: 'The amount %s is too low to stake successfully',

// Warning messages:
warning_low_fee_selected: 'Low Fee Selected',
Expand Down Expand Up @@ -1120,10 +1121,7 @@ const strings = {
price_change_buy_sell_trade: 'Would you like to buy, sell, or exchange %1$s?',
// Update notices
update_notice_deprecate_electrum_servers_title: 'Blockbook Upgrade',
update_notice_deprecate_electrum_servers_message:
'%s no longer uses Electrum Servers. If you would like to continue to use CUSTOM NODES, please input Blockbook compatible addresses.' +
'\n\n' +
'NOTE: If you had custom nodes enabled, those wallets will not sync until corrected.',
update_notice_deprecate_electrum_servers_message: `%s no longer uses Electrum Servers. If you would like to continue to use CUSTOM NODES, please input Blockbook compatible addresses.\n\nNOTE: If you had custom nodes enabled, those wallets will not sync until corrected.`,

error_boundary_title: 'Oops!',
error_boundary_message_s:
Expand Down Expand Up @@ -1221,9 +1219,7 @@ const strings = {
loan_breakdown_title: 'Loan Breakdown',
loan_close_swap_warning:
"Closing your loan will liquidate some of the deposited collateral if you do not have enough balance to repay the remaining principal and interest on your loan. The remaining collateral will be deposited back to your wallet.\n\nLiquidation most likely will incur a higer capital cost, if remaining principal isn't repaid.",
loan_close_loan_no_tx_needed_message:
`There appears to be no principal to repay nor collateral to withdraw.\n\n` +
`No transactions are required to close your account, however the account may re-appear after closing if there are pending on-chain transactions.`,
loan_close_loan_no_tx_needed_message: `There appears to be no principal to repay nor collateral to withdraw.\n\nNo transactions are required to close your account, however the account may re-appear after closing if there are pending on-chain transactions.`,
loan_close_loan_title: 'Close Loan',
loan_close_multiple_asset_error:
'Closing loans with multiple debt assets and/or deposited collateral assets is not supported.\n\nPlease specify funding sources to repay loans with using Repay.',
Expand Down Expand Up @@ -1859,7 +1855,7 @@ const strings = {

auto_log_off_failed_message_s: 'Failed to auto-logoff: %s',
contacts_load_failed_message_s: 'Failed to load contacts: %s'
}
} as const

// eslint-disable-next-line import/no-default-export
export default strings
5 changes: 4 additions & 1 deletion src/locales/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ import zh from './strings/zh.json'

const allLocales = { en, de, ru, es, esMX, it, pt, ja, fr, ko, vi, zh }

export const lstrings = { ...en }
export const lstrings = { ...en } as const
export type LStrings = typeof lstrings
export type LStringsKey = keyof LStrings
export type LStringsValues = LStrings[LStringsKey]

// Set the language at boot:
const [firstLocale] = getLocales()
Expand Down
1 change: 1 addition & 0 deletions src/locales/strings/enUS.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"error_paymentprotocol_no_payment_option": "No currencies available for this Payment Protocol invoice. Accepted currencies: %s",
"error_paymentprotocol_tx_verification_failed": "Payment Protocol transaction verification mismatch",
"error_spend_amount_less_then_min_s": "Spend amount is less than minimum of %s",
"error_amount_too_low_to_stake_s": "The amount %s is too low to stake successfully",
"warning_low_fee_selected": "Low Fee Selected",
"warning_custom_fee_selected": "Custom Fee Selected",
"warning_low_or_custom_fee": "Using a low fee may increase the amount of time it takes for your transaction to confirm. In rare instances your transaction can fail.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { eq, gt, sub } from 'biggystring'
import { EdgeCurrencyWallet, EdgeTransaction } from 'edge-core-js'

import { lstrings } from '../../../../locales/strings'
import { HumanFriendlyError } from '../../../../types/HumanFriendlyError'
import { infoServerData } from '../../../../util/network'
import { AssetId, ChangeQuote, PositionAllocation, QuoteAllocation, StakePosition } from '../../types'
import { asInfoServerResponse } from '../../util/internalTypes'
import { StakePolicyConfig } from '../types'
import { makeKilnApi } from '../util/kilnUtils'
import { KilnError, makeKilnApi } from '../util/kilnUtils'
import { StakePolicyAdapter } from './types'

export interface CardanoPooledKilnAdapterConfig {
Expand Down Expand Up @@ -71,7 +73,21 @@ export const makeCardanoKilnAdapter = (policyConfig: StakePolicyConfig<CardanoPo
throw new Error('Insufficient funds')
}

const stakeTransaction = await kiln.adaStakeTransaction(walletAddress, adapterConfig.poolId, accountId)
const result = await kiln.adaStakeTransaction(walletAddress, adapterConfig.poolId, accountId).catch(error => {
if (error instanceof Error) return error
throw error
})
if (result instanceof KilnError) {
if (/Value \d+ less than the minimum UTXO value \d+/.test(result.error)) {
const displayBalance = await wallet.nativeToDenomination(walletBalance, wallet.currencyInfo.currencyCode)
throw new HumanFriendlyError(lstrings.error_amount_too_low_to_stake_s, `${displayBalance} ${wallet.currencyInfo.currencyCode}`)
}
}
if (result instanceof Error) {
throw result
}

const stakeTransaction = result
const edgeTx: EdgeTransaction = await wallet.otherMethods.decodeStakingTx(stakeTransaction.unsigned_tx_serialized)

const allocations: QuoteAllocation[] = [
Expand Down
46 changes: 31 additions & 15 deletions src/plugins/stake-plugins/generic/util/kilnUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
import { asArray, asEither, asMaybe, asObject, asString, asValue, Cleaner } from 'cleaners'
import { asArray, asJSON, asMaybe, asObject, asString, asValue, Cleaner } from 'cleaners'

export class KilnError extends Error {
name: string
message: string
error: string

constructor(message: string, error: string) {
super(message)
this.name = 'KilnError'
this.message = message
this.error = error
}
}

export interface KilnApi {
adaGetStakes: (params: {
Expand Down Expand Up @@ -28,6 +41,8 @@ export const makeKilnApi = (baseUrl: string, apiKey: string): KilnApi => {
const res = await fetch(url, opts)
if (!res.ok) {
const message = await res.text()
const errorResponse = asMaybe(asKilnErrorResponse)(message)
if (errorResponse != null) throw new KilnError(errorResponse.message, errorResponse.error)
throw new Error(`Kiln fetch error: ${message}`)
}
const json = await res.json()
Expand Down Expand Up @@ -62,7 +77,6 @@ export const makeKilnApi = (baseUrl: string, apiKey: string): KilnApi => {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
const raw = await fetchKiln(`/v1/ada/stakes?${query.toString()}`)
const response = asKilnResponse(asArray(asAdaStake))(raw)
if ('message' in response) throw new Error('Kiln error: ' + response.message)
return response.data
},

Expand All @@ -77,7 +91,6 @@ export const makeKilnApi = (baseUrl: string, apiKey: string): KilnApi => {
})
})
const response = asKilnResponse(asAdaStakeTransaction)(raw)
if ('message' in response) throw new Error('Kiln error: ' + response.message)
return response.data
},

Expand All @@ -90,7 +103,6 @@ export const makeKilnApi = (baseUrl: string, apiKey: string): KilnApi => {
})
})
const response = asKilnResponse(asAdaUnstakeTransaction)(raw)
if ('message' in response) throw new Error('Kiln error: ' + response.message)
return response.data
},

Expand All @@ -104,22 +116,19 @@ export const makeKilnApi = (baseUrl: string, apiKey: string): KilnApi => {
})
})
const response = asKilnResponse(asAdaUnstakeTransaction)(raw)
if ('message' in response) throw new Error('Kiln error: ' + response.message)
return response.data
},

// https://docs.api.kiln.fi/reference/getethonchainv2stakes
async ethGetOnChainStakes(address) {
const raw = await fetchKiln(`/v1/eth/onchain/v2/stakes?wallets=${address}`)
const response = asKilnResponse(asArray(asEthOnChainStake))(raw)
if ('message' in response) throw new Error('Kiln error: ' + response.message)
return response.data
},
// https://docs.api.kiln.fi/reference/getethonchainv2operations
async ethGetOnChainOperations(address) {
const raw = await fetchKiln(`/v1/eth/onchain/v2/operations?wallets=${address}`)
const response = asKilnResponse(asArray(asMaybe(asExitOperation)))(raw)
if ('message' in response) throw new Error('Kiln error: ' + response.message)
const filteredOps = response.data.filter((op): op is ExitOperation => op != null)
return filteredOps
}
Expand All @@ -135,15 +144,22 @@ export const makeKilnApi = (baseUrl: string, apiKey: string): KilnApi => {
export interface KilnResponse<T> {
data: T
}

const asKilnResponse = <T>(asT: Cleaner<T>) =>
asEither(
asObject({
data: asT
}),
asObject({
message: asString
})
)
asObject({
data: asT
})

export interface KilnErrorResponse {
error: string
message: string
}
const asKilnErrorResponse = asJSON(
asObject<KilnErrorResponse>({
error: asString,
message: asString
})
)

//
// Ada
Expand Down
23 changes: 23 additions & 0 deletions src/types/HumanFriendlyError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { sprintf } from 'sprintf-js'

import { LStringsValues } from '../locales/strings'

/**
* Error class that is meant to be used for errors that are meant to be shown
* to the user. The error message must be an lstrings value along with any
* sprintf arguments.
*
* This error type is strictly a GUI error type because of it's dependency
* on lstrings.
*/
export class HumanFriendlyError extends Error {
name: string
message: string

constructor(format: LStringsValues, ...args: any[]) {
const message = sprintf(format, ...args)
super(message)
this.name = 'HumanFriendlyError'
this.message = message
}
}
Loading