mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 19:28:26 +00:00
Revamp mint keysets and keys management, melt swap fees
This commit is contained in:
+1
-1
@@ -19,7 +19,7 @@
|
||||
"postinstall": "patch-package --exclude 'nothing'"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cashu/cashu-ts": "minibits-cash/cashu-ts#1.0.0-minibits",
|
||||
"@cashu/cashu-ts": "minibits-cash/cashu-ts#1.0.0-minibits.2",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.5.2",
|
||||
|
||||
+187
-76
@@ -1,6 +1,11 @@
|
||||
import {cast, flow, Instance, SnapshotIn, SnapshotOut, types} from 'mobx-state-tree'
|
||||
import {cast, flow, getSnapshot, Instance, SnapshotIn, SnapshotOut, types} from 'mobx-state-tree'
|
||||
import {withSetPropAction} from './helpers/withSetPropAction'
|
||||
import type {CashuWallet, GetInfoResponse, MintKeys, MintKeyset} from '@cashu/cashu-ts'
|
||||
import type {
|
||||
CashuWallet,
|
||||
GetInfoResponse,
|
||||
MintKeys as CashuMintKeys,
|
||||
MintKeyset as CashuMintKeyset
|
||||
} from '@cashu/cashu-ts'
|
||||
import {colors, getRandomIconColor} from '../theme'
|
||||
import { log, MintClient } from '../services'
|
||||
|
||||
@@ -33,8 +38,6 @@ export type Balances = {
|
||||
unitBalances: UnitBalance[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
export enum MintStatus {
|
||||
ONLINE = 'ONLINE',
|
||||
OFFLINE = 'OFFLINE'
|
||||
@@ -43,13 +46,21 @@ export enum MintStatus {
|
||||
export type MintProofsCounter = {
|
||||
keyset: string
|
||||
counter: number
|
||||
unit: MintUnit
|
||||
input_fee_ppk: number
|
||||
unit: MintUnit
|
||||
inFlightFrom?: number // starting counter index for pending split request sent to mint (for recovery from failure to receive proofs)
|
||||
inFlightTo?: number // last counter index for pending split request sent to mint
|
||||
inFlightTid?: number // related tx id
|
||||
}
|
||||
|
||||
export const MintProofsCounterModel = 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)
|
||||
})
|
||||
/**
|
||||
* This represents a Cashu mint
|
||||
*/
|
||||
@@ -59,24 +70,61 @@ export const MintModel = types
|
||||
mintUrl: types.string,
|
||||
hostname: types.maybe(types.string),
|
||||
shortname: types.maybe(types.string),
|
||||
units: types.array(types.frozen<MintUnit>()),
|
||||
proofsCounters: types.array(
|
||||
types.model('MintProofsCounter', {
|
||||
keyset: types.string,
|
||||
unit: types.optional(types.frozen<MintUnit>(), 'sat'),
|
||||
counter: types.number,
|
||||
input_fee_ppk: types.optional(types.number, 0),
|
||||
inFlightFrom: types.maybe(types.number),
|
||||
inFlightTo: types.maybe(types.number),
|
||||
inFlightTid: types.maybe(types.number)
|
||||
})
|
||||
),
|
||||
units: types.array(types.frozen<MintUnit>()),
|
||||
keysets: types.array(types.frozen<CashuMintKeyset>()),
|
||||
keys: types.array(types.frozen<CashuMintKeys>()),
|
||||
proofsCounters: types.array(MintProofsCounterModel),
|
||||
color: types.optional(types.string, colors.palette.iconBlue200),
|
||||
status: types.optional(types.frozen<MintStatus>(), MintStatus.ONLINE),
|
||||
createdAt: types.optional(types.Date, new Date()),
|
||||
})
|
||||
.actions(withSetPropAction) // TODO? start to use across app to avoid pure setter methods, e.g. mint.setProp('color', '#ccc')
|
||||
.actions(self => ({
|
||||
addKeyset(keyset: CashuMintKeyset) {
|
||||
const alreadyExists = self.keysets.some(k => k.id === keyset.id)
|
||||
|
||||
if(!alreadyExists) {
|
||||
self.keysets.push(keyset)
|
||||
}
|
||||
|
||||
self.keysets = cast(self.keysets)
|
||||
},
|
||||
removeKeyset(keyset: CashuMintKeyset) {
|
||||
const index = self.keysets.findIndex(k => k.id === keyset.id)
|
||||
|
||||
if(index) {
|
||||
self.keysets.splice(index, 0)
|
||||
}
|
||||
|
||||
self.keysets = cast(self.keysets)
|
||||
},
|
||||
setIsActive(freshKeyset: CashuMintKeyset) {
|
||||
const keyset = self.keysets.find(k => k.id === freshKeyset.id)
|
||||
|
||||
if(keyset) {
|
||||
keyset.active = freshKeyset.active
|
||||
}
|
||||
|
||||
self.keysets = cast(self.keysets)
|
||||
},
|
||||
addKeys(keys: CashuMintKeys) {
|
||||
const alreadyExists = self.keys.some(k => k.id === keys.id)
|
||||
|
||||
if(!alreadyExists) {
|
||||
self.keys.push(keys)
|
||||
}
|
||||
|
||||
self.keys = cast(self.keys)
|
||||
},
|
||||
removeKeys(keys: CashuMintKeys) {
|
||||
const index = self.keys.findIndex(k => k.id === keys.id)
|
||||
|
||||
if(index) {
|
||||
self.keys.splice(index, 0)
|
||||
}
|
||||
|
||||
self.keys = cast(self.keys)
|
||||
},
|
||||
addUnit(unit: MintUnit) {
|
||||
const alreadyExists = self.units.some(u => u === unit)
|
||||
|
||||
@@ -95,43 +143,99 @@ export const MintModel = types
|
||||
|
||||
self.units = cast(self.units)
|
||||
},
|
||||
addProofsCounter(counter: MintProofsCounter) {
|
||||
const alreadyExists = self.proofsCounters.some(p => p.keyset === counter.keyset)
|
||||
|
||||
if(!alreadyExists) {
|
||||
self.proofsCounters.push(counter)
|
||||
}
|
||||
|
||||
self.proofsCounters = cast(self.proofsCounters)
|
||||
},
|
||||
removeProofsCounter(counter: MintProofsCounter) {
|
||||
const index = self.proofsCounters.findIndex(p => p.keyset === counter.keyset)
|
||||
|
||||
if(index) {
|
||||
self.proofsCounters.splice(index, 0)
|
||||
}
|
||||
|
||||
self.proofsCounters = cast(self.proofsCounters)
|
||||
},
|
||||
getProofsCounter(keysetId: string) {
|
||||
const counter = self.proofsCounters.find(c => c.keyset === keysetId)
|
||||
return counter
|
||||
},
|
||||
isUnitSupported(unit: MintUnit): boolean {
|
||||
return MintUnits.includes(unit) ? true : false
|
||||
},
|
||||
keysetExists(keyset: CashuMintKeyset): boolean {
|
||||
return self.keysets.some(k => k.id === keyset.id)
|
||||
},
|
||||
}))
|
||||
.actions(self => ({
|
||||
createProofsCounter(keyset: MintKeyset) {
|
||||
// Do not add unit the wallet does not have configured
|
||||
if (!MintUnits.includes(keyset.unit as MintUnit)) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, `Unsupported unit provided by the mint: ${keyset.unit}`)
|
||||
}
|
||||
|
||||
const existing = self.getProofsCounter(keyset.id)
|
||||
|
||||
if(existing) { // update fees if they can change (?)
|
||||
if (existing.unit !== keyset.unit) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, `Keyset unit mismatch, got ${keyset.unit}, expected ${existing.unit}`)
|
||||
}
|
||||
|
||||
existing.input_fee_ppk = keyset.input_fee_ppk || 0
|
||||
self.proofsCounters = cast(self.proofsCounters)
|
||||
return existing
|
||||
}
|
||||
|
||||
.actions(self => ({
|
||||
createProofsCounter(keyset: CashuMintKeyset) {
|
||||
const newCounter: MintProofsCounter = {
|
||||
keyset: keyset.id,
|
||||
unit: keyset.unit as MintUnit,
|
||||
input_fee_ppk: keyset.input_fee_ppk || 0,
|
||||
unit: keyset.unit as MintUnit,
|
||||
counter: 0,
|
||||
}
|
||||
|
||||
self.proofsCounters.push(newCounter)
|
||||
self.addUnit(keyset.unit as MintUnit)
|
||||
const instance = self.proofsCounters.find(c => c.keyset === keyset.id) as MintProofsCounter
|
||||
const proofsCounterInstance = MintProofsCounterModel.create(newCounter)
|
||||
self.addProofsCounter(newCounter)
|
||||
|
||||
log.trace('[ceateProofsCounter]', {newCounter: getSnapshot(proofsCounterInstance)})
|
||||
return proofsCounterInstance
|
||||
}
|
||||
}))
|
||||
.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)
|
||||
|
||||
log.trace('[ceateProofsCounter]', {newCounter: instance})
|
||||
return instance
|
||||
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.input_fee_ppk) {
|
||||
keyset.input_fee_ppk = 0
|
||||
}
|
||||
|
||||
if(!keyset.unit) {
|
||||
keyset.unit = 'sat'
|
||||
}
|
||||
|
||||
self.addKeyset(keyset)
|
||||
self.addUnit(keyset.unit as MintUnit)
|
||||
self.createProofsCounter(keyset)
|
||||
|
||||
log.trace('[initKeyset]', {newKeyset: keyset})
|
||||
},
|
||||
initKeys(key: CashuMintKeys) {
|
||||
// Do not add unit the wallet does not have configured
|
||||
if(!self.isUnitSupported(key.unit as MintUnit)) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, `Unsupported unit provided by the mint: ${key.unit}`)
|
||||
}
|
||||
|
||||
const existing = self.keys.find(k => k.id === key.id)
|
||||
|
||||
if(existing) {
|
||||
if (existing.unit !== key.unit) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, `Keyset unit mismatch, got ${key.unit}, expected ${existing.unit}`)
|
||||
}
|
||||
|
||||
return existing
|
||||
}
|
||||
|
||||
self.addKeys(key)
|
||||
log.trace('[initKeys]', {newKeys: key.id})
|
||||
},
|
||||
findInFlightProofsCounter() {
|
||||
const counter = self.proofsCounters.find(c => c.inFlightFrom && c.inFlightTo && c.inFlightTid)
|
||||
@@ -151,32 +255,39 @@ export const MintModel = types
|
||||
},
|
||||
}))
|
||||
.actions(self => ({
|
||||
getProofsCounterByUnit: flow(function* getProofsCounterByUnit(unit: MintUnit) {
|
||||
try {
|
||||
// Refresh current keys for this unit from new or existing in-memory wallet instance
|
||||
const wallet: CashuWallet = yield MintClient.getWallet(self.mintUrl, unit)
|
||||
const keyset = wallet.keyset
|
||||
|
||||
// Get or create new proofs counter for this keyset
|
||||
const counter = self.getProofsCounter(keyset.id)
|
||||
|
||||
if(!counter) {
|
||||
return self.createProofsCounter(keyset)
|
||||
}
|
||||
|
||||
return counter
|
||||
} catch(e: any) {
|
||||
log.warn('[getProofsCounterByUnit] Could not refresh keyset, using mint proofsCounter for unit', {unit})
|
||||
|
||||
const proofsCounterInstance = self.proofsCounters.find(c => c.unit === unit)
|
||||
|
||||
if(!proofsCounterInstance) {
|
||||
throw new AppError(Err.NOTFOUND_ERROR, 'Could not get keyset to create new mint proofsCounter for unit', {unit})
|
||||
}
|
||||
|
||||
return proofsCounterInstance
|
||||
refreshKeysets(freshKeysets: CashuMintKeyset[]) {
|
||||
// add new keyset if not exists
|
||||
for (const keyset of freshKeysets) {
|
||||
self.initKeyset(keyset)
|
||||
self.setIsActive(keyset)
|
||||
}
|
||||
}),
|
||||
},
|
||||
refreshKeys(freshKeys: CashuMintKeys[]) {
|
||||
for (const key of freshKeys) {
|
||||
self.initKeys(key)
|
||||
}
|
||||
},
|
||||
getProofsCounterByUnit(unit: MintUnit, useActiveKeyset: boolean = true) {
|
||||
let keyset: CashuMintKeyset | undefined
|
||||
|
||||
if(useActiveKeyset) {
|
||||
keyset = self.keysets.find(k => k.active === true && k.unit === unit)
|
||||
} else {
|
||||
keyset = self.keysets.find(k => k.active === false && k.unit === unit)
|
||||
}
|
||||
|
||||
if(!keyset) {
|
||||
throw new AppError(Err.NOTFOUND_ERROR, 'Mint has no keyset for this unit', {unit})
|
||||
}
|
||||
|
||||
const counter = self.proofsCounters.find(p => p.keyset === keyset?.id)
|
||||
|
||||
if(!counter) {
|
||||
return self.createProofsCounter(keyset)
|
||||
}
|
||||
|
||||
return counter
|
||||
},
|
||||
setHostname() {
|
||||
try {
|
||||
self.hostname = new URL(self.mintUrl).hostname
|
||||
@@ -299,10 +410,10 @@ export const MintModel = types
|
||||
self.proofsCounters = cast(self.proofsCounters)
|
||||
},
|
||||
getFeesForProofs(proofs: Proof[]): number {
|
||||
// Find the corresponding MintProofsCounter for each proof and sum the input fees
|
||||
// Find the corresponding keyset for each proof and sum the input fees
|
||||
const totalInputFees = proofs.reduce((sum, proof) => {
|
||||
const counter = self.proofsCounters.find(pc => pc.keyset === proof.id)
|
||||
return counter ? sum + counter.input_fee_ppk : sum
|
||||
const keyset = self.keysets.find(k => k.id === proof.id)
|
||||
return keyset && keyset.input_fee_ppk ? sum + keyset.input_fee_ppk : sum
|
||||
}, 0)
|
||||
|
||||
// Calculate the fees
|
||||
@@ -317,15 +428,15 @@ export const MintModel = types
|
||||
const mintBalance: MintBalance | undefined = getRootStore(self).proofsStore.getMintBalance(self.mintUrl)
|
||||
return mintBalance
|
||||
},
|
||||
get keysets(): string[] {
|
||||
return self.proofsCounters.map(c => c.keyset)
|
||||
}
|
||||
get keysetIds(): string[] {
|
||||
return self.keysets.map(k => k.id)
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
|
||||
export type Mint = {
|
||||
mintUrl: string
|
||||
mintUrl: string
|
||||
} & Partial<Instance<typeof MintModel>>
|
||||
export interface MintSnapshotOut extends SnapshotOut<typeof MintModel> {}
|
||||
export interface MintSnapshotIn extends SnapshotIn<typeof MintModel> {}
|
||||
+158
-151
@@ -12,157 +12,164 @@ import {
|
||||
import {log} from '../services/logService'
|
||||
import { MintClient } from '../services'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import { MintKeyset } from '@cashu/cashu-ts'
|
||||
import type {
|
||||
MintKeys as CashuMintKeys,
|
||||
MintKeyset as CashuMintKeyset
|
||||
} from '@cashu/cashu-ts'
|
||||
|
||||
import { MintUnit, MintUnits } from '../services/wallet/currency'
|
||||
|
||||
export type MintsByHostname = {
|
||||
hostname: string
|
||||
mints: Mint[]
|
||||
}
|
||||
export type MintsByHostname = {
|
||||
hostname: string
|
||||
mints: Mint[]
|
||||
}
|
||||
|
||||
export type MintsByUnit = {
|
||||
export type MintsByUnit = {
|
||||
unit: MintUnit
|
||||
mints: Mint[]
|
||||
}
|
||||
|
||||
export const MintsStoreModel = types
|
||||
.model('MintsStore', {
|
||||
mints: types.array(MintModel),
|
||||
blockedMintUrls: types.array(types.string),
|
||||
})
|
||||
.views(self => ({
|
||||
findByUrl: (mintUrl: string | URL) => {
|
||||
const mint = self.mints.find(m => m.mintUrl === mintUrl)
|
||||
return mint ? mint : undefined
|
||||
},
|
||||
}))
|
||||
.actions(withSetPropAction)
|
||||
.actions(self => ({
|
||||
addMint: flow(function* addMint(mintUrl: string) {
|
||||
if(!mintUrl.includes('.onion') && !mintUrl.startsWith('https')) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint URL needs to start with https')
|
||||
}
|
||||
|
||||
// create default wallet instance then download and cache up to date mint keys in that instance
|
||||
const allKeysets: MintKeyset[] = yield MintClient.getMintKeysets(mintUrl)
|
||||
const activeKeysets = allKeysets.filter(keyset => keyset.active === true)
|
||||
}
|
||||
|
||||
if(!activeKeysets || activeKeysets.length === 0) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint has no active keysets and is not operational', {mintUrl})
|
||||
}
|
||||
|
||||
const newMint: Mint = {
|
||||
mintUrl,
|
||||
}
|
||||
|
||||
const mintInstance = MintModel.create(newMint)
|
||||
|
||||
mintInstance.setHostname()
|
||||
mintInstance.setRandomColor()
|
||||
export const MintsStoreModel = types
|
||||
.model('MintsStore', {
|
||||
mints: types.array(MintModel),
|
||||
blockedMintUrls: types.array(types.string),
|
||||
})
|
||||
.views(self => ({
|
||||
findByUrl: (mintUrl: string | URL) => {
|
||||
const mint = self.mints.find(m => m.mintUrl === mintUrl)
|
||||
return mint ? mint : undefined
|
||||
},
|
||||
}))
|
||||
.actions(withSetPropAction)
|
||||
.actions(self => ({
|
||||
addMint: flow(function* addMint(mintUrl: string) {
|
||||
if(!mintUrl.includes('.onion') && !mintUrl.startsWith('https')) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint URL needs to start with https.')
|
||||
}
|
||||
|
||||
for(const keyset of activeKeysets) {
|
||||
try {
|
||||
if(keyset.active === true) {
|
||||
mintInstance.createProofsCounter(keyset)
|
||||
}
|
||||
} catch(e: any) {
|
||||
continue
|
||||
}
|
||||
// create default wallet instance then download and cache up to date mint keys in that instance
|
||||
const keysets: CashuMintKeyset[] = yield MintClient.getMintKeysets(mintUrl)
|
||||
const keys: CashuMintKeys[] = yield MintClient.getMintKeys(mintUrl)
|
||||
|
||||
if(!keysets || keysets.length === 0 || !keys || keys.length === 0) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint has no keysets and is not operational.', {mintUrl})
|
||||
}
|
||||
|
||||
const mintInstance = MintModel.create({mintUrl})
|
||||
|
||||
for(const keyset of keysets) {
|
||||
if (!mintInstance.isUnitSupported(keyset.unit as MintUnit)) {
|
||||
continue
|
||||
}
|
||||
|
||||
yield mintInstance.setShortname()
|
||||
self.mints.push(mintInstance)
|
||||
}),
|
||||
updateMint: flow(function* updateMint(mintUrl: string) {
|
||||
|
||||
mintInstance.initKeyset(keyset)
|
||||
}
|
||||
|
||||
for(const key of keys) {
|
||||
if (!mintInstance.isUnitSupported(key.unit as MintUnit)) {
|
||||
continue
|
||||
}
|
||||
|
||||
mintInstance.initKeys(key)
|
||||
}
|
||||
|
||||
mintInstance.setHostname()
|
||||
yield mintInstance.setShortname()
|
||||
|
||||
self.mints.push(mintInstance)
|
||||
}),
|
||||
updateMint: flow(function* updateMint(mintUrl: string) {
|
||||
const mintInstance = self.findByUrl(mintUrl)
|
||||
|
||||
if(!mintInstance) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Could not find mint to update', {mintUrl})
|
||||
}
|
||||
// refresh up to date mint keys
|
||||
const allKeysets: MintKeyset[] = yield MintClient.getMintKeysets(mintUrl)
|
||||
const keysets: CashuMintKeyset[] = yield MintClient.getMintKeysets(mintUrl)
|
||||
const keys: CashuMintKeys[] = yield MintClient.getMintKeys(mintUrl)
|
||||
|
||||
if(!allKeysets || allKeysets.length === 0) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint has no active keysets and is not operational', {mintUrl})
|
||||
if(!keysets || keysets.length === 0 || !keys || keys.length === 0) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint has no keysets and is not operational', {mintUrl})
|
||||
}
|
||||
|
||||
for(const keyset of allKeysets) {
|
||||
if(keyset.active === true) {
|
||||
// Do not add unit the wallet does not have configured
|
||||
if (!MintUnits.includes(keyset.unit as MintUnit)) {
|
||||
log.error(`Unsupported unit provided by the mint: ${keyset.unit}`)
|
||||
continue
|
||||
}
|
||||
|
||||
mintInstance.addUnit(keyset.unit as MintUnit) // add supported units by mint if not yet exist
|
||||
mintInstance.createProofsCounter(keyset) // create proofsCounters or update fees if exists
|
||||
for(const keyset of keysets) {
|
||||
if (!mintInstance.isUnitSupported(keyset.unit as MintUnit)) {
|
||||
continue
|
||||
}
|
||||
|
||||
mintInstance.initKeyset(keyset)
|
||||
}
|
||||
|
||||
for(const key of keys) {
|
||||
if (!mintInstance.isUnitSupported(key.unit as MintUnit)) {
|
||||
continue
|
||||
}
|
||||
|
||||
mintInstance.initKeys(key)
|
||||
}
|
||||
|
||||
yield mintInstance.setShortname()
|
||||
|
||||
}),
|
||||
removeMint(mintToBeRemoved: Mint) {
|
||||
if (self.blockedMintUrls.some(m => m === mintToBeRemoved.mintUrl)) {
|
||||
self.blockedMintUrls.remove(mintToBeRemoved.mintUrl)
|
||||
log.debug('[removeMint]', 'Mint removed from blockedMintUrls')
|
||||
}
|
||||
|
||||
let mintInstance: Mint | undefined
|
||||
|
||||
if (isStateTreeNode(mintToBeRemoved)) {
|
||||
mintInstance = mintToBeRemoved
|
||||
} else {
|
||||
mintInstance = self.findByUrl((mintToBeRemoved as Mint).mintUrl)
|
||||
}
|
||||
|
||||
if (mintInstance) {
|
||||
detach(mintInstance)
|
||||
destroy(mintInstance)
|
||||
log.info('[removeMint]', 'Mint removed from MintsStore')
|
||||
}
|
||||
},
|
||||
blockMint(mintToBeBlocked: Mint) {
|
||||
if(self.blockedMintUrls.some(url => url === mintToBeBlocked.mintUrl)) {
|
||||
return
|
||||
}
|
||||
|
||||
self.blockedMintUrls.push(mintToBeBlocked.mintUrl)
|
||||
log.debug('[blockMint]', 'Mint blocked in MintsStore')
|
||||
},
|
||||
unblockMint(blockedMint: Mint) {
|
||||
self.blockedMintUrls.remove(blockedMint.mintUrl)
|
||||
log.debug('[unblockMint]', 'Mint unblocked in MintsStore')
|
||||
}
|
||||
}))
|
||||
.views(self => ({
|
||||
get mintCount() {
|
||||
return self.mints.length
|
||||
},
|
||||
get allMints() {
|
||||
return self.mints
|
||||
},
|
||||
get groupedByHostname() {
|
||||
const grouped: Record<string, MintsByHostname> = {}
|
||||
|
||||
self.mints.forEach((mint: Mint) => {
|
||||
const {hostname} = mint
|
||||
|
||||
if (!grouped[hostname as string]) {
|
||||
grouped[hostname as string] = {
|
||||
hostname,
|
||||
mints: [],
|
||||
}
|
||||
}
|
||||
|
||||
grouped[hostname as string].mints.push(mint)
|
||||
})
|
||||
|
||||
return Object.values(grouped) as MintsByHostname[]
|
||||
},
|
||||
get groupedByUnit() {
|
||||
}),
|
||||
removeMint(mintToBeRemoved: Mint) {
|
||||
if (self.blockedMintUrls.some(m => m === mintToBeRemoved.mintUrl)) {
|
||||
self.blockedMintUrls.remove(mintToBeRemoved.mintUrl)
|
||||
log.debug('[removeMint]', 'Mint removed from blockedMintUrls')
|
||||
}
|
||||
|
||||
let mintInstance: Mint | undefined
|
||||
|
||||
if (isStateTreeNode(mintToBeRemoved)) {
|
||||
mintInstance = mintToBeRemoved
|
||||
} else {
|
||||
mintInstance = self.findByUrl((mintToBeRemoved as Mint).mintUrl)
|
||||
}
|
||||
|
||||
if (mintInstance) {
|
||||
detach(mintInstance)
|
||||
destroy(mintInstance)
|
||||
log.info('[removeMint]', 'Mint removed from MintsStore')
|
||||
}
|
||||
},
|
||||
blockMint(mintToBeBlocked: Mint) {
|
||||
if(self.blockedMintUrls.some(url => url === mintToBeBlocked.mintUrl)) {
|
||||
return
|
||||
}
|
||||
|
||||
self.blockedMintUrls.push(mintToBeBlocked.mintUrl)
|
||||
log.debug('[blockMint]', 'Mint blocked in MintsStore')
|
||||
},
|
||||
unblockMint(blockedMint: Mint) {
|
||||
self.blockedMintUrls.remove(blockedMint.mintUrl)
|
||||
log.debug('[unblockMint]', 'Mint unblocked in MintsStore')
|
||||
}
|
||||
}))
|
||||
.views(self => ({
|
||||
get mintCount() {
|
||||
return self.mints.length
|
||||
},
|
||||
get allMints() {
|
||||
return self.mints
|
||||
},
|
||||
get groupedByHostname() {
|
||||
const grouped: Record<string, MintsByHostname> = {}
|
||||
|
||||
self.mints.forEach((mint: Mint) => {
|
||||
const {hostname} = mint
|
||||
|
||||
if (!grouped[hostname as string]) {
|
||||
grouped[hostname as string] = {
|
||||
hostname,
|
||||
mints: [],
|
||||
}
|
||||
}
|
||||
|
||||
grouped[hostname as string].mints.push(mint)
|
||||
})
|
||||
|
||||
return Object.values(grouped) as MintsByHostname[]
|
||||
},
|
||||
get groupedByUnit() {
|
||||
const groupedByUnit: Record<string, MintsByUnit> = {}
|
||||
|
||||
self.mints.forEach(mint => {
|
||||
@@ -175,29 +182,29 @@ import { MintUnit, MintUnits } from '../services/wallet/currency'
|
||||
})
|
||||
|
||||
return Object.values(groupedByUnit) as MintsByUnit[]
|
||||
},
|
||||
alreadyExists(mintUrl: string) {
|
||||
return self.mints.some(m => m.mintUrl === mintUrl) ? true : false
|
||||
},
|
||||
isBlocked(mintUrl: string) {
|
||||
return self.blockedMintUrls.some(m => m === mintUrl) ? true : false
|
||||
},
|
||||
getBlockedFromList(mintUrls: string[]) {
|
||||
return mintUrls.filter(mintUrl =>
|
||||
self.blockedMintUrls.some(blockedUrl => blockedUrl === mintUrl),
|
||||
)
|
||||
},
|
||||
getMissingMints: (mintUrls: string[]) => {
|
||||
const missingMints: string[] = []
|
||||
for (const url of mintUrls) {
|
||||
if (!self.mints.find(mint => mint.mintUrl === url)) {
|
||||
missingMints.push(url)
|
||||
}
|
||||
}
|
||||
return missingMints
|
||||
},
|
||||
}))
|
||||
},
|
||||
alreadyExists(mintUrl: string) {
|
||||
return self.mints.some(m => m.mintUrl === mintUrl) ? true : false
|
||||
},
|
||||
isBlocked(mintUrl: string) {
|
||||
return self.blockedMintUrls.some(m => m === mintUrl) ? true : false
|
||||
},
|
||||
getBlockedFromList(mintUrls: string[]) {
|
||||
return mintUrls.filter(mintUrl =>
|
||||
self.blockedMintUrls.some(blockedUrl => blockedUrl === mintUrl),
|
||||
)
|
||||
},
|
||||
getMissingMints: (mintUrls: string[]) => {
|
||||
const missingMints: string[] = []
|
||||
for (const url of mintUrls) {
|
||||
if (!self.mints.find(mint => mint.mintUrl === url)) {
|
||||
missingMints.push(url)
|
||||
}
|
||||
}
|
||||
return missingMints
|
||||
},
|
||||
}))
|
||||
|
||||
export interface MintsStore extends Instance<typeof MintsStoreModel> {}
|
||||
export interface MintsStoreSnapshot
|
||||
export interface MintsStore extends Instance<typeof MintsStoreModel> {}
|
||||
export interface MintsStoreSnapshot
|
||||
extends SnapshotOut<typeof MintsStoreModel> {}
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import * as Sentry from '@sentry/react-native'
|
||||
import RNExitApp from 'react-native-exit-app'
|
||||
import type {RootStore} from '../RootStore'
|
||||
import {KeyChain, MinibitsClient, MMKVStorage, NostrClient} from '../../services'
|
||||
import {KeyChain, MinibitsClient, MintClient, MMKVStorage, NostrClient} from '../../services'
|
||||
import {Database} from '../../services'
|
||||
import { log } from '../../services/logService'
|
||||
import { rootStoreModelVersion } from '../RootStore'
|
||||
@@ -285,7 +285,6 @@ async function _runMigrations(rootStore: RootStore) {
|
||||
log.warn('[setupRootStore] Migration error', {message: e.name})
|
||||
}
|
||||
}
|
||||
|
||||
} catch (e: any) {
|
||||
throw new AppError(
|
||||
Err.STORAGE_ERROR,
|
||||
|
||||
@@ -142,8 +142,8 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
|
||||
toggleAddMintModal() // close
|
||||
setIsLoading(true)
|
||||
const activeKeysets: MintKeyset[] = await MintClient.getMintKeysets(mintUrl)
|
||||
const matchingKeyset = activeKeysets.find(keyset => selectedMint.keysets?.some(k => k === keyset.id))
|
||||
const keysets: MintKeyset[] = await MintClient.getMintKeysets(mintUrl)
|
||||
const matchingKeyset = keysets.find(keyset => selectedMint.keysets?.some(k => k.id === keyset.id))
|
||||
|
||||
if(!matchingKeyset) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'No keyset match, provided URL likely points to different mint.')
|
||||
|
||||
@@ -261,9 +261,9 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
recoveredMint.increaseProofsCounter(selectedKeyset.id as string, Math.abs(endIndex - startIndex))
|
||||
|
||||
const {spent, pending} = await MintClient.getSpentOrPendingProofsFromMint(
|
||||
proofs as Proof[],
|
||||
recoveredMint.mintUrl,
|
||||
selectedKeyset.unit as MintUnit,
|
||||
proofs as Proof[],
|
||||
)
|
||||
|
||||
log.debug('[restore]', `Spent and pending proofs`, {spent: spent.length, pending: pending.length})
|
||||
|
||||
+182
-94
@@ -8,6 +8,9 @@ import {
|
||||
deriveKeysetId,
|
||||
setGlobalRequestOptions,
|
||||
type Proof as CashuProof,
|
||||
type MintKeyset as CashuMintKeyset,
|
||||
type MintKeys as CashuMintKeys,
|
||||
MintKeys
|
||||
} from '@cashu/cashu-ts'
|
||||
import {rootStoreInstance} from '../models'
|
||||
import { KeyChain } from '../services'
|
||||
@@ -22,7 +25,7 @@ import { deriveSeedFromMnemonic } from '@cashu/cashu-ts'
|
||||
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'
|
||||
|
||||
let _mints: CashuMint[] = []
|
||||
let _wallets: CashuWallet[] = [] // used where seed is not required (perf)
|
||||
@@ -37,8 +40,8 @@ function isOnionMint(mintUrl: string) {
|
||||
}
|
||||
const TorVPNSetupInstructions = `
|
||||
Is your Tor VPN running?
|
||||
Mints on Tor require a Tor VPN like Orbot. You can get it on Google Play or Github (guardianproject/orbot).
|
||||
Tor can sometimes be slow, so requests might time out. If Orbot is running, try again in a bit.`
|
||||
Mints on Tor require a Tor VPN application like Orbot. You can get it on Google Play or Github.`
|
||||
|
||||
|
||||
const getOrCreateMnemonic = async function (): Promise<string> {
|
||||
let mnemonic: string | undefined = undefined
|
||||
@@ -93,7 +96,7 @@ const getSeed = async function (): Promise<Uint8Array | undefined> {
|
||||
}
|
||||
|
||||
|
||||
const getMint = function (mintUrl: string): CashuMint {
|
||||
const getMint = async function (mintUrl: string): Promise<CashuMint> {
|
||||
|
||||
const mint = _mints.find(m => m.mintUrl === mintUrl)
|
||||
|
||||
@@ -104,8 +107,30 @@ const getMint = function (mintUrl: string): CashuMint {
|
||||
setGlobalRequestOptions({
|
||||
headers: {'User-Agent': `Minibits/${JS_BUNDLE_VERSION}`}
|
||||
})
|
||||
|
||||
// create cashu-ts mint instance
|
||||
const newMint = new CashuMint(mintUrl)
|
||||
// get fresh keysets
|
||||
const {keysets} = await newMint.getKeySets()
|
||||
// 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})
|
||||
}
|
||||
// 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)
|
||||
|
||||
return newMint
|
||||
@@ -117,13 +142,72 @@ const getWallet = async function (
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
options?: {
|
||||
keysetId?: string // force specific (inactive) keys to swap from
|
||||
withSeed: boolean
|
||||
}
|
||||
): Promise<CashuWallet> {
|
||||
const cashuMint = getMint(mintUrl)
|
||||
// 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) {
|
||||
throw new AppError(Err.NOTFOUND_ERROR, 'Mint not found in the wallet state.', {
|
||||
mintUrl
|
||||
})
|
||||
}
|
||||
|
||||
// select keys to be used to find or create new cashu-ts wallet instance
|
||||
let walletKeys: MintKeys
|
||||
if(options && options.keysetId) {
|
||||
|
||||
const requestedKeys = mintInstance.keys.find(k => k.id === options.keysetId)
|
||||
|
||||
if(!requestedKeys) {
|
||||
throw new AppError(Err.NOTFOUND_ERROR, 'Wallet has not keys with provided keyset id.', {
|
||||
mintUrl,
|
||||
keysetId: options.keysetId
|
||||
})
|
||||
}
|
||||
|
||||
if(requestedKeys.unit !== unit) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Wallet unit and keys mismatch.', {
|
||||
mintUrl,
|
||||
keysetId: options.keysetId,
|
||||
unit
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if(!activeKeyset) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Wallet has not any active keyset for the selected unit.', {
|
||||
mintUrl,
|
||||
unit
|
||||
})
|
||||
}
|
||||
|
||||
const activeKeys = mintInstance.keys.find(k => k.id === activeKeyset.id)
|
||||
|
||||
if(!activeKeys) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Wallet has not any keys for the selected unit.', {
|
||||
mintUrl,
|
||||
unit
|
||||
})
|
||||
}
|
||||
|
||||
walletKeys = getSnapshot(activeKeys)
|
||||
}
|
||||
|
||||
if (options && options.withSeed) {
|
||||
const seedWallet = _seedWallets.find(w => w.mint.mintUrl === mintUrl && w.unit === unit)
|
||||
|
||||
const seedWallet = _seedWallets.find(
|
||||
w => w.mint.mintUrl === mintUrl &&
|
||||
w.keys.id === walletKeys.id
|
||||
)
|
||||
|
||||
if (seedWallet) {
|
||||
return seedWallet
|
||||
@@ -131,62 +215,37 @@ const getWallet = async function (
|
||||
|
||||
let seed: Uint8Array | undefined = undefined
|
||||
seed = await getSeed()
|
||||
|
||||
// Handle legacy pre-0.1.5 created wallets
|
||||
if(!seed) {
|
||||
const mnemonic = await getOrCreateMnemonic()
|
||||
seed = await getSeed()
|
||||
resetCachedWallets() // force all wallet instances to be recreated with seed
|
||||
}
|
||||
|
||||
const newSeedWallet = new CashuWallet(cashuMint, {
|
||||
unit,
|
||||
keys: walletKeys,
|
||||
mnemonicOrSeed: seed
|
||||
})
|
||||
|
||||
try {
|
||||
// make sure we have keys for wallet unit cached in wallet instance
|
||||
const keys = await newSeedWallet.getKeys(undefined, unit)
|
||||
_seedWallets.push(newSeedWallet)
|
||||
|
||||
if(!keys || keys.unit !== unit) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, `This mint does not currently support unit ${unit}`)
|
||||
}
|
||||
|
||||
_seedWallets.push(newSeedWallet)
|
||||
|
||||
log.trace('[getWallet]', 'Returning new cashuWallet instance with seed')
|
||||
return newSeedWallet
|
||||
} catch (e: any) {
|
||||
let message = 'Could not connect to the selected mint.'
|
||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||
throw new AppError(Err.NETWORK_ERROR, message, {message: e.message, caller: 'getWallet'})
|
||||
}
|
||||
log.trace('[getWallet]', 'Returning new cashuWallet instance with seed')
|
||||
|
||||
return newSeedWallet
|
||||
}
|
||||
|
||||
const wallet = _wallets.find(w => w.mint.mintUrl === mintUrl && w.unit === unit)
|
||||
const wallet = _wallets.find(
|
||||
w => w.mint.mintUrl === mintUrl &&
|
||||
w.keys.id === walletKeys.id
|
||||
)
|
||||
|
||||
if (wallet) {
|
||||
return wallet
|
||||
}
|
||||
|
||||
const newWallet = new CashuWallet(cashuMint, {
|
||||
unit,
|
||||
keys: walletKeys,
|
||||
mnemonicOrSeed: undefined
|
||||
})
|
||||
|
||||
try {
|
||||
|
||||
await newWallet.getKeys(undefined, unit)
|
||||
|
||||
_wallets.push(newWallet)
|
||||
|
||||
_wallets.push(newWallet)
|
||||
|
||||
log.trace('[getWallet]', 'Returning new cashuWallet instance')
|
||||
return newWallet
|
||||
} catch (e: any) {
|
||||
let message = 'Could not connect to the selected mint.'
|
||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||
throw new AppError(Err.NETWORK_ERROR, message, {message: e.message, caller: 'getWallet'})
|
||||
}
|
||||
log.trace('[getWallet]', 'Returning new cashuWallet instance')
|
||||
return newWallet
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +257,7 @@ const resetCachedWallets = function () {
|
||||
|
||||
|
||||
const getMintKeysets = async function (mintUrl: string) {
|
||||
const cashuMint = getMint(mintUrl)
|
||||
const cashuMint = await getMint(mintUrl)
|
||||
|
||||
try {
|
||||
const {keysets} = await cashuMint.getKeySets() // all
|
||||
@@ -211,23 +270,46 @@ const getMintKeysets = async function (mintUrl: string) {
|
||||
}
|
||||
|
||||
|
||||
const receiveFromMint = async function (
|
||||
const getMintKeys = async function (mintUrl: string) {
|
||||
const cashuMint = await getMint(mintUrl)
|
||||
|
||||
try {
|
||||
const {keysets} = await cashuMint.getKeys() // all
|
||||
return keysets
|
||||
} catch (e: any) {
|
||||
let message = 'Could not connect to the selected mint.'
|
||||
if (isOnionMint(mintUrl)) message += TorVPNSetupInstructions;
|
||||
throw new AppError(Err.CONNECTION_ERROR, message, { message: e.message, mintUrl })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const receive = async function (
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
decodedToken: CashuToken,
|
||||
amountPreferences: AmountPreference[],
|
||||
counter: number
|
||||
options: {
|
||||
preference: AmountPreference[],
|
||||
counter: number
|
||||
}
|
||||
) {
|
||||
try {
|
||||
const cashuWallet = await getWallet(mintUrl, unit, {withSeed: true}) // with seed
|
||||
const cashuWallet = await getWallet(
|
||||
mintUrl,
|
||||
unit,
|
||||
{
|
||||
withSeed: true,
|
||||
})
|
||||
|
||||
const proofs = await cashuWallet.receive(decodedToken, {
|
||||
keysetId: cashuWallet.keys.id,
|
||||
preference: amountPreferences,
|
||||
counter,
|
||||
pubkey: undefined,
|
||||
privkey: undefined
|
||||
})
|
||||
const proofs = await cashuWallet.receive(
|
||||
decodedToken,
|
||||
{
|
||||
keysetId: cashuWallet.keys.id,
|
||||
preference: options.preference,
|
||||
counter: options.counter,
|
||||
pubkey: undefined,
|
||||
privkey: undefined
|
||||
})
|
||||
|
||||
return proofs
|
||||
} catch (e: any) {
|
||||
@@ -237,59 +319,60 @@ const receiveFromMint = async function (
|
||||
|
||||
|
||||
|
||||
const sendFromMint = async function (
|
||||
const send = async function (
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
amountToSend: number,
|
||||
unit: MintUnit,
|
||||
proofsToSendFrom: Proof[],
|
||||
amountPreferences: AmountPreference[],
|
||||
counter: number
|
||||
options: {
|
||||
preference: AmountPreference[],
|
||||
counter: number
|
||||
}
|
||||
) {
|
||||
try {
|
||||
const cashuWallet = await getWallet(mintUrl, unit, {withSeed: true}) // with seed
|
||||
const cashuWallet = await getWallet(
|
||||
mintUrl,
|
||||
unit,
|
||||
{
|
||||
withSeed: true,
|
||||
})
|
||||
|
||||
log.debug('[MintClient.sendFromMint] counter', counter)
|
||||
log.debug('[MintClient.sendFromMint] counter', options.counter)
|
||||
|
||||
const {returnChange, send} = await cashuWallet.send(
|
||||
amountToSend,
|
||||
proofsToSendFrom,
|
||||
{
|
||||
keysetId: cashuWallet.keys.id,
|
||||
preference: amountPreferences,
|
||||
counter,
|
||||
preference: options.preference,
|
||||
counter: options.counter,
|
||||
pubkey: undefined,
|
||||
privkey: undefined
|
||||
}
|
||||
)
|
||||
|
||||
log.debug('[MintClient.sendFromMint] returnedProofs', returnChange)
|
||||
log.debug('[MintClient.sendFromMint] sentProofs', send)
|
||||
log.debug('[MintClient.sendFromMint] proofsToSend', send)
|
||||
|
||||
// do some basic validations that proof amounts from mints match
|
||||
const totalAmountToSendFrom = CashuUtils.getProofsAmount(proofsToSendFrom)
|
||||
const returnedAmount = CashuUtils.getProofsAmount(returnChange as Proof[])
|
||||
const proofsAmount = CashuUtils.getProofsAmount(send as Proof[])
|
||||
const sendAmount = CashuUtils.getProofsAmount(send as Proof[])
|
||||
|
||||
if (proofsAmount !== amountToSend) {
|
||||
if (sendAmount !== amountToSend) {
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
`Amount to be sent does not equal requested original amount. Original is ${amountToSend}, mint returned ${proofsAmount}`,
|
||||
`Amount to be sent provided by mint does not equal requested amount. Original is ${amountToSend}, mint returned ${sendAmount}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (totalAmountToSendFrom !== returnedAmount + proofsAmount) {
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
`Amount returned by the mint as a change ${returnedAmount} is incorrect, it should be ${
|
||||
totalAmountToSendFrom - proofsAmount
|
||||
}`,
|
||||
)
|
||||
}
|
||||
const mintFeePaid = totalAmountToSendFrom - amountToSend - returnedAmount
|
||||
|
||||
// we normalize naming of returned parameters
|
||||
return {
|
||||
returnedProofs: returnChange as Proof[],
|
||||
proofsToSend: send as Proof[],
|
||||
proofsToSend: send as Proof[],
|
||||
mintFeePaid
|
||||
}
|
||||
} catch (e: any) {
|
||||
let message = 'The mint could not return signatures necessary for this transaction'
|
||||
@@ -310,9 +393,9 @@ const sendFromMint = async function (
|
||||
|
||||
|
||||
const getSpentOrPendingProofsFromMint = async function (
|
||||
proofs: Proof[],
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
proofs: Proof[],
|
||||
) {
|
||||
try {
|
||||
|
||||
@@ -350,7 +433,7 @@ const createLightningMeltQuote = async function (
|
||||
encodedInvoice: string,
|
||||
) {
|
||||
try {
|
||||
const cashuMint = getMint(mintUrl)
|
||||
const cashuMint = await getMint(mintUrl)
|
||||
const lightningQuote: MeltQuoteResponse = await cashuMint.createMeltQuote({
|
||||
unit,
|
||||
request: encodedInvoice
|
||||
@@ -382,7 +465,9 @@ const payLightningMelt = async function (
|
||||
unit: MintUnit,
|
||||
lightningMeltQuote: MeltQuoteResponse, // invoice is stored by mint by quote
|
||||
proofsToPayFrom: CashuProof[], // proofAmount >= amount + fee_reserve
|
||||
counter: number
|
||||
options: {
|
||||
counter: number
|
||||
}
|
||||
) {
|
||||
try {
|
||||
const cashuWallet = await getWallet(mintUrl, unit, {withSeed: true}) // with seed
|
||||
@@ -393,7 +478,7 @@ const payLightningMelt = async function (
|
||||
proofsToPayFrom,
|
||||
{
|
||||
keysetId: cashuWallet.keys.id,
|
||||
counter
|
||||
counter: options.counter
|
||||
}
|
||||
)
|
||||
|
||||
@@ -427,7 +512,7 @@ const createLightningMintQuote = async function (
|
||||
amount: number,
|
||||
) {
|
||||
try {
|
||||
const cashuMint = getMint(mintUrl)
|
||||
const cashuMint = await getMint(mintUrl)
|
||||
const {
|
||||
request: encodedInvoice,
|
||||
quote: mintQuote,
|
||||
@@ -463,7 +548,7 @@ const checkLightningMintQuote = async function (
|
||||
quote: string,
|
||||
) {
|
||||
try {
|
||||
const cashuMint = getMint(mintUrl)
|
||||
const cashuMint = await getMint(mintUrl)
|
||||
const {
|
||||
request: encodedInvoice,
|
||||
quote: mintQuote,
|
||||
@@ -498,11 +583,13 @@ const checkLightningMintQuote = async function (
|
||||
|
||||
const mintProofs = async function (
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
amount: number,
|
||||
unit: MintUnit,
|
||||
mintQuote: string,
|
||||
amountPreferences: AmountPreference[],
|
||||
counter: number
|
||||
options: {
|
||||
preference: AmountPreference[],
|
||||
counter: number
|
||||
}
|
||||
) {
|
||||
try {
|
||||
const cashuWallet = await getWallet(mintUrl, unit, {withSeed: true}) // with seed
|
||||
@@ -512,8 +599,8 @@ const mintProofs = async function (
|
||||
mintQuote,
|
||||
{
|
||||
keysetId: cashuWallet.keys.id,
|
||||
preference: amountPreferences,
|
||||
counter,
|
||||
preference: options.preference,
|
||||
counter: options.counter,
|
||||
pubkey: undefined
|
||||
}
|
||||
)
|
||||
@@ -557,7 +644,7 @@ const restore = async function (
|
||||
try {
|
||||
const {indexFrom, indexTo, keysetId} = options
|
||||
// need special wallet instance to pass seed and keysetId directly
|
||||
const cashuMint = getMint(mintUrl)
|
||||
const cashuMint = await getMint(mintUrl)
|
||||
|
||||
const seedWallet = new CashuWallet(cashuMint, {
|
||||
unit: 'sat', // just use default unit as we restore by keyset
|
||||
@@ -589,7 +676,7 @@ const getMintInfo = async function (
|
||||
mintUrl: string,
|
||||
) {
|
||||
try {
|
||||
const cashuMint = getMint(mintUrl)
|
||||
const cashuMint = await getMint(mintUrl)
|
||||
const info = await cashuMint.getInfo()
|
||||
log.trace('[getMintInfo]', {info})
|
||||
return info
|
||||
@@ -616,8 +703,9 @@ export const MintClient = {
|
||||
getSeed,
|
||||
resetCachedWallets,
|
||||
getMintKeysets,
|
||||
receiveFromMint,
|
||||
sendFromMint,
|
||||
getMintKeys,
|
||||
receive,
|
||||
send,
|
||||
getSpentOrPendingProofsFromMint,
|
||||
createLightningMintQuote,
|
||||
checkLightningMintQuote,
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { getDefaultAmountPreference, getEncodedToken } from '@cashu/cashu-ts/src/utils'
|
||||
import { TransactionTaskResult } from '../walletService'
|
||||
import { WalletUtils } from './utils'
|
||||
import { formatCurrency, getCurrency } from './currency'
|
||||
import { MintUnit, formatCurrency, getCurrency } from './currency'
|
||||
|
||||
const {
|
||||
mintsStore,
|
||||
@@ -124,7 +124,7 @@ export const receiveTask = async function (
|
||||
const amountPreferences = getDefaultAmountPreference(amountToReceive)
|
||||
const countOfInFlightProofs = CashuUtils.getAmountPreferencesCount(amountPreferences)
|
||||
|
||||
log.trace('[receiveTask]', 'proofsCounter initial state', {proofsCounter: await mintInstance.getProofsCounterByUnit?.(unit)})
|
||||
log.trace('[receiveTask]', 'proofsCounter initial state', {proofsCounter: mintInstance.getProofsCounterByUnit?.(unit)})
|
||||
log.trace('[receiveTask]', 'amountPreferences', {amountPreferences, transactionId})
|
||||
log.trace('[receiveTask]', 'countOfInFlightProofs', {countOfInFlightProofs, transactionId})
|
||||
|
||||
@@ -132,14 +132,16 @@ export const receiveTask = async function (
|
||||
await WalletUtils.lockAndSetInFlight(mintInstance, unit, countOfInFlightProofs, transactionId)
|
||||
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = await mintInstance.getProofsCounterByUnit?.(unit)
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit?.(unit)
|
||||
|
||||
const receivedProofs = await MintClient.receiveFromMint(
|
||||
const receivedProofs = await MintClient.receive(
|
||||
mintToReceive,
|
||||
unit,
|
||||
unit as MintUnit,
|
||||
token,
|
||||
amountPreferences,
|
||||
lockedProofsCounter.inFlightFrom as number // MUST be counter value before increase
|
||||
{
|
||||
preference: amountPreferences,
|
||||
counter: lockedProofsCounter.inFlightFrom as number // MUST be counter value before increase
|
||||
}
|
||||
)
|
||||
|
||||
// If we've got valid response, decrease proofsCounter and let it be increased back in next step when adding proofs
|
||||
@@ -443,15 +445,17 @@ export const receiveOfflineCompleteTask = async function (
|
||||
await WalletUtils.lockAndSetInFlight(mintInstance, unit, countOfInFlightProofs, transaction.id as number)
|
||||
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = await mintInstance.getProofsCounterByUnit?.(unit)
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit?.(unit)
|
||||
|
||||
const receivedProofs = await MintClient.receiveFromMint(
|
||||
const receivedProofs = await MintClient.receive(
|
||||
mintToReceive,
|
||||
unit,
|
||||
unit as MintUnit,
|
||||
token,
|
||||
amountPreferences,
|
||||
lockedProofsCounter.inFlightFrom as number // MUST be counter value before increase
|
||||
)
|
||||
{
|
||||
preference: amountPreferences,
|
||||
counter: lockedProofsCounter.inFlightFrom as number // MUST be counter value before increase
|
||||
}
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
@@ -76,7 +76,7 @@ export const sendTask = async function (
|
||||
transactionId = storedTransaction.id as number
|
||||
|
||||
// get ready proofs to send and update proofs and pending proofs storage
|
||||
const proofsToSend = await sendFromMint(
|
||||
const {proofs: proofsToSend, mintFeePaid, mintFeeReserve} = await sendFromMint(
|
||||
mintBalanceToSendFrom,
|
||||
amountToSend,
|
||||
unit,
|
||||
@@ -87,7 +87,8 @@ export const sendTask = async function (
|
||||
// Update transaction status
|
||||
transactionData.push({
|
||||
status: TransactionStatus.PREPARED,
|
||||
//proofsToSend,
|
||||
mintFeeReserve,
|
||||
mintFeePaid,
|
||||
createdAt: new Date(),
|
||||
})
|
||||
|
||||
@@ -115,7 +116,7 @@ export const sendTask = async function (
|
||||
|
||||
// Update transaction status
|
||||
transactionData.push({
|
||||
status: TransactionStatus.PENDING,
|
||||
status: TransactionStatus.PENDING,
|
||||
encodedTokenToSend,
|
||||
createdAt: new Date(),
|
||||
})
|
||||
@@ -153,6 +154,7 @@ export const sendTask = async function (
|
||||
transaction: pendingTransaction,
|
||||
message: '',
|
||||
encodedTokenToSend,
|
||||
mintFeePaid
|
||||
} as TransactionTaskResult
|
||||
} catch (e: any) {
|
||||
// Update transaction status if we have any
|
||||
@@ -249,7 +251,11 @@ export const sendFromMint = async function (
|
||||
})
|
||||
|
||||
// We return cleaned proofs to be encoded as a sendable token
|
||||
return cleanedProofsToSend
|
||||
return {
|
||||
proofs: cleanedProofsToSend,
|
||||
mintFeeReserve: 0,
|
||||
mintFeePaid: 0
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -261,28 +267,27 @@ export const sendFromMint = async function (
|
||||
proofsFromMint,
|
||||
)
|
||||
|
||||
let proofsToSendFromAmount = CashuUtils.getProofsAmount(proofsToSendFrom)
|
||||
|
||||
let proofsToSendFromAmount = CashuUtils.getProofsAmount(proofsToSendFrom)
|
||||
|
||||
// swap will happen if we could not select proofs equal to amountToSend
|
||||
let returnedAmount = proofsToSendFromAmount - amountToSend
|
||||
let feesAmount: number = 0
|
||||
let mintFeeReserve: number = 0
|
||||
|
||||
if(returnedAmount > 0) {
|
||||
feesAmount = mintInstance.getFeesForProofs(proofsToSendFrom)
|
||||
mintFeeReserve = mintInstance.getFeesForProofs(proofsToSendFrom)
|
||||
// if we did not selected enough proofs to cover the fees we need some more
|
||||
if(feesAmount > returnedAmount) {
|
||||
const missingFeesAmount = feesAmount - returnedAmount
|
||||
if(mintFeeReserve > returnedAmount) {
|
||||
const missingFeesAmount = mintFeeReserve - returnedAmount
|
||||
const remainingProofs = proofsStore.getProofsSubset(proofsFromMint, proofsToSendFrom)
|
||||
const remainingProofsAmount = CashuUtils.getProofsAmount(remainingProofs)
|
||||
|
||||
log.warn('[sendFromMint]', 'Not enough proofs to cover feeAmount', {feesAmount, returnedAmount})
|
||||
log.warn('[sendFromMint]', 'Not enough proofs to cover feeReserve', {mintFeeReserve, returnedAmount})
|
||||
|
||||
if(missingFeesAmount > remainingProofsAmount) {
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
'There is not enough funds to send this payment with expected fees',
|
||||
{totalAmountFromMint, amountToSend, feesAmount},
|
||||
{totalAmountFromMint, amountToSend, mintFeeReserve},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -297,8 +302,8 @@ export const sendFromMint = async function (
|
||||
returnedAmount = proofsToSendFromAmount - amountToSend
|
||||
}
|
||||
|
||||
// decrease requested returned outputs by fees so mint can charge them
|
||||
returnedAmount -= feesAmount
|
||||
// decrease requested returned outputs by fees so that the mint can charge them
|
||||
returnedAmount -= mintFeeReserve
|
||||
}
|
||||
|
||||
// Inputs we are about to send
|
||||
@@ -319,19 +324,20 @@ export const sendFromMint = async function (
|
||||
await WalletUtils.lockAndSetInFlight(mintInstance, unit, countOfInFlightProofs, transactionId)
|
||||
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = await mintInstance.getProofsCounterByUnit?.(unit)
|
||||
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} = await MintClient.sendFromMint(
|
||||
const {returnedProofs, proofsToSend, mintFeePaid} = await MintClient.send(
|
||||
mintUrl,
|
||||
unit,
|
||||
amountToSend,
|
||||
unit,
|
||||
proofsToSendFrom,
|
||||
amountPreferences,
|
||||
lockedProofsCounter.inFlightFrom as number // MUST be counter value before increase
|
||||
)
|
||||
|
||||
{
|
||||
preference: amountPreferences,
|
||||
counter: lockedProofsCounter.inFlightFrom as number // MUST be counter value before increase
|
||||
}
|
||||
)
|
||||
|
||||
// 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)
|
||||
@@ -339,7 +345,7 @@ export const sendFromMint = async function (
|
||||
// add proofs returned by the mint after the split
|
||||
if (returnedProofs.length > 0) {
|
||||
log.trace('[sendFromMint] add returned proofs to spendable')
|
||||
const { addedProofs, addedAmount } = WalletUtils.addCashuProofs(
|
||||
WalletUtils.addCashuProofs(
|
||||
mintUrl,
|
||||
returnedProofs,
|
||||
{
|
||||
@@ -356,7 +362,7 @@ export const sendFromMint = async function (
|
||||
|
||||
// these might be original proofToSendFrom if they matched the exact amount and split was not necessary
|
||||
log.trace('[sendFromMint] add proofsToSend to pending')
|
||||
const { addedProofs, addedAmount } = WalletUtils.addCashuProofs(
|
||||
WalletUtils.addCashuProofs(
|
||||
mintUrl,
|
||||
proofsToSend,
|
||||
{
|
||||
@@ -380,8 +386,12 @@ export const sendFromMint = async function (
|
||||
}
|
||||
})
|
||||
|
||||
// We return cleaned proofs to be encoded as a sendable token
|
||||
return cleanedProofsToSend
|
||||
// We return cleaned proofs to be encoded as a sendable token + fees
|
||||
return {
|
||||
proofs: cleanedProofsToSend as CashuProof[],
|
||||
mintFeeReserve,
|
||||
mintFeePaid
|
||||
}
|
||||
} catch (e: any) {
|
||||
// release lock
|
||||
mintInstance?.resetInFlight(transactionId)
|
||||
|
||||
@@ -55,7 +55,7 @@ export const transferTask = async function (
|
||||
|
||||
try {
|
||||
if (amountToTransfer + meltQuote.fee_reserve > mintBalanceToTransferFrom.balances[unit]!) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint balance is insufficient to cover the amount to transfer with expected Lightning fees.')
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Mint balance is insufficient to cover the amount to transfer with the expected Lightning fees.')
|
||||
}
|
||||
|
||||
if(isBefore(invoiceExpiry, new Date())) {
|
||||
@@ -87,7 +87,7 @@ export const transferTask = async function (
|
||||
transactionId = storedTransaction.id as number
|
||||
|
||||
// get proofs ready to be paid to the mint
|
||||
proofsToPay = await sendFromMint(
|
||||
const {proofs: proofsToPay, mintFeePaid, mintFeeReserve} = await sendFromMint(
|
||||
mintBalanceToTransferFrom,
|
||||
amountToTransfer + meltQuote.fee_reserve,
|
||||
unit,
|
||||
@@ -101,6 +101,8 @@ export const transferTask = async function (
|
||||
// Update transaction status
|
||||
transactionData.push({
|
||||
status: TransactionStatus.PREPARED,
|
||||
mintFeeReserve,
|
||||
mintFeePaid,
|
||||
createdAt: new Date(),
|
||||
})
|
||||
|
||||
@@ -116,14 +118,16 @@ export const transferTask = async function (
|
||||
await WalletUtils.lockAndSetInFlight(mintInstance, unit, countOfInFlightProofs, transactionId)
|
||||
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = await mintInstance.getProofsCounterByUnit?.(unit)
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit?.(unit)
|
||||
|
||||
const {isPaid, preimage, feeSavedProofs} = await MintClient.payLightningMelt(
|
||||
mintUrl,
|
||||
unit,
|
||||
meltQuote,
|
||||
proofsToPay,
|
||||
lockedProofsCounter.inFlightFrom as number
|
||||
proofsToPay,
|
||||
{
|
||||
counter: lockedProofsCounter.inFlightFrom as number
|
||||
}
|
||||
)
|
||||
|
||||
mintInstance.decreaseProofsCounter(lockedProofsCounter.keyset, countOfInFlightProofs)
|
||||
@@ -178,10 +182,9 @@ export const transferTask = async function (
|
||||
}
|
||||
|
||||
// If real fees were less then estimated, cash the returned savings.
|
||||
let finalFee = meltQuote.fee_reserve
|
||||
let lightningFeePaid = meltQuote.fee_reserve
|
||||
|
||||
if (feeSavedProofs.length) {
|
||||
|
||||
if (feeSavedProofs.length) {
|
||||
const {addedAmount: feeSaved} = WalletUtils.addCashuProofs(
|
||||
mintUrl,
|
||||
feeSavedProofs,
|
||||
@@ -193,19 +196,20 @@ export const transferTask = async function (
|
||||
|
||||
)
|
||||
|
||||
finalFee = meltQuote.fee_reserve - feeSaved
|
||||
lightningFeePaid = meltQuote.fee_reserve - feeSaved
|
||||
}
|
||||
|
||||
// release lock
|
||||
mintInstance.resetInFlight(transactionId)
|
||||
|
||||
// Save final fee in db
|
||||
await transactionsStore.updateFee(transactionId, finalFee)
|
||||
await transactionsStore.updateFee(transactionId, lightningFeePaid + mintFeePaid)
|
||||
|
||||
// Update transaction status
|
||||
transactionData.push({
|
||||
status: TransactionStatus.COMPLETED,
|
||||
finalFee,
|
||||
lightningFeeReserve: meltQuote.fee_reserve,
|
||||
lightningFeePaid,
|
||||
preimage, // TODO add to tx details
|
||||
createdAt: new Date(),
|
||||
})
|
||||
@@ -226,7 +230,8 @@ export const transferTask = async function (
|
||||
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}.`,
|
||||
finalFee,
|
||||
lightningFeePaid,
|
||||
mintFeePaid
|
||||
} as TransactionTaskResult
|
||||
} catch (e: any) {
|
||||
// Update transaction status if we have any
|
||||
@@ -324,9 +329,9 @@ const _moveProofsFromPending = async function (
|
||||
// due to the timeout but mint's node keeps the payment as in-flight (e.g. receiving node holds the invoice)
|
||||
// In this case we need to keep such proofs as pending and not move them back to wallet as in other payment failures.
|
||||
const {pending: pendingByMint} = await MintClient.getSpentOrPendingProofsFromMint(
|
||||
proofsToMove as Proof[],
|
||||
mintUrl,
|
||||
unit,
|
||||
unit,
|
||||
proofsToMove as Proof[]
|
||||
)
|
||||
|
||||
let amountPendingByMint: number = 0
|
||||
|
||||
@@ -31,7 +31,7 @@ const lockAndSetInFlight = async function (
|
||||
retryCount: number = 0,
|
||||
): Promise<void> {
|
||||
|
||||
const currentCounter = await mint.getProofsCounterByUnit?.(unit)
|
||||
const currentCounter = mint.getProofsCounterByUnit?.(unit)
|
||||
log.trace('[lockAndSetInFlight] proofsCounter', {currentCounter})
|
||||
|
||||
if(!retryCount) {
|
||||
|
||||
@@ -100,6 +100,8 @@ export interface WalletTaskResult {
|
||||
|
||||
export interface TransactionTaskResult extends WalletTaskResult {
|
||||
transaction?: Transaction
|
||||
lightningFeePaid?: number
|
||||
mintFeePaid?: number
|
||||
}
|
||||
|
||||
export type ReceivedEventResult = {
|
||||
@@ -362,9 +364,9 @@ const _handleSpentByMintTask = async function (
|
||||
spent: spentProofs,
|
||||
pending: pendingProofs
|
||||
} = await MintClient.getSpentOrPendingProofsFromMint(
|
||||
proofsFromMint,
|
||||
mintUrl,
|
||||
mint && mint.units ? mint.units[0] : 'sat'
|
||||
mint && mint.units ? mint.units[0] : 'sat',
|
||||
proofsFromMint
|
||||
)
|
||||
|
||||
if(mint) {
|
||||
@@ -603,9 +605,9 @@ const _handleInFlightByMintTask = async function (mint: Mint, seed: Uint8Array):
|
||||
}
|
||||
|
||||
const {spent, pending} = await MintClient.getSpentOrPendingProofsFromMint(
|
||||
proofs as Proof[],
|
||||
mint.mintUrl,
|
||||
mint.units ? mint.units[0] : 'sat'
|
||||
mint.units ? mint.units[0] : 'sat',
|
||||
proofs as Proof[]
|
||||
)
|
||||
|
||||
const spentCount = spent.length
|
||||
@@ -819,17 +821,19 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
|
||||
)
|
||||
|
||||
// get locked counter values
|
||||
const lockedProofsCounter = await mintInstance.getProofsCounterByUnit(unit)
|
||||
const lockedProofsCounter = mintInstance.getProofsCounterByUnit(unit)
|
||||
|
||||
let proofs: CashuProof[] = []
|
||||
|
||||
proofs = (await MintClient.mintProofs(
|
||||
mint as string,
|
||||
unit,
|
||||
amount,
|
||||
mintQuote as string,
|
||||
amountPreferences,
|
||||
lockedProofsCounter.inFlightFrom as number
|
||||
unit,
|
||||
mintQuote,
|
||||
{
|
||||
preference: amountPreferences,
|
||||
counter: lockedProofsCounter.inFlightFrom as number
|
||||
}
|
||||
)) as CashuProof[]
|
||||
|
||||
mintInstance.decreaseProofsCounter(lockedProofsCounter.keyset, countOfInFlightProofs)
|
||||
|
||||
@@ -1202,9 +1202,9 @@
|
||||
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
|
||||
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
|
||||
|
||||
"@cashu/cashu-ts@minibits-cash/cashu-ts#1.0.0-minibits":
|
||||
"@cashu/cashu-ts@minibits-cash/cashu-ts#1.0.0-minibits.2":
|
||||
version "1.0.0-minibits"
|
||||
resolved "https://codeload.github.com/minibits-cash/cashu-ts/tar.gz/95cd37801f54700bed1b84113a774bd23f36aecb"
|
||||
resolved "https://codeload.github.com/minibits-cash/cashu-ts/tar.gz/03b26c1db3a72d3d8e975a72f63b0ed06dc1be34"
|
||||
dependencies:
|
||||
"@cashu/crypto" "^0.2.6"
|
||||
"@noble/curves" "^1.3.0"
|
||||
@@ -1214,9 +1214,9 @@
|
||||
buffer "^6.0.3"
|
||||
|
||||
"@cashu/crypto@^0.2.6":
|
||||
version "0.2.6"
|
||||
resolved "https://registry.yarnpkg.com/@cashu/crypto/-/crypto-0.2.6.tgz#fbc173f1a5c380085f62b5141ae62d309a427426"
|
||||
integrity sha512-qjytcY26MRntG6nJc9U2tSeDw+BApKQaIch58POjEiTuc7MbIxgR/l/xU5NzXa/nGrSLdNZQwl/o5RQDhc2otw==
|
||||
version "0.2.7"
|
||||
resolved "https://registry.yarnpkg.com/@cashu/crypto/-/crypto-0.2.7.tgz#26467fd2f5ecfeabcc74b9c65189a1e8c6ce85ce"
|
||||
integrity sha512-1aaDfUjiHNXoJqg8nW+341TLWV9W28DsVNXJUKcHL0yAmwLs5+56SSnb8LLDJzPamLVoYL0U0bda91klAzptig==
|
||||
dependencies:
|
||||
"@noble/curves" "^1.3.0"
|
||||
"@noble/hashes" "^1.3.3"
|
||||
|
||||
Reference in New Issue
Block a user