In case someone encounters the same problem, I will leave an answer to my question.
The problem is that phantom find first key derivation along the path m/44'/501'/0'/0'.
To get a wallet with the same public key as in phantom you need to use the following code from official documentation:
import { Keypair } from "@solana/web3.js";
import { HDKey } from "micro-ed25519-hdkey";
import * as bip39 from "bip39";
(async () => {
const mnemonic =
"neither lonely flavor argue grass remind eye tag avocado spot unusual intact";
const seed = bip39.mnemonicToSeedSync(mnemonic, ""); // (mnemonic, password)
const hd = HDKey.fromMasterSeed(seed.toString("hex"));
for (let i = 0; i < 10; i++) {
const path = `m/44'/501'/${i}'/0'`;
const keypair = Keypair.fromSeed(hd.derive(path).privateKey);
console.log(`${path} => ${keypair.publicKey.toBase58()}`);
}
})();
P.S.: someone wrote that if you top up your account, it will be higher in the list of keys along the derivation paths and the phantom will find it. But I haven't checked it.
UPD:
From the official phantom app docs:
How does Phantom import wallet addresses?
When importing addresses from an existing seed phrase, Phantom will scan for 20 addresses in
each of our three supported derivation paths (bip44change, bip44, and
a deprecated path), for a total of 60 addresses. For the convenience
of the user, Phantom will filter this list of addresses down to
wallets that have ever had signatures (i.e. have ever been used).
Phantom will then sort this filtered list based on how many signatures
each wallet has had plus the amount of lamports it currently owns.