Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto tokens #572

Merged
merged 6 commits into from
Nov 30, 2023
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

- added: Accept an `onNewTokens` callback from `EdgeCurrencyEngine`.
- added: Emit an `enabledDetectedTokens` event when auto-enabling tokens.
- added: Expose auto-detected tokens as `EdgeCurrencyWallet.detectedTokenIds`.
- changed: Save enabled tokens by their tokenId, not by their currency code.
- fixed: Add missing `export` to the `EdgeCorePluginFactory` type definition.

## 1.11.0 (2023-10-18)
Expand Down
16 changes: 7 additions & 9 deletions src/core/account/plugin-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,15 +93,15 @@ export class CurrencyConfig
payload: { accountId, pluginId, tokenId: newTokenId, token }
})

// Do we need to tweak enabled tokens?
if (oldToken.currencyCode !== token.currencyCode) {
if (newTokenId !== tokenId) {
// Enable the new token if the tokenId changed:
const { wallets } = ai.props.state.currency
for (const walletId of Object.keys(wallets)) {
const walletState = wallets[walletId]
if (
walletState.accountId !== accountId ||
walletState.pluginId !== pluginId ||
!walletState.enabledTokens.includes(oldToken.currencyCode)
!walletState.enabledTokenIds.includes(tokenId)
) {
continue
}
Expand All @@ -112,17 +112,15 @@ export class CurrencyConfig
type: 'CURRENCY_WALLET_ENABLED_TOKENS_CHANGED',
payload: {
walletId,
currencyCodes: uniqueStrings(
[...walletState.enabledTokens, token.currencyCode],
[oldToken.currencyCode]
enabledTokenIds: uniqueStrings(
[...walletState.enabledTokenIds, newTokenId],
[tokenId]
)
}
})
}
}

