mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 11:18:24 +00:00
Reimplement v3 counters
This commit is contained in:
+12
-9
@@ -1,10 +1,11 @@
|
|||||||
import {cast, detach, flow, getParent, getRoot, getSnapshot, IAnyStateTreeNode, Instance, isAlive, IStateTreeNode, SnapshotIn, SnapshotOut, types} from 'mobx-state-tree'
|
import {cast, detach, flow, getParent, getRoot, getSnapshot, IAnyStateTreeNode, Instance, isAlive, IStateTreeNode, SnapshotIn, SnapshotOut, types} from 'mobx-state-tree'
|
||||||
import {withSetPropAction} from './helpers/withSetPropAction'
|
import {withSetPropAction} from './helpers/withSetPropAction'
|
||||||
import {
|
import {
|
||||||
type GetInfoResponse,
|
type GetInfoResponse,
|
||||||
type MintKeys as CashuMintKeys,
|
type MintKeys as CashuMintKeys,
|
||||||
type MintKeyset as CashuMintKeyset,
|
type MintKeyset as CashuMintKeyset,
|
||||||
Mint as CashuMint,
|
Mint as CashuMint,
|
||||||
|
type MeltPreview,
|
||||||
} from '@cashu/cashu-ts'
|
} from '@cashu/cashu-ts'
|
||||||
import {colors, getRandomIconColor} from '../theme'
|
import {colors, getRandomIconColor} from '../theme'
|
||||||
import { log } from '../services'
|
import { log } from '../services'
|
||||||
@@ -16,7 +17,6 @@ import { generateId } from '../utils/utils'
|
|||||||
import { Proof } from './Proof'
|
import { Proof } from './Proof'
|
||||||
import { CashuProof, CashuUtils } from '../services/cashu/cashuUtils'
|
import { CashuProof, CashuUtils } from '../services/cashu/cashuUtils'
|
||||||
|
|
||||||
ß
|
|
||||||
export type MintBalance = {
|
export type MintBalance = {
|
||||||
mintUrl: string
|
mintUrl: string
|
||||||
balances: {
|
balances: {
|
||||||
@@ -49,10 +49,11 @@ const InFlightRequestModel = types.model('InFlightRequest', {
|
|||||||
request: types.frozen<any>(), // or replace `any` with your actual request type
|
request: types.frozen<any>(), // or replace `any` with your actual request type
|
||||||
})
|
})
|
||||||
|
|
||||||
// Sub-model for melt counter snapshots
|
// Sub-model for melt previews (v3.x uses MeltPreview instead of just counter)
|
||||||
const MeltCounterValueModel = types.model('MeltCounterValue', {
|
const MeltCounterValueModel = types.model('MeltCounterValue', {
|
||||||
transactionId: types.number,
|
transactionId: types.number,
|
||||||
counterAtMelt: types.number, // the counter value when melt started
|
counterAtMelt: types.number, // the counter value when melt started (kept for backward compatibility)
|
||||||
|
meltPreview: types.maybe(types.frozen<MeltPreview>()), // v3.x MeltPreview object for recovery
|
||||||
createdAt: types.optional(types.Date, () => new Date()), // optional: when it was added
|
createdAt: types.optional(types.Date, () => new Date()), // optional: when it was added
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -140,7 +141,7 @@ export const MintProofsCounterModel = types
|
|||||||
},
|
},
|
||||||
|
|
||||||
// === Melt counter tracking ===
|
// === Melt counter tracking ===
|
||||||
addMeltCounterValue(transactionId: number): number {
|
addMeltCounterValue(transactionId: number, meltPreview?: MeltPreview): number {
|
||||||
const key = transactionId.toString()
|
const key = transactionId.toString()
|
||||||
if (self.meltCounterValues.has(key)) {
|
if (self.meltCounterValues.has(key)) {
|
||||||
log.warn('[addMeltCounterValue]', 'Melt already tracked', { transactionId })
|
log.warn('[addMeltCounterValue]', 'Melt already tracked', { transactionId })
|
||||||
@@ -150,12 +151,14 @@ export const MintProofsCounterModel = types
|
|||||||
self.meltCounterValues.set(key, {
|
self.meltCounterValues.set(key, {
|
||||||
transactionId,
|
transactionId,
|
||||||
counterAtMelt: self.counter,
|
counterAtMelt: self.counter,
|
||||||
|
meltPreview,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
})
|
})
|
||||||
|
|
||||||
log.trace('[addMeltCounterValue]', {
|
log.trace('[addMeltCounterValue]', {
|
||||||
transactionId,
|
transactionId,
|
||||||
counterAtMelt: self.counter,
|
counterAtMelt: self.counter,
|
||||||
|
hasMeltPreview: !!meltPreview,
|
||||||
})
|
})
|
||||||
|
|
||||||
return self.counter
|
return self.counter
|
||||||
|
|||||||
+163
-108
@@ -14,6 +14,7 @@ import {
|
|||||||
MintQuoteBolt11Response,
|
MintQuoteBolt11Response,
|
||||||
type ProofState,
|
type ProofState,
|
||||||
type OperationCounters,
|
type OperationCounters,
|
||||||
|
type MeltPreview,
|
||||||
} from '@cashu/cashu-ts'
|
} from '@cashu/cashu-ts'
|
||||||
import { JS_BUNDLE_VERSION } from '@env'
|
import { JS_BUNDLE_VERSION } from '@env'
|
||||||
import {KeyChain, MinibitsClient, WalletKeys} from '../services'
|
import {KeyChain, MinibitsClient, WalletKeys} from '../services'
|
||||||
@@ -411,63 +412,86 @@ export const WalletStoreModel = types
|
|||||||
)
|
)
|
||||||
|
|
||||||
const currentCounter = mintInstance.getProofsCounterByKeysetId!(cashuWallet.keysetId)
|
const currentCounter = mintInstance.getProofsCounterByKeysetId!(cashuWallet.keysetId)
|
||||||
|
|
||||||
// outputs error healing
|
// outputs error healing
|
||||||
if(options && options.increaseCounterBy) {
|
if(options && options.increaseCounterBy) {
|
||||||
currentCounter.increaseProofsCounter(options.increaseCounterBy)
|
currentCounter.increaseProofsCounter(options.increaseCounterBy)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug('[WalletStore.receive] counter', currentCounter.counter)
|
log.debug('[WalletStore.receive] counter', currentCounter.counter)
|
||||||
|
|
||||||
|
// Sync wallet's internal counter with our stored counter (v3.x)
|
||||||
|
yield cashuWallet.counters.advanceToAtLeast(cashuWallet.keysetId, currentCounter.counter)
|
||||||
|
|
||||||
// P2PK locked tokens can be received only by the wallet they are locked to (if lock has not expired)
|
// P2PK locked tokens can be received only by the wallet they are locked to (if lock has not expired)
|
||||||
const isLocked = CashuUtils.isTokenP2PKLocked(decodedToken)
|
const isLocked = CashuUtils.isTokenP2PKLocked(decodedToken)
|
||||||
let isLockedToWallet = false
|
let isLockedToWallet = false
|
||||||
const walletKeys = yield self.getCachedWalletKeys()
|
const walletKeys = yield self.getCachedWalletKeys()
|
||||||
|
|
||||||
if(isLocked) {
|
if(isLocked) {
|
||||||
const lockedToPK = CashuUtils.getP2PKPubkeySecret(decodedToken.proofs[0].secret)
|
const lockedToPK = CashuUtils.getP2PKPubkeySecret(decodedToken.proofs[0].secret)
|
||||||
const locktime = CashuUtils.getP2PKLocktime(decodedToken.proofs[0].secret)
|
const locktime = CashuUtils.getP2PKLocktime(decodedToken.proofs[0].secret)
|
||||||
const walletP2PK = '02' + walletKeys.NOSTR.publicKey
|
const walletP2PK = '02' + walletKeys.NOSTR.publicKey
|
||||||
|
|
||||||
isLockedToWallet = lockedToPK === walletP2PK
|
isLockedToWallet = lockedToPK === walletP2PK
|
||||||
|
|
||||||
if(!isLockedToWallet) {
|
if(!isLockedToWallet) {
|
||||||
throw new AppError(Err.VALIDATION_ERROR, 'Ecash token is locked to another wallet, can not receive it.', {lockedToPK, walletP2PK, locktime})
|
throw new AppError(Err.VALIDATION_ERROR, 'Ecash token is locked to another wallet, can not receive it.', {lockedToPK, walletP2PK, locktime})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const receiveParams: ReceiveParams = options?.inFlightRequest?.request || {
|
const receiveParams: ReceiveParams = options?.inFlightRequest?.request || {
|
||||||
token: decodedToken,
|
token: decodedToken,
|
||||||
options: {
|
options: {
|
||||||
keysetId: cashuWallet.keysetId,
|
keysetId: cashuWallet.keysetId,
|
||||||
counter: currentCounter.counter,
|
// Note: counter is no longer passed in v3.x, wallet manages it internally
|
||||||
privkey: isLockedToWallet ? walletKeys.NOSTR.privateKey : undefined,
|
privkey: isLockedToWallet ? walletKeys.NOSTR.privateKey : undefined,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
if(cashuWallet.getMintInfo().nuts['19'] && !options?.inFlightRequest) {
|
if(cashuWallet.getMintInfo().nuts['19'] && !options?.inFlightRequest) {
|
||||||
currentCounter.addInFlightRequest(transactionId, receiveParams)
|
currentCounter.addInFlightRequest(transactionId, receiveParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let reservedCounters: OperationCounters | undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const proofs = yield cashuWallet.receive(
|
const proofs = yield cashuWallet.receive(
|
||||||
receiveParams.token,
|
receiveParams.token,
|
||||||
receiveParams.options
|
{
|
||||||
|
...receiveParams.options,
|
||||||
|
onCountersReserved: (info: OperationCounters) => {
|
||||||
|
reservedCounters = info
|
||||||
|
log.debug('[receive] Counters reserved', info)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
log.trace('[WalletStore.receive]', {proofs})
|
log.trace('[WalletStore.receive]', {proofs})
|
||||||
|
|
||||||
currentCounter.removeInFlightRequest(transactionId)
|
currentCounter.removeInFlightRequest(transactionId)
|
||||||
|
|
||||||
|
// Update our counter to match what the wallet used (v3.x)
|
||||||
|
if (reservedCounters) {
|
||||||
|
currentCounter.counter = reservedCounters.next
|
||||||
|
log.debug('[receive] Updated counter', {
|
||||||
|
keysetId: reservedCounters.keysetId,
|
||||||
|
start: reservedCounters.start,
|
||||||
|
count: reservedCounters.count,
|
||||||
|
next: reservedCounters.next
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const receivedAmount: number = CashuUtils.getProofsAmount(proofs as Proof[])
|
const receivedAmount: number = CashuUtils.getProofsAmount(proofs as Proof[])
|
||||||
const amountToReceive: number = CashuUtils.getProofsAmount(decodedToken.proofs)
|
const amountToReceive: number = CashuUtils.getProofsAmount(decodedToken.proofs)
|
||||||
const swapFeePaid = amountToReceive - receivedAmount
|
const swapFeePaid = amountToReceive - receivedAmount
|
||||||
|
|
||||||
return {
|
return {
|
||||||
proofs,
|
proofs,
|
||||||
swapFeePaid
|
swapFeePaid
|
||||||
} as {
|
} as {
|
||||||
proofs: CashuProof[],
|
proofs: CashuProof[],
|
||||||
swapFeePaid: number
|
swapFeePaid: number
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,50 +540,74 @@ export const WalletStoreModel = types
|
|||||||
)
|
)
|
||||||
|
|
||||||
const currentCounter = mintInstance.getProofsCounterByKeysetId!(cashuWallet.keysetId)
|
const currentCounter = mintInstance.getProofsCounterByKeysetId!(cashuWallet.keysetId)
|
||||||
|
|
||||||
// outputs error healing
|
// outputs error healing
|
||||||
if(options && options.increaseCounterBy) {
|
if(options && options.increaseCounterBy) {
|
||||||
currentCounter.increaseProofsCounter(options.increaseCounterBy)
|
currentCounter.increaseProofsCounter(options.increaseCounterBy)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug('[WalletStore.send] counter', currentCounter.counter)
|
log.debug('[WalletStore.send] counter', currentCounter.counter)
|
||||||
|
|
||||||
|
// Sync wallet's internal counter with our stored counter (v3.x)
|
||||||
|
yield cashuWallet.counters.advanceToAtLeast(cashuWallet.keysetId, currentCounter.counter)
|
||||||
|
|
||||||
const p2pk = options?.p2pk
|
const p2pk = options?.p2pk
|
||||||
|
|
||||||
const sendParams: SendParams = options?.inFlightRequest?.request || {
|
const sendParams: SendParams = options?.inFlightRequest?.request || {
|
||||||
amount: amountToSend,
|
amount: amountToSend,
|
||||||
proofs: CashuUtils.exportProofs(proofsToSendFrom),
|
proofs: CashuUtils.exportProofs(proofsToSendFrom),
|
||||||
options: {
|
options: {
|
||||||
keysetId: cashuWallet.keysetId,
|
keysetId: cashuWallet.keysetId,
|
||||||
counter: p2pk && p2pk.pubkey ? undefined : currentCounter.counter, // p2pk is not deterministic
|
// Note: counter is no longer passed in v3.x, wallet manages it internally
|
||||||
includeFees: false, // fee reserve needs to be already in proofsToSendFrom
|
includeFees: false, // fee reserve needs to be already in proofsToSendFrom
|
||||||
p2pk
|
p2pk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
if(cashuWallet.getMintInfo().nuts['19'] && !options?.inFlightRequest) {
|
if(cashuWallet.getMintInfo().nuts['19'] && !options?.inFlightRequest) {
|
||||||
currentCounter.addInFlightRequest(transactionId, sendParams)
|
currentCounter.addInFlightRequest(transactionId, sendParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let reservedCounters: OperationCounters | undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const {keep, send} = yield cashuWallet.swap(
|
const {keep, send} = yield cashuWallet.swap(
|
||||||
sendParams.amount,
|
sendParams.amount,
|
||||||
sendParams.proofs,
|
sendParams.proofs,
|
||||||
sendParams.options
|
{
|
||||||
|
...sendParams.options,
|
||||||
|
onCountersReserved: (info: OperationCounters) => {
|
||||||
|
reservedCounters = info
|
||||||
|
log.debug('[send] Counters reserved', info)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
currentCounter.removeInFlightRequest(transactionId)
|
currentCounter.removeInFlightRequest(transactionId)
|
||||||
|
|
||||||
|
// Update our counter to match what the wallet used (v3.x)
|
||||||
|
if (reservedCounters) {
|
||||||
|
currentCounter.counter = reservedCounters.next
|
||||||
|
log.debug('[send] Updated counter', {
|
||||||
|
keysetId: reservedCounters.keysetId,
|
||||||
|
start: reservedCounters.start,
|
||||||
|
count: reservedCounters.count,
|
||||||
|
next: reservedCounters.next
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
log.trace(`[WalletStore.send] ${keep.length} returnedProofs`, {keep})
|
log.trace(`[WalletStore.send] ${keep.length} returnedProofs`, {keep})
|
||||||
log.trace(`[WalletStore.send] ${send.length} proofsToSend`, {send})
|
log.trace(`[WalletStore.send] ${send.length} proofsToSend`, {send})
|
||||||
|
|
||||||
const totalAmountToSendFrom: number = CashuUtils.getProofsAmount(proofsToSendFrom)
|
const totalAmountToSendFrom: number = CashuUtils.getProofsAmount(proofsToSendFrom)
|
||||||
const returnedAmount: number = CashuUtils.getProofsAmount(keep)
|
const returnedAmount: number = CashuUtils.getProofsAmount(keep)
|
||||||
const swapFeePaid = totalAmountToSendFrom - amountToSend - returnedAmount
|
const swapFeePaid = totalAmountToSendFrom - amountToSend - returnedAmount
|
||||||
|
|
||||||
return {
|
return {
|
||||||
returnedProofs: keep as CashuProof[],
|
returnedProofs: keep as CashuProof[],
|
||||||
proofsToSend: send as CashuProof[],
|
proofsToSend: send as CashuProof[],
|
||||||
swapFeePaid
|
swapFeePaid
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -745,11 +793,12 @@ export const WalletStoreModel = types
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
if(cashuWallet.getMintInfo().nuts['19'] && !options?.inFlightRequest) {
|
if(cashuWallet.getMintInfo().nuts['19'] && !options?.inFlightRequest) {
|
||||||
currentCounter.addInFlightRequest(transactionId, mintParams)
|
currentCounter.addInFlightRequest(transactionId, mintParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
let reservedCounters: OperationCounters | undefined = undefined
|
let reservedCounters: OperationCounters | undefined
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@@ -758,8 +807,8 @@ export const WalletStoreModel = types
|
|||||||
mintParams.quote,
|
mintParams.quote,
|
||||||
{
|
{
|
||||||
keysetId: mintParams.options?.keysetId,
|
keysetId: mintParams.options?.keysetId,
|
||||||
onCountersReserved: (info) => {
|
onCountersReserved: (info: OperationCounters) => {
|
||||||
reservedCounters = info as OperationCounters
|
reservedCounters = info
|
||||||
log.debug('[mintProofsBolt11] Counters reserved', info)
|
log.debug('[mintProofsBolt11] Counters reserved', info)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -864,66 +913,71 @@ export const WalletStoreModel = types
|
|||||||
currentCounter.increaseProofsCounter(options.increaseCounterBy)
|
currentCounter.increaseProofsCounter(options.increaseCounterBy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// store local counter value to be able to recover the change if needed
|
// Sync wallet's internal counter with our stored counter (v3.x)
|
||||||
const counterValueForMelt = currentCounter.addMeltCounterValue(transactionId)
|
yield cashuWallet.counters.advanceToAtLeast(cashuWallet.keysetId, currentCounter.counter)
|
||||||
|
|
||||||
// Preemptively increase local counter in case we miss response with change from mint
|
log.trace('[WalletStore.payLightningMelt] Preparing melt', {
|
||||||
currentCounter.increaseProofsCounter(proofsToMeltFrom.length) // ?
|
localCounter: currentCounter.counter,
|
||||||
|
proofsCount: proofsToMeltFrom.length
|
||||||
let countChangeOutputs = 0
|
})
|
||||||
if (meltQuote.fee_reserve > 0) {
|
|
||||||
countChangeOutputs = Math.ceil(Math.log2(meltQuote.fee_reserve)) || 1
|
let reservedCounters: OperationCounters | undefined
|
||||||
currentCounter.increaseProofsCounter(countChangeOutputs)
|
|
||||||
}
|
// Step 1: Prepare the melt (creates change outputs deterministically)
|
||||||
|
const meltPreview: MeltPreview = yield cashuWallet.prepareMelt(
|
||||||
log.trace('[WalletStore.payLightningMelt] Counter state before melt', {localCounter: currentCounter.counter, counterValueForMelt})
|
'bolt11',
|
||||||
|
meltQuote,
|
||||||
const meltParams: MeltParams = options?.inFlightRequest?.request || {
|
CashuUtils.exportProofs(proofsToMeltFrom),
|
||||||
meltQuote,
|
{
|
||||||
proofsToSend: CashuUtils.exportProofs(proofsToMeltFrom),
|
keysetId: cashuWallet.keysetId,
|
||||||
options: {
|
onCountersReserved: (info: OperationCounters) => {
|
||||||
keysetId: cashuWallet.keysetId,
|
reservedCounters = info
|
||||||
counter: counterValueForMelt
|
log.debug('[prepareMelt] Counters reserved', info)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
|
|
||||||
if(cashuWallet.getMintInfo().nuts['19'] && !options?.inFlightRequest) {
|
// Store the MeltPreview for potential recovery
|
||||||
currentCounter.addInFlightRequest(transactionId, meltParams)
|
currentCounter.addMeltCounterValue(transactionId, meltPreview)
|
||||||
}
|
|
||||||
|
// Update our counter to match what the wallet used (v3.x)
|
||||||
|
if (reservedCounters) {
|
||||||
|
currentCounter.counter = reservedCounters.next
|
||||||
|
log.debug('[prepareMelt] Updated counter', {
|
||||||
|
keysetId: reservedCounters.keysetId,
|
||||||
|
start: reservedCounters.start,
|
||||||
|
count: reservedCounters.count,
|
||||||
|
next: reservedCounters.next
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Step 2: Complete the melt (sends to mint and constructs change proofs)
|
||||||
|
const meltResponse: MeltProofsResponse = yield cashuWallet.completeMelt(meltPreview)
|
||||||
|
|
||||||
const meltResponse = yield cashuWallet.meltProofsBolt11(
|
// Remove the stored preview on success
|
||||||
meltParams.meltQuote,
|
currentCounter.removeMeltCounterValue(transactionId)
|
||||||
meltParams.proofsToSend,
|
|
||||||
meltParams.options
|
|
||||||
)
|
|
||||||
|
|
||||||
currentCounter.removeInFlightRequest(transactionId)
|
|
||||||
|
|
||||||
log.trace('[payLightningMelt]', {meltResponse})
|
log.trace('[payLightningMelt]', {meltResponse})
|
||||||
// we normalize naming of returned parameters
|
return meltResponse
|
||||||
return meltResponse as MeltProofsResponse
|
|
||||||
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if(!e.message.toLowerCase().includes('timeout') &&
|
// On error, keep the MeltPreview stored so we can attempt recovery later
|
||||||
!e.message.toLowerCase().includes('network request failed')) {
|
log.error('[payLightningMelt] Melt failed, MeltPreview stored for recovery', {
|
||||||
// remove in-flight request only if it was not a timeout or network error
|
transactionId,
|
||||||
currentCounter.removeInFlightRequest(transactionId)
|
error: e.message
|
||||||
// we might roll back preemptive counter increase
|
})
|
||||||
// currentCounter.decreaseProofsCounter(countChangeOutputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
let message = 'Lightning payment failed.'
|
let message = 'Lightning payment failed.'
|
||||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
Err.MINT_ERROR,
|
Err.MINT_ERROR,
|
||||||
message,
|
message,
|
||||||
{
|
{
|
||||||
message: e.message,
|
message: e.message,
|
||||||
caller: 'payLightningMelt',
|
caller: 'payLightningMelt',
|
||||||
mintUrl,
|
mintUrl,
|
||||||
code: e.code || undefined,
|
code: e.code || undefined,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -957,56 +1011,57 @@ export const WalletStoreModel = types
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
recoverMeltQuoteChange: flow(function* recoverMeltQuoteChange(
|
recoverMeltQuoteChange: flow(function* recoverMeltQuoteChange(
|
||||||
mintUrl: string,
|
mintUrl: string,
|
||||||
meltQuote: MeltQuoteBolt11Response,
|
meltQuote: MeltQuoteBolt11Response,
|
||||||
transactionId: number
|
transactionId: number
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const mintInstance = self.getMintModelInstance(mintUrl)
|
const mintInstance = self.getMintModelInstance(mintUrl)
|
||||||
|
|
||||||
if(!mintInstance) {
|
if(!mintInstance) {
|
||||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint instance', {mintUrl})
|
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint instance', {mintUrl})
|
||||||
}
|
}
|
||||||
|
|
||||||
const cashuWallet = yield self.getWallet(
|
const cashuWallet = yield self.getWallet(
|
||||||
mintUrl,
|
mintUrl,
|
||||||
'sat',
|
'sat',
|
||||||
{
|
{
|
||||||
withSeed: true,
|
withSeed: true,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const currentCounter = mintInstance.getProofsCounterByKeysetId!(cashuWallet.keysetId)
|
const currentCounter = mintInstance.getProofsCounterByKeysetId!(cashuWallet.keysetId)
|
||||||
const counterValueForMelt = currentCounter.getMeltCounterValue(transactionId)?.counterAtMelt
|
const meltCounterValue = currentCounter.getMeltCounterValue(transactionId)
|
||||||
|
|
||||||
if(counterValueForMelt === undefined) {
|
if(!meltCounterValue) {
|
||||||
throw new AppError(Err.VALIDATION_ERROR, 'Change already claimed - counterAtMelt not available for this transaction', {mintUrl, transactionId})
|
throw new AppError(Err.VALIDATION_ERROR, 'Change already claimed - melt data not available for this transaction', {mintUrl, transactionId})
|
||||||
}
|
}
|
||||||
|
|
||||||
const {change}: MeltProofsResponse = yield cashuWallet.recoverMeltQuoteChange(
|
const meltPreview = meltCounterValue.meltPreview
|
||||||
meltQuote,
|
|
||||||
{
|
if(!meltPreview) {
|
||||||
keysetId: cashuWallet.keysetId,
|
throw new AppError(Err.VALIDATION_ERROR, 'MeltPreview not found - this transaction may be from an older version', {mintUrl, transactionId})
|
||||||
counter: counterValueForMelt
|
}
|
||||||
}
|
|
||||||
)
|
// Use completeMelt with the stored MeltPreview to recover change
|
||||||
|
const {change}: MeltProofsResponse = yield cashuWallet.completeMelt(meltPreview)
|
||||||
|
|
||||||
currentCounter.removeMeltCounterValue(transactionId)
|
currentCounter.removeMeltCounterValue(transactionId)
|
||||||
|
|
||||||
log.info('[recoverMeltQuoteChange]', {change})
|
log.info('[recoverMeltQuoteChange]', {change})
|
||||||
|
|
||||||
return change
|
return change
|
||||||
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
let message = 'The mint could not return change from a melt quote.'
|
let message = 'The mint could not return change from a melt quote.'
|
||||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||||
throw new AppError(
|
throw new AppError(
|
||||||
Err.MINT_ERROR,
|
Err.MINT_ERROR,
|
||||||
message,
|
message,
|
||||||
{
|
{
|
||||||
message: e.message,
|
message: e.message,
|
||||||
caller: 'recoverMeltQuoteChange',
|
caller: 'recoverMeltQuoteChange',
|
||||||
mintUrl,
|
mintUrl,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1094,4 +1149,4 @@ export const WalletStoreModel = types
|
|||||||
|
|
||||||
export interface WalletStore extends Instance<typeof WalletStoreModel> {}
|
export interface WalletStore extends Instance<typeof WalletStoreModel> {}
|
||||||
export interface WalletStoreSnapshot
|
export interface WalletStoreSnapshot
|
||||||
extends SnapshotOut<typeof WalletStoreModel> {}
|
extends SnapshotOut<typeof WalletStoreModel> {}
|
||||||
Reference in New Issue
Block a user