mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 19:28:26 +00:00
Finalize first fees implementation
This commit is contained in:
@@ -468,6 +468,7 @@
|
||||
"invoice": "Lightning invoice to pay",
|
||||
"isOffline": "Redeem online",
|
||||
"lightningFee": "Lightning network fee",
|
||||
"fee": "Fee",
|
||||
"memoFromInvoice": "Memo from invoice",
|
||||
"memoFromSender": "Memo from sender",
|
||||
"receiveOfflineComplete": "Redeem to wallet",
|
||||
|
||||
+45
-33
@@ -15,6 +15,7 @@ import { getRootStore } from './helpers/getRootStore'
|
||||
import { generateId } from '../utils/utils'
|
||||
import { Proof } from './Proof'
|
||||
|
||||
|
||||
// used as a helper type across app
|
||||
/* export type Balance = {
|
||||
balance: number
|
||||
@@ -52,7 +53,7 @@ export type MintProofsCounter = {
|
||||
inFlightTid?: number // related tx id
|
||||
}
|
||||
|
||||
export const MintProofsCounterModel = types
|
||||
/* export const MintProofsCounterModel = types
|
||||
.model('MintProofsCounter', {
|
||||
keyset: types.string,
|
||||
unit: types.optional(types.frozen<MintUnit>(), 'sat'),
|
||||
@@ -60,7 +61,7 @@ export const MintProofsCounterModel = types
|
||||
inFlightFrom: types.maybe(types.number),
|
||||
inFlightTo: types.maybe(types.number),
|
||||
inFlightTid: types.maybe(types.number)
|
||||
})
|
||||
})*/
|
||||
/**
|
||||
* This represents a Cashu mint
|
||||
*/
|
||||
@@ -73,7 +74,16 @@ export const MintModel = types
|
||||
units: types.array(types.frozen<MintUnit>()),
|
||||
keysets: types.array(types.frozen<CashuMintKeyset>()),
|
||||
keys: types.array(types.frozen<CashuMintKeys>()),
|
||||
proofsCounters: types.array(MintProofsCounterModel),
|
||||
proofsCounters: types.array(
|
||||
types.model('MintProofsCounter', {
|
||||
keyset: types.string,
|
||||
unit: types.optional(types.frozen<MintUnit>(), 'sat'),
|
||||
counter: types.number,
|
||||
inFlightFrom: types.maybe(types.number),
|
||||
inFlightTo: types.maybe(types.number),
|
||||
inFlightTid: types.maybe(types.number)
|
||||
})
|
||||
),
|
||||
color: types.optional(types.string, colors.palette.iconBlue200),
|
||||
status: types.optional(types.frozen<MintStatus>(), MintStatus.ONLINE),
|
||||
createdAt: types.optional(types.Date, new Date()),
|
||||
@@ -147,6 +157,7 @@ export const MintModel = types
|
||||
const alreadyExists = self.proofsCounters.some(p => p.keyset === counter.keyset)
|
||||
|
||||
if(!alreadyExists) {
|
||||
log.trace('[addProofsCounter]', {counter})
|
||||
self.proofsCounters.push(counter)
|
||||
}
|
||||
|
||||
@@ -180,43 +191,44 @@ export const MintModel = types
|
||||
counter: 0,
|
||||
}
|
||||
|
||||
const proofsCounterInstance = MintProofsCounterModel.create(newCounter)
|
||||
self.addProofsCounter(newCounter)
|
||||
|
||||
log.trace('[ceateProofsCounter]', {newCounter: getSnapshot(proofsCounterInstance)})
|
||||
return proofsCounterInstance
|
||||
return self.getProofsCounter(keyset.id)
|
||||
}
|
||||
}))
|
||||
.actions(self => ({
|
||||
initKeyset(keyset: CashuMintKeyset) {
|
||||
// Do not add unit the wallet does not have configured
|
||||
if(!self.isUnitSupported(keyset.unit as MintUnit)) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, `Unsupported unit provided by the mint: ${keyset.unit}`)
|
||||
}
|
||||
|
||||
const existing = self.keysets.find(k => k.id === keyset.id)
|
||||
|
||||
if(existing) {
|
||||
if (existing.unit !== keyset.unit) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, `Keyset unit mismatch, got ${keyset.unit}, expected ${existing.unit}`)
|
||||
}
|
||||
try {
|
||||
if(!self.isUnitSupported(keyset.unit as MintUnit)) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, `Unsupported unit provided by the mint: ${keyset.unit}`)
|
||||
}
|
||||
|
||||
return existing
|
||||
}
|
||||
const existing = self.keysets.find(k => k.id === keyset.id)
|
||||
|
||||
if(!keyset.input_fee_ppk) {
|
||||
keyset.input_fee_ppk = 0
|
||||
}
|
||||
if(existing) {
|
||||
if (existing.unit !== keyset.unit) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, `Keyset unit mismatch, got ${keyset.unit}, expected ${existing.unit}`)
|
||||
}
|
||||
|
||||
return existing
|
||||
}
|
||||
|
||||
if(!keyset.unit) {
|
||||
keyset.unit = 'sat'
|
||||
}
|
||||
if(!keyset.input_fee_ppk) {
|
||||
keyset.input_fee_ppk = 0
|
||||
}
|
||||
|
||||
self.addKeyset(keyset)
|
||||
self.addUnit(keyset.unit as MintUnit)
|
||||
self.createProofsCounter(keyset)
|
||||
if(!keyset.unit) {
|
||||
keyset.unit = 'sat'
|
||||
}
|
||||
|
||||
log.trace('[initKeyset]', {newKeyset: keyset})
|
||||
self.addKeyset(keyset)
|
||||
self.addUnit(keyset.unit as MintUnit)
|
||||
self.createProofsCounter(keyset)
|
||||
|
||||
log.trace('[initKeyset]', {newKeyset: keyset})
|
||||
} catch (e: any) {
|
||||
throw new AppError(Err.WALLET_ERROR, '[initKeyset] ' + e.message)
|
||||
}
|
||||
},
|
||||
initKeys(key: CashuMintKeys) {
|
||||
// Do not add unit the wallet does not have configured
|
||||
@@ -409,7 +421,7 @@ export const MintModel = types
|
||||
|
||||
self.proofsCounters = cast(self.proofsCounters)
|
||||
},
|
||||
getFeesForProofs(proofs: Proof[]): number {
|
||||
getMintFeeReserve(proofs: Proof[]): number {
|
||||
// Find the corresponding keyset for each proof and sum the input fees
|
||||
const totalInputFees = proofs.reduce((sum, proof) => {
|
||||
const keyset = self.keysets.find(k => k.id === proof.id)
|
||||
@@ -417,10 +429,10 @@ export const MintModel = types
|
||||
}, 0)
|
||||
|
||||
// Calculate the fees
|
||||
const fees = Math.max(Math.floor((totalInputFees + 999) / 1000), 0)
|
||||
const feeReserve = Math.max(Math.floor((totalInputFees + 999) / 1000), 0)
|
||||
|
||||
log.debug('*** [getFeesForProofs]', {fees})
|
||||
return fees
|
||||
log.debug('[getMintFeeReserve]', {feeReserve})
|
||||
return feeReserve
|
||||
}
|
||||
}))
|
||||
.views(self => ({
|
||||
|
||||
@@ -45,15 +45,25 @@ export const ProofsStoreModel = types
|
||||
getByMint(
|
||||
mintUrl: string,
|
||||
options: {
|
||||
unit?: MintUnit,
|
||||
isPending: boolean,
|
||||
unit?: MintUnit,
|
||||
keysetIds?: string[]
|
||||
}
|
||||
|
||||
): Proof[] | undefined {
|
||||
const proofs = options.isPending ? self.pendingProofs : self.proofs
|
||||
|
||||
let proofs: Proof[] = []
|
||||
if(options.keysetIds && options.keysetIds.length > 0) {
|
||||
proofs = options.isPending ?
|
||||
self.pendingProofs.filter(p => options.keysetIds?.includes(p.id)) :
|
||||
self.proofs.filter(p => options.keysetIds?.includes(p.id))
|
||||
} else {
|
||||
proofs = options.isPending ? self.pendingProofs : self.proofs
|
||||
}
|
||||
|
||||
if (options.unit) {
|
||||
return proofs.filter(proof => proof.mintUrl === mintUrl && proof.unit === options.unit)
|
||||
return proofs.filter(
|
||||
proof => proof.mintUrl === mintUrl &&
|
||||
proof.unit === options.unit
|
||||
)
|
||||
.slice()
|
||||
.sort((a, b) => b.amount - a.amount)
|
||||
}
|
||||
@@ -62,7 +72,7 @@ export const ProofsStoreModel = types
|
||||
.slice()
|
||||
.sort((a, b) => b.amount - a.amount)
|
||||
|
||||
},
|
||||
},
|
||||
getProofInstance(proof: Proof, isPending: boolean = false) {
|
||||
let proofInstance: Proof | undefined
|
||||
if (isStateTreeNode(proof)) {
|
||||
|
||||
@@ -389,9 +389,6 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{isLoading && <Loading />}
|
||||
{error && <ErrorModal error={error} />}
|
||||
{info && <InfoModal message={info} />}
|
||||
</View>
|
||||
<BottomModal
|
||||
isVisible={isResultModalVisible ? true : false}
|
||||
@@ -454,6 +451,9 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
onBackButtonPress={toggleResultModal}
|
||||
onBackdropPress={toggleResultModal}
|
||||
/>
|
||||
{isLoading && <Loading />}
|
||||
{error && <ErrorModal error={error} />}
|
||||
{info && <InfoModal message={info} />}
|
||||
</Screen>
|
||||
)
|
||||
},
|
||||
|
||||
@@ -304,7 +304,7 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
contactToSendTo?.picture
|
||||
)
|
||||
|
||||
return navigation.navigate('Wallet', {})
|
||||
// return navigation.navigate('Wallet', {})
|
||||
} catch(e: any) {
|
||||
log.error(e.name, e.message) // silent
|
||||
}
|
||||
@@ -687,9 +687,6 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
{isLoading && <Loading />}
|
||||
{error && <ErrorModal error={error} />}
|
||||
{info && <InfoModal message={info} />}
|
||||
</View>
|
||||
<BottomModal
|
||||
isVisible={isProofSelectorModalVisible}
|
||||
@@ -781,6 +778,9 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
onBackButtonPress={toggleResultModal}
|
||||
onBackdropPress={toggleResultModal}
|
||||
/>
|
||||
{isLoading && <Loading />}
|
||||
{error && <ErrorModal error={error} />}
|
||||
{info && <InfoModal message={info} />}
|
||||
</Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -556,6 +556,12 @@ const ReceiveInfoBlock = function (props: {
|
||||
label="tranDetailScreen.type"
|
||||
value={transaction.type as string}
|
||||
/>
|
||||
<TranItem
|
||||
label="tranDetailScreen.fee"
|
||||
value={transaction.fee}
|
||||
unit={transaction.unit}
|
||||
isCurrency={true}
|
||||
/>
|
||||
{encodedTokenToRetry ? (
|
||||
<View
|
||||
style={{flexDirection: 'row', justifyContent: 'space-between'}}>
|
||||
@@ -808,6 +814,12 @@ const ReceiveOfflineInfoBlock = function (props: {
|
||||
label="tranDetailScreen.type"
|
||||
value={transaction.type as string}
|
||||
/>
|
||||
<TranItem
|
||||
label="tranDetailScreen.fee"
|
||||
value={transaction.fee}
|
||||
unit={transaction.unit}
|
||||
isCurrency={true}
|
||||
/>
|
||||
<TranItem
|
||||
label="tranDetailScreen.status"
|
||||
value={transaction.status as string}
|
||||
@@ -962,6 +974,12 @@ const SendInfoBlock = function (props: {
|
||||
label="tranDetailScreen.type"
|
||||
value={transaction.type as string}
|
||||
/>
|
||||
<TranItem
|
||||
label="tranDetailScreen.fee"
|
||||
value={transaction.fee}
|
||||
unit={transaction.unit}
|
||||
isCurrency={true}
|
||||
/>
|
||||
{transaction.status === TransactionStatus.PENDING ? (
|
||||
<View
|
||||
style={{flexDirection: 'row', justifyContent: 'space-between'}}>
|
||||
@@ -1202,6 +1220,12 @@ const TopupInfoBlock = function (props: {
|
||||
label="tranDetailScreen.type"
|
||||
value={transaction.type as string}
|
||||
/>
|
||||
<TranItem
|
||||
label="tranDetailScreen.fee"
|
||||
value={transaction.fee}
|
||||
unit={transaction.unit}
|
||||
isCurrency={true}
|
||||
/>
|
||||
{paymentRequest && isInternetReachable ? (
|
||||
<View
|
||||
style={{flexDirection: 'row', justifyContent: 'space-between'}}>
|
||||
@@ -1408,10 +1432,6 @@ const TransferInfoBlock = function (props: {
|
||||
isCurrency={true}
|
||||
isFirst={true}
|
||||
/>
|
||||
<TranItem
|
||||
label="tranDetailScreen.lightningFee"
|
||||
value={`${transaction.fee}`}
|
||||
/>
|
||||
{transaction.memo && (
|
||||
<TranItem
|
||||
label="tranDetailScreen.memoFromInvoice"
|
||||
@@ -1428,6 +1448,12 @@ const TransferInfoBlock = function (props: {
|
||||
label="tranDetailScreen.type"
|
||||
value={transaction.type as string}
|
||||
/>
|
||||
<TranItem
|
||||
label="tranDetailScreen.fee"
|
||||
value={transaction.fee}
|
||||
unit={transaction.unit}
|
||||
isCurrency={true}
|
||||
/>
|
||||
<TranItem
|
||||
label="tranDetailScreen.status"
|
||||
value={transaction.status as string}
|
||||
|
||||
@@ -151,31 +151,6 @@ const getMintsFromToken = function (token: Token): string[] {
|
||||
}
|
||||
|
||||
|
||||
const updateMintProofs = function (
|
||||
token: Token,
|
||||
mintUrl: string,
|
||||
updatedProofs: Proof[],
|
||||
): Token {
|
||||
// Find the index of the mint to update
|
||||
const mintIndex = token.token.findIndex(mint => mint.mint === mintUrl)
|
||||
|
||||
if (mintIndex === -1) {
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
`Mint ${mintUrl} not found in token`,
|
||||
)
|
||||
}
|
||||
|
||||
// Clone the token instance
|
||||
const updatedToken: Token = cloneDeep(token)
|
||||
|
||||
// Update the proofs for the specified mint
|
||||
updatedToken.token[mintIndex].proofs = updatedProofs
|
||||
|
||||
return updatedToken
|
||||
}
|
||||
|
||||
|
||||
const getProofsFromTokenEntries = (tokenEntries: TokenEntry[]) => {
|
||||
const proofs: Proof[] = []
|
||||
|
||||
@@ -187,6 +162,63 @@ const getProofsFromTokenEntries = (tokenEntries: TokenEntry[]) => {
|
||||
}
|
||||
|
||||
|
||||
const findExactMatch = function (requestedAmount: number, proofs: Proof[]): Proof[] | null {
|
||||
const result: Proof[] = [];
|
||||
function backtrack(start: number, remaining: number): boolean {
|
||||
if (remaining === 0) {
|
||||
return true;
|
||||
}
|
||||
for (let i = start; i < proofs.length; i++) {
|
||||
if (proofs[i].amount > remaining) continue;
|
||||
result.push(proofs[i]);
|
||||
if (backtrack(i + 1, remaining - proofs[i].amount)) {
|
||||
return true;
|
||||
}
|
||||
result.pop();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
proofs.sort((a, b) => b.amount - a.amount);
|
||||
if (backtrack(0, requestedAmount)) {
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const findMinExcess = function (requestedAmount: number, proofs: Proof[]): Proof[] {
|
||||
proofs.sort((a, b) => b.amount - a.amount);
|
||||
const selectedProofs: Proof[] = [];
|
||||
let currentAmount = 0;
|
||||
|
||||
for (const proof of proofs) {
|
||||
if (currentAmount >= requestedAmount) {
|
||||
break;
|
||||
}
|
||||
selectedProofs.push(proof);
|
||||
currentAmount += proof.amount;
|
||||
}
|
||||
|
||||
return selectedProofs;
|
||||
}
|
||||
|
||||
const getProofsToSend = function (requestedAmount: number, proofs: Proof[]): Proof[] {
|
||||
const proofsAmount = getProofsAmount(proofs)
|
||||
if(requestedAmount > proofsAmount) {
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
'There is not enough funds to send this amount',
|
||||
{requestedAmount, proofsAmount, caller: 'getProofsToSend'})
|
||||
}
|
||||
const exactMatch = findExactMatch(requestedAmount, proofs);
|
||||
if (exactMatch) {
|
||||
return exactMatch;
|
||||
}
|
||||
|
||||
return findMinExcess(requestedAmount, proofs);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* returns a subset of tokens, so that not all tokens are sent to mint for smaller amounts.
|
||||
* @param amount
|
||||
@@ -204,51 +236,6 @@ const getProofsFromTokenEntries = (tokenEntries: TokenEntry[]) => {
|
||||
return proofSubset
|
||||
} */
|
||||
|
||||
export const getProofsToSend = (amount: number, proofs: Proof[]) => {
|
||||
if (proofs.reduce((s, t) => (s += t.amount), 0) < amount) {
|
||||
// there are not enough proofs to pay the amount
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Not enough proofs to match requested amount', {amount})
|
||||
}
|
||||
|
||||
// sort proofs by amount ascending
|
||||
proofs = proofs.slice().sort((a, b) => a.amount - b.amount);
|
||||
// remember next bigger proof as a fallback
|
||||
const nextBigger = proofs.find((p) => p.amount > amount);
|
||||
|
||||
// go through smaller proofs until sum is bigger than amount
|
||||
const smallerProofs = proofs.filter((p) => p.amount <= amount);
|
||||
// sort by amount descending
|
||||
smallerProofs.sort((a, b) => b.amount - a.amount);
|
||||
|
||||
let selectedProofs: Proof[] = [];
|
||||
|
||||
if (smallerProofs.length == 0 && nextBigger) {
|
||||
// if there are no smaller proofs, take the next bigger proof as a fallback
|
||||
return [nextBigger];
|
||||
} else if (smallerProofs.length == 0 && !nextBigger) {
|
||||
// no proofs available
|
||||
return [];
|
||||
}
|
||||
|
||||
// recursively select the largest proof of smallerProofs, subtract the amount from the remainder
|
||||
// and call coinSelect again with the remainder and the rest of the smallerProofs (without the largest proof)
|
||||
let remainder = amount;
|
||||
selectedProofs = [smallerProofs[0]];
|
||||
remainder -= smallerProofs[0].amount;
|
||||
if (remainder > 0) {
|
||||
selectedProofs = selectedProofs.concat(getProofsToSend(remainder, smallerProofs.slice(1)));
|
||||
}
|
||||
let sum = selectedProofs.reduce((s, t) => (s += t.amount), 0);
|
||||
|
||||
// if sum of selectedProofs is smaller than amount, take next bigger proof instead as a fallback
|
||||
if (sum < amount && nextBigger) {
|
||||
selectedProofs = [nextBigger];
|
||||
}
|
||||
|
||||
log.trace("[getProofsToSend] ### selected amounts", "sum", selectedProofs.reduce((s, t) => (s += t.amount), 0), selectedProofs.map(p => p.amount));
|
||||
return selectedProofs
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* removes a set of tokens from another set of tokens, and returns the remaining.
|
||||
@@ -303,7 +290,7 @@ const getMintFromProof = function (
|
||||
): Mint | undefined {
|
||||
let mint: Mint | undefined
|
||||
mints.forEach(m => {
|
||||
if (m.keysets?.includes(proof.id)) {
|
||||
if (m.keysetIds?.includes(proof.id)) {
|
||||
mint = m
|
||||
}
|
||||
})
|
||||
@@ -322,7 +309,8 @@ export const CashuUtils = {
|
||||
getProofsAmount,
|
||||
getAmountPreferencesCount,
|
||||
getMintsFromToken,
|
||||
updateMintProofs,
|
||||
findMinExcess,
|
||||
// updateMintProofs,
|
||||
getProofsFromTokenEntries,
|
||||
getProofsToSend,
|
||||
getProofsSubset,
|
||||
|
||||
@@ -26,6 +26,7 @@ import { isObj } from '@cashu/cashu-ts/src/utils'
|
||||
import { JS_BUNDLE_VERSION } from '@env'
|
||||
import { MintUnit } from './wallet/currency'
|
||||
import { getSnapshot } from 'mobx-state-tree'
|
||||
import { Token } from '../models/Token'
|
||||
|
||||
let _mints: CashuMint[] = []
|
||||
let _wallets: CashuWallet[] = [] // used where seed is not required (perf)
|
||||
@@ -114,22 +115,22 @@ const getMint = async function (mintUrl: string): Promise<CashuMint> {
|
||||
// get mint model from wallet state
|
||||
const mintInstance = mintsStore.findByUrl(mintUrl)
|
||||
|
||||
if(!mintInstance) {
|
||||
throw new AppError(Err.NOTFOUND_ERROR, 'Missing mint in the wallet state', {mintUrl})
|
||||
// skip checks if this is new mint being added
|
||||
if(mintInstance) {
|
||||
const newKeysets = keysets.filter(freshKeyset => {
|
||||
return !mintInstance.keysets.some(keyset => keyset.id === freshKeyset.id);
|
||||
})
|
||||
|
||||
if(newKeysets.length > 0) {
|
||||
// if we heve new keysets, get and sync new keys
|
||||
const {keysets} = await newMint.getKeys()
|
||||
mintInstance.refreshKeys(keysets)
|
||||
}
|
||||
|
||||
// sync wallet state with fresh keysets, active statuses and keys
|
||||
mintInstance.refreshKeysets(keysets)
|
||||
}
|
||||
// check if we've got new keysets not existing in wallet state
|
||||
const newKeysets = keysets.filter(freshKeyset => {
|
||||
return !mintInstance.keysets.some(keyset => keyset.id === freshKeyset.id);
|
||||
})
|
||||
|
||||
if(newKeysets.length > 0) {
|
||||
// if we heve new keysets, get and sync new keys
|
||||
const {keysets} = await newMint.getKeys()
|
||||
mintInstance.refreshKeys(keysets)
|
||||
}
|
||||
|
||||
// sync wallet state with fresh keysets, active statuses and keys
|
||||
mintInstance.refreshKeysets(keysets)
|
||||
|
||||
// store cashu-ts mint instance in memory
|
||||
_mints.push(newMint)
|
||||
|
||||
@@ -142,13 +143,13 @@ const getWallet = async function (
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
options?: {
|
||||
keysetId?: string // force specific (inactive) keys to swap from
|
||||
keysetId?: string
|
||||
withSeed: boolean
|
||||
}
|
||||
): Promise<CashuWallet> {
|
||||
// syncs mint model in wallet state and returns cashu-ts mint class instance
|
||||
const cashuMint = await getMint(mintUrl)
|
||||
|
||||
|
||||
// mint model from wallet state
|
||||
const mintInstance = mintsStore.findByUrl(mintUrl)
|
||||
if(!mintInstance) {
|
||||
@@ -156,7 +157,7 @@ const getWallet = async function (
|
||||
mintUrl
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// select keys to be used to find or create new cashu-ts wallet instance
|
||||
let walletKeys: MintKeys
|
||||
if(options && options.keysetId) {
|
||||
@@ -181,7 +182,9 @@ const getWallet = async function (
|
||||
walletKeys = requestedKeys
|
||||
} else {
|
||||
// if not we find first active keyset and related keys
|
||||
const activeKeyset = mintInstance.keysets.find(k => k.unit === unit && k.active === true)
|
||||
const activeKeyset = mintInstance.keysets.find(k => k.unit === unit && k.active === true)
|
||||
|
||||
|
||||
|
||||
if(!activeKeyset) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Wallet has not any active keyset for the selected unit.', {
|
||||
@@ -199,7 +202,7 @@ const getWallet = async function (
|
||||
})
|
||||
}
|
||||
|
||||
walletKeys = getSnapshot(activeKeys)
|
||||
walletKeys = activeKeys
|
||||
}
|
||||
|
||||
if (options && options.withSeed) {
|
||||
@@ -288,6 +291,7 @@ const receive = async function (
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
decodedToken: CashuToken,
|
||||
mintFeeReserve: number,
|
||||
options: {
|
||||
preference: AmountPreference[],
|
||||
counter: number
|
||||
@@ -301,8 +305,11 @@ const receive = async function (
|
||||
withSeed: true,
|
||||
})
|
||||
|
||||
const amountToReceive = CashuUtils.getTokenAmounts(decodedToken as Token).totalAmount
|
||||
|
||||
const proofs = await cashuWallet.receive(
|
||||
decodedToken,
|
||||
decodedToken,
|
||||
mintFeeReserve,
|
||||
{
|
||||
keysetId: cashuWallet.keys.id,
|
||||
preference: options.preference,
|
||||
@@ -311,7 +318,10 @@ const receive = async function (
|
||||
privkey: undefined
|
||||
})
|
||||
|
||||
return proofs
|
||||
const receivedAmount = CashuUtils.getProofsAmount(proofs as Proof[])
|
||||
const mintFeePaid = amountToReceive - receivedAmount
|
||||
|
||||
return {proofs, mintFeePaid}
|
||||
} catch (e: any) {
|
||||
throw new AppError(Err.MINT_ERROR, e.message)
|
||||
}
|
||||
@@ -322,6 +332,7 @@ const receive = async function (
|
||||
const send = async function (
|
||||
mintUrl: string,
|
||||
amountToSend: number,
|
||||
mintFeeReserve: number,
|
||||
unit: MintUnit,
|
||||
proofsToSendFrom: Proof[],
|
||||
options: {
|
||||
@@ -337,10 +348,11 @@ const send = async function (
|
||||
withSeed: true,
|
||||
})
|
||||
|
||||
log.debug('[MintClient.sendFromMint] counter', options.counter)
|
||||
log.debug('[MintClient.send] counter', options.counter)
|
||||
|
||||
const {returnChange, send} = await cashuWallet.send(
|
||||
amountToSend,
|
||||
mintFeeReserve,
|
||||
proofsToSendFrom,
|
||||
{
|
||||
keysetId: cashuWallet.keys.id,
|
||||
@@ -351,8 +363,8 @@ const send = async function (
|
||||
}
|
||||
)
|
||||
|
||||
log.debug('[MintClient.sendFromMint] returnedProofs', returnChange)
|
||||
log.debug('[MintClient.sendFromMint] proofsToSend', send)
|
||||
log.debug('[MintClient.send] returnedProofs', returnChange)
|
||||
log.debug('[MintClient.send] proofsToSend', send)
|
||||
|
||||
// do some basic validations that proof amounts from mints match
|
||||
const totalAmountToSendFrom = CashuUtils.getProofsAmount(proofsToSendFrom)
|
||||
@@ -397,10 +409,8 @@ const getSpentOrPendingProofsFromMint = async function (
|
||||
unit: MintUnit,
|
||||
proofs: Proof[],
|
||||
) {
|
||||
try {
|
||||
|
||||
const cashuWallet = await getWallet(mintUrl, unit, {withSeed: true})
|
||||
|
||||
try {
|
||||
const cashuWallet = await getWallet(mintUrl, unit, {withSeed: true})
|
||||
const spentPendingProofs = await cashuWallet.checkProofsSpent(proofs)
|
||||
|
||||
log.trace('[CashuMintClient.getSpentOrPendingProofsFromMint]', {mintUrl, spentPendingProofs})
|
||||
|
||||
@@ -12,12 +12,15 @@ import {CashuUtils} from '../cashu/cashuUtils'
|
||||
import AppError, {Err} from '../../utils/AppError'
|
||||
import {Token} from '../../models/Token'
|
||||
import {
|
||||
type Token as CashuToken
|
||||
TokenEntry,
|
||||
type Token as CashuToken,
|
||||
type Proof as CashuProof
|
||||
} from '@cashu/cashu-ts'
|
||||
import { getDefaultAmountPreference, getEncodedToken } from '@cashu/cashu-ts/src/utils'
|
||||
import { TransactionTaskResult } from '../walletService'
|
||||
import { WalletUtils } from './utils'
|
||||
import { MintUnit, formatCurrency, getCurrency } from './currency'
|
||||
import { Proof } from '../../models/Proof'
|
||||
|
||||
const {
|
||||
mintsStore,
|
||||
@@ -120,9 +123,12 @@ export const receiveTask = async function (
|
||||
}
|
||||
|
||||
// Increase the proofs counter before the mint call so that in case the response
|
||||
// is not received our recovery index counts for sigs the mint has already issued (prevents duplicate b_b bug)
|
||||
// is not received our recovery index counts for sigs the mint has already issued
|
||||
const amountPreferences = getDefaultAmountPreference(amountToReceive)
|
||||
const countOfInFlightProofs = CashuUtils.getAmountPreferencesCount(amountPreferences)
|
||||
const tokenEntries: Array<TokenEntry> = token.token
|
||||
const proofsToReceive = tokenEntries[0].proofs as Proof[]
|
||||
const mintFeeReserve = mintInstance.getMintFeeReserve(proofsToReceive)
|
||||
|
||||
log.trace('[receiveTask]', 'proofsCounter initial state', {proofsCounter: mintInstance.getProofsCounterByUnit?.(unit)})
|
||||
log.trace('[receiveTask]', 'amountPreferences', {amountPreferences, transactionId})
|
||||
@@ -132,17 +138,21 @@ export const receiveTask = async function (
|
||||
await WalletUtils.lockAndSetInFlight(mintInstance, unit, countOfInFlightProofs, transactionId)
|
||||
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit?.(unit)
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit?.(unit)!
|
||||
|
||||
const receivedProofs = await MintClient.receive(
|
||||
const receivedResult = await MintClient.receive(
|
||||
mintToReceive,
|
||||
unit as MintUnit,
|
||||
token,
|
||||
mintFeeReserve,
|
||||
{
|
||||
preference: amountPreferences,
|
||||
counter: lockedProofsCounter.inFlightFrom as number // MUST be counter value before increase
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
const receivedProofs = receivedResult.proofs
|
||||
const mintFeePaid = receivedResult.mintFeePaid
|
||||
|
||||
// If we've got valid response, decrease proofsCounter and let it be increased back in next step when adding proofs
|
||||
mintInstance.decreaseProofsCounter(lockedProofsCounter.keyset, countOfInFlightProofs)
|
||||
@@ -163,6 +173,8 @@ export const receiveTask = async function (
|
||||
transactionData.push({
|
||||
status: TransactionStatus.PREPARED,
|
||||
updatedToken,
|
||||
mintFeeReserve,
|
||||
mintFeePaid,
|
||||
createdAt: new Date(),
|
||||
})
|
||||
|
||||
@@ -210,6 +222,10 @@ export const receiveTask = async function (
|
||||
const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance!
|
||||
await transactionsStore.updateBalanceAfter(transactionId, balanceAfter)
|
||||
|
||||
if(mintFeePaid > 0) {
|
||||
await transactionsStore.updateFee(transactionId, mintFeePaid)
|
||||
}
|
||||
|
||||
return {
|
||||
taskFunction: RECEIVE,
|
||||
mintUrl: mintInstance.mintUrl,
|
||||
@@ -435,7 +451,12 @@ export const receiveOfflineCompleteTask = async function (
|
||||
|
||||
// Increase the proofs counter before the mint call so that in case the response
|
||||
// is not received our recovery index counts for sigs the mint has already issued (prevents duplicate b_b bug)
|
||||
const amountPreferences = getDefaultAmountPreference(transaction.amount)
|
||||
|
||||
const tokenEntries: Array<TokenEntry> = token.token
|
||||
const proofsToReceive = tokenEntries[0].proofs as Proof[]
|
||||
const mintFeeReserve = mintInstance.getMintFeeReserve(proofsToReceive)
|
||||
// We will receive amount less fees paid for a swap for fresh ecash
|
||||
const amountPreferences = getDefaultAmountPreference(transaction.amount - mintFeeReserve)
|
||||
const countOfInFlightProofs = CashuUtils.getAmountPreferencesCount(amountPreferences)
|
||||
|
||||
log.trace('[receiveOfflineCompleteTask]', 'amountPreferences', amountPreferences)
|
||||
@@ -445,23 +466,27 @@ export const receiveOfflineCompleteTask = async function (
|
||||
await WalletUtils.lockAndSetInFlight(mintInstance, unit, countOfInFlightProofs, transaction.id as number)
|
||||
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit?.(unit)
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit?.(unit)!
|
||||
|
||||
const receivedProofs = await MintClient.receive(
|
||||
const receivedResult = await MintClient.receive(
|
||||
mintToReceive,
|
||||
unit as MintUnit,
|
||||
token,
|
||||
mintFeeReserve,
|
||||
{
|
||||
preference: amountPreferences,
|
||||
counter: lockedProofsCounter.inFlightFrom as number // MUST be counter value before increase
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
const receivedProofs = receivedResult.proofs
|
||||
const mintFeePaid = receivedResult.mintFeePaid
|
||||
|
||||
// If we've got valid response, decrease proofsCounter and let it be increased back in next step when adding proofs
|
||||
mintInstance.decreaseProofsCounter(lockedProofsCounter.keyset, countOfInFlightProofs)
|
||||
|
||||
// store swapped proofs as encoded token in tx data
|
||||
const tokenEntryToSend = {
|
||||
const tokenEntryToSend: TokenEntry = {
|
||||
mint: mintToReceive,
|
||||
proofs: receivedProofs,
|
||||
}
|
||||
@@ -475,6 +500,8 @@ export const receiveOfflineCompleteTask = async function (
|
||||
transactionData.push({
|
||||
status: TransactionStatus.PREPARED,
|
||||
updatedToken,
|
||||
mintFeeReserve,
|
||||
mintFeePaid,
|
||||
createdAt: new Date(),
|
||||
})
|
||||
|
||||
@@ -522,6 +549,10 @@ export const receiveOfflineCompleteTask = async function (
|
||||
const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance!
|
||||
await transactionsStore.updateBalanceAfter(transaction.id as number, balanceAfter)
|
||||
|
||||
if(mintFeePaid > 0) {
|
||||
await transactionsStore.updateFee(transaction.id as number, mintFeePaid)
|
||||
}
|
||||
|
||||
return {
|
||||
taskFunction: RECEIVE_OFFLINE_COMPLETE,
|
||||
mintUrl: mintInstance.mintUrl,
|
||||
|
||||
+133
-66
@@ -18,13 +18,13 @@ import {
|
||||
} from '@cashu/cashu-ts'
|
||||
import { getDefaultAmountPreference, isObj } from '@cashu/cashu-ts/src/utils'
|
||||
import { TransactionTaskResult, WalletTask } from '../walletService'
|
||||
import { MintBalance } from '../../models/Mint'
|
||||
import { MintBalance, MintProofsCounter } from '../../models/Mint'
|
||||
import { Proof } from '../../models/Proof'
|
||||
import { poller } from '../../utils/poller'
|
||||
import { WalletUtils } from './utils'
|
||||
import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
|
||||
import { MintUnit } from './currency'
|
||||
import { getProofsToSend } from '../../services/cashu/cashuUtils'
|
||||
import { boolean } from 'mobx-state-tree/dist/internal'
|
||||
|
||||
const {
|
||||
mintsStore,
|
||||
@@ -129,6 +129,10 @@ export const sendTask = async function (
|
||||
|
||||
const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance!
|
||||
await transactionsStore.updateBalanceAfter(transactionId, balanceAfter)
|
||||
|
||||
if(mintFeePaid > 0) {
|
||||
await transactionsStore.updateFee(transactionId, mintFeePaid)
|
||||
}
|
||||
|
||||
log.trace('[send] totalBalance after', balanceAfter)
|
||||
|
||||
@@ -203,9 +207,11 @@ export const sendFromMint = async function (
|
||||
)
|
||||
}
|
||||
|
||||
const proofsFromMint = proofsStore.getByMint(mintUrl, {isPending: false, unit}) as Proof[]
|
||||
|
||||
log.debug('[sendFromMint]', 'proofsFromMint count', {proofsCount: proofsFromMint.length, amountToSend})
|
||||
|
||||
|
||||
const proofsFromMint = proofsStore.getByMint(mintUrl, {isPending: false, unit}) as Proof[]
|
||||
|
||||
log.debug('[sendFromMint]', 'proofsFromMint count', {mintBalance: mintBalance.balances[unit], amountToSend})
|
||||
|
||||
if (proofsFromMint.length < 1) {
|
||||
throw new AppError(
|
||||
@@ -219,8 +225,8 @@ export const sendFromMint = async function (
|
||||
if (totalAmountFromMint < amountToSend) {
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
'There is not enough funds to send this payment',
|
||||
{totalAmountFromMint, amountToSend},
|
||||
'There is not enough funds to send this amount',
|
||||
{totalAmountFromMint, amountToSend, caller: 'sendFromMint'},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -259,25 +265,78 @@ export const sendFromMint = async function (
|
||||
}
|
||||
|
||||
/*
|
||||
* if we did not selected ecash but amount and we might need a swap of ecash by the mint to match exact amount
|
||||
*/
|
||||
* SWAP or DIRECT SEND
|
||||
* if we did not selected ecash but amount and we might need a swap of ecash by the mint to match exact amount
|
||||
*/
|
||||
|
||||
// Prioritize send from inactive keysets
|
||||
let proofsToSendFrom: Proof[] = []
|
||||
const inactiveKeysetIds = mintInstance?.keysets.filter(k => k.active === false).map(k => k.id)
|
||||
const activeKeysetIds = mintInstance?.keysets.filter(k => k.active === true).map(k => k.id)
|
||||
|
||||
const proofsToSendFrom = getProofsToSend(
|
||||
amountToSend,
|
||||
proofsFromMint,
|
||||
)
|
||||
log.trace('[sendFromMint]', {inactiveKeysetIds, activeKeysetIds})
|
||||
|
||||
if(inactiveKeysetIds.length > 0) {
|
||||
|
||||
const proofsFromInactiveKeysets = proofsStore.getByMint(mintUrl, {isPending: false, unit, keysetIds: inactiveKeysetIds})
|
||||
const proofsFromActiveKeysets = proofsStore.getByMint(mintUrl, {isPending: false, unit, keysetIds: activeKeysetIds})
|
||||
|
||||
let proofsToSendFromAmount = CashuUtils.getProofsAmount(proofsToSendFrom)
|
||||
if(proofsFromInactiveKeysets && proofsFromInactiveKeysets.length > 0) {
|
||||
let proofsFromInactiveKeysetsAmount = CashuUtils.getProofsAmount(proofsFromInactiveKeysets)
|
||||
|
||||
// swap will happen if we could not select proofs equal to amountToSend
|
||||
let returnedAmount = proofsToSendFromAmount - amountToSend
|
||||
log.trace('[sendFromMint]', {proofsFromInactiveKeysetsAmount})
|
||||
|
||||
if(proofsFromInactiveKeysetsAmount >= amountToSend) {
|
||||
proofsToSendFrom = CashuUtils.getProofsToSend(
|
||||
amountToSend,
|
||||
proofsFromInactiveKeysets
|
||||
)
|
||||
} else {
|
||||
const remainingAmount = amountToSend - proofsFromInactiveKeysetsAmount
|
||||
const remainingProofs = CashuUtils.findMinExcess(remainingAmount, proofsFromActiveKeysets!)
|
||||
proofsToSendFrom = CashuUtils.getProofsToSend(
|
||||
amountToSend,
|
||||
[...proofsFromInactiveKeysets, ...remainingProofs]
|
||||
)
|
||||
}
|
||||
} else {
|
||||
proofsToSendFrom = CashuUtils.getProofsToSend(
|
||||
amountToSend,
|
||||
proofsFromMint
|
||||
)
|
||||
}
|
||||
} else {
|
||||
proofsToSendFrom = CashuUtils.getProofsToSend(
|
||||
amountToSend,
|
||||
proofsFromMint
|
||||
)
|
||||
}
|
||||
|
||||
let proofsToSendFromAmount = CashuUtils.getProofsAmount(proofsToSendFrom)
|
||||
// swap will happen if we could not select proofs equal to amountToSend
|
||||
let mintFeeReserve: number = 0
|
||||
let mintFeePaid: number = 0
|
||||
let proofsToSend: Proof[] = []
|
||||
let returnedProofs: Proof[] = []
|
||||
let isSwapNeeded: boolean = proofsToSendFromAmount - amountToSend > 0 ? true : false
|
||||
let returnedAmount = 0
|
||||
|
||||
if(returnedAmount > 0) {
|
||||
mintFeeReserve = mintInstance.getFeesForProofs(proofsToSendFrom)
|
||||
log.trace('[sendFromMint]', {proofsToSendFromAmount, amountToSend})
|
||||
/*
|
||||
* SWAP is needed, could involve a fee
|
||||
* if we did not selected ecash but amount and we might need a swap of ecash by the mint to match exact amount
|
||||
*/
|
||||
if(isSwapNeeded) {
|
||||
// Calculate feeReserve from mint fee rate
|
||||
mintFeeReserve = mintInstance.getMintFeeReserve(proofsToSendFrom)
|
||||
// This is expected to get back from mint as a split remainder - we deduct fee that mint will keep
|
||||
returnedAmount = proofsToSendFromAmount - amountToSend - mintFeeReserve
|
||||
|
||||
log.debug('[sendFromMint] Swap is needed.', {mintFeeReserve, returnedAmount})
|
||||
|
||||
// if we did not selected enough proofs to cover the fees we need some more
|
||||
if(mintFeeReserve > returnedAmount) {
|
||||
const missingFeesAmount = mintFeeReserve - returnedAmount
|
||||
if(returnedAmount < 0) {
|
||||
const missingFeesAmount = Math.abs(returnedAmount)
|
||||
const remainingProofs = proofsStore.getProofsSubset(proofsFromMint, proofsToSendFrom)
|
||||
const remainingProofsAmount = CashuUtils.getProofsAmount(remainingProofs)
|
||||
|
||||
@@ -290,8 +349,8 @@ export const sendFromMint = async function (
|
||||
{totalAmountFromMint, amountToSend, mintFeeReserve},
|
||||
)
|
||||
}
|
||||
|
||||
const proofsToPayFees = getProofsToSend(
|
||||
// select additional proof(s) to pay fees
|
||||
const proofsToPayFees = CashuUtils.getProofsToSend(
|
||||
missingFeesAmount,
|
||||
remainingProofs
|
||||
)
|
||||
@@ -299,51 +358,51 @@ export const sendFromMint = async function (
|
||||
// add more proofs into inputs and recalculate amounts
|
||||
proofsToSendFrom.push(...proofsToPayFees)
|
||||
proofsToSendFromAmount = CashuUtils.getProofsAmount(proofsToSendFrom)
|
||||
returnedAmount = proofsToSendFromAmount - amountToSend
|
||||
returnedAmount = proofsToSendFromAmount - amountToSend - mintFeeReserve
|
||||
}
|
||||
|
||||
// decrease requested returned outputs by fees so that the mint can charge them
|
||||
returnedAmount -= mintFeeReserve
|
||||
}
|
||||
// Outputs denominations we asked for to get
|
||||
const amountPreferences = getDefaultAmountPreference(amountToSend)
|
||||
// Output denominations we are about to get as a split remainder
|
||||
const returnedAmountPreferences = getDefaultAmountPreference(returnedAmount)
|
||||
|
||||
// Inputs we are about to send
|
||||
const amountPreferences = getDefaultAmountPreference(amountToSend)
|
||||
// Outputs we are about to get back if swap occurs
|
||||
const returnedAmountPreferences = getDefaultAmountPreference(returnedAmount)
|
||||
const countOfProofsToSend = CashuUtils.getAmountPreferencesCount(amountPreferences)
|
||||
const countOfReturnedProofs = CashuUtils.getAmountPreferencesCount(returnedAmountPreferences)
|
||||
const countOfInFlightProofs = countOfProofsToSend + countOfReturnedProofs
|
||||
|
||||
const countOfProofsToSend = CashuUtils.getAmountPreferencesCount(amountPreferences)
|
||||
const countOfReturnedProofs = CashuUtils.getAmountPreferencesCount(returnedAmountPreferences)
|
||||
const countOfInFlightProofs = countOfProofsToSend + countOfReturnedProofs
|
||||
|
||||
log.trace('[sendFromMint]', 'amountPreferences', {amountPreferences, returnedAmountPreferences})
|
||||
log.trace('[sendFromMint]', 'countOfInFlightProofs', countOfInFlightProofs)
|
||||
|
||||
// Increase the proofs counter before the mint call so that in case the response
|
||||
// is not received our recovery index counts for sigs the mint has already issued (prevents duplicate b_b bug)
|
||||
// + acquire lock and set inFlight values
|
||||
await WalletUtils.lockAndSetInFlight(mintInstance, unit, countOfInFlightProofs, transactionId)
|
||||
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit?.(unit)
|
||||
|
||||
// if split to required denominations was necessary, this gets it done with the mint and we get the return
|
||||
|
||||
const {returnedProofs, proofsToSend, mintFeePaid} = await MintClient.send(
|
||||
mintUrl,
|
||||
amountToSend,
|
||||
unit,
|
||||
proofsToSendFrom,
|
||||
{
|
||||
preference: amountPreferences,
|
||||
counter: lockedProofsCounter.inFlightFrom as number // MUST be counter value before increase
|
||||
}
|
||||
)
|
||||
log.trace('[sendFromMint]', 'amountPreferences', {amountPreferences, returnedAmountPreferences})
|
||||
log.trace('[sendFromMint]', 'countOfInFlightProofs', countOfInFlightProofs)
|
||||
|
||||
// If we've got valid response, decrease proofsCounter and let it be increased back in next step when adding proofs
|
||||
mintInstance.decreaseProofsCounter(lockedProofsCounter.keyset, countOfInFlightProofs)
|
||||
// Increase the proofs counter before the mint call so that in case the response
|
||||
// is not received our recovery index counts for sigs the mint has already issued (prevents duplicate b_b bug)
|
||||
// + acquire lock and set inFlight values
|
||||
await WalletUtils.lockAndSetInFlight(mintInstance, unit, countOfInFlightProofs, transactionId)
|
||||
|
||||
// add proofs returned by the mint after the split
|
||||
if (returnedProofs.length > 0) {
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit(unit)!
|
||||
|
||||
// if split to required denominations was necessary, this gets it done with the mint and we get the return
|
||||
|
||||
const sendResult = await MintClient.send(
|
||||
mintUrl,
|
||||
amountToSend,
|
||||
mintFeeReserve,
|
||||
unit,
|
||||
proofsToSendFrom,
|
||||
{
|
||||
preference: amountPreferences,
|
||||
counter: lockedProofsCounter.inFlightFrom as number // MUST be counter value before increase
|
||||
}
|
||||
)
|
||||
|
||||
returnedProofs = sendResult.returnedProofs
|
||||
proofsToSend = sendResult.proofsToSend
|
||||
mintFeePaid = sendResult.mintFeePaid
|
||||
|
||||
// If we've got valid response, decrease proofsCounter and let it be increased back in next step when adding proofs
|
||||
mintInstance.decreaseProofsCounter(lockedProofsCounter.keyset, countOfInFlightProofs)
|
||||
|
||||
// add proofs returned by the mint after the split
|
||||
log.trace('[sendFromMint] add returned proofs to spendable')
|
||||
WalletUtils.addCashuProofs(
|
||||
mintUrl,
|
||||
@@ -352,16 +411,24 @@ export const sendFromMint = async function (
|
||||
unit,
|
||||
transactionId,
|
||||
isPending: false
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
} else if (returnedAmount === 0) {
|
||||
/*
|
||||
* SWAP not needed, all selected proofs will be sent
|
||||
*/
|
||||
log.trace('[sendFromMint] Swap is not necessary, all proofsToSendFrom will be sent.')
|
||||
proofsToSend = [...proofsToSendFrom]
|
||||
|
||||
} else {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Amount to keep can not be negative')
|
||||
}
|
||||
|
||||
// remove used proofs and move sent proofs to pending
|
||||
log.trace('[sendFromMint] remove proofsToSendFrom from spendable')
|
||||
proofsStore.removeProofs(proofsToSendFrom)
|
||||
|
||||
// these might be original proofToSendFrom if they matched the exact amount and split was not necessary
|
||||
log.trace('[sendFromMint] add proofsToSend to pending')
|
||||
// these might be original proofToSendFrom if they matched the exact amount and split was not necessary
|
||||
WalletUtils.addCashuProofs(
|
||||
mintUrl,
|
||||
proofsToSend,
|
||||
|
||||
@@ -86,10 +86,19 @@ export const transferTask = async function (
|
||||
|
||||
transactionId = storedTransaction.id as number
|
||||
|
||||
// calculate fees charged by mint for melt transaction to prepare enough proofs
|
||||
const proofsFromMint = proofsStore.getByMint(mintUrl, {isPending: false, unit}) as Proof[]
|
||||
let proofsToSendFrom = CashuUtils.getProofsToSend(
|
||||
amountToTransfer + meltQuote.fee_reserve,
|
||||
proofsFromMint
|
||||
)
|
||||
let meltFeeReserve = mintInstance.getMintFeeReserve(proofsToSendFrom)
|
||||
log.trace('[transfer]', {meltFeeReserve, amountWithFees: amountToTransfer + meltQuote.fee_reserve + meltFeeReserve,})
|
||||
|
||||
// get proofs ready to be paid to the mint
|
||||
const {proofs: proofsToPay, mintFeePaid, mintFeeReserve} = await sendFromMint(
|
||||
mintBalanceToTransferFrom,
|
||||
amountToTransfer + meltQuote.fee_reserve,
|
||||
amountToTransfer + meltQuote.fee_reserve + meltFeeReserve,
|
||||
unit,
|
||||
[],
|
||||
transactionId,
|
||||
@@ -110,21 +119,21 @@ export const transferTask = async function (
|
||||
transactionId,
|
||||
TransactionStatus.PREPARED,
|
||||
JSON.stringify(transactionData),
|
||||
)
|
||||
)
|
||||
|
||||
// we do not know how much we will get so use big enough constant to increase and lock
|
||||
const countOfInFlightProofs = 10
|
||||
// number of outputs we can get back with returned lightning fees
|
||||
const countOfInFlightProofs = Math.ceil(Math.log2(meltQuote.fee_reserve)) || 1
|
||||
// temp increase the counter + acquire lock and set inFlight values
|
||||
await WalletUtils.lockAndSetInFlight(mintInstance, unit, countOfInFlightProofs, transactionId)
|
||||
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit?.(unit)
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit?.(unit)!
|
||||
|
||||
const {isPaid, preimage, feeSavedProofs} = await MintClient.payLightningMelt(
|
||||
mintUrl,
|
||||
unit,
|
||||
meltQuote,
|
||||
proofsToPay,
|
||||
proofsToPay,
|
||||
{
|
||||
counter: lockedProofsCounter.inFlightFrom as number
|
||||
}
|
||||
@@ -203,7 +212,9 @@ export const transferTask = async function (
|
||||
mintInstance.resetInFlight(transactionId)
|
||||
|
||||
// Save final fee in db
|
||||
await transactionsStore.updateFee(transactionId, lightningFeePaid + mintFeePaid)
|
||||
if(lightningFeePaid + mintFeePaid !== meltQuote.fee_reserve) {
|
||||
await transactionsStore.updateFee(transactionId, lightningFeePaid + mintFeePaid)
|
||||
}
|
||||
|
||||
// Update transaction status
|
||||
transactionData.push({
|
||||
@@ -229,7 +240,7 @@ export const transferTask = async function (
|
||||
taskFunction: TRANSFER,
|
||||
mintUrl,
|
||||
transaction: completedTransaction,
|
||||
message: `Lightning invoice has been successfully paid and settled with your Minibits ecash. Final network fee has been ${formatCurrency(finalFee, getCurrency(unit).code)} ${getCurrency(unit).code}.`,
|
||||
message: `Lightning invoice has been successfully paid and settled with your Minibits ecash. Fee has been ${formatCurrency(lightningFeePaid + mintFeePaid, getCurrency(unit).code)} ${getCurrency(unit).code}.`,
|
||||
lightningFeePaid,
|
||||
mintFeePaid
|
||||
} as TransactionTaskResult
|
||||
|
||||
@@ -821,7 +821,7 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
|
||||
)
|
||||
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit(unit)
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit(unit)!
|
||||
|
||||
let proofs: CashuProof[] = []
|
||||
|
||||
|
||||
Reference in New Issue
Block a user