mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-08-11 00:47:42 +00:00
219 lines
8.6 KiB
TypeScript
219 lines
8.6 KiB
TypeScript
import {Instance, SnapshotOut, types, flow} from 'mobx-state-tree'
|
|
import { Metadata } from 'nostr-tools/kinds'
|
|
import {KeyChain, MinibitsClient, NostrClient, NostrUnsignedEvent, WalletTask} from '../services'
|
|
import {log} from '../services/logService'
|
|
import { Err } from '../utils/AppError'
|
|
import { getRandomUsername } from '../utils/usernames'
|
|
import { getRootStore } from './helpers/getRootStore'
|
|
|
|
|
|
export type WalletProfile = {
|
|
pubkey: string
|
|
walletId: string
|
|
name: string
|
|
nip05: string
|
|
picture: string
|
|
lud16?: string | null
|
|
device?: string | null
|
|
isOwnProfile: boolean
|
|
}
|
|
|
|
export type WalletProfileRecord = {
|
|
id: number
|
|
pubkey: string
|
|
walletId: string
|
|
name: string
|
|
nip05: string
|
|
lud16?: string | null
|
|
device?: string | null
|
|
avatar: string
|
|
createdAt: string
|
|
}
|
|
|
|
export const WalletProfileStoreModel = types
|
|
.model('WalletProfileStore', {
|
|
pubkey: types.optional(types.string, ''),
|
|
walletId: types.optional(types.string, ''),
|
|
name: types.optional(types.string, ''),
|
|
nip05: types.optional(types.string, ''),
|
|
picture: types.optional(types.string, ''),
|
|
lud16: types.maybe(types.maybeNull(types.string)),
|
|
device: types.maybe(types.maybeNull(types.string)),
|
|
isOwnProfile: types.optional(types.boolean, false),
|
|
})
|
|
.actions(self => ({
|
|
publishToRelays: flow(function* publishToRelays() {
|
|
try {
|
|
const {pubkey, name, picture, nip05, lud16} = self
|
|
|
|
// announce to minibits relay + default public relays with replaceable event
|
|
const profileEvent: NostrUnsignedEvent = {
|
|
kind: Metadata,
|
|
pubkey,
|
|
tags: [],
|
|
content: JSON.stringify({
|
|
name,
|
|
picture,
|
|
nip05,
|
|
lud16,
|
|
}),
|
|
created_at: Math.floor(Date.now() / 1000)
|
|
}
|
|
|
|
const rootStore = getRootStore(self)
|
|
const {relaysStore} = rootStore
|
|
|
|
// new wallet profile has not yet the relays
|
|
if(relaysStore.allUrls.length === 0) {
|
|
// saves default relays and creates subscription for incoming nostr messages
|
|
WalletTask.receiveEventsFromRelaysQueue()
|
|
}
|
|
|
|
const relaysToPublish: string[] = relaysStore.allUrls
|
|
|
|
log.debug('[publishToRelays]', 'Publish profile to relays', {profileEvent, relaysToPublish})
|
|
|
|
const publishedEvent: Event | undefined = yield NostrClient.publish(
|
|
profileEvent,
|
|
relaysToPublish
|
|
)
|
|
|
|
return publishedEvent
|
|
|
|
} catch (e: any) {
|
|
log.error(e.name, e.message)
|
|
return false // silent
|
|
}
|
|
})
|
|
}))
|
|
.actions(self => ({
|
|
hydrate: flow(function* hydrate(profileRecord: WalletProfileRecord) {
|
|
const {name, nip05, lud16, avatar, pubkey, walletId} = profileRecord
|
|
|
|
self.pubkey = pubkey
|
|
self.walletId = walletId
|
|
self.name = name // default name is set on server side, equals walletId
|
|
self.nip05 = nip05 // default is name@minibits.cash set on server side
|
|
self.lud16 = lud16 // equals to nip05 for all @minibits.cash addresses, set on server side
|
|
self.picture = avatar // default picture is set on server side
|
|
|
|
yield self.publishToRelays()
|
|
})
|
|
}))
|
|
.actions(self => ({
|
|
create: flow(function* create(publicKey: string, walletId: string, seedHash: string) {
|
|
|
|
let profileRecord: WalletProfileRecord
|
|
|
|
log.trace('[create]', {seedHash, publicKey, walletId})
|
|
|
|
try {
|
|
// creates new profile. If all params equal existing one, it is returned
|
|
profileRecord = yield MinibitsClient.createWalletProfile(publicKey, walletId, seedHash)
|
|
self.hydrate(profileRecord)
|
|
|
|
log.info('[create]', 'Wallet profile saved in WalletProfileStore', {self})
|
|
return self
|
|
} catch (e: any) {
|
|
// Unlikely we might hit the same walletId so we retry with another one
|
|
if(e.name === Err.ALREADY_EXISTS_ERROR) {
|
|
// recreate walletId + default name
|
|
const name = getRandomUsername()
|
|
// attempt to create new unique profile again
|
|
profileRecord = yield MinibitsClient.createWalletProfile(publicKey, name, seedHash)
|
|
|
|
log.error('[create]', 'Profile reset executed to resolve duplicate walletId on the server.', {caller: 'create', walletId, newWalletId: name})
|
|
self.hydrate(profileRecord)
|
|
return
|
|
}
|
|
throw e
|
|
}
|
|
}),
|
|
updateName: flow(function* updateName(name: string) {
|
|
|
|
let profileRecord: WalletProfileRecord = yield MinibitsClient.updateWalletProfile(
|
|
self.pubkey,
|
|
{
|
|
name,
|
|
}
|
|
)
|
|
|
|
self.hydrate(profileRecord)
|
|
|
|
log.debug('[updateName]', 'Wallet name updated in the WalletProfileStore', {self})
|
|
return self
|
|
}),
|
|
updatePicture: flow(function* updatePicture(picture: string) {
|
|
|
|
let profileRecord: WalletProfileRecord = yield MinibitsClient.updateWalletProfile(
|
|
self.pubkey,
|
|
{
|
|
avatar: picture, // this is png in base64
|
|
}
|
|
)
|
|
|
|
self.picture = profileRecord.avatar + '?r=' + Math.floor(Math.random() * 100) // force refresh as image URL stays the same
|
|
|
|
const publishedEvent = yield self.publishToRelays()
|
|
|
|
log.debug('[updatePicture]', 'Wallet picture updated in the WalletProfileStore', {self, publishedEvent})
|
|
return self
|
|
}),
|
|
updateNip05: flow(function* updateNip05(newPubkey: string, name: string, nip05: string, lud16: string, picture: string, isOwnProfile: boolean) {
|
|
|
|
log.trace('[updateNip05]', {currentPubkey: self.pubkey, newPubkey})
|
|
|
|
let profileRecord: WalletProfileRecord = yield MinibitsClient.updateWalletProfileNip05(
|
|
self.pubkey,
|
|
{
|
|
newPubkey,
|
|
nip05,
|
|
lud16,
|
|
name,
|
|
avatar: picture
|
|
}
|
|
)
|
|
|
|
log.trace('[updateNip05]', 'profileRecord', {profileRecord})
|
|
|
|
self.hydrate(profileRecord)
|
|
|
|
log.info('[updateNip05]', 'Wallet nip05 updated in the WalletProfileStore', {self})
|
|
return self
|
|
}),
|
|
recover: flow(function* recover(publicKey: string, walletId: string, seedHash: string) {
|
|
|
|
let profileRecord: WalletProfileRecord = yield MinibitsClient.recoverProfile(
|
|
publicKey, walletId, seedHash
|
|
)
|
|
|
|
self.hydrate(profileRecord)
|
|
|
|
log.info('[recover]', 'Wallet profile recovered in WalletProfileStore', {self})
|
|
return self
|
|
}),
|
|
setDevice: flow(function* setDevice(device: string) {
|
|
try {
|
|
if(!self.pubkey) {
|
|
// skip call for new installs without a profile
|
|
return
|
|
}
|
|
|
|
yield MinibitsClient.updateDeviceToken(self.pubkey, {deviceToken: device})
|
|
self.device = device
|
|
} catch (e: any) {
|
|
log.error('[setDevice]', e.message)
|
|
}
|
|
})
|
|
}))
|
|
.views(self => ({
|
|
get npub() {
|
|
return NostrClient.getNpubkey(self.pubkey)
|
|
},
|
|
}))
|
|
|
|
export interface WalletProfileStore
|
|
extends Instance<typeof WalletProfileStoreModel> {}
|
|
export interface WalletProfileStoreSnapshot
|
|
extends SnapshotOut<typeof WalletProfileStoreModel> {}
|