Files
app.zeusln.zeus/utils/MigrationUtils.ts
T

1083 lines
39 KiB
TypeScript

import { Platform } from 'react-native';
import * as Keychain from 'react-native-keychain';
import { sleep } from './SleepUtils';
import { settingsStore } from '../stores/Stores';
import {
Settings,
DEFAULT_FIAT_RATES_SOURCE,
DEFAULT_FIAT,
DEFAULT_LSP_MAINNET,
DEFAULT_LSP_TESTNET,
DEFAULT_NOSTR_RELAYS,
localeMigrationMapping,
DEFAULT_NEUTRINO_PEERS_MAINNET,
DEFAULT_NEUTRINO_PEERS_TESTNET,
DEFAULT_LSPS1_HOST_MAINNET,
DEFAULT_LSPS1_HOST_TESTNET,
DEFAULT_LSPS1_PUBKEY_MAINNET,
DEFAULT_LSPS1_PUBKEY_TESTNET,
DEFAULT_LSPS1_REST_MAINNET,
DEFAULT_LSPS1_REST_TESTNET,
DEFAULT_SPEEDLOADER,
DEFAULT_NOSTR_RELAYS_2023,
PosEnabled,
DEFAULT_SLIDE_TO_PAY_THRESHOLD,
STORAGE_KEY,
LEGACY_CURRENCY_CODES_KEY,
CURRENCY_CODES_KEY
} from '../stores/SettingsStore';
import { LEGACY_NOTES_KEY, NOTES_KEY } from '../stores/NotesStore';
import { LEGACY_CONTACTS_KEY, CONTACTS_KEY } from '../stores/ContactStore';
import {
LEGACY_LAST_CHANNEL_BACKUP_STATUS,
LEGACY_LAST_CHANNEL_BACKUP_TIME,
LAST_CHANNEL_BACKUP_STATUS,
LAST_CHANNEL_BACKUP_TIME
} from '../stores/ChannelBackupStore';
import {
LEGACY_ADDRESS_ACTIVATED_STRING,
LEGACY_HASHES_STORAGE_STRING,
ADDRESS_ACTIVATED_STRING,
HASHES_STORAGE_STRING
} from '../stores/LightningAddressStore';
import {
LEGACY_POS_HIDDEN_KEY,
LEGACY_POS_STANDALONE_KEY,
POS_HIDDEN_KEY,
POS_STANDALONE_KEY
} from '../stores/PosStore';
import {
LEGACY_CATEGORY_KEY,
LEGACY_PRODUCT_KEY,
CATEGORY_KEY,
PRODUCT_KEY
} from '../stores/InventoryStore';
import { LEGACY_UNIT_KEY, UNIT_KEY } from '../stores/UnitsStore';
import {
LEGACY_HIDDEN_ACCOUNTS_KEY,
HIDDEN_ACCOUNTS_KEY
} from '../stores/UTXOsStore';
import { LEGACY_LSPS1_ORDERS_KEY, LSPS_ORDERS_KEY } from '../stores/LSPStore';
import { LNC_STORAGE_KEY, hash } from '../backends/LNC/credentialStore';
import {
SWAPS_KEY,
REVERSE_SWAPS_KEY,
SWAPS_RESCUE_KEY,
SWAPS_LAST_USED_KEY
} from '../utils/SwapUtils';
import {
LEGACY_ACTIVITY_FILTERS_KEY,
ACTIVITY_FILTERS_KEY
} from '../stores/ActivityStore';
const LEGACY_IS_BACKED_UP_KEY = 'backup-complete';
export const IS_BACKED_UP_KEY = 'backup-complete-v2';
const KEYCHAIN_MIGRATION_KEY = 'ios-keychain-cloud-sync-migration-v1';
const CASHU_MIGRATION_KEY = 'ios-keychain-cashu-fix-v1';
import EncryptedStorage from 'react-native-encrypted-storage';
import Storage from '../storage';
class MigrationsUtils {
/**
* Migrates a key from old keychain (cloud or local) to new Storage namespace.
* Safe order: read → write → verify → delete
*
* Since Storage now uses a "zeus:" prefix, old and new keys are in different
* namespaces, so deleting old keys won't affect newly written data.
*/
private async migrateKey(key: string): Promise<string | null> {
try {
// 1. Check if already migrated to new Storage namespace
const existingData = await Storage.getItem(key);
if (existingData) return existingData;
// 2. Read from old keychain (local first, then cloud)
const credentials = await this.readFromOldKeychain(key);
if (!credentials) return null;
console.log(`[Migration] Moving ${key} to Local Storage...`);
// iOS: Add delay after read to allow iCloud sync
if (Platform.OS === 'ios') {
await sleep(500);
}
// 3. Write to new Storage namespace (zeus:key)
const writeSuccess = await Storage.setItem(
key,
credentials.password
);
if (!writeSuccess) {
throw new Error(
`Write failed for ${key}. Storage.setItem returned false.`
);
}
// 4. Verify write succeeded by reading back
const verifyData = await Storage.getItem(key);
if (!verifyData) {
throw new Error(
`Verification failed for ${key}. Data not found after write.`
);
}
// iOS: Add delay before delete to allow iCloud sync
if (Platform.OS === 'ios') {
await sleep(500);
}
// 5. Only delete old keychain entries after successful write + verify
await this.deleteFromOldKeychain(key);
console.log(`[Migration] Successfully migrated ${key}`);
return credentials.password;
} catch (error) {
console.error(`[Migration] Failed to migrate ${key}:`, error);
throw error;
}
}
/**
* Reads from old keychain (without zeus: prefix).
* Tries local keychain first, then cloud keychain as fallback.
*/
private async readFromOldKeychain(key: string) {
try {
// Try local keychain first
const localCreds = await Keychain.getInternetCredentials(key);
if (localCreds) return localCreds;
// Fallback to cloud keychain
return await Keychain.getInternetCredentials(key, {
cloudSync: true
});
} catch (e) {
console.warn(`[Migration] Read error for ${key}`, e);
return null;
}
}
/**
* Deletes from old keychain entries (without zeus: prefix).
*
* DISABLED: To prevent potential data loss during migration.
* Old keychain entries will remain but are harmless (orphaned data).
* The new Storage uses a "zeus:" prefix, so old and new keys don't conflict.
*/
private async deleteFromOldKeychain(key: string) {
// Safety measure: skip deletion to prevent any potential data loss
console.log(`[Migration] Skipping delete for ${key} (safety measure)`);
return;
// Original delete code - commented out for safety:
// try {
// await Keychain.resetInternetCredentials({
// server: key,
// cloudSync: true
// });
// } catch (e) {
// console.warn(
// `[Migration] Error deleting from cloud keychain: ${key}`,
// e
// );
// }
// try {
// await Keychain.resetInternetCredentials({ server: key });
// } catch (e) {
// console.warn(
// `[Migration] Error deleting from local keychain: ${key}`,
// e
// );
// }
}
private async migrateCashuForNode(lndDir: string) {
console.log(`Migrating Cashu data for node: ${lndDir}`);
const cashuKeys = [
`${lndDir}-cashu-mintUrls`,
`${lndDir}-cashu-selectedMintUrl`,
`${lndDir}-cashu-totalBalanceSats`,
`${lndDir}-cashu-invoices`,
`${lndDir}-cashu-payments`,
`${lndDir}-cashu-received-tokens`,
`${lndDir}-cashu-sent-tokens`,
`${lndDir}-cashu-seed-version`,
`${lndDir}-cashu-seed-phrase`,
`${lndDir}-cashu-seed`
];
for (const key of cashuKeys) {
await this.migrateKey(key);
}
const mintUrlsJson = await this.migrateKey(`${lndDir}-cashu-mintUrls`);
if (mintUrlsJson) {
try {
const mintUrls = JSON.parse(mintUrlsJson);
if (Array.isArray(mintUrls)) {
for (const mintUrl of mintUrls) {
const walletId = `${lndDir}==${mintUrl}`;
const walletKeys = [
`${walletId}-mintInfo`,
`${walletId}-counter`,
`${walletId}-proofs`,
`${walletId}-balance`,
`${walletId}-pubkey`
];
for (const wKey of walletKeys) {
await this.migrateKey(wKey);
}
}
}
} catch (e) {
console.warn(`Failed to parse mintUrls for ${lndDir}`, e);
}
}
}
public async legacySettingsMigrations(settings: string) {
const newSettings = JSON.parse(settings) as Settings;
if (!newSettings.fiatRatesSource) {
newSettings.fiatRatesSource = DEFAULT_FIAT_RATES_SOURCE;
}
// migrate fiat settings from older versions
if (!newSettings.fiat || newSettings.fiat === 'Disabled') {
newSettings.fiat = DEFAULT_FIAT;
newSettings.fiatEnabled = false;
} else if (newSettings.fiatEnabled == null) {
newSettings.fiatEnabled = true;
}
// set default LSPs if not defined
if (newSettings.enableLSP === undefined) {
newSettings.enableLSP = true;
}
if (!newSettings.lspMainnet) {
newSettings.lspMainnet = DEFAULT_LSP_MAINNET;
}
if (!newSettings.lspTestnet) {
newSettings.lspTestnet = DEFAULT_LSP_TESTNET;
}
// default Lightning Address settings
if (!newSettings.lightningAddress) {
newSettings.lightningAddress = {
enabled: false,
automaticallyAccept: true,
automaticallyAcceptAttestationLevel: 2,
automaticallyRequestOlympusChannels: false, // deprecated
routeHints: false,
allowComments: true,
nostrPrivateKey: '',
nostrRelays: DEFAULT_NOSTR_RELAYS,
notifications: 0,
mintUrl: '' // Cashu
};
}
// migrate locale to ISO 639-1
if (
newSettings.locale != null &&
localeMigrationMapping[newSettings.locale]
) {
newSettings.locale = localeMigrationMapping[newSettings.locale];
}
const MOD_KEY = 'lsp-taproot-mod';
const mod = await EncryptedStorage.getItem(MOD_KEY);
if (!mod) {
newSettings.requestSimpleTaproot = true;
settingsStore.setSettings(JSON.stringify(newSettings));
await EncryptedStorage.setItem(MOD_KEY, 'true');
}
const MOD_KEY2 = 'lsp-preview-mod';
const mod2 = await EncryptedStorage.getItem(MOD_KEY2);
if (!mod2) {
if (newSettings?.lspMainnet === 'https://lsp-preview.lnolymp.us') {
newSettings.lspMainnet = DEFAULT_LSP_MAINNET;
}
if (newSettings?.lspTestnet === 'https://testnet-lsp.lnolymp.us') {
newSettings.lspTestnet = DEFAULT_LSP_TESTNET;
}
settingsStore.setSettings(JSON.stringify(newSettings));
await EncryptedStorage.setItem(MOD_KEY2, 'true');
}
const MOD_KEY3 = 'neutrino-peers-mod1';
const mod3 = await EncryptedStorage.getItem(MOD_KEY3);
if (!mod3) {
const neutrinoPeersMainnetOld = [
'btcd1.lnolymp.us',
'btcd2.lnolymp.us',
'btcd-mainnet.lightning.computer',
'node.eldamar.icu',
'noad.sathoarder.com'
];
if (
JSON.stringify(newSettings?.neutrinoPeersMainnet) ===
JSON.stringify(neutrinoPeersMainnetOld)
) {
newSettings.neutrinoPeersMainnet =
DEFAULT_NEUTRINO_PEERS_MAINNET;
}
settingsStore.setSettings(JSON.stringify(newSettings));
await EncryptedStorage.setItem(MOD_KEY3, 'true');
}
const MOD_KEY4 = 'lsps1-hosts1';
const mod4 = await EncryptedStorage.getItem(MOD_KEY4);
if (!mod4) {
if (!newSettings?.lsps1HostMainnet) {
newSettings.lsps1HostMainnet = DEFAULT_LSPS1_HOST_MAINNET;
}
if (!newSettings?.lsps1HostTestnet) {
newSettings.lsps1HostTestnet = DEFAULT_LSPS1_HOST_TESTNET;
}
if (!newSettings?.lsps1PubkeyMainnet) {
newSettings.lsps1PubkeyMainnet = DEFAULT_LSPS1_PUBKEY_MAINNET;
}
if (!newSettings?.lsps1PubkeyTestnet) {
newSettings.lsps1PubkeyTestnet = DEFAULT_LSPS1_PUBKEY_TESTNET;
}
if (!newSettings?.lsps1RestMainnet) {
newSettings.lsps1RestMainnet = DEFAULT_LSPS1_REST_MAINNET;
}
if (!newSettings?.lsps1RestTestnet) {
newSettings.lsps1RestTestnet = DEFAULT_LSPS1_REST_TESTNET;
}
if (!newSettings?.lsps1Token) {
newSettings.lsps1Token = '';
}
settingsStore.setSettings(JSON.stringify(newSettings));
await EncryptedStorage.setItem(MOD_KEY4, 'true');
}
const MOD_KEY5 = 'millisat_amounts';
const mod5 = await EncryptedStorage.getItem(MOD_KEY5);
if (!mod5) {
if (!newSettings?.display?.showMillisatoshiAmounts) {
if (!newSettings.display) {
newSettings.display = {
showMillisatoshiAmounts: true
};
} else {
newSettings.display.showMillisatoshiAmounts = true;
}
}
settingsStore.setSettings(JSON.stringify(newSettings));
await EncryptedStorage.setItem(MOD_KEY5, 'true');
}
const MOD_KEY6 = 'egs-host';
const mod6 = await EncryptedStorage.getItem(MOD_KEY6);
if (!mod6) {
if (!newSettings?.speedloader) {
newSettings.speedloader = DEFAULT_SPEEDLOADER;
newSettings.customSpeedloader = '';
}
settingsStore.setSettings(JSON.stringify(newSettings));
await EncryptedStorage.setItem(MOD_KEY6, 'true');
}
// switch off bimodal pathfinding while bug exists
// https://github.com/lightningnetwork/lnd/issues/9085
const MOD_KEY7 = 'bimodal-bug-9085';
const mod7 = await EncryptedStorage.getItem(MOD_KEY7);
if (!mod7) {
if (newSettings?.bimodalPathfinding) {
newSettings.bimodalPathfinding = false;
}
settingsStore.setSettings(JSON.stringify(newSettings));
await EncryptedStorage.setItem(MOD_KEY7, 'true');
}
const MOD_KEY8 = 'nostr-relays-2024';
const mod8 = await EncryptedStorage.getItem(MOD_KEY8);
if (!mod8) {
if (
JSON.stringify(newSettings?.lightningAddress?.nostrRelays) ===
JSON.stringify(DEFAULT_NOSTR_RELAYS_2023)
) {
newSettings.lightningAddress.nostrRelays = DEFAULT_NOSTR_RELAYS;
}
settingsStore.setSettings(JSON.stringify(newSettings));
await EncryptedStorage.setItem(MOD_KEY8, 'true');
}
// migrate old POS squareEnabled setting to posEnabled
if (newSettings?.pos?.squareEnabled) {
newSettings.pos.posEnabled = PosEnabled.Square;
newSettings.pos.squareEnabled = false;
}
if (!newSettings.neutrinoPeersMainnet) {
newSettings.neutrinoPeersMainnet = DEFAULT_NEUTRINO_PEERS_MAINNET;
}
if (!newSettings.neutrinoPeersTestnet) {
newSettings.neutrinoPeersTestnet = DEFAULT_NEUTRINO_PEERS_TESTNET;
}
if (newSettings.payments == null) {
newSettings.payments = {
slideToPayThreshold: DEFAULT_SLIDE_TO_PAY_THRESHOLD
};
} else if (newSettings.payments.slideToPayThreshold == null) {
newSettings.payments.slideToPayThreshold =
DEFAULT_SLIDE_TO_PAY_THRESHOLD;
}
return newSettings;
}
public async storageMigrationV2(settings: any) {
const migrationTasks = [];
// Settings migration
console.log('Attemping settings migration');
const settingsMigration = Storage.setItem(STORAGE_KEY, settings).then(
(writeSuccess) => {
console.log('Settings migration status', writeSuccess);
return writeSuccess;
}
);
migrationTasks.push(settingsMigration);
// Contacts migration
const contactsMigration = (async () => {
try {
const contacts = await EncryptedStorage.getItem(
LEGACY_CONTACTS_KEY
);
if (contacts) {
console.log('Attemping contacts migration');
const writeSuccess = await Storage.setItem(
CONTACTS_KEY,
contacts
);
console.log('Contacts migration status', writeSuccess);
return writeSuccess;
}
} catch (error) {
console.error(
'Error loading contacts from encrypted storage',
error
);
return false;
}
})();
migrationTasks.push(contactsMigration);
// Notes migration
const notesMigration = (async () => {
try {
const storedKeys = await EncryptedStorage.getItem(
LEGACY_NOTES_KEY
);
if (storedKeys) {
const noteKeys = JSON.parse(storedKeys);
console.log('Attemping notes migration');
const writeSuccess = await Storage.setItem(
NOTES_KEY,
noteKeys
);
console.log('Notes keys migration status', writeSuccess);
// Load all legacy notes
const notesPromises = noteKeys.map(async (key: string) => {
const note = await EncryptedStorage.getItem(key);
if (note) {
const writeSuccess = await Storage.setItem(
key,
note
);
console.log(
`Notes keys migration status: ${key}`,
writeSuccess
);
return writeSuccess;
}
});
const noteResults = await Promise.all(notesPromises);
return (
writeSuccess &&
noteResults.every((result) => result !== false)
);
}
} catch (error) {
console.error(
'Error loading note keys from encrypted storage',
error
);
return false;
}
})();
migrationTasks.push(notesMigration);
// Lightning address migration
const lightningAddressMigration = (async () => {
try {
let activatedSuccess: any = true;
let hashesSuccess: any = true;
const activated = await EncryptedStorage.getItem(
LEGACY_ADDRESS_ACTIVATED_STRING
);
if (activated) {
console.log(
'Attemping lightning address activated migration'
);
activatedSuccess = await Storage.setItem(
ADDRESS_ACTIVATED_STRING,
activated
);
console.log(
'Lightning address activated migration status',
activatedSuccess
);
}
const hashes = await EncryptedStorage.getItem(
LEGACY_HASHES_STORAGE_STRING
);
if (hashes) {
console.log('Attemping lightning address hashes migration');
hashesSuccess = await Storage.setItem(
HASHES_STORAGE_STRING,
hashes
);
console.log(
'Lightning address hashes migration status',
hashesSuccess
);
}
return activatedSuccess && hashesSuccess;
} catch (error) {
console.error(
'Error loading lightning address data from encrypted storage',
error
);
return false;
}
})();
migrationTasks.push(lightningAddressMigration);
// Backup status migration
const backupStatusMigration = (async () => {
try {
let statusSuccess: any = true;
let timeSuccess: any = true;
const status = await EncryptedStorage.getItem(
LEGACY_LAST_CHANNEL_BACKUP_STATUS
);
if (status) {
console.log('Attemping backup status migration');
statusSuccess = await Storage.setItem(
LAST_CHANNEL_BACKUP_STATUS,
status
);
console.log(
'Backup status migration status',
statusSuccess
);
}
const time = await EncryptedStorage.getItem(
LEGACY_LAST_CHANNEL_BACKUP_TIME
);
if (time) {
console.log('Attemping backup time migration');
timeSuccess = await Storage.setItem(
LAST_CHANNEL_BACKUP_TIME,
time
);
console.log('Backup time migration status', timeSuccess);
}
return statusSuccess && timeSuccess;
} catch (error) {
console.error(
'Error loading backup status from encrypted storage',
error
);
return false;
}
})();
migrationTasks.push(backupStatusMigration);
// POS migration
const posMigration = (async () => {
try {
let hiddenKeySuccess: any = true;
let standaloneKeySuccess: any = true;
let categoriesSuccess: any = true;
let productsSuccess: any = true;
const posHiddenKey = await EncryptedStorage.getItem(
LEGACY_POS_HIDDEN_KEY
);
if (posHiddenKey) {
console.log('Attemping POS hidden key migration');
hiddenKeySuccess = await Storage.setItem(
POS_HIDDEN_KEY,
posHiddenKey
);
console.log(
'POS hidden key migration status',
hiddenKeySuccess
);
}
const posStandaloneKey = await EncryptedStorage.getItem(
LEGACY_POS_STANDALONE_KEY
);
if (posStandaloneKey) {
console.log('Attemping POS standalone key migration');
standaloneKeySuccess = await Storage.setItem(
POS_STANDALONE_KEY,
posStandaloneKey
);
console.log(
'POS standalone key migration status',
standaloneKeySuccess
);
}
const categories = await EncryptedStorage.getItem(
LEGACY_CATEGORY_KEY
);
if (categories) {
console.log('Attemping POS categories migration');
categoriesSuccess = await Storage.setItem(
CATEGORY_KEY,
categories
);
console.log(
'POS categories migration status',
categoriesSuccess
);
}
const products = await EncryptedStorage.getItem(
LEGACY_PRODUCT_KEY
);
if (products) {
console.log('Attemping POS products migration');
productsSuccess = await Storage.setItem(
PRODUCT_KEY,
products
);
console.log(
'POS products migration status',
productsSuccess
);
}
return (
hiddenKeySuccess &&
standaloneKeySuccess &&
categoriesSuccess &&
productsSuccess
);
} catch (error) {
console.error(
'Error loading POS data from encrypted storage',
error
);
return false;
}
})();
migrationTasks.push(posMigration);
// Units migration
const unitsMigration = (async () => {
try {
const units = await EncryptedStorage.getItem(LEGACY_UNIT_KEY);
if (units) {
console.log('Attemping units migration');
const writeSuccess = await Storage.setItem(UNIT_KEY, units);
console.log('Units migration status', writeSuccess);
return writeSuccess;
}
} catch (error) {
console.error(
'Error loading units data from encrypted storage',
error
);
return false;
}
})();
migrationTasks.push(unitsMigration);
// Hidden accounts migration
const hiddenAccountsMigration = (async () => {
try {
const accounts = await EncryptedStorage.getItem(
LEGACY_HIDDEN_ACCOUNTS_KEY
);
if (accounts) {
console.log('Attemping hidden accounts migration');
const writeSuccess = await Storage.setItem(
HIDDEN_ACCOUNTS_KEY,
accounts
);
console.log(
'Hidden accounts migration status',
writeSuccess
);
return writeSuccess;
}
} catch (error) {
console.error(
'Error loading hidden accounts from encrypted storage',
error
);
return false;
}
})();
migrationTasks.push(hiddenAccountsMigration);
// Currency codes migration
const currencyCodesMigration = (async () => {
try {
const currencyCodes = await EncryptedStorage.getItem(
LEGACY_CURRENCY_CODES_KEY
);
if (currencyCodes) {
console.log('Attemping currency codes migration');
const writeSuccess = await Storage.setItem(
CURRENCY_CODES_KEY,
currencyCodes
);
console.log(
'Currency codes migration status',
writeSuccess
);
return writeSuccess;
}
} catch (error) {
console.error(
'Error loading currency codes from encrypted storage',
error
);
return false;
}
})();
migrationTasks.push(currencyCodesMigration);
// Activity filters migration
const activityFiltersMigration = (async () => {
try {
const activityFilters = await EncryptedStorage.getItem(
LEGACY_ACTIVITY_FILTERS_KEY
);
if (activityFilters) {
console.log('Attemping activity filters migration');
const writeSuccess = await Storage.setItem(
ACTIVITY_FILTERS_KEY,
activityFilters
);
console.log(
'Activity filters migration status',
writeSuccess
);
return writeSuccess;
}
} catch (error) {
console.error(
'Error loading activity filters from encrypted storage',
error
);
return false;
}
})();
migrationTasks.push(activityFiltersMigration);
// Embedded LND is backed up migration
const embeddedLndIsBackedUpMigration = (async () => {
try {
const embeddedLndIsBackedUp = await EncryptedStorage.getItem(
LEGACY_IS_BACKED_UP_KEY
);
if (embeddedLndIsBackedUp) {
console.log(
'Attemping Embedded LND is backed up migration'
);
const writeSuccess = await Storage.setItem(
IS_BACKED_UP_KEY,
embeddedLndIsBackedUp
);
console.log(
'Embedded LND is backed up migration status',
writeSuccess
);
return writeSuccess;
}
} catch (error) {
console.error(
'Error loading Embedded LND is backed up from encrypted storage',
error
);
return false;
}
})();
migrationTasks.push(embeddedLndIsBackedUpMigration);
// LSPS1 orders migration
const lsps1OrdersMigration = (async () => {
try {
const lsps1orders = await EncryptedStorage.getItem(
LEGACY_LSPS1_ORDERS_KEY
);
if (lsps1orders) {
console.log('Attemping LSPS1 orders migration');
const writeSuccess = await Storage.setItem(
LSPS_ORDERS_KEY,
lsps1orders
);
console.log('LSPS1 orders migration status', writeSuccess);
return writeSuccess;
}
} catch (error) {
console.error(
'Error loading LSPS1 orders from encrypted storage',
error
);
return false;
}
})();
migrationTasks.push(lsps1OrdersMigration);
// LNC migrations
const lncMigration = (async () => {
try {
const migrationPromises =
settings?.nodes.map(async (node: any) => {
if (node.implementation === 'lightning-node-connect') {
const baseKey = `${LNC_STORAGE_KEY}:${hash(
node.pairingPhrase
)}`;
const hostKey = `${baseKey}:host`;
const baseKeyData: string =
(await EncryptedStorage.getItem(baseKey)) ||
'{}';
console.log(
'Attemping LNC base key migration',
baseKey
);
const writeSuccess1 = await Storage.setItem(
baseKey,
JSON.parse(baseKeyData)
);
console.log(
'LNC base key migration status',
writeSuccess1
);
const hostKeyData = await EncryptedStorage.getItem(
hostKey
);
console.log(
'Attemping LNC host key migration',
baseKey
);
const writeSuccess2 = await Storage.setItem(
hostKey,
hostKeyData
);
console.log(
'LNC host key migration status',
writeSuccess2
);
return writeSuccess1 && writeSuccess2;
}
}) || [];
const results = await Promise.all(migrationPromises);
return results.every((result) => result !== false);
} catch (error) {
console.error(
'Error loading LNC data from encrypted storage',
error
);
return false;
}
})();
migrationTasks.push(lncMigration);
const results = await Promise.all(migrationTasks);
console.log('storageMigrationV2 completed!', results);
return results.every((result) => result === true);
}
public async migrateCashuSeedVersion(cashuStore: any) {
// cashuStore is passed as 'any' to avoid circular dependency issues
// but it's an instance of CashuStore
// TODO fix circular dependency
if (
cashuStore.settingsStore?.implementation === 'embedded-lnd' &&
cashuStore.seedVersion === undefined
) {
console.log('Migrating Cashu seed version to v1');
cashuStore.seedVersion = 'v1';
try {
await Storage.setItem(
`${cashuStore.getNodeDir()}-cashu-seed-version`,
'v1'
);
console.log('Cashu seed version migrated and saved as v1.');
} catch (error) {
console.error(
'Error saving migrated Cashu seed version:',
error
);
}
}
}
public async keychainCloudSyncMigration() {
try {
const hasMigrated = await EncryptedStorage.getItem(
KEYCHAIN_MIGRATION_KEY
);
if (hasMigrated !== 'true') {
console.log('Attempting keychain cloud sync migration...');
const settingsData = await this.migrateKey(STORAGE_KEY);
if (settingsData) {
settingsStore.isMigrating = true;
}
const migrationKeys = [
CONTACTS_KEY,
LAST_CHANNEL_BACKUP_STATUS,
LAST_CHANNEL_BACKUP_TIME,
ADDRESS_ACTIVATED_STRING,
HASHES_STORAGE_STRING,
POS_HIDDEN_KEY,
POS_STANDALONE_KEY,
CATEGORY_KEY,
PRODUCT_KEY,
UNIT_KEY,
HIDDEN_ACCOUNTS_KEY,
CURRENCY_CODES_KEY,
ACTIVITY_FILTERS_KEY,
IS_BACKED_UP_KEY,
LSPS_ORDERS_KEY,
SWAPS_KEY,
REVERSE_SWAPS_KEY,
SWAPS_RESCUE_KEY,
SWAPS_LAST_USED_KEY
];
for (const key of migrationKeys) {
await this.migrateKey(key);
}
const notesListJson = await this.migrateKey(NOTES_KEY);
if (notesListJson) {
const noteKeys = JSON.parse(notesListJson);
if (Array.isArray(noteKeys)) {
for (const noteKey of noteKeys) {
await this.migrateKey(noteKey);
}
}
}
if (settingsData) {
const settings = JSON.parse(settingsData);
if (settings.nodes && Array.isArray(settings.nodes)) {
for (const node of settings.nodes) {
if (
node.implementation ===
'lightning-node-connect' &&
node.pairingPhrase
) {
const baseKey = `${LNC_STORAGE_KEY}:${hash(
node.pairingPhrase
)}`;
const hostKey = `${baseKey}:host`;
await this.migrateKey(baseKey);
await this.migrateKey(hostKey);
}
}
}
}
await EncryptedStorage.setItem(KEYCHAIN_MIGRATION_KEY, 'true');
console.log(
'Keychain cloud sync migration completed successfully.'
);
}
const cashuMigration = await EncryptedStorage.getItem(
CASHU_MIGRATION_KEY
);
if (cashuMigration !== 'true') {
// Only run the Cashu keychain migration if the main keychain
// migration actually ran this time (hasMigrated was not 'true'
// when we entered). If the main migration was already done from
// a prior launch, there can't be unmigrated Cashu data in the
// old keychain — skip the expensive keychain reads.
if (hasMigrated !== 'true') {
console.log('Running Cashu Multi-Node Migration...');
const settingsJson = await Storage.getItem(STORAGE_KEY);
if (settingsJson) {
settingsStore.isMigrating = true;
const settings = JSON.parse(settingsJson);
if (settings.nodes && Array.isArray(settings.nodes)) {
for (const node of settings.nodes) {
if (node.implementation === 'embedded-lnd') {
const lndDir = node.lndDir || 'lnd';
await this.migrateCashuForNode(lndDir);
}
}
}
}
console.log('Cashu Migration Completed.');
} else {
console.log(
'Skipping Cashu migration - main keychain migration was already done.'
);
}
await EncryptedStorage.setItem(CASHU_MIGRATION_KEY, 'true');
}
} catch (error) {
console.error('Error during keychain cloud sync migration:', error);
}
}
}
const migrationsUtils = new MigrationsUtils();
export default migrationsUtils;