@exodus/address-provider
Advanced tools
Comparing version 11.2.0 to 11.3.0
@@ -6,2 +6,13 @@ # Change Log | ||
## [11.3.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/address-provider@11.2.0...@exodus/address-provider@11.3.0) (2024-07-16) | ||
### Features | ||
- add full key identifier to address meta ([#7595](https://github.com/ExodusMovement/exodus-hydra/issues/7595)) ([ebb8456](https://github.com/ExodusMovement/exodus-hydra/commit/ebb84561f24030af723714a9cf29e4a82fedf6a1)) | ||
### Bug Fixes | ||
- fall back to mocked address for default path params/purpose ([#7684](https://github.com/ExodusMovement/exodus-hydra/issues/7684)) ([e3a00bd](https://github.com/ExodusMovement/exodus-hydra/commit/e3a00bdd38464b75c563f8ad36d260384362e816)) | ||
- use key identifier with enumerable derivation path ([#7854](https://github.com/ExodusMovement/exodus-hydra/issues/7854)) ([afd9653](https://github.com/ExodusMovement/exodus-hydra/commit/afd96533198a870568a83c4ecf03ead17d7797c1)) | ||
## [11.2.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/address-provider@11.1.0...@exodus/address-provider@11.2.0) (2024-07-08) | ||
@@ -8,0 +19,0 @@ |
@@ -95,4 +95,3 @@ import { Address, AddressSet } from '@exodus/models' | ||
const keyId = new KeyIdentifier(keyIdArgs) | ||
const keyIdentifier = new KeyIdentifier(keyIdArgs) | ||
const canUseCache = !asset.api.features?.abstractAccounts | ||
@@ -102,13 +101,16 @@ const cached = | ||
? await this.#addressCache.get({ | ||
walletAccountName: walletAccount.toString(), | ||
walletAccountName, | ||
baseAssetName: asset.baseAsset.name, | ||
derivationPath: keyId.derivationPath, | ||
derivationPath: keyIdentifier.derivationPath, | ||
}) | ||
: undefined | ||
const { purpose: derivedPurpose } = parseDerivationPath(keyId.derivationPath) | ||
const { purpose: derivedPurpose } = parseDerivationPath(keyIdentifier.derivationPath) | ||
if (cached) { | ||
const path = createPath({ chainIndex, addressIndex }) | ||
return Address.fromJSON({ meta: { path, purpose: derivedPurpose }, ...cached }) | ||
return Address.fromJSON({ | ||
meta: { path, purpose: derivedPurpose, keyIdentifier, walletAccount: walletAccountName }, | ||
...cached, | ||
}) | ||
} | ||
@@ -119,3 +121,3 @@ | ||
asset, | ||
keyIdentifier: keyId, | ||
keyIdentifier, | ||
purpose: derivedPurpose, | ||
@@ -135,3 +137,3 @@ chainIndex, | ||
baseAssetName: asset.baseAsset.name, | ||
derivationPath: keyId.derivationPath, | ||
derivationPath: keyIdentifier.derivationPath, | ||
address, | ||
@@ -152,2 +154,4 @@ }) | ||
}) => { | ||
const walletAccountName = walletAccount.toString() | ||
if (asset.api.features?.abstractAccounts) { | ||
@@ -157,3 +161,5 @@ const accountName = await this.#getMainAccountName({ walletAccount, assetName: asset.name }) | ||
path: createPath({ chainIndex, addressIndex }), | ||
walletAccount: walletAccountName, | ||
purpose, | ||
keyIdentifier, | ||
}) | ||
@@ -163,3 +169,3 @@ } | ||
const publicKey = await this.#publicKeyProvider.getPublicKey({ | ||
walletAccount: walletAccount.toString(), | ||
walletAccount: walletAccountName, | ||
keyIdentifier, | ||
@@ -181,3 +187,3 @@ }) | ||
stakingPublicKey = await this.#publicKeyProvider.getPublicKey({ | ||
walletAccount: walletAccount.toString(), | ||
walletAccount: walletAccountName, | ||
keyIdentifier: stakingKeyIdentifier, | ||
@@ -192,3 +198,5 @@ }) | ||
path: createPath({ chainIndex, addressIndex }), | ||
walletAccount: walletAccountName, | ||
purpose, | ||
keyIdentifier, | ||
}) | ||
@@ -205,8 +213,9 @@ } | ||
}) => { | ||
const walletAccountName = walletAccount.toString() | ||
const [publicKey, multisigData] = await Promise.all([ | ||
this.#publicKeyProvider.getPublicKey({ | ||
walletAccount: walletAccount.toString(), | ||
walletAccount: walletAccountName, | ||
keyIdentifier, | ||
}), | ||
this.#multisigAtom.get().then((s) => s[walletAccount.toString()]), | ||
this.#multisigAtom.get().then((s) => s[walletAccountName]), | ||
]) | ||
@@ -235,3 +244,5 @@ | ||
path: createPath({ chainIndex, addressIndex }), | ||
walletAccount: walletAccountName, | ||
purpose, | ||
keyIdentifier, | ||
spendingInfo: { | ||
@@ -238,0 +249,0 @@ redeem, |
@@ -66,3 +66,3 @@ import typeforce from '@exodus/typeforce' | ||
const asset = this.#getAsset(assetName) | ||
return getAddressesFromTxLog({ asset, txLog }) | ||
return getAddressesFromTxLog({ asset, txLog, walletAccount }) | ||
}) | ||
@@ -69,0 +69,0 @@ |
import addressCacheMemoryModuleDefinition from '@exodus/address-cache/module/memory' | ||
import { set, isNil } from '@exodus/basic-utils' | ||
import assert from 'minimalistic-assert' | ||
import { Address, WalletAccount } from '@exodus/models' | ||
import lodash from 'lodash' | ||
import { isNil } from '@exodus/basic-utils' | ||
import { get, merge } from 'lodash' | ||
import { AddressProvider } from './address-provider' | ||
import { createPath } from './utils' | ||
import { getDefaultPathIndexes } from '../utils' | ||
import KeyIdentifier from '@exodus/key-identifier' | ||
const { get, merge } = lodash | ||
const getPath = ({ walletAccount, asset, purpose, chainIndex, addressIndex }) => [ | ||
walletAccount.toString(), | ||
asset.baseAsset?.name || asset.name, | ||
[purpose, chainIndex, addressIndex].join('/'), | ||
'address', | ||
] | ||
class MockableAddressProvider extends AddressProvider { | ||
@@ -33,90 +24,131 @@ #assetsModule | ||
async #getMockAddress({ purpose, assetName, walletAccount, chainIndex, addressIndex }) { | ||
const hasIndex = !isNil(addressIndex) && !isNil(chainIndex) | ||
const hasPurpose = !isNil(purpose) | ||
const asset = this.#getAsset(assetName) | ||
const walletAccountName = walletAccount.toString() | ||
const mockAddresses = await this.#storage.get('mockableAddressProvider') | ||
return get( | ||
mockAddresses?.addresses, | ||
getPath({ walletAccount, asset, purpose, chainIndex, addressIndex }) | ||
const keyIdentifier = new KeyIdentifier( | ||
asset.baseAsset.api.getKeyIdentifier({ | ||
purpose, | ||
accountIndex: walletAccount.index, | ||
chainIndex, | ||
addressIndex, | ||
compatibilityMode: walletAccount.compatibilityMode, | ||
}) | ||
) | ||
if (hasPurpose && hasIndex) { | ||
const address = get(mockAddresses?.addresses, [ | ||
walletAccountName, | ||
asset.baseAsset?.name || assetName, | ||
`${purpose}/${chainIndex}/${addressIndex}`, | ||
'address', | ||
]) | ||
if (address) { | ||
return new Address(address, { | ||
path: createPath({ chainIndex, addressIndex }), | ||
purpose, | ||
walletAccount: walletAccountName, | ||
keyIdentifier, | ||
}) | ||
} | ||
} | ||
if (hasPurpose) { | ||
const address = get(mockAddresses?.addresses, [ | ||
walletAccountName, | ||
asset.baseAsset?.name || assetName, | ||
`${purpose}`, | ||
'address', | ||
]) | ||
if (address) | ||
return new Address(address, { | ||
purpose, | ||
walletAccount: walletAccountName, | ||
keyIdentifier, | ||
}) | ||
} | ||
const address = get(mockAddresses?.addresses, [ | ||
walletAccountName, | ||
asset.baseAsset?.name || assetName, | ||
'address', | ||
]) | ||
if (address) { | ||
return new Address(address, { | ||
purpose: purpose ?? (await this.getDefaultPurpose({ walletAccount, assetName })), | ||
walletAccount: walletAccountName, | ||
keyIdentifier, | ||
}) | ||
} | ||
} | ||
#normalizeOptions = async (opts) => { | ||
let { | ||
walletAccount = WalletAccount.DEFAULT_NAME, | ||
purpose, | ||
assetName, | ||
chainIndex, | ||
addressIndex, | ||
} = opts | ||
getAddress = async (opts) => { | ||
let { purpose, assetName, walletAccount, chainIndex, addressIndex } = opts | ||
assert(!isNil(walletAccount), `need to specify a walletAccount`) | ||
assert(!isNil(assetName), `need to specify an asset`) | ||
// not required with some compatibility modes, e.g. 'xverse' | ||
assert( | ||
!isNil(chainIndex) || isNil(addressIndex), | ||
'chainIndex must be provided if addressIndex is provided' | ||
'provide both "chainIndex" and "addressIndex" or neither' | ||
) | ||
const mockAddress = await this.#getMockAddress(opts) | ||
if (mockAddress) return mockAddress | ||
purpose = purpose ?? (await this.getDefaultPurpose({ walletAccount, assetName })) | ||
if (isNil(addressIndex) && isNil(chainIndex)) { | ||
const asset = this.#getAsset(assetName) | ||
;({ chainIndex, addressIndex } = getDefaultPathIndexes({ asset })) | ||
} | ||
return { purpose, assetName, walletAccount, chainIndex, addressIndex } | ||
return super.getAddress({ ...opts, purpose }) | ||
} | ||
getAddress = async (opts) => { | ||
opts = await this.#normalizeOptions(opts) | ||
const mockAddressString = await this.#getMockAddress(opts) | ||
mockAddress = async ({ | ||
walletAccount = WalletAccount.DEFAULT_NAME, | ||
assetName, | ||
address, | ||
purpose, | ||
chainIndex, | ||
addressIndex, | ||
}) => { | ||
assert(!isNil(walletAccount), `need to specify a walletAccount`) | ||
assert(!isNil(assetName), `need to specify an asset`) | ||
assert(!isNil(address), `need to specify an address`) | ||
assert( | ||
!isNil(chainIndex) || isNil(addressIndex), | ||
'chainIndex must be provided if addressIndex is provided' | ||
) | ||
const hasIndex = !isNil(addressIndex) && !isNil(chainIndex) | ||
const hasPurpose = !isNil(purpose) | ||
if (mockAddressString) { | ||
const { purpose, chainIndex, addressIndex } = opts | ||
return new Address(mockAddressString, { | ||
path: createPath({ chainIndex, addressIndex }), | ||
purpose, | ||
}) | ||
const currentConfig = await this.#storage.get('mockableAddressProvider') | ||
let newAddress | ||
if (hasPurpose && hasIndex) { | ||
const key = `${purpose}/${chainIndex}/${addressIndex}` | ||
newAddress = { [walletAccount]: { [assetName]: { [key]: { address } } } } | ||
} else if (hasPurpose) { | ||
newAddress = { [walletAccount]: { [assetName]: { [purpose]: { address } } } } | ||
} else { | ||
newAddress = { [walletAccount]: { [assetName]: { address } } } | ||
} | ||
return super.getAddress(opts) | ||
const newConfig = merge({}, currentConfig, { addresses: newAddress }) | ||
await this.#storage.set('mockableAddressProvider', newConfig) | ||
} | ||
mockAddress = async ({ address, ...opts }) => { | ||
const { | ||
walletAccount = WalletAccount.DEFAULT_NAME, | ||
assetName, | ||
purpose, | ||
chainIndex, | ||
addressIndex, | ||
} = await this.#normalizeOptions(opts) | ||
const asset = this.#getAsset(assetName) | ||
const currentConfig = | ||
(await this.#storage.get('mockableAddressProvider')) ?? Object.create(null) | ||
const newConfig = { addresses: Object.create(null) } | ||
set( | ||
newConfig.addresses, | ||
getPath({ walletAccount, asset, purpose, chainIndex, addressIndex }), | ||
address.toString() | ||
) | ||
await this.#storage.set( | ||
'mockableAddressProvider', | ||
merge(Object.create(null), currentConfig, newConfig) | ||
) | ||
} | ||
importReport = async (report) => { | ||
const addresses = Object.create(null) | ||
for (const walletAccount in report) { | ||
addresses[walletAccount] = Object.create(null) | ||
for (const assetName in report[walletAccount]) { | ||
const asset = this.#getAsset(assetName) | ||
addresses[walletAccount][assetName] = Object.create(null) | ||
for (const bip in report[walletAccount][assetName]) { | ||
const { address, chain: [chainIndex, addressIndex] = [0, 0] } = | ||
report[walletAccount][assetName][bip] | ||
const { address } = report[walletAccount][assetName][bip] | ||
const purposeStr = bip.replace(/^bip/, '') | ||
if (isNaN(purposeStr)) throw new Error(`Invalid bip: ${bip}`) | ||
const purpose = parseInt(purposeStr, 10) | ||
const path = getPath({ walletAccount, asset, purpose, chainIndex, addressIndex }) | ||
set(addresses, path, address) | ||
// The report only has a single address per purpose. | ||
// This will cause all chainIndex/addressIndex pairs to fall back to the same address. | ||
addresses[walletAccount][assetName][purposeStr] = { address } | ||
} | ||
@@ -123,0 +155,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import { AddressSet } from '@exodus/models' | ||
import { Address, AddressSet } from '@exodus/models' | ||
import lodash from 'lodash' | ||
@@ -6,2 +6,3 @@ import assert from 'minimalistic-assert' | ||
import { isNil } from '@exodus/basic-utils' | ||
import KeyIdentifier from '@exodus/key-identifier' | ||
@@ -47,3 +48,3 @@ const { groupBy } = lodash | ||
export const getAddressesFromTxLog = ({ asset, txLog }) => { | ||
export const getAddressesFromTxLog = ({ asset, txLog, walletAccount }) => { | ||
const changeAddresses = AddressSet.fromArray( | ||
@@ -57,8 +58,27 @@ [...txLog].map((tx) => tx.data?.changeAddress).filter(Boolean) | ||
return [...allAddresses].map((address) => ({ | ||
address, | ||
purpose: resolvePurpose({ asset, address }), | ||
chainIndex: address.pathArray[0], | ||
addressIndex: address.pathArray[1], | ||
})) | ||
return [...allAddresses].map((address) => { | ||
const purpose = resolvePurpose({ asset, address }) | ||
const chainIndex = address.pathArray[0] | ||
const addressIndex = address.pathArray[1] | ||
return { | ||
address: new Address(address.toString(), { | ||
...address.meta, | ||
purpose, | ||
walletAccount: walletAccount.toString(), | ||
keyIdentifier: new KeyIdentifier( | ||
asset.baseAsset.api.getKeyIdentifier({ | ||
purpose, | ||
accountIndex: walletAccount.index, | ||
chainIndex, | ||
addressIndex, | ||
compatibilityMode: walletAccount.compatibilityMode, | ||
}) | ||
), | ||
}), | ||
purpose, | ||
chainIndex, | ||
addressIndex, | ||
} | ||
}) | ||
} | ||
@@ -65,0 +85,0 @@ |
{ | ||
"name": "@exodus/address-provider", | ||
"version": "11.2.0", | ||
"version": "11.3.0", | ||
"description": "Address provider for deriving and tracking used and unused addresses.", | ||
@@ -39,3 +39,3 @@ "author": "Exodus Movement, Inc.", | ||
"@exodus/bip32": "^2.0.0", | ||
"@exodus/key-identifier": "^1.0.0", | ||
"@exodus/key-identifier": "^1.2.1", | ||
"@exodus/key-utils": "^3.3.0", | ||
@@ -78,3 +78,3 @@ "@exodus/models": "^11.13.0", | ||
"@exodus/monero-lib": "^6.4.0", | ||
"@exodus/public-key-provider": "^2.2.2", | ||
"@exodus/public-key-provider": "^2.3.1", | ||
"@exodus/ripple-lib": "^2.9.6", | ||
@@ -90,3 +90,3 @@ "@exodus/solana-lib": "^2.0.0", | ||
}, | ||
"gitHead": "6eb57a7122a8f6751a018d6135e9d8088f86d010" | ||
"gitHead": "7dbd2025af2c73ae13960b1fbaa10998c4a69e98" | ||
} |
71939
1189