Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- added: `EdgeCurrencyWallet.walletSettings` and `EdgeCurrencyWallet.changeWalletSettings`, plus matching engine plumbing.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CHANGELOG omits new imported public API property

Low Severity

The PR adds a new public imported property on EdgeCurrencyWallet (in both types.ts and currency-wallet-api.ts), but the CHANGELOG entry only covers walletSettings and changeWalletSettings. Since imported is a newly exposed API on EdgeCurrencyWallet, it warrants its own added: entry in the CHANGELOG.

Additional Locations (2)
Fix in Cursor Fix in Web

Triggered by learned rule: CHANGELOG entries must use correct semantic prefix

Reviewed by Cursor Bugbot for commit f87eace. Configure here.


## 2.43.6 (2026-04-02)

- fixed: Upgraded @nymproject/mix-fetch with promised reliability improvements.
Expand Down
28 changes: 27 additions & 1 deletion src/core/account/account-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ import {
EdgeSwapQuote,
EdgeSwapRequest,
EdgeSwapRequestOptions,
EdgeWalletInfo,
EdgeWalletInfoFull,
EdgeWalletStates
} from '../../types/types'
import { makeEdgeResult } from '../../util/edgeResult'
import { base58 } from '../../util/encoding'
import { saveWalletSettings } from '../currency/wallet/currency-wallet-files'
import { getPublicWalletInfo } from '../currency/wallet/currency-wallet-pixie'
import {
finishWalletCreation,
Expand All @@ -55,13 +57,14 @@ import {
import { changePin, checkPin2, deletePin } from '../login/pin2'
import { changeRecovery, deleteRecovery } from '../login/recovery2'
import { listSplittableWalletTypes, splitWalletInfo } from '../login/splitting'
import { asEdgeStorageKeys } from '../login/storage-keys'
import { changeVoucherStatus } from '../login/vouchers'
import {
findCurrencyPluginId,
getCurrencyTools
} from '../plugins/plugins-selectors'
import { ApiInput } from '../root-pixie'
import { makeLocalDisklet } from '../storage/repo'
import { makeLocalDisklet, makeRepoPaths } from '../storage/repo'
import { makeStorageWalletApi } from '../storage/storage-api'
import { fetchSwapQuotes } from '../swap/swap-api'
import { changeWalletStates } from './account-files'
Expand All @@ -72,6 +75,18 @@ import { makeLobbyApi } from './lobby-api'
import { makeMemoryWalletInner } from './memory-wallet'
import { CurrencyConfig, SwapConfig } from './plugin-api'

async function prewriteWalletSettings(
ai: ApiInput,
walletInfo: EdgeWalletInfo,
walletSettings: object
): Promise<void> {
const { io } = ai.props
const storageKeys = asEdgeStorageKeys(walletInfo.keys)
const { disklet } = makeRepoPaths(io, storageKeys)

await saveWalletSettings(disklet, walletSettings)
}

/**
* Creates an unwrapped account API object around an account state object.
*/
Expand Down Expand Up @@ -642,6 +657,9 @@ export function makeAccountApi(ai: ApiInput, accountId: string): EdgeAccount {
ai.props.log.breadcrumb('EdgeAccount.createCurrencyWallet', {})

const walletInfo = await makeCurrencyWalletKeys(ai, walletType, opts)
if (opts.walletSettings != null) {
await prewriteWalletSettings(ai, walletInfo, opts.walletSettings)
}
Comment thread
cursor[bot] marked this conversation as resolved.
const childKey = decryptChildKey(stashTree, sessionKey, login.loginId)
await applyKit(ai, sessionKey, makeKeysKit(ai, childKey, [walletInfo]))
return await finishWalletCreation(ai, accountId, walletInfo.id, opts)
Expand Down Expand Up @@ -674,6 +692,14 @@ export function makeAccountApi(ai: ApiInput, accountId: string): EdgeAccount {
)
)

// Prewrite wallet settings before applyKit triggers the pixie:
for (let i = 0; i < walletInfos.length; i++) {
const { walletSettings } = createWallets[i]
if (walletSettings != null) {
await prewriteWalletSettings(ai, walletInfos[i], walletSettings)
}
}

// Store the keys on the server:
const childKey = decryptChildKey(stashTree, sessionKey, login.loginId)
await applyKit(ai, sessionKey, makeKeysKit(ai, childKey, walletInfos))
Expand Down
3 changes: 2 additions & 1 deletion src/core/account/memory-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const makeMemoryWalletInner = async (
walletType: string,
opts: EdgeCreateCurrencyWalletOptions = {}
): Promise<EdgeMemoryWallet> => {
const { keys } = opts
const { keys, walletSettings = {} } = opts
if (keys == null) throw new Error('No keys provided')

const walletId = `memorywallet-${memoryWalletCount++}`
Expand Down Expand Up @@ -113,6 +113,7 @@ export const makeMemoryWalletInner = async (
lightMode: true,
log,
userSettings: { ...(config.userSettings ?? {}) },
walletSettings,
walletLocalDisklet: makeMemoryDisklet(),
walletLocalEncryptedDisklet: makeMemoryDisklet()
})
Expand Down
17 changes: 16 additions & 1 deletion src/core/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
EdgeTokenMap,
EdgeTransaction,
EdgeWalletInfo,
EdgeWalletStates
EdgeWalletStates,
JsonObject
} from '../types/types'
import { SwapSettings } from './account/account-types'
import { ClientInfo } from './context/client-file'
Expand Down Expand Up @@ -359,6 +360,20 @@ export type RootAction =
walletId: string
}
}
| {
type: 'CURRENCY_WALLET_CHANGED_WALLET_SETTINGS'
payload: {
walletId: string
walletSettings: JsonObject
}
}
| {
type: 'CURRENCY_WALLET_LOADED_WALLET_SETTINGS_FILE'
payload: {
walletId: string
walletSettings: JsonObject
}
}
| {
type: 'INFO_CACHE_FETCHED'
payload: InfoCacheFile
Expand Down
18 changes: 17 additions & 1 deletion src/core/currency/wallet/currency-wallet-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ import {
EdgeTokenId,
EdgeTokenIdOptions,
EdgeTransaction,
EdgeWalletInfo
EdgeWalletInfo,
JsonObject
} from '../../../types/types'
import { makeMetaTokens } from '../../account/custom-tokens'
import { splitWalletInfo } from '../../login/splitting'
Expand All @@ -63,6 +64,7 @@ import {
loadTxFiles,
renameCurrencyWallet,
saveTxMetadataFile,
saveWalletSettingsFile,
setCurrencyWalletFiat,
setupNewTxMetadata,
updateCurrencyWalletTxMetadata
Expand Down Expand Up @@ -135,6 +137,9 @@ export function makeCurrencyWalletApi(
get id(): string {
return storageWalletApi.id
},
get imported(): boolean {
return walletInfo.imported === true
},
get localDisklet(): Disklet {
return storageWalletApi.localDisklet
},
Expand Down Expand Up @@ -193,6 +198,17 @@ export function makeCurrencyWalletApi(
return div(nativeAmount, multiplier, multiplier.length)
},

// User settings for this wallet:
get walletSettings(): JsonObject {
return input.props.walletState.walletSettings
},
async changeWalletSettings(settings: JsonObject): Promise<void> {
if (input.props.walletState.currencyInfo.hasWalletSettings !== true) {
throw new Error('Wallet settings unsupported')
}
await saveWalletSettingsFile(input, settings)
},

// Chain state:
get balances(): EdgeBalances {
return input.props.walletState.balances
Expand Down
4 changes: 4 additions & 0 deletions src/core/currency/wallet/currency-wallet-cleaners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,10 @@ export const asTokensFile = asObject({
detectedTokenIds: asArray(asString)
})

export const asWalletSettingsFile = asObject({
walletSettings: asOptional(asJsonObject, () => ({}))
})

const asTransactionAsset = asObject<TransactionAsset>({
assetAction: asOptional(asEdgeAssetAction),
metadata: asEdgeMetadata,
Expand Down
56 changes: 55 additions & 1 deletion src/core/currency/wallet/currency-wallet-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
EdgeSubscribedAddress,
EdgeTokenId,
EdgeTransaction,
EdgeTxAction
EdgeTxAction,
JsonObject
} from '../../../types/types'
import { makeJsonFile } from '../../../util/file-helpers'
import { fetchAppIdInfo } from '../../account/lobby-api'
Expand All @@ -30,6 +31,7 @@ import {
asTransactionFile,
asWalletFiatFile,
asWalletNameFile,
asWalletSettingsFile,
LegacyTransactionFile,
TransactionAsset,
TransactionFile
Expand All @@ -45,6 +47,7 @@ const LEGACY_TOKENS_FILE = 'EnabledTokens.json'
const SEEN_TX_CHECKPOINT_FILE = 'seenTxCheckpoint.json'
const TOKENS_FILE = 'Tokens.json'
const WALLET_NAME_FILE = 'WalletName.json'
const WALLET_SETTINGS_FILE = 'WalletSettings.json'

const legacyAddressFile = makeJsonFile(asLegacyAddressFile)
const legacyMapFile = makeJsonFile(asLegacyMapFile)
Expand All @@ -55,6 +58,7 @@ const tokensFile = makeJsonFile(asTokensFile)
const transactionFile = makeJsonFile(asTransactionFile)
const walletFiatFile = makeJsonFile(asWalletFiatFile)
const walletNameFile = makeJsonFile(asWalletNameFile)
const walletSettingsFile = makeJsonFile(asWalletSettingsFile)

/**
* Updates the enabled tokens on a wallet.
Expand Down Expand Up @@ -284,6 +288,52 @@ export async function loadTokensFile(
})
}

/**
* Loads wallet-specific settings.
*/
export async function loadWalletSettingsFile(
input: CurrencyWalletInput
): Promise<void> {
const { dispatch, state, walletId } = input.props
const disklet = getStorageWalletDisklet(state, walletId)

const clean = await walletSettingsFile.load(disklet, WALLET_SETTINGS_FILE)
dispatch({
type: 'CURRENCY_WALLET_LOADED_WALLET_SETTINGS_FILE',
payload: {
walletId,
walletSettings: clean?.walletSettings ?? {}
}
})
}

/**
* Persists wallet settings to disk.
*/
export async function saveWalletSettingsFile(
input: CurrencyWalletInput,
walletSettings: JsonObject
): Promise<void> {
const { dispatch, state, walletId } = input.props
const disklet = getStorageWalletDisklet(state, walletId)

await saveWalletSettings(disklet, walletSettings)

dispatch({
type: 'CURRENCY_WALLET_CHANGED_WALLET_SETTINGS',
payload: { walletId, walletSettings }
})
}

export async function saveWalletSettings(
disklet: Disklet,
walletSettings: JsonObject
): Promise<void> {
await walletSettingsFile.save(disklet, WALLET_SETTINGS_FILE, {
walletSettings
})
}

/**
* Loads transaction metadata files.
*/
Expand Down Expand Up @@ -697,6 +747,10 @@ export async function reloadWalletFiles(
if (changes.includes(TOKENS_FILE) || changes.includes(LEGACY_TOKENS_FILE)) {
await loadTokensFile(input)
}
const { hasWalletSettings = false } = input.props.walletState.currencyInfo
if (hasWalletSettings && changes.includes(WALLET_SETTINGS_FILE)) {
await loadWalletSettingsFile(input)
}
if (changes.includes(CURRENCY_FILE)) {
await loadFiatFile(input)
}
Expand Down
48 changes: 41 additions & 7 deletions src/core/currency/wallet/currency-wallet-pixie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
EdgeCurrencyTools,
EdgeCurrencyWallet,
EdgeTokenMap,
EdgeWalletInfo
EdgeWalletInfo,
JsonObject
} from '../../../types/types'
import { makeJsonFile } from '../../../util/file-helpers'
import { makePeriodicTask, PeriodicTask } from '../../../util/periodic-task'
Expand Down Expand Up @@ -45,9 +46,14 @@ import {
loadSeenTxCheckpointFile,
loadTokensFile,
loadTxFileNames,
loadWalletSettingsFile,
writeTokensFile
} from './currency-wallet-files'
import { CurrencyWalletState, initialTokenIds } from './currency-wallet-reducer'
import {
CurrencyWalletState,
initialTokenIds,
initialWalletSettings
} from './currency-wallet-reducer'
import { tokenIdsToCurrencyCodes, uniqueStrings } from './enabled-tokens'

export interface CurrencyWalletOutput {
Expand Down Expand Up @@ -118,6 +124,11 @@ export const walletPixie: TamePixie<CurrencyWalletProps> = combinePixies({
// so the engine can start in the right state:
await loadTokensFile(input)

const { hasWalletSettings = false } = walletState.currencyInfo
if (hasWalletSettings) {
await loadWalletSettingsFile(input)
}

// Start the engine:
const accountState = state.accounts[accountId]
const engine = await plugin.makeCurrencyEngine(publicWalletInfo, {
Expand All @@ -138,7 +149,8 @@ export const walletPixie: TamePixie<CurrencyWalletProps> = combinePixies({
// User settings:
customTokens: accountState.customTokens[pluginId] ?? {},
enabledTokenIds: input.props.walletState.allEnabledTokenIds,
userSettings: accountState.userSettings[pluginId] ?? {}
userSettings: accountState.userSettings[pluginId] ?? {},
walletSettings: input.props.walletState.walletSettings
})
input.onOutput(engine)

Expand Down Expand Up @@ -462,7 +474,8 @@ export const walletPixie: TamePixie<CurrencyWalletProps> = combinePixies({

watcher(input: CurrencyWalletInput) {
let lastState: CurrencyWalletState | undefined
let lastSettings: object = {}
let lastUserSettings: object = {}
let lastWalletSettings: JsonObject = initialWalletSettings
let lastTokens: EdgeTokenMap = {}
let lastEnabledTokenIds: string[] = initialTokenIds

Expand All @@ -480,11 +493,12 @@ export const walletPixie: TamePixie<CurrencyWalletProps> = combinePixies({
lastState = walletState

// Update engine settings:
const userSettings = accountState.userSettings[pluginId] ?? lastSettings
if (lastSettings !== userSettings && engine != null) {
const userSettings =
accountState.userSettings[pluginId] ?? lastUserSettings
if (lastUserSettings !== userSettings && engine != null) {
await engine.changeUserSettings(userSettings)
}
lastSettings = userSettings
lastUserSettings = userSettings

// Update the custom tokens:
const customTokens = accountState.customTokens[pluginId] ?? lastTokens
Expand All @@ -505,6 +519,26 @@ export const walletPixie: TamePixie<CurrencyWalletProps> = combinePixies({
}
lastTokens = customTokens

// Update wallet-scoped settings:
const { hasWalletSettings = false } = walletState.currencyInfo
const { walletSettings } = walletState
const settingsChanged = lastWalletSettings !== walletSettings

if (
settingsChanged &&
engine?.changeWalletSettings != null &&
hasWalletSettings
) {
input.props.log.warn(
`walletSettings applying: ${JSON.stringify(walletSettings)}`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Agentic Security Review
Severity: MEDIUM
Raw walletSettings are logged via JSON.stringify(walletSettings). Because these settings are plugin-defined, they may include secrets or tokens, and this log line can expose sensitive values to log sinks.

Impact: Sensitive wallet configuration data could be disclosed through logs and any downstream log aggregation.

)
await engine.changeWalletSettings(walletSettings).catch(error => {
input.props.log.warn(`walletSettings error: ${String(error)}`)
input.props.onError(error)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug wallet settings warn logs

Low Severity

The wallet-settings watcher logs every apply and error at warn with JSON.stringify of settings. That looks like temporary instrumentation, adds noisy production logs, and may write wallet configuration into crash or log pipelines.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f87eace. Configure here.

})
}
lastWalletSettings = walletSettings
Comment thread
cursor[bot] marked this conversation as resolved.

// Update enabled tokens:
const { allEnabledTokenIds } = walletState
if (lastEnabledTokenIds !== allEnabledTokenIds && engine != null) {
Expand Down
Loading
Loading