// Remove the old token if the tokenId changed:
if (newTokenId !== tokenId) {
// Remove the old token if the tokenId changed:
Jon-edge marked this conversation as resolved.
Show resolved Hide resolved
ai.props.dispatch({
type: 'ACCOUNT_CUSTOM_TOKEN_REMOVED',
payload: { accountId, pluginId, tokenId }
Expand Down
35 changes: 26 additions & 9 deletions src/core/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,23 @@ export type RootAction =
txidHashes: TxidHashes
}
}
| {
// Called when a currency engine fires the onAddressChecked callback.
type: 'CURRENCY_ENGINE_CHANGED_UNACTIVATED_TOKEN_IDS'
payload: {
unactivatedTokenIds: string[]
walletId: string
}
}
| {
// Called when a currency engine fires the onNewTokens callback.
type: 'CURRENCY_ENGINE_DETECTED_TOKENS'
payload: {
detectedTokenIds: string[]
enablingTokenIds: string[]
walletId: string
}
}
| {
type: 'CURRENCY_ENGINE_GOT_TXS'
payload: {
Expand Down Expand Up @@ -247,7 +264,7 @@ export type RootAction =
| {
type: 'CURRENCY_WALLET_ENABLED_TOKENS_CHANGED'
payload: {
currencyCodes: string[]
enabledTokenIds: string[]
walletId: string
}
}
Expand Down Expand Up @@ -287,6 +304,14 @@ export type RootAction =
walletId: string
}
}
| {
type: 'CURRENCY_WALLET_LOADED_TOKEN_FILE'
payload: {
detectedTokenIds: string[]
enabledTokenIds: string[]
walletId: string
}
}
| {
// Called when a currency wallet receives a new name.
type: 'CURRENCY_WALLET_NAME_CHANGED'
Expand All @@ -303,14 +328,6 @@ export type RootAction =
walletInfo: EdgeWalletInfo
}
}
| {
// Called when a currency engine fires the onAddressChecked callback.
type: 'CURRENCY_ENGINE_CHANGED_UNACTIVATED_TOKEN_IDS'
payload: {
unactivatedTokenIds: string[]
walletId: string
}
}
| {
// Fired when we fetch exchange pairs from some server.
type: 'EXCHANGE_PAIRS_FETCHED'
Expand Down
28 changes: 13 additions & 15 deletions src/core/currency/wallet/currency-wallet-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ import {
} from './currency-wallet-files'
import { CurrencyWalletInput } from './currency-wallet-pixie'
import { MergedTransaction } from './currency-wallet-reducer'
import { tokenIdsToCurrencyCodes, uniqueStrings } from './enabled-tokens'
import { upgradeMemos } from './upgrade-memos'

const fakeMetadata = {
Expand Down Expand Up @@ -197,26 +196,25 @@ export function makeCurrencyWalletApi(

// Tokens:
async changeEnabledTokenIds(tokenIds: string[]): Promise<void> {
const { dispatch, state, walletId, walletState } = input.props
const { builtinTokens, customTokens } = state.accounts[accountId]
const { currencyInfo } = walletState
const { dispatch, walletId, walletState } = input.props
const { accountId, pluginId } = walletState
const accountState = input.props.state.accounts[accountId]
const allTokens = accountState.allTokens[pluginId] ?? {}

const enabledTokenIds = tokenIds.filter(
tokenId => allTokens[tokenId] != null
)

dispatch({
type: 'CURRENCY_WALLET_ENABLED_TOKENS_CHANGED',
payload: {
walletId,
currencyCodes: uniqueStrings(
tokenIdsToCurrencyCodes(
builtinTokens[pluginId],
customTokens[pluginId],
currencyInfo,
tokenIds
)
)
}
payload: { walletId, enabledTokenIds }
})
},

get detectedTokenIds(): string[] {
return input.props.walletState.detectedTokenIds
},

get enabledTokenIds(): string[] {
return input.props.walletState.enabledTokenIds
},
Expand Down
38 changes: 36 additions & 2 deletions src/core/currency/wallet/currency-wallet-callbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import { combineTxWithFile } from './currency-wallet-api'
import { asIntegerString } from './currency-wallet-cleaners'
import {
loadAddressFiles,
loadEnabledTokensFile,
loadFiatFile,
loadNameFile,
loadTokensFile,
loadTxFileNames,
setupNewTxMetadata
} from './currency-wallet-files'
Expand All @@ -34,6 +34,7 @@ import {
mergeTx,
TxidHashes
} from './currency-wallet-reducer'
import { uniqueStrings } from './enabled-tokens'

let throttleRateLimitMs = 5000

Expand Down Expand Up @@ -122,6 +123,39 @@ export function makeCurrencyWalletCallbacks(
})
},

onNewTokens(tokenIds: string[]) {
pushUpdate({
id: walletId,
action: 'onNewTokens',
updateFunc: () => {
// Before we update redux, figure out what's truly new:
const { detectedTokenIds, enabledTokenIds } = input.props.walletState
const enablingTokenIds = uniqueStrings(tokenIds, [
...detectedTokenIds,
...enabledTokenIds
])

// Update redux:
input.props.dispatch({
type: 'CURRENCY_ENGINE_DETECTED_TOKENS',
payload: {
detectedTokenIds: tokenIds,
enablingTokenIds,
walletId
}
})

// Fire an event to the GUI:
if (enablingTokenIds.length > 0) {
const walletApi = input.props?.walletOutput?.walletApi
if (walletApi != null) {
emit(walletApi, 'enabledDetectedTokens', enablingTokenIds)
}
}
}
})
},

onUnactivatedTokenIdsChanged(unactivatedTokenIds: string[]) {
pushUpdate({
id: walletId,
Expand Down Expand Up @@ -333,7 +367,7 @@ export function watchCurrencyWallet(input: CurrencyWalletInput): void {
const changes = getStorageWalletLastChanges(props.state, walletId)
if (changes !== lastChanges) {
lastChanges = changes
await loadEnabledTokensFile(input)
await loadTokensFile(input)
await loadFiatFile(input)
await loadNameFile(input)
await loadTxFileNames(input)
Expand Down
20 changes: 15 additions & 5 deletions src/core/currency/wallet/currency-wallet-cleaners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,22 @@ export function asIntegerString(raw: unknown): string {
// ---------------------------------------------------------------------

/**
* This uses currency codes, since we cannot break the data on disk.
* To fix this one day, we can either migrate to a new file name,
* or we can use `asEither` to switch between this format
* and some new format based on token ID's.
* Old core versions used currency codes instead of tokenId's.
*/
export const asEnabledTokensFile = asArray<string>(asString)
export const asLegacyTokensFile = asArray<string>(asString)

/**
* Stores enabled tokenId's on disk.
*/
export const asTokensFile = asObject({
// All the tokens that the engine should check.
// This includes both manually-enabled tokens and auto-enabled tokens:
enabledTokenIds: asArray(asString),

// These tokenId's have been detected on-chain at least once.
// The user can still remove them from the enabled tokens list.
detectedTokenIds: asArray(asString)
})

export const asTransactionFile = asObject<TransactionFile>({
txid: asString,
Expand Down
78 changes: 55 additions & 23 deletions src/core/currency/wallet/currency-wallet-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import {
} from '../../storage/storage-selectors'
import { combineTxWithFile } from './currency-wallet-api'
import {
asEnabledTokensFile,
asLegacyAddressFile,
asLegacyMapFile,
asLegacyTokensFile,
asLegacyTransactionFile,
asTokensFile,
asTransactionFile,
asWalletFiatFile,
asWalletNameFile,
Expand All @@ -31,31 +32,38 @@ import {
} from './currency-wallet-cleaners'
import { CurrencyWalletInput } from './currency-wallet-pixie'
import { TxFileNames } from './currency-wallet-reducer'
import { currencyCodesToTokenIds } from './enabled-tokens'

const CURRENCY_FILE = 'Currency.json'
const ENABLED_TOKENS_FILE = 'EnabledTokens.json'
const LEGACY_MAP_FILE = 'fixedLegacyFileNames.json'
const LEGACY_TOKENS_FILE = 'EnabledTokens.json'
const TOKENS_FILE = 'Tokens.json'
const WALLET_NAME_FILE = 'WalletName.json'

const enabledTokensFile = makeJsonFile(asEnabledTokensFile)
const legacyAddressFile = makeJsonFile(asLegacyAddressFile)
const legacyMapFile = makeJsonFile(asLegacyMapFile)
const legacyTokensFile = makeJsonFile(asLegacyTokensFile)
const legacyTransactionFile = makeJsonFile(asLegacyTransactionFile)
const tokensFile = makeJsonFile(asTokensFile)
const transactionFile = makeJsonFile(asTransactionFile)
const walletFiatFile = makeJsonFile(asWalletFiatFile)
const walletNameFile = makeJsonFile(asWalletNameFile)

/**
* Updates the enabled tokens on a wallet.
*/
export async function changeEnabledTokens(
export async function writeTokensFile(
swansontec marked this conversation as resolved.
Show resolved Hide resolved
input: CurrencyWalletInput,
currencyCodes: string[]
detectedTokenIds: string[],
enabledTokenIds: string[]
): Promise<void> {
const { state, walletId } = input.props
const disklet = getStorageWalletDisklet(state, walletId)

await enabledTokensFile.save(disklet, ENABLED_TOKENS_FILE, currencyCodes)
await tokensFile.save(disklet, TOKENS_FILE, {
detectedTokenIds,
enabledTokenIds
})
}

/**
Expand Down Expand Up @@ -146,23 +154,6 @@ export async function setCurrencyWalletFiat(
})
}

export async function loadEnabledTokensFile(
input: CurrencyWalletInput
): Promise<void> {
const { dispatch, state, walletId } = input.props
const disklet = getStorageWalletDisklet(state, walletId)

const clean = await enabledTokensFile.load(disklet, ENABLED_TOKENS_FILE)
if (clean == null) return

// Future currencyCode to tokenId logic will live here.

dispatch({
type: 'CURRENCY_WALLET_ENABLED_TOKENS_CHANGED',
payload: { walletId: input.props.walletId, currencyCodes: clean }
})
}

/**
* Loads the wallet fiat currency file.
*/
Expand Down Expand Up @@ -219,6 +210,47 @@ export async function loadNameFile(input: CurrencyWalletInput): Promise<void> {
})
}

/**
* Load the enabled tokens file, with fallback to the legacy file.
*/
export async function loadTokensFile(
input: CurrencyWalletInput
): Promise<void> {
const { dispatch, state, walletId } = input.props
const disklet = getStorageWalletDisklet(state, walletId)

const clean = await tokensFile.load(disklet, TOKENS_FILE)
if (clean != null) {
dispatch({
type: 'CURRENCY_WALLET_LOADED_TOKEN_FILE',
payload: { walletId: input.props.walletId, ...clean }
})
return
}

const legacyCurrencyCodes = await legacyTokensFile.load(
disklet,
LEGACY_TOKENS_FILE
)
const { accountId, currencyInfo, pluginId } = input.props.walletState
const accountState = input.props.state.accounts[accountId]
const tokenIds = currencyCodesToTokenIds(
accountState.builtinTokens[pluginId],
accountState.customTokens[pluginId],
currencyInfo,
legacyCurrencyCodes ?? []
)

dispatch({
type: 'CURRENCY_WALLET_LOADED_TOKEN_FILE',
payload: {
walletId: input.props.walletId,
detectedTokenIds: [],
enabledTokenIds: tokenIds
}
})
}

/**
* Loads transaction metadata files.
*/
Expand Down
Loading
Loading