Skip to content
Closed
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
61 changes: 61 additions & 0 deletions src/app/wallets/get-send-all-onchain-amount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { USDAmount } from "@domain/shared"

import Ibex from "@services/ibex/client"
import { IbexError } from "@services/ibex/errors"
import { baseLogger } from "@services/logger"

// #270: `onChainPaymentSendAll` handed IBEX the entire wallet balance, and IBEX
// charges the on-chain fee on top of the amount it forwards. A full-balance
// request therefore either fails at the rail or leaves dust behind instead of
// sweeping the wallet. Estimate the fee for this payout and subtract it so the
// sweep lands.
//
// A failed estimate is not a reason to refuse the send: every branch below
// returns a usable amount, and the caller keeps the pre-existing behaviour
// (whole balance) when the rail has nothing to say about the fee.
export const getSendAllOnChainAmount = async ({
amount,
address,
}: {
amount: USDAmount
address: OnChainAddress
}): Promise<USDAmount> => {
const resp = await Ibex.estimateOnchainFee(amount, address)
if (resp instanceof IbexError) {
baseLogger.warn(
{ address, message: resp.message },
"Onchain fee estimate unavailable for sendAll; sweeping the full balance",
)
return amount
}

const fee = resp.fee === undefined ? Number.NaN : Number(resp.fee)
if (!Number.isFinite(fee) || fee <= 0) {
baseLogger.warn(
{ address, fee: resp.fee },
"Onchain fee estimate returned no fee for sendAll; sweeping the full balance",
)
return amount
}

const feeAmount = USDAmount.dollars(fee)
if (feeAmount instanceof Error) {
baseLogger.warn(
{ address, fee, message: feeAmount.message },
"Could not parse the onchain fee estimate for sendAll; sweeping the full balance",
)
return amount
}

// A fee that eats the whole balance is not a sendable sweep: sending
// `amount - fee` would be zero or negative, which the rail refuses anyway.
if (!amount.isGreaterThan(feeAmount)) {
baseLogger.warn(
{ address, fee },
"Onchain fee covers the whole balance for sendAll; nothing left to sweep",
)
return amount
}

return amount.subtract(feeAmount)
}
83 changes: 42 additions & 41 deletions src/app/wallets/index.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,42 @@
export * from "./add-invoice-for-wallet"
export * from "./add-pending-on-chain-transaction"
export * from "./remove-pending-on-chain-transaction"
export * from "./add-settled-on-chain-transaction"
export * from "./create-on-chain-address"
export * from "./get-balance-for-wallet"
export * from "./get-last-on-chain-address"
export * from "./get-on-chain-fee"
export * from "./get-pending-onchain-balance-for-wallet"
export * from "./get-transaction-by-id"
export * from "./get-transactions-by-addresses"
export * from "./get-transactions-by-hash"
export * from "./get-transactions-for-wallet"
export * from "./send-on-chain"
export * from "./register-broadcasted-payout-txn"
export * from "./settle-payout-txn"
export * from "./update-legacy-on-chain-receipt"
export * from "./update-pending-invoices"
export * from "./validate"
export * from "./usd-wallet-amount"

import { WalletsRepository } from "@services/mongoose"

export const getWallet = async (walletId: WalletId) => {
const wallets = WalletsRepository()
return wallets.findById(walletId)
}

export const listWalletsByAccountId = async (
accountId: AccountId,
): Promise<Wallet[] | RepositoryError> => {
return WalletsRepository().listByAccountId(accountId)
}

export const listWalletIds = async (
walletCurrency: WalletCurrency,
): Promise<WalletId[] | RepositoryError> => {
const wallets = await WalletsRepository().listByWalletCurrency(walletCurrency)
if (wallets instanceof Error) return wallets
return wallets.map((wallet) => wallet.id)
}
export * from "./add-invoice-for-wallet"
export * from "./add-pending-on-chain-transaction"
export * from "./remove-pending-on-chain-transaction"
export * from "./add-settled-on-chain-transaction"
export * from "./create-on-chain-address"
export * from "./get-balance-for-wallet"
export * from "./get-last-on-chain-address"
export * from "./get-on-chain-fee"
export * from "./get-pending-onchain-balance-for-wallet"
export * from "./get-transaction-by-id"
export * from "./get-transactions-by-addresses"
export * from "./get-transactions-by-hash"
export * from "./get-transactions-for-wallet"
export * from "./get-send-all-onchain-amount"
export * from "./send-on-chain"
export * from "./register-broadcasted-payout-txn"
export * from "./settle-payout-txn"
export * from "./update-legacy-on-chain-receipt"
export * from "./update-pending-invoices"
export * from "./validate"
export * from "./usd-wallet-amount"

import { WalletsRepository } from "@services/mongoose"

export const getWallet = async (walletId: WalletId) => {
const wallets = WalletsRepository()
return wallets.findById(walletId)
}

export const listWalletsByAccountId = async (
accountId: AccountId,
): Promise<Wallet[] | RepositoryError> => {
return WalletsRepository().listByAccountId(accountId)
}

export const listWalletIds = async (
walletCurrency: WalletCurrency,
): Promise<WalletId[] | RepositoryError> => {
const wallets = await WalletsRepository().listByWalletCurrency(walletCurrency)
if (wallets instanceof Error) return wallets
return wallets.map((wallet) => wallet.id)
}
Loading