View on GitHub
File Changes
  - &restore_cache
      restore_cache:
        keys:
-
          - generic-tools-cache-{{ checksum "package-lock.json" }}
+
          - generic-tools-cache-{{ checksum "package.json" }}-{{ checksum "package-lock.json" }}
  - &save_cache
      save_cache:
        paths:
         - ~/.npm
         - ~/.rustup
         - ~/.cargo
-
        key: generic-tools-cache-{{ checksum "package-lock.json" }}
+
        key: generic-tools-cache-{{ checksum "package.json" }}-{{ checksum "package-lock.json" }}
  - &setenv
      run: bash .circleci/set_dynamic_env.sh
  - &global_environment
r
+0/-116
-
{
-
  "parser": "@przemyslawzalewski/babel-eslint",
-
  "parserOptions": {
-
    "ecmaVersion": 6,
-
    "sourceType": "module",
-
    "ecmaFeatures": {
-
      "legacyDecorators": true
-
    }
-
},
-
  "extends": "airbnb",
-
  "env": {
-
    "browser": true,
-
    "mocha": true,
-
    "node": true,
-
    "jest": true
-
  },
-
  "rules": {
-
    "func-names": "off",
-
    "new-cap": "off",
-
    "arrow-parens": ["off"],
-
    "consistent-return": "off",
-
    "comma-dangle": "off",
-
    "generator-star-spacing": "off",
-
    "import/no-unresolved": ["error", { "ignore": ["electron"] }],
-
    "import/no-extraneous-dependencies": "off",
-
    "import/no-dynamic-require": "off",
-
    "import/no-named-as-default": "off",
-
    "import/no-named-as-default-member": "off",
-
    "import/prefer-default-export": "off",
-
    "import/order": "off",
-
    "lines-between-class-members": "off",
-
    "no-multi-spaces": "off",
-
    "no-restricted-globals": "off",
-
    "no-restricted-syntax": "off",
-
    "no-return-await": "off",
-
    "no-use-before-define": "off",
-
    "object-curly-newline": "off",
-
    "operator-linebreak": 0,
-
    "prefer-destructuring": 0,
-
    "promise/param-names": 2,
-
    "promise/always-return": 2,
-
    "promise/catch-or-return": 2,
-
    "promise/no-native": 0,
-
    "react/button-has-type": 1,
-
    "react/destructuring-assignment": 0,
-
    "react/no-array-index-key": 1,
-
    "react/jsx-no-bind": "off",
-
    "react/jsx-filename-extension": ["error", { "extensions": [".js", ".jsx"] }],
-
    "react/jsx-closing-bracket-location": 1,
-
    "react/jsx-one-expression-per-line": "off",
-
    "react/jsx-wrap-multilines": "off",
-
    "react/prefer-stateless-function": "off",
-
    "react/no-unused-prop-types": "off",
-
    "react/prop-types": 0,
-
    "react/require-default-props": 1,
-
    "react/sort-comp": 0,
-
    "react/static-property-placement": ["warn", "static public field"],
-
    "react/state-in-constructor": ["warn", "never"],
-
    "react/jsx-props-no-spreading": 0,
-
    "react/jsx-curly-newline": 0,
-
    "class-methods-use-this": 0,
-
    "no-continue": 0,
-
    "no-duplicate-imports": 0,
-
    "no-param-reassign": 0,
-
    "no-plusplus": 0,
-
    "no-bitwise": 0,
-
    "no-underscore-dangle": 0,
-
    "no-console": 0,
-
    "no-mixed-operators": 0,
-
    "no-multi-assign": 0,
-
    "no-undef-init": 0, // need this to improve Flow type inference
-
    "no-unneeded-ternary": ["error", { "defaultAssignment": true }],
-
    "prefer-template": 0,
-
    "no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
-
    "no-trailing-spaces": 1,
-
    "padded-blocks": 0,
-
    "no-undef": 1,
-
    "arrow-body-style": 0,
-
    "key-spacing": 1,
-
    "no-empty-function": 1,
-
    "max-len": 1,
-
    "no-useless-escape": 1,
-
    "prefer-const": 1,
-
    "object-curly-spacing": 1,
-
    "spaced-comment": 1,
-
    "quotes": ["error", "single", {"avoidEscape": true, "allowTemplateLiterals": true}],
-
    "import/imports-first": 1,
-
    "no-multiple-empty-lines": 1,
-
    "react/jsx-indent": 1,
-
    "flowtype/define-flow-type": 1,
-
    "flowtype/use-flow-type": 1,
-
    "flowtype/require-valid-file-annotation": 1,
-
    "global-require": "off",
-
    "no-await-in-loop": 0,
-
    "no-unused-expressions": 0,
-
    "no-lone-blocks": 0,
-
    "import/no-cycle": 0, // doesn't work with Flow unfortunately
-
    "max-classes-per-file": 0,
-
    "no-floating-promise/no-floating-promise": 2
-
  },
-
  "plugins": [
-
    "import",
-
    "promise",
-
    "react",
-
    "flowtype",
-
    "no-floating-promise"
-
  ],
-
  "globals": {
-
    "chrome": true,
-
    "API": true,
-
    "NETWORK": true,
-
    "MOBX_DEV_TOOLS": true,
-
    "CONFIG": true,
-
    "yoroi": true
-
  }
-
}
a
+118/-0
+
module.exports = {
+
  parser: '@przemyslawzalewski/babel-eslint',
+
  parserOptions: {
+
    ecmaVersion: 6,
+
    sourceType: 'module',
+
    ecmaFeatures: {
+
      legacyDecorators: true
+
    }
+
},
+
  extends: 'airbnb',
+
  env: {
+
    browser: true,
+
    mocha: true,
+
    node: true,
+
    jest: true
+
  },
+
  rules: {
+
    'func-names': 'off',
+
    'new-cap': 'off',
+
    'arrow-parens': ['off'],
+
    'consistent-return': 'off',
+
    'comma-dangle': 'off',
+
    'generator-star-spacing': 'off',
+
    'import/no-unresolved': ['error', {
+
      ignore: ['electron', 'js-chain-libs', 'cardano-wallet-browser']
+
    }],
+
    'import/no-extraneous-dependencies': 'off',
+
    'import/no-dynamic-require': 'off',
+
    'import/no-named-as-default': 'off',
+
    'import/no-named-as-default-member': 'off',
+
    'import/prefer-default-export': 'off',
+
    'import/order': 'off',
+
    'lines-between-class-members': 'off',
+
    'no-multi-spaces': 'off',
+
    'no-restricted-globals': 'off',
+
    'no-restricted-syntax': 'off',
+
    'no-return-await': 'off',
+
    'no-use-before-define': 'off',
+
    'object-curly-newline': 'off',
+
    'operator-linebreak': 0,
+
    'prefer-destructuring': 0,
+
    'promise/param-names': 2,
+
    'promise/always-return': 2,
+
    'promise/catch-or-return': 2,
+
    'promise/no-native': 0,
+
    'react/button-has-type': 1,
+
    'react/destructuring-assignment': 0,
+
    'react/no-array-index-key': 1,
+
    'react/jsx-no-bind': 'off',
+
    'react/jsx-filename-extension': ['error', { extensions: ['.js', '.jsx'] }],
+
    'react/jsx-closing-bracket-location': 1,
+
    'react/jsx-one-expression-per-line': 'off',
+
    'react/jsx-wrap-multilines': 'off',
+
    'react/prefer-stateless-function': 'off',
+
    'react/no-unused-prop-types': 'off',
+
    'react/prop-types': 0,
+
    'react/require-default-props': 1,
+
    'react/sort-comp': 0,
+
    'react/static-property-placement': ['warn', 'static public field'],
+
    'react/state-in-constructor': ['warn', 'never'],
+
    'react/jsx-props-no-spreading': 0,
+
    'react/jsx-curly-newline': 0,
+
    'class-methods-use-this': 0,
+
    'no-continue': 0,
+
    'no-duplicate-imports': 0,
+
    'no-param-reassign': 0,
+
    'no-plusplus': 0,
+
    'no-bitwise': 0,
+
    'no-underscore-dangle': 0,
+
    'no-console': 0,
+
    'no-mixed-operators': 0,
+
    'no-multi-assign': 0,
+
    'no-undef-init': 0, // need this to improve Flow type inference
+
    'no-unneeded-ternary': ['error', { defaultAssignment: true }],
+
    'prefer-template': 0,
+
    'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
+
    'no-trailing-spaces': 1,
+
    'padded-blocks': 0,
+
    'no-undef': 1,
+
    'arrow-body-style': 0,
+
    'key-spacing': 1,
+
    'no-empty-function': 1,
+
    'max-len': 1,
+
    'no-useless-escape': 1,
+
    'prefer-const': 1,
+
    'object-curly-spacing': 1,
+
    'spaced-comment': 1,
+
    'quotes': ['error', 'single', {avoidEscape: true, allowTemplateLiterals: true}],
+
    'import/imports-first': 1,
+
    'no-multiple-empty-lines': 1,
+
    'react/jsx-indent': 1,
+
    'flowtype/define-flow-type': 1,
+
    'flowtype/use-flow-type': 1,
+
    'flowtype/require-valid-file-annotation': 1,
+
    'global-require': 'off',
+
    'no-await-in-loop': 0,
+
    'no-unused-expressions': 0,
+
    'no-lone-blocks': 0,
+
    'import/no-cycle': 0, // doesn't work with Flow unfortunately
+
    'max-classes-per-file': 0,
+
    'no-floating-promise/no-floating-promise': 2
+
  },
+
  plugins: [
+
    'import',
+
    'promise',
+
    'react',
+
    'flowtype',
+
    'no-floating-promise'
+
  ],
+
  globals: {
+
    chrome: true,
+
    API: true,
+
    NETWORK: true,
+
    MOBX_DEV_TOOLS: true,
+
    CONFIG: true,
+
    yoroi: true
+
  }
+
}
  flattenInsertTree,
  Bip44DerivationLevels,
} from './lib/storage/database/walletTypes/bip44/api/utils';
+
import type { CoreAddressT } from './lib/storage/database/primitives/enums';
import {
  PublicDeriver,
} from './lib/storage/models/PublicDeriver/index';
  UnusedAddressesError,
} from '../common';
import LocalizableError from '../../i18n/LocalizableError';
-
import { scanAccountByVersion, } from './restoreAdaWallet';
+
import { scanBip44Account, } from './restoration/byron/scan';
import type {
  BaseSignRequest,
  UnsignedTxResponse,

                      
export type GetAllAddressesForDisplayRequest = {
  publicDeriver: IPublicDeriver & IGetAllUtxos,
+
  type: CoreAddressT,
};
export type GetAllAddressesForDisplayResponse = Array<{|
  ...Address, ...Value, ...Addressing, ...UsedStatus
export type GetChainAddressesForDisplayRequest = {
  publicDeriver: IPublicDeriver & IHasChains & IDisplayCutoff,
  chainsRequest: IHasChainsRequest,
+
  type: CoreAddressT,
};
export type GetChainAddressesForDisplayResponse = Array<{|
  ...Address, ...Value, ...Addressing, ...UsedStatus
      RustModule.WalletV2.AccountIndex.new(request.accountIndex)
    );
    try {
-
      const reverseAddressLookup = new Map<number, string>();
-

                      
      // need this to persist outside the scope of the hashToIds lambda
      // since the lambda is called multiple times
      // and we need keep a globally unique index
-
      const foundAddresses = new Map<string, number>();
-
      const insertTree = await scanAccountByVersion({
+
      const reverseAddressLookup = new Map<number, Array<string>>();
+
      const foundAddresses = new Set<string>();
+

                      
+
      // TODO: this is using legacy scanning. Need an option for legacy vs shelley
+
      const insertTree = await scanBip44Account({
        accountPublicKey: accountKey.key().public().to_hex(),
        lastUsedInternal: -1,
        lastUsedExternal: -1,
        checkAddressesInUse,
-
        hashToIds: (addresses) => {
-
          for (const address of addresses) {
-
            if (!foundAddresses.has(address)) {
-
              reverseAddressLookup.set(foundAddresses.size, address);
-
              foundAddresses.set(address, foundAddresses.size);
+
        addByHash: (address) => {
+
          if (!foundAddresses.has(address.address.data)) {
+
            let family = reverseAddressLookup.get(address.keyDerivationId);
+
            if (family == null) {
+
              family = [];
+
              reverseAddressLookup.set(address.keyDerivationId, family);
            }
+
            family.push(address.address.data);
+
            foundAddresses.add(address.address.data);
          }
-
          return Promise.resolve(foundAddresses);
+
          return Promise.resolve();
        },
        protocolMagic,
      });
      const flattenedTree = flattenInsertTree(insertTree);

                      
-
      const addresses = flattenedTree.map(leaf => {
-
        const address = reverseAddressLookup.get(leaf.insert.AddressId);
-
        if (address == null) throw new Error('restoreWalletForTransfer should never happen');
-
        return {
+
      const addressResult = [];
+
      for (let i = 0; i < flattenedTree.length; i++) {
+
        const leaf = flattenedTree[i];
+
        // triggers the insert
+
        await leaf.insert({
+
          // this is done in-memory so no need for a real DB
+
          db: (null: any),
+
          tx: (null: any),
+
          lockedTables: [],
+
          keyDerivationId: i
+
        });
+
        const family = reverseAddressLookup.get(i);
+
        if (family == null) throw new Error('restoreWalletForTransfer should never happen');
+
        const result = family.map(address => ({
          address,
          addressing: {
            startLevel: Bip44DerivationLevels.ACCOUNT.level,
            path: [request.accountIndex].concat(leaf.path),
          },
-
        };
-
      });
+
        }));
+
        addressResult.push(...result);
+
      }

                      
      Logger.debug('AdaApi::restoreWalletForTransfer success');

                      
      return {
        masterKey: rootPk.key().to_hex(),
-
        addresses,
+
        addresses: addressResult,
      };
    } catch (error) {
      Logger.error('AdaApi::restoreWalletForTransfer error: ' + stringifyError(error));

                      
import { RustModule } from './rustLoader';
import type { GenerateAddressFunc } from '../adaAddressProcessing';
-
import { v2genAddressBatchFunc } from '../../restoreAdaWallet';
+
import { v2genAddressBatchFunc } from '../../restoration/byron/scan';
import blakejs from 'blakejs';
import crc32 from 'buffer-crc32';
import type { WalletAccountNumberPlate } from '../storage/models/PublicDeriver/interfaces';
// @flow

                      
-
// eslint-disable-next-line
import typeof * as WasmV2 from 'cardano-wallet-browser';
-
// eslint-disable-next-line
import typeof * as WasmV3 from 'js-chain-libs';

                      
class Module {
  _wasmv2: WasmV2;
  _wasmv3: WasmV3;

                      
  async load() {
-
    this._wasmv2 = await import('cardano-wallet-browser'); // eslint-disable-line
-
    this._wasmv3 = await import('js-chain-libs'); // eslint-disable-line
+
    this._wasmv2 = await import('cardano-wallet-browser');
+
    this._wasmv3 = await import('js-chain-libs');
  }

                      
  // Need to expose through a getter to get Flow to detect the type correctly
  const groupsOfAddresses = chunk(addresses, addressesLimit);
  const groupedTxsPromises = groupsOfAddresses.map(apiCall);
  const groupedTxs = await Promise.all(groupedTxsPromises);
-
  // TODO: verify the latest block of each group is still in the blockchain
+
  // Note: all queries belong to the same chain since untilBlock is the same
  return groupedTxs.reduce((accum, chunkTxs) => accum.concat(chunkTxs), []);
}

                      
+
// @flow
+

                      
+
import type { lf$Database, lf$Transaction, } from 'lovefield';
+
import {
+
  AddAddress,
+
} from '../database/primitives/api/write';
+
import {
+
  GetAddress,
+
} from '../database/primitives/api/read';
+
import type {
+
  AddressRow,
+
} from '../database/primitives/tables';
+
import type {
+
  CoreAddressT,
+
} from '../database/primitives/enums';
+
import { RustModule } from '../../cardanoCrypto/rustLoader';
+
import { CoreAddressTypes, } from '../database/primitives/enums';
+
import {
+
  addressToKind,
+
} from './utils';
+
import { getAllTables } from '../database/utils';
+
import type { InsertRequest } from '../database/walletTypes/common/utils';
+

                      
+
export type AddByHashRequest = {|
+
  ...InsertRequest,
+
  address: {|
+
    type: CoreAddressT,
+
    data: string,
+
  |},
+
|};
+
export type AddByHashFunc = AddByHashRequest => Promise<void>;
+
export function rawGenAddByHash(
+
  ownAddressIds: Set<number>,
+
) {
+
  return async (
+
    request: AddByHashRequest
+
  ): Promise<void> => {
+
    const deps = {
+
      GetAddress, AddAddress
+
    };
+
    const depsTables = Array.from(
+
      getAllTables(...Object.keys(deps).map(key => deps[key]))
+
    );
+
    const locked = new Set(request.lockedTables);
+
    for (const table of depsTables) {
+
      if (!locked.has(table)) {
+
        throw new Error('rawGenAddByHash missing lock on ' + table);
+
      }
+
    }
+
    const rows = await deps.GetAddress.getByHash(
+
      request.db, request.tx,
+
      [request.address.data]
+
    );
+
    for (const row of rows) {
+
      if (ownAddressIds.has(row.AddressId)) {
+
        return;
+
      }
+
    }
+
    await deps.AddAddress.addFromCanonicalByHash(
+
      request.db, request.tx,
+
      [{
+
        ...request.address,
+
        keyDerivationId: request.keyDerivationId,
+
      }]
+
    );
+
  };
+
}
+

                      
+
export type HashToIdsRequest = {|
+
  db: lf$Database,
+
  tx: lf$Transaction,
+
  lockedTables: Array<string>,
+
  hashes: Array<string>,
+
|};
+
export type HashToIdsFunc = HashToIdsRequest => Promise<Map<string, number>>;
+
export function rawGenHashToIdsFunc(
+
  ownAddressIds: Set<number>,
+
): HashToIdsFunc {
+
  return async (
+
    request: HashToIdsRequest
+
  ): Promise<Map<string, number>> => {
+
    const deps = {
+
      GetAddress, AddAddress
+
    };
+
    const depsTables = Array.from(
+
      getAllTables(...Object.keys(deps).map(key => deps[key]))
+
    );
+
    const locked = new Set(request.lockedTables);
+
    for (const table of depsTables) {
+
      if (!locked.has(table)) {
+
        throw new Error('rawGenAddByHash missing lock on ' + table);
+
      }
+
    }
+

                      
+
    const dedupedHashes = Array.from(new Set(request.hashes));
+
    const rows = await deps.GetAddress.getByHash(request.db, request.tx, dedupedHashes);
+
    const addressRowMap: Map<string, Array<$ReadOnly<AddressRow>>> = rows.reduce(
+
      (map, nextElement) => {
+
        const array = map.get(nextElement.Hash) || [];
+
        map.set(
+
          nextElement.Hash,
+
          [...array, nextElement]
+
        );
+
        return map;
+
      },
+
      new Map()
+
    );
+
    const notFound: Array<string> = [];
+
    const finalMapping: Map<string, number> = new Map();
+
    for (const address of dedupedHashes) {
+
      if (addressRowMap.has(address)) {
+
        const ids = addressRowMap.get(address);
+
        if (ids == null) throw new Error('should never happen');
+
        const ownId = ids.filter(id => ownAddressIds.has(id.AddressId));
+
        if (ownId.length > 1) {
+
          throw new Error('Address associated multiple times with same wallet');
+
        }
+
        if (ownId.length === 1) {
+
          finalMapping.set(address, ownId[0].AddressId);
+
          continue;
+
        }
+
        // length = 0
+
        notFound.push(address);
+
      } else {
+
        notFound.push(address);
+
      }
+
    }
+
    const notFoundWithoutCanonical = [];
+
    const addressWithType = notFound.map(addr => ({
+
      data: addr,
+
      type: addressToKind(addr),
+
    }));
+
    for (const address of addressWithType) {
+
      if (address.type !== CoreAddressTypes.SHELLEY_GROUP) {
+
        notFoundWithoutCanonical.push(address);
+
      } else {
+
        // for group addresses we have to look at the payment key
+
        // to see if there exists a canonical address
+
        const wasmAddress = RustModule.WalletV3.Address.from_bytes(
+
          Buffer.from(address.data, 'hex')
+
        );
+
        const groupAddress = wasmAddress.to_group_address();
+
        if (groupAddress == null) throw new Error('rawGenHashToIdsFunc Should never happen');
+
        const canonical = RustModule.WalletV3.Address.single_from_public_key(
+
          groupAddress.get_spending_key(),
+
          wasmAddress.get_discrimination()
+
        );
+
        const hash = Buffer.from(canonical.as_bytes()).toString('hex');
+
        // TODO: make this batched
+
        const addressRows = (await deps.GetAddress.getByHash(request.db, request.tx, [hash]))
+
          .filter(addressRow => ownAddressIds.has(addressRow.AddressId));
+

                      
+
        if (addressRows.length > 1) {
+
          throw new Error('rawGenHashToIdsFunc Should never happen multi-match');
+
        } else if (addressRows.length === 0) {
+
          notFoundWithoutCanonical.push(address);
+
        } else {
+
          const keyDerivationId = await deps.GetAddress.getKeyForFamily(
+
            request.db, request.tx,
+
            addressRows[0].AddressId
+
          );
+
          if (keyDerivationId == null) throw new Error('rawGenHashToIdsFunc Should never happen no mapping');
+
          const newAddr = await deps.AddAddress.addFromCanonicalByHash(request.db, request.tx, [{
+
            keyDerivationId,
+
            data: address.data,
+
            type: CoreAddressTypes.SHELLEY_GROUP,
+
          }]);
+
          finalMapping.set(address.data, newAddr[0].AddressId);
+
          ownAddressIds.add(newAddr[0].AddressId);
+
        }
+
      }
+
    }
+
    // note: must be foreign
+
    // because we should have synced address history before ever calling this
+
    const newEntries = await deps.AddAddress.addForeignByHash(
+
      request.db, request.tx,
+
      addressWithType
+
    );
+
    for (let i = 0; i < newEntries.length; i++) {
+
      finalMapping.set(notFound[i], newEntries[i].AddressId);
+
    }
+
    return finalMapping;
+
  };
+
}
        "AddressId": 1,
        "Digest": 2.625159121708784e-245,
        "Hash": "2cWKMJemoBahV1J8CooPHv6kiRwvURBHKH6x8qNyjXLEHQLPc36Z4Hz9pBHbhB2wdDS5Q",
+
        "Type": 0,
      },
      Object {
        "AddressId": 2,
        "Digest": 5.722525766106571e-175,
        "Hash": "2cWKMJemoBajVVEZhJEsjNHKgjsTsi9kWqejTQEghsNyg4fXD66D3YWvbRhfUVfHWooa7",
+
        "Type": 0,
      },
      Object {
        "AddressId": 3,
        "Digest": -3.219045380546553e-79,
        "Hash": "2cWKMJemoBajyD34hiLDYDKM4jDY65zcPQH9RfW1KyVURUWQtfrAtVQYKyBysMjFTiUUj",
+
        "Type": 0,
      },
      Object {
        "AddressId": 4,
        "Digest": -1.2072712420728654e-129,
        "Hash": "2cWKMJemoBajQf5KTfA8htULcbNh6ad6Do1UVeCEaj8PAhEDVsqq1zyDFrWn3gHwqbXWZ",
+
        "Type": 0,
      },
      Object {
        "AddressId": 5,
        "Digest": 4.604445688743196e+269,
        "Hash": "2cWKMJemoBam9FHms2YNoTSaKGn5xCbN5FRhAa3seKgkfrAYujWrX8PRiFF2jVVMuM455",
+
        "Type": 0,
      },
      Object {
        "AddressId": 6,
        "Digest": -4.4796651863531186e-274,
        "Hash": "2cWKMJemoBakQfaRgK4hzbneQU1cz1VQidgsCzRPNXZzRmFfpW9AoFvmeUdQBZbzhrKwf",
+
        "Type": 0,
      },
      Object {
        "AddressId": 7,
        "Digest": 7.161673823755841e+143,
        "Hash": "2cWKMJemoBajKQwmreignV6bWHMy52VAw2Xg1JNT7DkTX9VSCyQt45vjzu1oHks6aKBQ9",
+
        "Type": 0,
      },
      Object {
        "AddressId": 8,
        "Digest": 6.420156535669085e+72,
        "Hash": "2cWKMJemoBaieRPed6xgtaYeoAvrp8k4NFWeBv4noqYNfjkNX2EY7jkBAA9PbZmuY2fgX",
+
        "Type": 0,
      },
      Object {
        "AddressId": 9,
        "Digest": 3792.7225960259916,
        "Hash": "2cWKMJemoBam3EPRozJ36mHpcHHJuf1F3cTBAH8sN6kPS1HFSxUrPpDedysJKS3GFPss5",
+
        "Type": 0,
      },
      Object {
        "AddressId": 10,
        "Digest": -1.0741353372289304e-84,
        "Hash": "2cWKMJemoBak2H91XpvcvdQztZ4rJSmGg258XMfJANawzWcNcvxVF7WbE8pGQYovNYkcP",
+
        "Type": 0,
      },
      Object {
        "AddressId": 11,
        "Digest": -1.464161111236789e-98,
        "Hash": "2cWKMJemoBaikNBAKubYWG821tgjW8z7Tyi7NCKcZAuUgpXYEyb8yv6vhJ4jtEtGkfpRa",
+
        "Type": 0,
      },
      Object {
        "AddressId": 12,
        "Digest": 1.9191719756549796e+224,
        "Hash": "2cWKMJemoBajjnXhSiczSaPc48WWALFCaqipDmXh5j2KmQsmbW7pkEC8mEmNTefCmcSpT",
+
        "Type": 0,
      },
      Object {
        "AddressId": 13,
        "Digest": 1.1721430977337218e-8,
        "Hash": "2cWKMJemoBajN8htLpD7QC4nBmvUQH2HwPRYktCyFYUTNkQNzRu9qqV3U38yYQRDJpPHr",
+
        "Type": 0,
      },
      Object {
        "AddressId": 14,
        "Digest": 2.0497331866122915e-175,
        "Hash": "2cWKMJemoBahsfMnovpCAHsaF2CWb7ygu2yw9uAdeVRftL8B7hwuwrPDsmRgMGDC2XsxR",
+
        "Type": 0,
      },
      Object {
        "AddressId": 15,
        "Digest": -5.033242376582583e-262,
        "Hash": "2cWKMJemoBajVu74QGDFCe1wrwdNFVRrbKRAW4PnpqHrC7tXveRPsUnxGVScEFj5FWZcL",
+
        "Type": 0,
      },
      Object {
        "AddressId": 16,
        "Digest": -4.5928679679194e+107,
        "Hash": "2cWKMJemoBaiak5D1EHyqFYbpaqULHfKJBLDfFGu67Ze4EVFJSAS4wCqVG8CKCPACZbQL",
+
        "Type": 0,
      },
      Object {
        "AddressId": 17,
        "Digest": 9.040293098460135e-234,
        "Hash": "2cWKMJemoBajgeMibYfj8EdyqFvwbhVr6Xa6TEWub2qWWv1cgadLDzxCfNsuNcLdm4zfQ",
+
        "Type": 0,
      },
      Object {
        "AddressId": 18,
        "Digest": -4.406302555475232e+110,
        "Hash": "2cWKMJemoBahBG4bXa98bYLr2D4hAKrGeBchFSuamemTzKp2x5n79fuXYyVJQZ8D4GxzJ",
+
        "Type": 0,
      },
      Object {
        "AddressId": 19,
        "Digest": -3.864821372571322e-247,
        "Hash": "2cWKMJemoBaivNoG8BqCcU2P6qAjyGbvHbnEycQ219jKjtdnRDG8PTfxkDmynEbaN4aQg",
+
        "Type": 0,
      },
      Object {
        "AddressId": 20,
        "Digest": -1.3868428914272181e-117,
        "Hash": "2cWKMJemoBajxKobGHH4FWX8MYipNcYd8QWU6GJNfam6H7HJ4CJFvv42jqiPayM6skcPs",
+
        "Type": 0,
      },
      Object {
        "AddressId": 21,
        "Digest": 3.042350486593611e+41,
        "Hash": "2cWKMJemoBai5YBzrvj8yR7AxofNVRRQ3HeGZ6ze2jedGJWAZuv1kk83DDHhbM4etgZoX",
+
        "Type": 0,
      },
      Object {
        "AddressId": 22,
        "Digest": 2417189.5135076824,
        "Hash": "2cWKMJemoBakV7enMMUGQbWXqBKDN9FStBkBuhW4ygj7VrJZxFCA8PmwuDEvdM5dc3csF",
+
        "Type": 0,
      },
      Object {
        "AddressId": 23,
        "Digest": 1.829585263112195e+294,
        "Hash": "2cWKMJemoBai1bXTT8Lt3dPNtaAmgbfuQU3hU1c5qoveXj11dZypXXkVGxw9Enr1EyQDw",
+
        "Type": 0,
      },
      Object {
        "AddressId": 24,
        "Digest": -1.8230633576816955e-25,
        "Hash": "2cWKMJemoBakDXb35d6UZn6dnZ7cP5dTurMVcHm2GuUzYKvNCtAcboehN7Z6cMGzb52GU",
+
        "Type": 0,
      },
      Object {
        "AddressId": 25,
        "Digest": 4.074532135366891e+207,
        "Hash": "2cWKMJemoBajJqhkoKSDUXKn6XvTmyFP7K2pYhzz6aPZJWDrSoGamoMQDNWgKeJ5Gnb9o",
+
        "Type": 0,
      },
      Object {
        "AddressId": 26,
        "Digest": 1.7622261527484512e-214,
        "Hash": "2cWKMJemoBajkjfuEq85Bet3dvaboabvG8QX3fatHfYaySWF1NM7fubpSQDEGwSp45SCc",
+
        "Type": 0,
      },
      Object {
        "AddressId": 27,
        "Digest": -7.689837290109897e+76,
        "Hash": "2cWKMJemoBamPe6rXgdePGqKJEUVKEDWM3K3X5PzSQR8EcNAR7aYyNjuoCYNmmn1CcLAN",
+
        "Type": 0,
      },
      Object {
        "AddressId": 28,
        "Digest": -3.720058212445789e+98,
        "Hash": "2cWKMJemoBajQ1kxkMDvdRbQinwGKxbwybh482MN1vZMPxZsJPjE19HDD5WuE2TMASs5B",
+
        "Type": 0,
      },
      Object {
        "AddressId": 29,
        "Digest": 1.4153889647676344e+49,
        "Hash": "2cWKMJemoBah9w9PvS31gfSQmKz6d9P6j5VYDpdRNqDpdz7NkWdrFpJgoXdkAz3hodUTv",
+
        "Type": 0,
      },
      Object {
        "AddressId": 30,
        "Digest": 4.492564910453611e+25,
        "Hash": "2cWKMJemoBahtui5egjw6SzdpDEDXZLuqUcjvsx5natYKH1dvA9PQeUpprt6UKewxS56P",
+
        "Type": 0,
      },
      Object {
        "AddressId": 31,
        "Digest": -7.329019949926326e+276,
        "Hash": "2cWKMJemoBaigkuZU1JBwkxPsSwRCwZPYyVdgBxBJy5gYquNEGXWQnwnRopf4huHWuvqC",
+
        "Type": 0,
      },
      Object {
        "AddressId": 32,
        "Digest": -2.2186464530075643e-207,
        "Hash": "2cWKMJemoBaiNE2gK34iCPDzNcceSLXu9rP3sFhd95fB43Gim14UjTSVARgGAY6PCi7FM",
+
        "Type": 0,
      },
      Object {
        "AddressId": 33,
        "Digest": -8.877983143732731e-109,
        "Hash": "2cWKMJemoBaiyeqzjoMZXQwSQzDf4krS7ZYtqDDTxqxXtPHyW18eVSjfmUoMPqC9moHL2",
+
        "Type": 0,
      },
      Object {
        "AddressId": 34,
        "AddressId": 1,
        "Digest": 2.625159121708784e-245,
        "Hash": "2cWKMJemoBahV1J8CooPHv6kiRwvURBHKH6x8qNyjXLEHQLPc36Z4Hz9pBHbhB2wdDS5Q",
+
        "Type": 0,
      },
      Object {
        "AddressId": 2,
        "Digest": 5.722525766106571e-175,
        "Hash": "2cWKMJemoBajVVEZhJEsjNHKgjsTsi9kWqejTQEghsNyg4fXD66D3YWvbRhfUVfHWooa7",
+
        "Type": 0,
      },
      Object {
        "AddressId": 3,
        "Digest": -3.219045380546553e-79,
        "Hash": "2cWKMJemoBajyD34hiLDYDKM4jDY65zcPQH9RfW1KyVURUWQtfrAtVQYKyBysMjFTiUUj",
+
        "Type": 0,
      },
      Object {
        "AddressId": 4,
        "Digest": -1.2072712420728654e-129,
        "Hash": "2cWKMJemoBajQf5KTfA8htULcbNh6ad6Do1UVeCEaj8PAhEDVsqq1zyDFrWn3gHwqbXWZ",
+
        "Type": 0,
      },
      Object {
        "AddressId": 5,
        "Digest": 4.604445688743196e+269,
        "Hash": "2cWKMJemoBam9FHms2YNoTSaKGn5xCbN5FRhAa3seKgkfrAYujWrX8PRiFF2jVVMuM455",
+
        "Type": 0,
      },
      Object {
        "AddressId": 6,
        "Digest": -4.4796651863531186e-274,
        "Hash": "2cWKMJemoBakQfaRgK4hzbneQU1cz1VQidgsCzRPNXZzRmFfpW9AoFvmeUdQBZbzhrKwf",
+
        "Type": 0,
      },
      Object {
        "AddressId": 7,
        "Digest": 7.161673823755841e+143,
        "Hash": "2cWKMJemoBajKQwmreignV6bWHMy52VAw2Xg1JNT7DkTX9VSCyQt45vjzu1oHks6aKBQ9",
+
        "Type": 0,
      },
      Object {
        "AddressId": 8,
        "Digest": 6.420156535669085e+72,
        "Hash": "2cWKMJemoBaieRPed6xgtaYeoAvrp8k4NFWeBv4noqYNfjkNX2EY7jkBAA9PbZmuY2fgX",
+
        "Type": 0,
      },
      Object {
        "AddressId": 9,
        "Digest": 3792.7225960259916,
        "Hash": "2cWKMJemoBam3EPRozJ36mHpcHHJuf1F3cTBAH8sN6kPS1HFSxUrPpDedysJKS3GFPss5",
+
        "Type": 0,
      },
      Object {
        "AddressId": 10,
        "Digest": -1.0741353372289304e-84,
        "Hash": "2cWKMJemoBak2H91XpvcvdQztZ4rJSmGg258XMfJANawzWcNcvxVF7WbE8pGQYovNYkcP",
+
        "Type": 0,
      },
      Object {
        "AddressId": 11,
        "Digest": -1.464161111236789e-98,
        "Hash": "2cWKMJemoBaikNBAKubYWG821tgjW8z7Tyi7NCKcZAuUgpXYEyb8yv6vhJ4jtEtGkfpRa",
+
        "Type": 0,
      },
      Object {
        "AddressId": 12,
        "Digest": 1.9191719756549796e+224,
        "Hash": "2cWKMJemoBajjnXhSiczSaPc48WWALFCaqipDmXh5j2KmQsmbW7pkEC8mEmNTefCmcSpT",
+
        "Type": 0,
      },
      Object {
        "AddressId": 13,
        "Digest": 1.1721430977337218e-8,
        "Hash": "2cWKMJemoBajN8htLpD7QC4nBmvUQH2HwPRYktCyFYUTNkQNzRu9qqV3U38yYQRDJpPHr",
+
        "Type": 0,
      },
      Object {
        "AddressId": 14,
        "Digest": 2.0497331866122915e-175,
        "Hash": "2cWKMJemoBahsfMnovpCAHsaF2CWb7ygu2yw9uAdeVRftL8B7hwuwrPDsmRgMGDC2XsxR",
+
        "Type": 0,
      },
      Object {
        "AddressId": 15,
        "Digest": -5.033242376582583e-262,
        "Hash": "2cWKMJemoBajVu74QGDFCe1wrwdNFVRrbKRAW4PnpqHrC7tXveRPsUnxGVScEFj5FWZcL",
+
        "Type": 0,
      },
      Object {
        "AddressId": 16,
        "Digest": -4.5928679679194e+107,
        "Hash": "2cWKMJemoBaiak5D1EHyqFYbpaqULHfKJBLDfFGu67Ze4EVFJSAS4wCqVG8CKCPACZbQL",
+
        "Type": 0,
      },
      Object {
        "AddressId": 17,
        "Digest": 9.040293098460135e-234,
        "Hash": "2cWKMJemoBajgeMibYfj8EdyqFvwbhVr6Xa6TEWub2qWWv1cgadLDzxCfNsuNcLdm4zfQ",
+
        "Type": 0,
      },
      Object {
        "AddressId": 18,
        "Digest": -4.406302555475232e+110,
        "Hash": "2cWKMJemoBahBG4bXa98bYLr2D4hAKrGeBchFSuamemTzKp2x5n79fuXYyVJQZ8D4GxzJ",
+
        "Type": 0,
      },
      Object {
        "AddressId": 19,
        "Digest": -3.864821372571322e-247,
        "Hash": "2cWKMJemoBaivNoG8BqCcU2P6qAjyGbvHbnEycQ219jKjtdnRDG8PTfxkDmynEbaN4aQg",
+
        "Type": 0,
      },
      Object {
        "AddressId": 20,
        "Digest": -1.3868428914272181e-117,
        "Hash": "2cWKMJemoBajxKobGHH4FWX8MYipNcYd8QWU6GJNfam6H7HJ4CJFvv42jqiPayM6skcPs",
+
        "Type": 0,
      },
      Object {
        "AddressId": 21,
        "Digest": 3.042350486593611e+41,
        "Hash": "2cWKMJemoBai5YBzrvj8yR7AxofNVRRQ3HeGZ6ze2jedGJWAZuv1kk83DDHhbM4etgZoX",
+
        "Type": 0,
      },
      Object {
        "AddressId": 22,
        "Digest": 2417189.5135076824,
        "Hash": "2cWKMJemoBakV7enMMUGQbWXqBKDN9FStBkBuhW4ygj7VrJZxFCA8PmwuDEvdM5dc3csF",
+
        "Type": 0,
      },
      Object {
        "AddressId": 23,
        "Digest": 1.829585263112195e+294,
        "Hash": "2cWKMJemoBai1bXTT8Lt3dPNtaAmgbfuQU3hU1c5qoveXj11dZypXXkVGxw9Enr1EyQDw",
+
        "Type": 0,
      },
      Object {
        "AddressId": 24,
        "Digest": -1.8230633576816955e-25,
        "Hash": "2cWKMJemoBakDXb35d6UZn6dnZ7cP5dTurMVcHm2GuUzYKvNCtAcboehN7Z6cMGzb52GU",
+
        "Type": 0,
      },
      Object {
        "AddressId": 25,
        "Digest": 4.074532135366891e+207,
        "Hash": "2cWKMJemoBajJqhkoKSDUXKn6XvTmyFP7K2pYhzz6aPZJWDrSoGamoMQDNWgKeJ5Gnb9o",
+
        "Type": 0,
      },
      Object {
        "AddressId": 26,
        "Digest": 1.7622261527484512e-214,
        "Hash": "2cWKMJemoBajkjfuEq85Bet3dvaboabvG8QX3fatHfYaySWF1NM7fubpSQDEGwSp45SCc",
+
        "Type": 0,
      },
      Object {
        "AddressId": 27,
        "Digest": -7.689837290109897e+76,
        "Hash": "2cWKMJemoBamPe6rXgdePGqKJEUVKEDWM3K3X5PzSQR8EcNAR7aYyNjuoCYNmmn1CcLAN",
+
        "Type": 0,
      },
      Object {
        "AddressId": 28,
        "Digest": -3.720058212445789e+98,
        "Hash": "2cWKMJemoBajQ1kxkMDvdRbQinwGKxbwybh482MN1vZMPxZsJPjE19HDD5WuE2TMASs5B",
+
        "Type": 0,
      },
      Object {
        "AddressId": 29,
        "Digest": 1.4153889647676344e+49,
        "Hash": "2cWKMJemoBah9w9PvS31gfSQmKz6d9P6j5VYDpdRNqDpdz7NkWdrFpJgoXdkAz3hodUTv",
+
        "Type": 0,
      },
      Object {
        "AddressId": 30,
        "Digest": 4.492564910453611e+25,
        "Hash": "2cWKMJemoBahtui5egjw6SzdpDEDXZLuqUcjvsx5natYKH1dvA9PQeUpprt6UKewxS56P",
+
        "Type": 0,
      },
      Object {
        "AddressId": 31,
        "Digest": -7.329019949926326e+276,
        "Hash": "2cWKMJemoBaigkuZU1JBwkxPsSwRCwZPYyVdgBxBJy5gYquNEGXWQnwnRopf4huHWuvqC",
+
        "Type": 0,
      },
      Object {
        "AddressId": 32,
        "Digest": -2.2186464530075643e-207,
        "Hash": "2cWKMJemoBaiNE2gK34iCPDzNcceSLXu9rP3sFhd95fB43Gim14UjTSVARgGAY6PCi7FM",
+
        "Type": 0,
      },
      Object {
        "AddressId": 33,
        "Digest": -8.877983143732731e-109,
        "Hash": "2cWKMJemoBaiyeqzjoMZXQwSQzDf4krS7ZYtqDDTxqxXtPHyW18eVSjfmUoMPqC9moHL2",
+
        "Type": 0,
      },
      Object {
        "AddressId": 34,
        "AddressId": 1,
        "Digest": 2.625159121708784e-245,
        "Hash": "2cWKMJemoBahV1J8CooPHv6kiRwvURBHKH6x8qNyjXLEHQLPc36Z4Hz9pBHbhB2wdDS5Q",
+
        "Type": 0,
      },
      Object {
        "AddressId": 2,
        "Digest": 5.722525766106571e-175,
        "Hash": "2cWKMJemoBajVVEZhJEsjNHKgjsTsi9kWqejTQEghsNyg4fXD66D3YWvbRhfUVfHWooa7",
+
        "Type": 0,
      },
      Object {
        "AddressId": 3,
        "Digest": -3.219045380546553e-79,
        "Hash": "2cWKMJemoBajyD34hiLDYDKM4jDY65zcPQH9RfW1KyVURUWQtfrAtVQYKyBysMjFTiUUj",
+
        "Type": 0,
      },
      Object {
        "AddressId": 4,
        "Digest": -1.2072712420728654e-129,
        "Hash": "2cWKMJemoBajQf5KTfA8htULcbNh6ad6Do1UVeCEaj8PAhEDVsqq1zyDFrWn3gHwqbXWZ",
+
        "Type": 0,
      },
      Object {
        "AddressId": 5,
        "Digest": 4.604445688743196e+269,
        "Hash": "2cWKMJemoBam9FHms2YNoTSaKGn5xCbN5FRhAa3seKgkfrAYujWrX8PRiFF2jVVMuM455",
+
        "Type": 0,
      },
      Object {
        "AddressId": 6,
        "Digest": -4.4796651863531186e-274,
        "Hash": "2cWKMJemoBakQfaRgK4hzbneQU1cz1VQidgsCzRPNXZzRmFfpW9AoFvmeUdQBZbzhrKwf",
+
        "Type": 0,
      },
      Object {
        "AddressId": 7,
        "Digest": 7.161673823755841e+143,
        "Hash": "2cWKMJemoBajKQwmreignV6bWHMy52VAw2Xg1JNT7DkTX9VSCyQt45vjzu1oHks6aKBQ9",
+
        "Type": 0,
      },
      Object {
        "AddressId": 8,
        "Digest": 6.420156535669085e+72,
        "Hash": "2cWKMJemoBaieRPed6xgtaYeoAvrp8k4NFWeBv4noqYNfjkNX2EY7jkBAA9PbZmuY2fgX",
+
        "Type": 0,
      },
      Object {
        "AddressId": 9,
        "Digest": 3792.7225960259916,
        "Hash": "2cWKMJemoBam3EPRozJ36mHpcHHJuf1F3cTBAH8sN6kPS1HFSxUrPpDedysJKS3GFPss5",
+
        "Type": 0,
      },
      Object {
        "AddressId": 10,
        "Digest": -1.0741353372289304e-84,
        "Hash": "2cWKMJemoBak2H91XpvcvdQztZ4rJSmGg258XMfJANawzWcNcvxVF7WbE8pGQYovNYkcP",
+
        "Type": 0,
      },
      Object {
        "AddressId": 11,
        "Digest": -1.464161111236789e-98,
        "Hash": "2cWKMJemoBaikNBAKubYWG821tgjW8z7Tyi7NCKcZAuUgpXYEyb8yv6vhJ4jtEtGkfpRa",
+
        "Type": 0,
      },
      Object {
        "AddressId": 12,
        "Digest": 1.9191719756549796e+224,
        "Hash": "2cWKMJemoBajjnXhSiczSaPc48WWALFCaqipDmXh5j2KmQsmbW7pkEC8mEmNTefCmcSpT",
+
        "Type": 0,
      },
      Object {
        "AddressId": 13,
        "Digest": 1.1721430977337218e-8,
        "Hash": "2cWKMJemoBajN8htLpD7QC4nBmvUQH2HwPRYktCyFYUTNkQNzRu9qqV3U38yYQRDJpPHr",
+
        "Type": 0,
      },
      Object {
        "AddressId": 14,
        "Digest": 2.0497331866122915e-175,
        "Hash": "2cWKMJemoBahsfMnovpCAHsaF2CWb7ygu2yw9uAdeVRftL8B7hwuwrPDsmRgMGDC2XsxR",
+
        "Type": 0,
      },
      Object {
        "AddressId": 15,
        "Digest": -5.033242376582583e-262,
        "Hash": "2cWKMJemoBajVu74QGDFCe1wrwdNFVRrbKRAW4PnpqHrC7tXveRPsUnxGVScEFj5FWZcL",
+
        "Type": 0,
      },
      Object {
        "AddressId": 16,
        "Digest": -4.5928679679194e+107,
        "Hash": "2cWKMJemoBaiak5D1EHyqFYbpaqULHfKJBLDfFGu67Ze4EVFJSAS4wCqVG8CKCPACZbQL",
+
        "Type": 0,
      },
      Object {
        "AddressId": 17,
        "Digest": 9.040293098460135e-234,
        "Hash": "2cWKMJemoBajgeMibYfj8EdyqFvwbhVr6Xa6TEWub2qWWv1cgadLDzxCfNsuNcLdm4zfQ",
+
        "Type": 0,
      },
      Object {
        "AddressId": 18,
        "Digest": -4.406302555475232e+110,
        "Hash": "2cWKMJemoBahBG4bXa98bYLr2D4hAKrGeBchFSuamemTzKp2x5n79fuXYyVJQZ8D4GxzJ",
+
        "Type": 0,
      },
      Object {
        "AddressId": 19,
        "Digest": -3.864821372571322e-247,
        "Hash": "2cWKMJemoBaivNoG8BqCcU2P6qAjyGbvHbnEycQ219jKjtdnRDG8PTfxkDmynEbaN4aQg",
+
        "Type": 0,
      },
      Object {
        "AddressId": 20,
        "Digest": -1.3868428914272181e-117,
        "Hash": "2cWKMJemoBajxKobGHH4FWX8MYipNcYd8QWU6GJNfam6H7HJ4CJFvv42jqiPayM6skcPs",
+
        "Type": 0,
      },
      Object {
        "AddressId": 21,
        "Digest": 3.042350486593611e+41,
        "Hash": "2cWKMJemoBai5YBzrvj8yR7AxofNVRRQ3HeGZ6ze2jedGJWAZuv1kk83DDHhbM4etgZoX",
+
        "Type": 0,
      },
      Object {
        "AddressId": 22,
        "Digest": 2417189.5135076824,
        "Hash": "2cWKMJemoBakV7enMMUGQbWXqBKDN9FStBkBuhW4ygj7VrJZxFCA8PmwuDEvdM5dc3csF",
+
        "Type": 0,
      },
      Object {
        "AddressId": 23,
        "Digest": 1.829585263112195e+294,
        "Hash": "2cWKMJemoBai1bXTT8Lt3dPNtaAmgbfuQU3hU1c5qoveXj11dZypXXkVGxw9Enr1EyQDw",
+
        "Type": 0,
      },
      Object {
        "AddressId": 24,
        "Digest": -1.8230633576816955e-25,
        "Hash": "2cWKMJemoBakDXb35d6UZn6dnZ7cP5dTurMVcHm2GuUzYKvNCtAcboehN7Z6cMGzb52GU",
+
        "Type": 0,
      },
      Object {
        "AddressId": 25,
        "Digest": 4.074532135366891e+207,
        "Hash": "2cWKMJemoBajJqhkoKSDUXKn6XvTmyFP7K2pYhzz6aPZJWDrSoGamoMQDNWgKeJ5Gnb9o",
+
        "Type": 0,
      },
      Object {
        "AddressId": 26,
        "Digest": 1.7622261527484512e-214,
        "Hash": "2cWKMJemoBajkjfuEq85Bet3dvaboabvG8QX3fatHfYaySWF1NM7fubpSQDEGwSp45SCc",
+
        "Type": 0,
      },
      Object {
        "AddressId": 27,
        "Digest": -7.689837290109897e+76,
        "Hash": "2cWKMJemoBamPe6rXgdePGqKJEUVKEDWM3K3X5PzSQR8EcNAR7aYyNjuoCYNmmn1CcLAN",
+
        "Type": 0,
      },
      Object {
        "AddressId": 28,
        "Digest": -3.720058212445789e+98,
        "Hash": "2cWKMJemoBajQ1kxkMDvdRbQinwGKxbwybh482MN1vZMPxZsJPjE19HDD5WuE2TMASs5B",
+
        "Type": 0,
      },
      Object {
        "AddressId": 29,
        "Digest": 1.4153889647676344e+49,
        "Hash": "2cWKMJemoBah9w9PvS31gfSQmKz6d9P6j5VYDpdRNqDpdz7NkWdrFpJgoXdkAz3hodUTv",
+
        "Type": 0,
      },
      Object {
        "AddressId": 30,
        "Digest": 4.492564910453611e+25,
        "Hash": "2cWKMJemoBahtui5egjw6SzdpDEDXZLuqUcjvsx5natYKH1dvA9PQeUpprt6UKewxS56P",
+
        "Type": 0,
      },
      Object {
        "AddressId": 31,
        "Digest": -7.329019949926326e+276,
        "Hash": "2cWKMJemoBaigkuZU1JBwkxPsSwRCwZPYyVdgBxBJy5gYquNEGXWQnwnRopf4huHWuvqC",
+
        "Type": 0,
      },
      Object {
        "AddressId": 32,
        "Digest": -2.2186464530075643e-207,
        "Hash": "2cWKMJemoBaiNE2gK34iCPDzNcceSLXu9rP3sFhd95fB43Gim14UjTSVARgGAY6PCi7FM",
+
        "Type": 0,
      },
      Object {
        "AddressId": 33,
        "Digest": -8.877983143732731e-109,
        "Hash": "2cWKMJemoBaiyeqzjoMZXQwSQzDf4krS7ZYtqDDTxqxXtPHyW18eVSjfmUoMPqC9moHL2",
+
        "Type": 0,
      },
      Object {
        "AddressId": 34,
  RemoteTransaction, RemoteUnspentOutput
} from '../../../state-fetch/types';
import { RollbackApiError, } from '../../../../errors';
-
import { RustModule } from '../../../cardanoCrypto/rustLoader';
+
import { addressToKind } from '../utils';
+
import { CoreAddressTypes } from '../../database/primitives/enums';

                      
export function genCheckAddressesInUse(
  blockchain: Array<RemoteTransaction>,
      for (let j = 0; j < tx.outputs.length; j++) {
        const address = tx.outputs[j].address;
        if (ourAddressSet.has(address)) {
-
          try {
-
            // Need to try parsing as a legacy address first
-
            // Since parsing as bech32 directly may give a wrong result if the address contains a 1
-
            RustModule.WalletV2.Address.from_base58(address);
-
          } catch (_e1) {
-
            try {
-
              const wasmAddr = RustModule.WalletV3.Address.from_string(address);
-
              const txType = wasmAddr.get_discrimination();
-
              if (
-
                txType !== RustModule.WalletV3.AddressKind.Single &&
-
                txType !== RustModule.WalletV3.AddressKind.Group
-
              ) {
-
                throw new Error('genUtxoForAddresses non-utxo address in utxo endpoint');
-
              }
-
            } catch (_e2) {
-
              throw new Error('genUtxoForAddresses Unknown output type');
-
            }
+
          const kind = addressToKind(address);
+
          if (
+
            kind !== CoreAddressTypes.CARDANO_LEGACY &&
+
            kind !== CoreAddressTypes.SHELLEY_SINGLE &&
+
            kind !== CoreAddressTypes.SHELLEY_GROUP
+
          ) {
+
            throw new Error('genUtxoForAddresses non-utxo address in utxo endpoint');
          }
          const key = JSON.stringify({
            id: tx.hash,
import type {
  BlockInsert, BlockRow,
  TransactionInsert, TransactionRow,
-
  TxStatusCodesType,
} from '../database/primitives/tables';
+
import type {
+
  TxStatusCodesType,
+
} from '../database/primitives/enums';
import {
  GetAddress,
  GetBlock,
  GetTransaction,
  GetTxAndBlock,
} from '../database/primitives/api/read';
-
import { GetOrAddAddress, ModifyTransaction, } from '../database/primitives/api/write';
+
import { AddAddress, ModifyTransaction, } from '../database/primitives/api/write';
import { ModifyMultipartTx } from  '../database/transactionModels/multipart/api/write';
import { digetForHash, } from '../database/primitives/api/utils';
import {
  InputTypes,
} from '../../state-fetch/types';
import type {
-
  HashToIdsFunc,
  ToAbsoluteSlotNumberFunc,
} from '../models/utils';
import type {
import type {
  AccountingTransactionInputInsert, AccountingTransactionOutputInsert,
} from '../database/transactionModels/account/tables';
-
import { TxStatusCodes, } from '../database/primitives/tables';
+
import { TxStatusCodes, CoreAddressTypes, } from '../database/primitives/enums';
import {
  asScanAddresses,
} from '../models/common/traits';
import type { DbTxIO, DbTxInChain } from '../database/transactionModels/multipart/tables';
import {
  genToAbsoluteSlotNumber,
-
  rawGenHashToIdsFunc,
  rawGetAddressRowsForWallet,
} from  '../models/utils';
+
import {
+
  rawGenHashToIdsFunc,
+
} from './hashMapper';
+
import type {
+
  HashToIdsFunc,
+
} from './hashMapper';
import { STABLE_SIZE } from '../../../../../config/numbersConfig';
import { RollbackApiError } from '../../../errors';
import { getFromUserPerspective, } from '../../../transactions/utils';

                      
-
import { RustModule } from '../../cardanoCrypto/rustLoader';
import type {
  FilterFunc, HistoryFunc, BestBlockFunc,
  RemoteTxState,
  RemoteTransaction,
} from '../../state-fetch/types';
+
import { addressToKind } from './utils';

                      
export async function rawGetUtxoTransactions(
  db: lf$Database,
    undefined,
    derivationTables,
  );
-
  const addressIds = addresses.map(address => address.addr.AddressId);
+
  const addressIds = addresses.flatMap(address => address.addrs.map(addr => addr.AddressId));
  const txIds = await deps.AssociateTxWithIOs.getTxIdsForAddresses(
    db, dbTx,
    {
      GetAddress,
      GetPathWithSpecific,
      GetUtxoTxOutputsWithTx,
-
      GetOrAddAddress,
+
      AddAddress,
      GetPublicDeriver,
      AddDerivationTree,
      MarkUtxo,
    GetAddress: Class<GetAddress>,
    GetPathWithSpecific: Class<GetPathWithSpecific>,
    GetUtxoTxOutputsWithTx: Class<GetUtxoTxOutputsWithTx>,
-
    GetOrAddAddress: Class<GetOrAddAddress>,
+
    AddAddress: Class<AddAddress>,
    GetPublicDeriver: Class<GetPublicDeriver>,
    AddDerivationTree: Class<AddDerivationTree>,
    MarkUtxo: Class<MarkUtxo>,
          GetAddress: deps.GetAddress,
          GetPathWithSpecific: deps.GetPathWithSpecific,
          GetUtxoTxOutputsWithTx: deps.GetUtxoTxOutputsWithTx,
-
          GetOrAddAddress: deps.GetOrAddAddress,
+
          AddAddress: deps.AddAddress,
          GetPublicDeriver: deps.GetPublicDeriver,
          AddDerivationTree: deps.AddDerivationTree,
          ModifyDisplayCutoff: deps.ModifyDisplayCutoff,

                      
    // 3) get new txs from fetcher

                      
-
    // important: we fetched addresses AFTER scanning for new addresses
+
    // important: get addresses for our wallet AFTER scanning for new addresses
    const {
      utxoAddresses,
      accountingAddresses,
      };
    const txsFromNetwork = await getTransactionsHistoryForAddresses({
      ...requestKind,
-
      // TODO: don't send grop keys
      addresses: [
-
        ...utxoAddresses.map(address => address.Hash),
+
        ...utxoAddresses
+
          // Note: don't send group keys
+
          // Okay to filter them because the payment key is duplicated inside the single addresses
+
          .filter(address => address.Type !== CoreAddressTypes.SHELLEY_GROUP)
+
          .map(address => address.Hash),
        ...accountingAddresses.map(address => address.Hash),
      ],
      untilBlock,
    });

                      
    // 4) save data to local DB
+
    // WARNING: this can also modify the address set
+
    // ex: a new group address is found
    await updateTransactionBatch(
      db,
      dbTx,
        txIds,
        txsFromNetwork,
        hashToIds: rawGenHashToIdsFunc(
-
          db, dbTx,
-
          { GetOrAddAddress: deps.GetOrAddAddress },
          new Set([
            ...utxoAddressIds,
            ...accountingAddressIds,
          ])
        ),
        toAbsoluteSlotNumber,
+
        derivationTables,
      }
    );
  }
    txIds: Array<number>,
    txsFromNetwork: Array<RemoteTransaction>,
    hashToIds: HashToIdsFunc,
+
    derivationTables: Map<number, string>,
  }
): Promise<Array<DbTxInChain>> {
  const { TransactionSeed, BlockSeed } = await deps.GetEncryptionMeta.get(db, dbTx);

                      
  // 2) Add new transactions
  const newTxsForDb = await networkTxToDbTx(
+
    db,
+
    dbTx,
+
    request.derivationTables,
    unseedNewTxs,
    request.hashToIds,
    request.toAbsoluteSlotNumber,
  return txsAddedToBlock;
}

                      
-
export async function networkTxToDbTx(
+
async function networkTxToDbTx(
+
  db: lf$Database,
+
  dbTx: lf$Transaction,
+
  derivationTables: Map<number, string>,
  newTxs: Array<RemoteTransaction>,
  hashToIds: HashToIdsFunc,
  toAbsoluteSlotNumber: ToAbsoluteSlotNumberFunc,
      ...tx.outputs.map(output => output.address),
    ]),
  ));
-
  const idMapping = await hashToIds(allAddresses);
+
  const idMapping = await hashToIds({
+
    db,
+
    tx: dbTx,
+
    lockedTables: Array.from(derivationTables.values()),
+
    hashes: allAddresses
+
  });

                      
  const getIdOrThrow = (hash: string): number => {
    // recall: we know all these ids should already be present
        }
        for (let i = 0; i < networkTx.outputs.length; i++) {
          const output = networkTx.outputs[i];
-
          // assume single type since that's the type of legacy addresses
-
          let txType;
-
          try {
+
// @flow
+

                      
+
import type { CoreAddressT } from '../database/primitives/enums';
+
import { CoreAddressTypes } from '../database/primitives/enums';
+
import { RustModule } from '../../cardanoCrypto/rustLoader';
+

                      
+
export function addressToKind(
+
  address: string
+
): CoreAddressT {
+
  try {
+
    // Need to try parsing as a legacy address first
+
    // Since parsing as bech32 directly may give a wrong result if the address contains a 1
+
    RustModule.WalletV2.Address.from_base58(address);
+
    return CoreAddressTypes.CARDANO_LEGACY;
+
  } catch (_e1) {
+
    try {
+
      const wasmAddr = RustModule.WalletV3.Address.from_bytes(
+
        Buffer.from(address, 'hex')
+
      );
+
      switch (wasmAddr.get_kind()) {
+
        case RustModule.WalletV3.AddressKind.Single: return CoreAddressTypes.SHELLEY_SINGLE;
+
        case RustModule.WalletV3.AddressKind.Group: return CoreAddressTypes.SHELLEY_GROUP;
+
        case RustModule.WalletV3.AddressKind.Account: return CoreAddressTypes.SHELLEY_ACCOUNT;
+
        case RustModule.WalletV3.AddressKind.Multisig: return CoreAddressTypes.SHELLEY_MULTISIG;
+
        default: throw new Error('addressToKind unknown address type ' + address);
+
      }
+
    } catch (_e2) {
+
      throw new Error('addressToKind failed to parse address type ' + address);
+
    }
+
  }
+
}
  addAdhocPublicDeriver: StateConstraint<
    CurrentState,
    HasBip44Wrapper & HasConceptualWallet,
-
    CurrentState => AddAdhocPublicDeriverRequest,
+
    CurrentState => AddAdhocPublicDeriverRequest<any>,
    CurrentState & HasPublicDeriver<mixed>
-
  > = (
-
    request: CurrentState => AddAdhocPublicDeriverRequest,
+
  > = <Insert>(
+
    request: CurrentState => AddAdhocPublicDeriverRequest<Insert>,
  ) => {
    return this.updateData<HasBip44Wrapper & HasConceptualWallet, HasPublicDeriver<mixed>>(
      { publicDeriver: [] },
import type {
  TreeInsert,
} from '../database/walletTypes/common/utils';
+
import type { Bip44ChainInsert } from '../database/walletTypes/common/tables';
import {
-
  GetOrAddAddress,
+
  AddAddress,
} from '../database/primitives/api/write';
+
import { GetAddress } from '../database/primitives/api/read';
import type { KeyInsert } from '../database/primitives/tables';
import type { HWFeatures, } from '../database/walletTypes/core/tables';

                      
  HasPublicDeriver,
  HasRoot,
} from './walletBuilder';
+
import type { AddByHashFunc } from './hashMapper';
+
import { rawGenAddByHash } from './hashMapper';
+
import { addByronAddress } from '../../../restoration/byron/scan';

                      

                      
// TODO: maybe move this inside walletBuilder somehow so it's all done in the same transaction
+
/**
+
 * We generate addresses here instead of relying on scanning functions
+
 * This is because scanning depends on having an internet connection
+
 * But we need to ensure the address maintains the BIP44 gap regardless of internet connection
+
 */
export async function getAccountDefaultDerivations(
  settings: RustModule.WalletV2.BlockchainSettings,
  accountPublicKey: RustModule.WalletV2.Bip44AccountPublic,
-
  hashToIds: (addressHash: Array<string>) => Promise<Array<number>>,
-
): Promise<TreeInsert< { DisplayCutoff: null | number }>> {
+
  addByHash: AddByHashFunc,
+
): Promise<TreeInsert<Bip44ChainInsert>> {
  const addressesIndex = range(
    0,
    BIP44_SCAN_SIZE
  );

                      
-
  const externalIds = await hashToIds(
-
    addressesIndex.map(i => (
-
      accountPublicKey
-
        .bip44_chain(false)
-
        .address_key(RustModule.WalletV2.AddressKeyIndex.new(i))
-
        .bootstrap_era_address(settings).to_base58()
-
    ))
-
  );
-
  const internalIds = await hashToIds(
-
    addressesIndex.map(i => (
-
      accountPublicKey
-
        .bip44_chain(true)
-
        .address_key(RustModule.WalletV2.AddressKeyIndex.new(i))
-
        .bootstrap_era_address(settings).to_base58()
-
    ))
-
  );
+
  const externalAddrs = addressesIndex.map(i => (
+
    accountPublicKey
+
      .bip44_chain(false)
+
      .address_key(RustModule.WalletV2.AddressKeyIndex.new(i))
+
      .bootstrap_era_address(settings).to_base58()
+
  ));
+
  const internalAddrs = addressesIndex.map(i => (
+
    accountPublicKey
+
      .bip44_chain(true)
+
      .address_key(RustModule.WalletV2.AddressKeyIndex.new(i))
+
      .bootstrap_era_address(settings).to_base58()
+
  ));
  /**
   * Even if the user has no internet connection and scanning fails,
   * we need to initialize our wallets with the bip44 gap size directly
   */
  const externalAddresses = addressesIndex.map(i => ({
    index: i,
-
    insert: {
-
      AddressId: externalIds[i],
-
    }
+
    insert: async insertRequest => {
+
      return await addByronAddress(
+
        addByHash,
+
        insertRequest,
+
        externalAddrs[i]
+
      );
+
    },
  }));
  const internalAddresses = addressesIndex.map(i => ({
    index: i,
-
    insert: {
-
      AddressId: internalIds[i],
-
    }
+
    insert: async insertRequest => {
+
      return await addByronAddress(
+
        addByHash,
+
        insertRequest,
+
        internalAddrs[i]
+
      );
+
    },
  }));

                      
  return [
    {
      index: 0,
-
      insert: { DisplayCutoff: 0 },
+
      insert: insertRequest => Promise.resolve({
+
        KeyDerivationId: insertRequest.keyDerivationId,
+
        DisplayCutoff: 0
+
      }),
      children: externalAddresses,
    },
    {
      index: 1,
-
      insert: { DisplayCutoff: null },
+
      insert: insertRequest => Promise.resolve({
+
        KeyDerivationId: insertRequest.keyDerivationId,
+
        DisplayCutoff: null,
+
      }),
      children: internalAddresses,
    }
  ];
    RustModule.WalletV2.AccountIndex.new(request.accountIndex)
  ).public();

                      
-
  const deps = Object.freeze({
-
    GetOrAddAddress,
-
  });
-
  const depTables = Object
-
    .keys(deps)
-
    .map(key => deps[key])
-
    .flatMap(table => getAllSchemaTables(request.db, table));
-

                      
-
  // Note: we generate initial addresses in a separate database query
-
  // from creation of the actual wallet
-
  const initialDerivations = await raii(
-
    request.db,
-
    depTables,
-
    async tx => {
-
      const hashToIdFunc = async (
-
        addressHash: Array<string>
-
      ): Promise<Array<number>> => {
-
        const rows = await deps.GetOrAddAddress.addByHash(
-
          request.db, tx,
-
          addressHash
-
        );
-
        return rows.map(row => row.AddressId);
-
      };
-

                      
-
      return await getAccountDefaultDerivations(
-
        request.settings,
-
        accountPublicKey,
-
        hashToIdFunc,
-
      );
-
    }
+
  const initialDerivations = await getAccountDefaultDerivations(
+
    request.settings,
+
    accountPublicKey,
+
    rawGenAddByHash(new Set()),
  );

                      
  const pathToPrivate = []; // private deriver level === root level
              Parent: null,
              Index: null,
            }),
-
            levelInfo: id => ({
-
              KeyDerivationId: id,
+
            levelInfo: insertRequest => Promise.resolve({
+
              KeyDerivationId: insertRequest.keyDerivationId,
            }),
          },
          tree: rootDerivation => ({
  if (request.accountIndex < HARD_DERIVATION_START) {
    throw new Error('createHardwareWallet needs hardened index');
  }
-
  const deps = Object.freeze({
-
    GetOrAddAddress,
-
  });
-
  const depTables = Object
-
    .keys(deps)
-
    .map(key => deps[key])
-
    .flatMap(table => getAllSchemaTables(request.db, table));
-
  // Note: we generate initial addresses in a separate database query
-
  // from creation of the actual wallet
-
  const initialDerivations = await raii(
-
    request.db,
-
    depTables,
-
    async tx => {
-
      const hashToIdFunc = async (
-
        addressHash: Array<string>
-
      ): Promise<Array<number>> => {
-
        const rows = await deps.GetOrAddAddress.addByHash(
-
          request.db, tx,
-
          addressHash
-
        );
-
        return rows.map(row => row.AddressId);
-
      };
-

                      
-
      return await getAccountDefaultDerivations(
  BlockRow,
  EncryptionMetaRow,
  KeyRow,
-
  TxStatusCodesType,
  TransactionRow,
+
  AddressMappingRow,
} from '../tables';
+
import type {
+
  TxStatusCodesType,
+
  CoreAddressT,
+
} from '../enums';
import * as Tables from '../tables';
+
import {
+
  digetForHash,
+
} from './utils';
import { getRowFromKey, getRowIn, StaleStateError, } from '../../utils';

                      
+
export class GetEncryptionMeta {
+
  static ownTables = Object.freeze({
+
    [Tables.EncryptionMetaSchema.name]: Tables.EncryptionMetaSchema,
+
  });
+
  static depTables = Object.freeze({});
+

                      
+
  static async exists(
+
    db: lf$Database,
+
    tx: lf$Transaction,
+
  ): Promise<boolean> {
+
    const row = await getRowFromKey<EncryptionMetaRow>(
+
      db, tx,
+
      0,
+
      GetEncryptionMeta.ownTables[Tables.EncryptionMetaSchema.name].name,
+
      GetEncryptionMeta.ownTables[Tables.EncryptionMetaSchema.name].properties.EncryptionMetaId,
+
    );
+
    return row !== undefined;
+
  }
+

                      
+
  static async get(
+
    db: lf$Database,
+
    tx: lf$Transaction,
+
  ): Promise<$ReadOnly<EncryptionMetaRow>> {
+
    const row = await getRowFromKey<EncryptionMetaRow>(
+
      db, tx,
+
      0,
+
      GetEncryptionMeta.ownTables[Tables.EncryptionMetaSchema.name].name,
+
      GetEncryptionMeta.ownTables[Tables.EncryptionMetaSchema.name].properties.EncryptionMetaId,
+
    );
+
    if (row === undefined) {
+
      throw new Error('GetEncryptionMeta::get no encryption meta found');
+
    }
+
    return row;
+
  }
+
}
+

                      
export class GetKey {
  static ownTables = Object.freeze({
    [Tables.KeySchema.name]: Tables.KeySchema,
export class GetAddress {
  static ownTables = Object.freeze({
    [Tables.AddressSchema.name]: Tables.AddressSchema,
+
    [Tables.AddressMappingSchema.name]: Tables.AddressMappingSchema,
+
  });
+
  static depTables = Object.freeze({
+
    GetEncryptionMeta
  });
-
  static depTables = Object.freeze({});

                      
  static async getById(
    db: lf$Database,
      ids
    );
  }
-
}

                      
-
export class GetEncryptionMeta {
-
  static ownTables = Object.freeze({
-
    [Tables.EncryptionMetaSchema.name]: Tables.EncryptionMetaSchema,
-
  });
-
  static depTables = Object.freeze({});
-

                      
-
  static async exists(
+
  static async getByHash(
    db: lf$Database,
    tx: lf$Transaction,
-
  ): Promise<boolean> {
-
    const row = await getRowFromKey<EncryptionMetaRow>(
+
    addressHash: Array<string>,
+
  ): Promise<$ReadOnlyArray<$ReadOnly<AddressRow>>> {
+
    const { AddressSeed } = await GetAddress.depTables.GetEncryptionMeta.get(db, tx);
+
    const digests = addressHash.map<number>(hash => digetForHash(hash, AddressSeed));
+

                      
+
    const addressRows = await getRowIn<AddressRow>(
      db, tx,
-
      0,
-
      GetEncryptionMeta.ownTables[Tables.EncryptionMetaSchema.name].name,
-
      GetEncryptionMeta.ownTables[Tables.EncryptionMetaSchema.name].properties.EncryptionMetaId,
+
      GetAddress.ownTables[Tables.AddressSchema.name].name,
+
      GetAddress.ownTables[Tables.AddressSchema.name].properties.Digest,
+
      digests
    );
-
    return row !== undefined;
+

                      
+
    return addressRows;
  }

                      
-
  static async get(
+
  static async fromCanonical(
    db: lf$Database,
    tx: lf$Transaction,
-
  ): Promise<$ReadOnly<EncryptionMetaRow>> {
-
    const row = await getRowFromKey<EncryptionMetaRow>(
+
    keyDerivationId: Array<number>,
+
    /**
+
     * void -> do not filter by type
+
     */
+
    types: void | Array<CoreAddressT>,
+
  ): Promise<Map<number, Array<$ReadOnly<AddressRow>>>> {
+
    const mappingSchema = GetAddress.ownTables[Tables.AddressMappingSchema.name];
+
    const mappingTable = db.getSchema().table(mappingSchema.name);
+
    const addressSchema = GetAddress.ownTables[Tables.AddressSchema.name];
+
    const addressTable = db.getSchema().table(addressSchema.name);
+
    const query = db
+
      .select()
+
      .from(mappingTable)
+
      .innerJoin(
+
        addressTable,
+
        op.and(
+
          mappingTable[mappingSchema.properties.AddressId].eq(
+
            addressTable[addressSchema.properties.AddressId]
+
          ),
+
          ...(types != null
+
            ? [addressTable[addressSchema.properties.Type].in(
+
              types
+
            )]
+
            : []
+
          )
+
        )
+
      )
+
      .where(
+
        mappingTable[mappingSchema.properties.KeyDerivationId].in(
+
          keyDerivationId
+
        )
+
      );
+
    const result: $ReadOnlyArray<{|
+
      AddressMapping: $ReadOnly<AddressMappingRow>,
+
      Address: $ReadOnly<AddressRow>,
+
    |}> = await tx.attach(query);
+

                      
+
    const addressRowMap: Map<number, Array<$ReadOnly<AddressRow>>> = result.reduce(
+
      (map, nextElement) => {
+
        const array = map.get(nextElement.AddressMapping.KeyDerivationId) || [];
+
        map.set(
+
          nextElement.AddressMapping.KeyDerivationId,
+
          [...array, nextElement.Address]
+
        );
+
        return map;
+
      },
+
      new Map()
+
    );
+
    return addressRowMap;
+
  }
+

                      
+
  static async getKeyForFamily(
+
    db: lf$Database,
+
    tx: lf$Transaction,
+
    addressId: number,
+
  ): Promise<number | void> {
+
    const row = await getRowFromKey<AddressMappingRow>(
      db, tx,
-
      0,
-
      GetEncryptionMeta.ownTables[Tables.EncryptionMetaSchema.name].name,
-
      GetEncryptionMeta.ownTables[Tables.EncryptionMetaSchema.name].properties.EncryptionMetaId,
+
      addressId,
+
      GetAddress.ownTables[Tables.AddressMappingSchema.name].name,
+
      GetAddress.ownTables[Tables.AddressMappingSchema.name].properties.AddressId,
    );
-
    if (row === undefined) {
-
      throw new Error('GetEncryptionMeta::get no encryption meta found');
-
    }
-
    return row;
+
    return row === undefined
+
      ? row
+
      : row.KeyDerivationId;
  }
}

                      

                      
import * as Tables from '../tables';
import type {
+
  AddressMappingInsert, AddressMappingRow,
  KeyDerivationInsert, KeyDerivationRow,
  BlockInsert, BlockRow,
  KeyInsert, KeyRow,
  AddressInsert, AddressRow,
  EncryptionMetaInsert, EncryptionMetaRow,
-
  TxStatusCodesType,
  DbTransaction,
  TransactionInsert, TransactionRow,
  DbBlock,
} from '../tables';
+
import type {
+
  CoreAddressT,
+
  TxStatusCodesType,
+
} from '../enums';
import {
  digetForHash,
} from './utils';
import {
  addNewRowToTable,
+
  addBatchToTable,
  addOrReplaceRow,
  getRowIn,
  StaleStateError,
  GetBlock,
  GetEncryptionMeta,
} from './read';
+
import type { InsertRequest } from '../../walletTypes/common/utils';

                      
export class AddKey {
  static ownTables = Object.freeze({
  }
}

                      
-
export class GetOrAddAddress {
+
export class AddAddress {
  static ownTables = Object.freeze({
    [Tables.AddressSchema.name]: Tables.AddressSchema,
+
    [Tables.AddressMappingSchema.name]: Tables.AddressMappingSchema,
  });
  static depTables = Object.freeze({
    GetEncryptionMeta,
  });

                      
-
  static async addByHash(
+
  static async addForeignByHash(
    db: lf$Database,
    tx: lf$Transaction,
-
    addressHash: Array<string>,
+
    address: Array<{|
+
      data: string,
+
      type: CoreAddressT,
+
    |}>,
  ): Promise<Array<$ReadOnly<AddressRow>>> {
-
    const { AddressSeed } = await GetOrAddAddress.depTables.GetEncryptionMeta.get(db, tx);
-
    const digests = addressHash.map<number>(hash => digetForHash(hash, AddressSeed));
+
    const { AddressSeed } = await AddAddress.depTables.GetEncryptionMeta.get(db, tx);
+
    const digests = address.map<number>(meta => digetForHash(meta.data, AddressSeed));

                      
-
    const result = [];
-
    for (let i = 0; i < addressHash.length; i++) {
-
      const newRow = await addNewRowToTable<AddressInsert, AddressRow>(
-
        db, tx,
-
        {
-
          Digest: digests[i],
-
          Hash: addressHash[i],
-
        },
-
        GetOrAddAddress.ownTables[Tables.AddressSchema.name].name,
-
      );
-
      result.push(newRow);
-
    }
+
    const result = await addBatchToTable<AddressInsert, AddressRow>(
+
      db, tx,
+
      address.map((meta, i) => ({
+
        Digest: digests[i],
+
        Hash: meta.data,
+
        Type: meta.type,
+
      })),
+
      AddAddress.ownTables[Tables.AddressSchema.name].name,
+
    );

                      
    return result;
  }

                      
-
  static async getByHash(
+
  static async addFromCanonicalByHash(
    db: lf$Database,
    tx: lf$Transaction,
-
    addressHash: Array<string>,
-
  ): Promise<$ReadOnlyArray<$ReadOnly<AddressRow>>> {
-
    const { AddressSeed } = await GetOrAddAddress.depTables.GetEncryptionMeta.get(db, tx);
-
    const digests = addressHash.map<number>(hash => digetForHash(hash, AddressSeed));
+
    address: Array<{|
+
      keyDerivationId: number,
+
      data: string,
+
      type: CoreAddressT,
+
    |}>,
+
  ): Promise<Array<$ReadOnly<AddressRow>>> {
+
    const addressEntries = await AddAddress.addForeignByHash(
+
      db, tx,
+
      address.map(meta => ({ data: meta.data, type: meta.type }))
+
    );

                      
-
    const addressRows = await getRowIn<AddressRow>(
+
    await addBatchToTable<AddressMappingInsert, AddressMappingRow>(
      db, tx,
-
      GetOrAddAddress.ownTables[Tables.AddressSchema.name].name,
-
      GetOrAddAddress.ownTables[Tables.AddressSchema.name].properties.Digest,
-
      digests
+
      address.map((meta, i) => ({
+
        KeyDerivationId: meta.keyDerivationId,
+
        AddressId: addressEntries[i].AddressId,
+
      })),
+
      AddAddress.ownTables[Tables.AddressMappingSchema.name].name,
    );

                      
-
    return addressRows;
+
    return addressEntries;
  }
}

                      
      private: number | null,
      public: number | null,
    |} => KeyDerivationInsert,
-
  levelInfo: number => Insert,
+
  levelInfo: InsertRequest => Promise<Insert>,
|};

                      
export type DerivationQueryResult<Row> = {|
    db: lf$Database,
    tx: lf$Transaction,
    request: AddDerivationRequest<Insert>,
+
    lockedTables: Array<string>,
    levelSpecificTableName: string,
  ): Promise<DerivationQueryResult<Row>> {
    const privateKey = request.privateKeyInfo === null
      await addNewRowToTable<Insert, Row>(
        db,
        tx,
-
        request.levelInfo(KeyDerivation.KeyDerivationId),
+
        await request.levelInfo({
+
          db,
+
          tx,
+
          lockedTables,
+
          keyDerivationId: KeyDerivation.KeyDerivationId
+
        }),
        levelSpecificTableName,
      );

                      
  }
}

                      
-
// TODO: move this and related classes to walletTypes/common/api
export class GetOrAddDerivation {
  static ownTables = Object.freeze({
    [Tables.KeyDerivationSchema.name]: Tables.KeyDerivationSchema,
    parentDerivationId: number | null,
    childIndex: number | null,
    request: AddDerivationRequest<Insert>,
+
    lockedTables: Array<string>,
    levelSpecificTableName: string,
  ): Promise<DerivationQueryResult<Row>> {
    const childResult = parentDerivationId == null || childIndex == null
    const addResult = await GetOrAddDerivation.depTables.AddDerivation.add<Insert, Row>(
      db, tx,
      request,
+
      lockedTables,
      levelSpecificTableName,
    );
    return addResult;
+
// @flow
+

                      
+
export const CoreAddressTypes = Object.freeze({
+
  CARDANO_LEGACY: 0,
+
  /**
+
   * Note: we store Shelley addresses as the full payload (not just payment key)
+
   * since it's easier to extract a key from a payload then the invverse
+
   *
+
   * This also matches how the remote works as it has to return the full payload
+
   * so we can tell the address type
+
   */
+
  SHELLEY_SINGLE: 1,
+
  SHELLEY_GROUP: 2,
+
  SHELLEY_ACCOUNT: 3,
+
  SHELLEY_MULTISIG: 4,
+
});
+
export type CoreAddressT = $Values<typeof CoreAddressTypes>;
+

                      
+
export const TxStatusCodes = Object.freeze({
+
  NOT_IN_REMOTE: -3,
+
  ROLLBACK_FAIL: -2,
+
  FAIL_RESPONSE: -1,
+
  PENDING: 0,
+
  IN_BLOCK: 1,
+
});
+
export type TxStatusCodesType = $Values<typeof TxStatusCodes>;

                      
import { Type } from 'lovefield';
import type { lf$schema$Builder } from 'lovefield';
+
import type { TxStatusCodesType } from './enums';

                      
export type KeyInsert = {|
  Hash: string,

                      
export type AddressInsert = {|
  Digest: number,
+
  Type: number,
  Hash: string,
|};
export type AddressRow = {|
  properties: {
    AddressId: 'AddressId',
    Digest: 'Digest',
+
    Type: 'Type',
    Hash: 'Hash',
  }
};
  +block: $ReadOnly<BlockRow>;
|};

                      
-
export const TxStatusCodes = Object.freeze({
-
  NOT_IN_REMOTE: -3,
-
  ROLLBACK_FAIL: -2,
-
  FAIL_RESPONSE: -1,
-
  PENDING: 0,
-
  IN_BLOCK: 1,
-
});
-
export type TxStatusCodesType = $Values<typeof TxStatusCodes>
-

                      
export type TransactionInsert = {|
  Digest: number,
  Hash: string,
  }
};

                      
+
export type CanonicalAddressInsert = {|
+
  KeyDerivationId: number,
+
|};
+
export type CanonicalAddressRow = {|
+
  CanonicalAddressId: number,
+
  ...CanonicalAddressInsert,
+
|};
+
/**
+
 * We save here a "canonical address" instead of an address type depending on the purpose
+
 * This is because some cryptocurrencies have multiple addresses per derivation path
+
 *  ex: group addresses in Cardano Shelley
+
 * We therefore need a 1-many mapping between canonical to hash
+
 * but we can only build this mapping if we have 1 table for all purposes
+
 *  - so we can use foreign key to this known table
+
 *
+
 * This means that metadata can't be stored directly here like other derivation levels
+
 * that's okay since since any metadata would be associated w/ a hash and not the derivation anyway
+
 */
+
export const CanonicalAddressSchema: {
+
  +name: 'CanonicalAddress',
+
  properties: $ObjMapi<CanonicalAddressRow, ToSchemaProp>
+
} = {
+
  name: 'CanonicalAddress',
+
  properties: {
+
    CanonicalAddressId: 'CanonicalAddressId',
+
    KeyDerivationId: 'KeyDerivationId',
+
  }
+
};
+

                      
+
export type AddressMappingInsert = {|
+
  KeyDerivationId: number,
+
  AddressId: number,
+
|};
+
export type AddressMappingRow = {|
+
  AddressMappingId: number,
+
  ...AddressMappingInsert,
+
|};
+
export const AddressMappingSchema: {
+
  +name: 'AddressMapping',
+
  properties: $ObjMapi<AddressMappingRow, ToSchemaProp>
+
} = {
+
  name: 'AddressMapping',
+
  properties: {
+
    AddressMappingId: 'AddressMappingId',
+
    KeyDerivationId: 'KeyDerivationId',
+
    /**
+
     * We need to specify an index into another table instead of storing the hash here directly
+
     * This is because we need an address table entry for every input & output in a transaction
+
     * even if it doesn't belong to you.
+
     * We can't make that a foreign key to this table because this table has a "KeyDerivationId"
+
     * We can't make the "KeyDerivationId" nullable because you can't create an index on a nullable
+
     */
+
    AddressId: 'AddressId',
+
  }
+
};
+

                      
export type DbTransaction = {|
  +transaction: $ReadOnly<TransactionRow>,
|};
  schemaBuilder.createTable(AddressSchema.name)
    .addColumn(AddressSchema.properties.AddressId, Type.INTEGER)
    .addColumn(AddressSchema.properties.Digest, Type.NUMBER)
+
    .addColumn(AddressSchema.properties.Type, Type.NUMBER)
    .addColumn(AddressSchema.properties.Hash, Type.STRING)
    .addPrimaryKey(
      ([AddressSchema.properties.AddressId]: Array<string>),
      'Address_Digest_Index',
      ([AddressSchema.properties.Digest]: Array<string>),
      false // not unique. There is a (very small) chance of collisions
+
    )
+
    .addIndex(
+
      'Address_Type_Index',
+
      ([AddressSchema.properties.Type]: Array<string>),
+
      false
    );

                      

                      
      ([TransactionSchema.properties.Digest]: Array<string>),
      false // not unique. There is a (very small) chance of collisions
    );
+
  // CanonicalAddress
+
  schemaBuilder.createTable(CanonicalAddressSchema.name)
+
    .addColumn(CanonicalAddressSchema.properties.CanonicalAddressId, Type.INTEGER)
+
    .addColumn(CanonicalAddressSchema.properties.KeyDerivationId, Type.INTEGER)
+
    .addPrimaryKey(
+
      ([CanonicalAddressSchema.properties.CanonicalAddressId]: Array<string>),
+
      true
+
    )
+
    .addForeignKey('CanonicalAddress_Bip44Derivation', {
+
      local: CanonicalAddressSchema.properties.KeyDerivationId,
+
      ref: `${KeyDerivationSchema.name}.${KeyDerivationSchema.properties.KeyDerivationId}`
+
    })
+
    .addIndex(
+
      'CanonicalAddress_KeyDerivation_Index',
+
      ([CanonicalAddressSchema.properties.KeyDerivationId]: Array<string>),
+
      true
+
    );
+
  // AddressMapping
+
  schemaBuilder.createTable(AddressMappingSchema.name)
+
    .addColumn(AddressMappingSchema.properties.AddressMappingId, Type.INTEGER)
+
    .addColumn(AddressMappingSchema.properties.KeyDerivationId, Type.INTEGER)
+
    .addColumn(AddressMappingSchema.properties.AddressId, Type.INTEGER)
+
    .addPrimaryKey(
+
      ([AddressMappingSchema.properties.AddressMappingId]: Array<string>),
+
      true
+
    )
+
    .addForeignKey('AddressMapping_KeyDerivation', {
+
      local: AddressMappingSchema.properties.KeyDerivationId,
+
      ref: `${KeyDerivationSchema.name}.${KeyDerivationSchema.properties.KeyDerivationId}`
+
    })
+
    .addForeignKey('AddressMapping_Address', {
+
      local: AddressMappingSchema.properties.AddressId,
+
      ref: `${AddressSchema.name}.${AddressSchema.properties.AddressId}`
+
    })
+
    .addIndex(
+
      'AddressMapping_KeyDerivation_Index',
+
      ([AddressMappingSchema.properties.KeyDerivationId]: Array<string>),
+
      false
+
    );
};
Diff too large – View on GitHub