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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- added: Add tokenId-based denomination conversion utils to wallets
- added: Add `payoutTokenId` to `EdgeTxSwap`

## 2.36.0 (2025-11-04)

- added: Added `EdgeSubscribedAddress` type for `onSubscribeAddresses` and `startEngine`.
Expand Down
28 changes: 28 additions & 0 deletions src/core/currency/currency-selectors.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import {
EdgeCurrencyInfo,
EdgeCurrencyWallet,
EdgeToken,
EdgeTokenId,
EdgeTokenMap
} from '../../types/types'
import { ApiInput, RootProps } from '../root-pixie'

/** @deprecated Use `getTokenMultiplier` instead. */
export function getCurrencyMultiplier(
currencyInfo: EdgeCurrencyInfo,
allTokens: EdgeTokenMap,
Expand All @@ -28,6 +31,31 @@ export function getCurrencyMultiplier(
return '1'
}

export function getTokenMultiplier(
currencyInfo: EdgeCurrencyInfo,
allTokens: EdgeTokenMap,
tokenId: EdgeTokenId
): string {
if (tokenId == null) {
for (const denomination of currencyInfo.denominations) {
if (denomination.name === currencyInfo.currencyCode) {
return denomination.multiplier
}
}
} else {
const token: EdgeToken | undefined = allTokens[tokenId]
if (token != null) {
for (const denomination of token.denominations) {
if (denomination.name === token.currencyCode) {
return denomination.multiplier
}
}
}
}

return '1'
}

Comment on lines +34 to +58
Copy link
Contributor

@swansontec swansontec Nov 25, 2025

Choose a reason for hiding this comment

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

The core should not provide utility functions like this, since everything goes over an expensive bridge. The denominationToNative and nativeToDenomination functions were historical accidents, and deprecation message says to "Use the information in EdgeCurrencyInfo / EdgeToken". We want to delete these guys, not replace them.

These types of utilities should move into edge-currency-accountbased and edge-react-gui in some sort of /utils file, since then they can be fast & synchronous.

export function waitForCurrencyWallet(
ai: ApiInput,
walletId: string
Expand Down
33 changes: 31 additions & 2 deletions src/core/currency/wallet/currency-wallet-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '../../../client-side'
import {
upgradeCurrencyCode,
upgradeSwapData,
upgradeTxNetworkFees
} from '../../../types/type-helpers'
import {
Expand Down Expand Up @@ -42,7 +43,10 @@ import {
import { makeMetaTokens } from '../../account/custom-tokens'
import { toApiInput } from '../../root-pixie'
import { makeStorageWalletApi } from '../../storage/storage-api'
import { getCurrencyMultiplier } from '../currency-selectors'
import {
getCurrencyMultiplier,
getTokenMultiplier
} from '../currency-selectors'
import { makeCurrencyWalletCallbacks } from './currency-wallet-callbacks'
import {
asEdgeAssetAction,
Expand Down Expand Up @@ -162,6 +166,28 @@ export function makeCurrencyWalletApi(
get currencyInfo(): EdgeCurrencyInfo {
return plugin.currencyInfo
},
async convertDenominatedToNative(
denominatedAmount: string,
tokenId: EdgeTokenId
): Promise<string> {
const multiplier = getTokenMultiplier(
plugin.currencyInfo,
input.props.state.accounts[accountId].allTokens[pluginId],
tokenId
)
return mul(denominatedAmount, multiplier)
},
async convertNativeToDenominated(
nativeAmount: string,
tokenId: EdgeTokenId
): Promise<string> {
const multiplier = getTokenMultiplier(
plugin.currencyInfo,
input.props.state.accounts[accountId].allTokens[pluginId],
tokenId
)
return div(nativeAmount, multiplier, multiplier.length)
},
async denominationToNative(
denominatedAmount: string,
currencyCode: string
Expand Down Expand Up @@ -597,9 +623,12 @@ export function makeCurrencyWalletApi(
tx.currencyCode = upgradedCurrency.currencyCode
tx.tokenId = upgradedCurrency.tokenId
if (metadata != null) tx.metadata = metadata
if (swapData != null) tx.swapData = asEdgeTxSwap(swapData)
if (savedAction != null) tx.savedAction = asEdgeTxAction(savedAction)
if (assetAction != null) tx.assetAction = asEdgeAssetAction(assetAction)
if (swapData != null) {
tx.swapData = asEdgeTxSwap(swapData)
upgradeSwapData(tx)
}
if (input.props.state.login.deviceDescription != null)
tx.deviceDescription = input.props.state.login.deviceDescription

Expand Down
5 changes: 3 additions & 2 deletions src/core/currency/wallet/currency-wallet-cleaners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ interface LegacyMapFile {
// building-block cleaners
// ---------------------------------------------------------------------

export const asEdgeTokenId = asEither(asString, asNull)

const asFeeRate: Cleaner<'high' | 'standard' | 'low'> = asValue(
'high',
'standard',
Expand All @@ -141,6 +143,7 @@ export const asEdgeTxSwap = asObject<EdgeTxSwap>({
// Address information:
payoutAddress: asString,
payoutCurrencyCode: asString,
payoutTokenId: asEdgeTokenId,
Copy link

Choose a reason for hiding this comment

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

Bug: Required field breaks loading old transaction files

The payoutTokenId field in asEdgeTxSwap is required but old transaction files saved to disk won't have this field. When loading existing transactions via transactionFile.load, the cleaner will fail validation. The field needs to be wrapped with asOptional to handle backward compatibility with existing data, since upgradeSwapData only runs during makeSpend and not when loading persisted transactions.

Fix in Cursor Fix in Web

Copy link
Contributor

Choose a reason for hiding this comment

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

This, exactly. We cannot add new mandatory fields to old data. If we truly wanted to go down this path, we would first have to add the tokenId as an optional field, and then incorporate a migration path for old data.

However, since this is a strict save-and-return workflow, I would propose not to add a migration path, but to simply add the payoutTokenId: asOptional(asEdgeTokenId), and a payoutTokenId?: EdgeTokenId to the types. That way we don't mess with old data, and the GUI is the one who has to deal with any backwards-compatibility issues. This backwards-compatibility is not terribly complex, since the GUI pretty much just shows this value as-is on the tx details scene.

payoutNativeAmount: asString,
payoutWalletId: asString,
refundAddress: asOptional(asString)
Expand All @@ -158,8 +161,6 @@ export function asIntegerString(raw: unknown): string {
// file cleaners
// ---------------------------------------------------------------------

export const asEdgeTokenId = asEither(asString, asNull)

export const asEdgeAssetAmount = asObject<EdgeAssetAmount>({
pluginId: asString,
tokenId: asEdgeTokenId,
Expand Down
9 changes: 9 additions & 0 deletions src/types/type-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,12 @@ export const upgradeTxNetworkFees = (tx: EdgeTransaction): void => {
}
}
}

// Swap data could be present without the payoutTokenId. We can add it if there's a swap savedAction present.
export const upgradeSwapData = (tx: EdgeTransaction): void => {
if (tx.swapData != null && tx.swapData?.payoutTokenId === undefined) {
if (tx.savedAction != null && tx.savedAction.actionType === 'swap') {
tx.swapData.payoutTokenId = tx.savedAction.toAsset.tokenId
}
}
}
11 changes: 11 additions & 0 deletions src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,7 @@ export interface EdgeTxSwap {
payoutAddress: string
payoutCurrencyCode: string
payoutNativeAmount: string
payoutTokenId: EdgeTokenId
payoutWalletId: string
refundAddress?: string
}
Expand Down Expand Up @@ -1368,6 +1369,16 @@ export interface EdgeCurrencyWallet {
// Generic:
readonly otherMethods: EdgeOtherMethods

readonly convertDenominatedToNative: (
denominatedAmount: string,
tokenId: EdgeTokenId
) => Promise<string>

readonly convertNativeToDenominated: (
nativeAmount: string,
tokenId: EdgeTokenId
) => Promise<string>

/** @deprecated Use the information in EdgeCurrencyInfo / EdgeToken. */
readonly denominationToNative: (
denominatedAmount: string,
Expand Down
1 change: 1 addition & 0 deletions test/core/currency/wallet/currency-wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,7 @@ describe('currency wallets', function () {
},
payoutAddress: 'get it here',
payoutCurrencyCode: 'TOKEN',
payoutTokenId: 'TOKEN',
payoutNativeAmount: '1',
payoutWalletId: wallet.id
}
Expand Down
3 changes: 3 additions & 0 deletions test/fake/fake-transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ export const walletTxs: { [txid: string]: Partial<EdgeTransaction> } = {
},
payoutAddress: '0x1d21f26739b20caf898aa1d2c37007577309e466',
payoutCurrencyCode: 'eth',
payoutTokenId: null,
payoutNativeAmount: '85486000000000000',
payoutWalletId: '2D6BLbfk/Ex8jIt8wvy/uuD2rRjGklj00gW0gUfxrnc=',
refundAddress: '33zRiHEfmpt9Hirga6GQb8Xp71veiq3NhJ'
Expand Down Expand Up @@ -328,6 +329,7 @@ export const walletTxs: { [txid: string]: Partial<EdgeTransaction> } = {
},
payoutAddress: '0x1d21f26739b20caf898aa1d2c37007577309e466',
payoutCurrencyCode: 'eth',
payoutTokenId: null,
payoutNativeAmount: '45188000000000000',
payoutWalletId: '2D6BLbfk/Ex8jIt8wvy/uuD2rRjGklj00gW0gUfxrnc=',
refundAddress: '33eFPT8vFvgCHMijNR7LpqNZBMR2utcq1P'
Expand Down Expand Up @@ -378,6 +380,7 @@ export const walletTxs: { [txid: string]: Partial<EdgeTransaction> } = {
},
payoutAddress: 'MKC9DxwFjXCSprj6HNXBvvqDGviMrTmV9q',
payoutCurrencyCode: 'ltc',
payoutTokenId: null,
payoutNativeAmount: '12518100',
payoutWalletId: 'aumm1+iQsXDXxiSYzuX8qToQpwQXX03CTVKomKPTLaQ=',
refundAddress: '33eFPT8vFvgCHMijNR7LpqNZBMR2utcq1P'
Expand Down
Loading