From ecab4c6805edc2d7d7cf14f122a6c275e42dd4be Mon Sep 17 00:00:00 2001 From: Minimoi Date: Thu, 15 Jan 2026 18:57:31 +0100 Subject: [PATCH 1/4] feat: add Aptos chain support to hdwallet-core and hdwallet-native - Add Aptos wallet types and interfaces to hdwallet-core - Implement AptosAdapter for Ed25519 key derivation - Add Aptos support to native wallet with BLAKE2b-256 address generation - Configure SLIP-44 coin type 637 for Aptos derivation path Closes: #[Aptos Implementation] --- packages/hdwallet-core/src/aptos.ts | 86 ++++++++++++++++++ packages/hdwallet-core/src/index.ts | 1 + packages/hdwallet-core/src/utils.ts | 1 + packages/hdwallet-native/src/aptos.ts | 57 ++++++++++++ .../src/crypto/isolation/adapters/aptos.ts | 88 +++++++++++++++++++ packages/hdwallet-native/src/native.ts | 37 +++++--- 6 files changed, 257 insertions(+), 13 deletions(-) create mode 100644 packages/hdwallet-core/src/aptos.ts create mode 100644 packages/hdwallet-native/src/aptos.ts create mode 100644 packages/hdwallet-native/src/crypto/isolation/adapters/aptos.ts diff --git a/packages/hdwallet-core/src/aptos.ts b/packages/hdwallet-core/src/aptos.ts new file mode 100644 index 000000000..8b8784fe9 --- /dev/null +++ b/packages/hdwallet-core/src/aptos.ts @@ -0,0 +1,86 @@ +import { addressNListToBIP32, slip44ByCoin } from "./utils"; +import { BIP32Path, HDWallet, HDWalletInfo, PathDescription } from "./wallet"; + +export interface AptosGetAddress { + addressNList: BIP32Path; + showDisplay?: boolean; +} + +export interface AptosSignTx { + addressNList: BIP32Path; + /** Transaction bytes to sign */ + transactionBytes: Uint8Array; +} + +export interface AptosSignedTx { + signature: string; + publicKey: string; +} + +export interface AptosGetAccountPaths { + accountIdx: number; +} + +export interface AptosAccountPath { + addressNList: BIP32Path; +} + +export interface AptosWalletInfo extends HDWalletInfo { + readonly _supportsAptosInfo: boolean; + + /** + * Returns a list of bip32 paths for a given account index in preferred order + * from most to least preferred. + */ + aptosGetAccountPaths(msg: AptosGetAccountPaths): Array; + + /** + * Returns "next" account path, if any. + */ + aptosNextAccountPath(msg: AptosAccountPath): AptosAccountPath | undefined; +} + +export interface AptosWallet extends AptosWalletInfo, HDWallet { + readonly _supportsAptos: boolean; + + aptosGetAddress(msg: AptosGetAddress): Promise; + aptosGetAddresses?(msgs: AptosGetAddress[]): Promise; + aptosSignTx(msg: AptosSignTx): Promise; +} + +export function aptosDescribePath(path: BIP32Path): PathDescription { + const pathStr = addressNListToBIP32(path); + const unknown: PathDescription = { + verbose: pathStr, + coin: "Aptos", + isKnown: false, + }; + + if (path.length != 5) return unknown; + if (path[0] != 0x80000000 + 44) return unknown; + if (path[1] != 0x80000000 + slip44ByCoin("Aptos")) return unknown; + if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) return unknown; + if ((path[3] & 0x80000000) >>> 0 !== 0x80000000) return unknown; + if ((path[4] & 0x80000000) >>> 0 !== 0x80000000) return unknown; + + const index = path[2] & 0x7fffffff; + return { + verbose: `Aptos Account #${index}`, + accountIdx: index, + wholeAccount: true, + coin: "Aptos", + isKnown: true, + }; +} + +// The standard derivation path for Aptos is: m/44'/637'/'/0'/0' +// Aptos uses SLIP-0010 which requires all derivation to be hardened for Ed25519 +// https://aptos.dev/concepts/accounts#accounts-and-signatures +export function aptosGetAccountPaths(msg: AptosGetAccountPaths): Array { + const slip44 = slip44ByCoin("Aptos"); + return [ + { + addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0x80000000 + 0, 0x80000000 + 0], + }, + ]; +} diff --git a/packages/hdwallet-core/src/index.ts b/packages/hdwallet-core/src/index.ts index 3a08dddfc..87a40224d 100644 --- a/packages/hdwallet-core/src/index.ts +++ b/packages/hdwallet-core/src/index.ts @@ -1,4 +1,5 @@ export * from "./arkeo"; +export * from "./aptos"; export * from "./binance"; export * from "./bitcoin"; export * from "./cosmos"; diff --git a/packages/hdwallet-core/src/utils.ts b/packages/hdwallet-core/src/utils.ts index a12ee3a95..c12acf808 100644 --- a/packages/hdwallet-core/src/utils.ts +++ b/packages/hdwallet-core/src/utils.ts @@ -160,6 +160,7 @@ export const slip44Table = Object.freeze({ Starknet: 9004, Tron: 195, Sui: 784, + Aptos: 637, Near: 397, // EVM chains all use the same SLIP44 Ethereum: 60, diff --git a/packages/hdwallet-native/src/aptos.ts b/packages/hdwallet-native/src/aptos.ts new file mode 100644 index 000000000..fa8ba927f --- /dev/null +++ b/packages/hdwallet-native/src/aptos.ts @@ -0,0 +1,57 @@ +import * as core from "@shapeshiftoss/hdwallet-core"; + +import { Isolation } from "./crypto"; +import { AptosAdapter } from "./crypto/isolation/adapters/aptos"; +import { NativeHDWalletBase } from "./native"; + +export function MixinNativeAptosWalletInfo>(Base: TBase) { + // eslint-disable-next-line @typescript-eslint/no-shadow + return class MixinNativeAptosWalletInfo extends Base implements core.AptosWalletInfo { + readonly _supportsAptosInfo = true; + + aptosGetAccountPaths(msg: core.AptosGetAccountPaths): Array { + return core.aptosGetAccountPaths(msg); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + aptosNextAccountPath(_msg: core.AptosAccountPath): core.AptosAccountPath | undefined { + throw new Error("Method not implemented"); + } + }; +} + +export function MixinNativeAptosWallet>(Base: TBase) { + // eslint-disable-next-line @typescript-eslint/no-shadow + return class MixinNativeAptosWallet extends Base { + readonly _supportsAptos = true; + + aptosAdapter: AptosAdapter | undefined; + + async aptosInitializeWallet(ed25519MasterKey: Isolation.Core.Ed25519.Node): Promise { + const nodeAdapter = new Isolation.Adapters.Ed25519(ed25519MasterKey); + this.aptosAdapter = new AptosAdapter(nodeAdapter); + } + + aptosWipe() { + this.aptosAdapter = undefined; + } + + async aptosGetAddress(msg: core.AptosGetAddress): Promise { + return this.needsMnemonic(!!this.aptosAdapter, () => { + return this.aptosAdapter!.getAddress(msg.addressNList); + }); + } + + async aptosSignTx(msg: core.AptosSignTx): Promise { + return this.needsMnemonic(!!this.aptosAdapter, async () => { + const signature = await this.aptosAdapter!.signTransaction(msg.transactionBytes, msg.addressNList); + const publicKey = await this.aptosAdapter!.getPublicKey(msg.addressNList); + + return { + signature, + publicKey, + }; + }); + } + }; +} diff --git a/packages/hdwallet-native/src/crypto/isolation/adapters/aptos.ts b/packages/hdwallet-native/src/crypto/isolation/adapters/aptos.ts new file mode 100644 index 000000000..a7383bc0a --- /dev/null +++ b/packages/hdwallet-native/src/crypto/isolation/adapters/aptos.ts @@ -0,0 +1,88 @@ +import * as core from "@shapeshiftoss/hdwallet-core"; +import { createBLAKE2b } from "hash-wasm"; + +import { Isolation } from "../.."; + +const ED25519_PUBLIC_KEY_SIZE = 32; + +export class AptosAdapter { + protected readonly nodeAdapter: Isolation.Adapters.Ed25519; + + constructor(nodeAdapter: Isolation.Adapters.Ed25519) { + this.nodeAdapter = nodeAdapter; + } + + /** + * Get Aptos address from BIP32 path + * Address generation: + * 1. Get Ed25519 public key (32 bytes) + * 2. Hash with BLAKE2b-256 + * 3. Result is the 32-byte Aptos address (as hex string with 0x prefix) + */ + async getAddress(addressNList: core.BIP32Path): Promise { + const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToHardenedBIP32(addressNList)); + const publicKey = await nodeAdapter.getPublicKey(); + + if (publicKey.length !== ED25519_PUBLIC_KEY_SIZE) { + throw new Error(`Invalid Ed25519 public key size: ${publicKey.length}`); + } + + // Hash public key with BLAKE2b-256 + const blake2b = await createBLAKE2b(256); + blake2b.init(); + blake2b.update(publicKey); + const addressBytes = blake2b.digest("binary"); + + // Convert to hex string with 0x prefix + const addressHex = Array.from(addressBytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + + return "0x" + addressHex; + } + + /** + * Sign Aptos transaction + * Transaction signing: + * 1. Receive transaction bytes + * 2. Hash with BLAKE2b-256 + * 3. Sign hash with Ed25519 private key + * 4. Return signature as hex string + */ + async signTransaction(transactionBytes: Uint8Array, addressNList: core.BIP32Path): Promise { + const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToHardenedBIP32(addressNList)); + + // Hash transaction with BLAKE2b-256 + const blake2b = await createBLAKE2b(256); + blake2b.init(); + blake2b.update(transactionBytes); + const messageHash = blake2b.digest("binary"); + + // Sign the hash with Ed25519 + const signature = await nodeAdapter.node.sign(messageHash); + + // Convert signature to hex string + const signatureHex = Array.from(signature) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + + return signatureHex; + } + + /** + * Get public key for address verification + */ + async getPublicKey(addressNList: core.BIP32Path): Promise { + const nodeAdapter = await this.nodeAdapter.derivePath(core.addressNListToHardenedBIP32(addressNList)); + const publicKey = await nodeAdapter.getPublicKey(); + + // Convert to hex string + const publicKeyHex = Array.from(publicKey) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + + return publicKeyHex; + } +} + +export default AptosAdapter; diff --git a/packages/hdwallet-native/src/native.ts b/packages/hdwallet-native/src/native.ts index 8b2965ae9..43b9607f8 100644 --- a/packages/hdwallet-native/src/native.ts +++ b/packages/hdwallet-native/src/native.ts @@ -6,6 +6,7 @@ import isObject from "lodash/isObject"; import type { NativeAdapterArgs } from "./adapter"; import { MixinNativeArkeoWallet, MixinNativeArkeoWalletInfo } from "./arkeo"; +import { MixinNativeAptosWallet, MixinNativeAptosWalletInfo } from "./aptos"; import { MixinNativeBinanceWallet, MixinNativeBinanceWalletInfo } from "./binance"; import { MixinNativeBTCWallet, MixinNativeBTCWalletInfo } from "./bitcoin"; import { MixinNativeCosmosWallet, MixinNativeCosmosWalletInfo } from "./cosmos"; @@ -135,13 +136,15 @@ class NativeHDWalletInfo MixinNativeStarknetWalletInfo( MixinNativeTronWalletInfo( MixinNativeSuiWalletInfo( - MixinNativeNearWalletInfo( - MixinNativeThorchainWalletInfo( - MixinNativeMayachainWalletInfo( - MixinNativeSecretWalletInfo( - MixinNativeTerraWalletInfo( - MixinNativeKavaWalletInfo( - MixinNativeArkeoWalletInfo(MixinNativeOsmosisWalletInfo(NativeHDWalletBase)) + MixinNativeAptosWalletInfo( + MixinNativeNearWalletInfo( + MixinNativeThorchainWalletInfo( + MixinNativeMayachainWalletInfo( + MixinNativeSecretWalletInfo( + MixinNativeTerraWalletInfo( + MixinNativeKavaWalletInfo( + MixinNativeArkeoWalletInfo(MixinNativeOsmosisWalletInfo(NativeHDWalletBase)) + ) ) ) ) @@ -166,6 +169,7 @@ class NativeHDWalletInfo core.StarknetWalletInfo, core.TronWalletInfo, core.SuiWalletInfo, + core.AptosWalletInfo, core.NearWalletInfo, core.ThorchainWalletInfo, core.MayachainWalletInfo, @@ -205,6 +209,8 @@ class NativeHDWalletInfo return core.solanaDescribePath(msg.path); case "sui": return core.suiDescribePath(msg.path); + case "aptos": + return core.aptosDescribePath(msg.path); case "near": return core.nearDescribePath(msg.path); case "secret": @@ -246,12 +252,14 @@ export class NativeHDWallet MixinNativeStarknetWallet( MixinNativeTronWallet( MixinNativeSuiWallet( - MixinNativeNearWallet( - MixinNativeThorchainWallet( - MixinNativeMayachainWallet( - MixinNativeSecretWallet( - MixinNativeTerraWallet( - MixinNativeKavaWallet(MixinNativeOsmosisWallet(MixinNativeArkeoWallet(NativeHDWalletInfo))) + MixinNativeAptosWallet( + MixinNativeNearWallet( + MixinNativeThorchainWallet( + MixinNativeMayachainWallet( + MixinNativeSecretWallet( + MixinNativeTerraWallet( + MixinNativeKavaWallet(MixinNativeOsmosisWallet(MixinNativeArkeoWallet(NativeHDWalletInfo))) + ) ) ) ) @@ -275,6 +283,7 @@ export class NativeHDWallet core.StarknetWallet, core.TronWallet, core.SuiWallet, + core.AptosWallet, core.NearWallet, core.ThorchainWallet, core.MayachainWallet, @@ -421,6 +430,7 @@ export class NativeHDWallet super.arkeoInitializeWallet(secp256k1MasterKey), super.solanaInitializeWallet(ed25519MasterKey), super.suiInitializeWallet(ed25519MasterKey), + super.aptosInitializeWallet(ed25519MasterKey), super.nearInitializeWallet(ed25519MasterKey), ]); @@ -465,6 +475,7 @@ export class NativeHDWallet super.solanaWipe(); super.suiWipe(); + super.aptosWipe(); super.nearWipe(); super.btcWipe(); super.ethWipe(); From 9b55ec666fba67bc0f4d7bd342cbe1587cc02ef6 Mon Sep 17 00:00:00 2001 From: Minimoi Date: Thu, 15 Jan 2026 19:48:30 +0100 Subject: [PATCH 2/4] feat: add Aptos wallet types and interfaces - Add AptosWallet, AptosAccountPath, etc. to hdwallet-core - Add SLIP-44 coin type 637 for Aptos derivation path - Support Aptos address generation with BLAKE2b-256 --- packages/hdwallet-core/src/aptosWallet.ts | 84 +++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 packages/hdwallet-core/src/aptosWallet.ts diff --git a/packages/hdwallet-core/src/aptosWallet.ts b/packages/hdwallet-core/src/aptosWallet.ts new file mode 100644 index 000000000..071cdda8c --- /dev/null +++ b/packages/hdwallet-core/src/aptosWallet.ts @@ -0,0 +1,84 @@ +import * as core from "./wallet"; + +export interface AptosGetAddress { + addressNList: core.BIP32Path; + showDisplay?: boolean; +} + +export interface AptosSignTx { + addressNList: core.BIP32Path; + /** Transaction bytes to sign */ + transactionBytes: Uint8Array; +} + +export interface AptosSignedTx { + signature: string; + publicKey: string; +} + +export interface AptosGetAccountPaths { + accountIdx: number; +} + +export interface AptosAccountPath { + addressNList: core.BIP32Path; +} + +export interface AptosWalletInfo extends core.HDWalletInfo { + readonly _supportsAptosInfo: boolean; + + /** + * Returns a list of bip32 paths for a given account index in preferred order + * from most to least preferred. + */ + aptosGetAccountPaths(msg: AptosGetAccountPaths): Array; + + /** + * Returns "next" account path, if any. + */ + aptosNextAccountPath(msg: AptosAccountPath): AptosAccountPath | undefined; +} + +export interface AptosWallet extends AptosWalletInfo, core.HDWallet { + readonly _supportsAptos: boolean; + + aptosGetAddress(msg: AptosGetAddress): Promise; + aptosGetAddresses?(msgs: AptosGetAddress[]): Promise; + aptosSignTx(msg: AptosSignTx): Promise; +} + +export function aptosDescribePath(path: core.BIP32Path): core.PathDescription { + const pathStr = core.addressNListToBIP32(path); + const unknown: core.PathDescription = { + verbose: pathStr, + coin: "Aptos", + isKnown: false, + }; + + if (path.length != 5) return unknown; + if (path[0] != 0x80000000 + 44) return unknown; + if (path[1] != 0x80000000 + core.slip44ByCoin("Aptos")) return unknown; + if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) return unknown; + if ((path[3] & 0x80000000) >>> 0 !== 0x80000000) return unknown; + if ((path[4] & 0x80000000) >>> 0 !== 0x80000000) return unknown; + + const index = path[2] & 0x7fffffff; + return { + verbose: `Aptos Account #${index}`, + accountIdx: index, + wholeAccount: true, + coin: "Aptos", + isKnown: true, + }; +} + +// The standard derivation path for Aptos is: m/44'/637'/'/0'/0' +// Aptos uses SLIP-0010 which requires all derivation to be hardened for Ed25519 +export function aptosGetAccountPaths(msg: AptosGetAccountPaths): Array { + const slip44 = core.slip44ByCoin("Aptos"); + return [ + { + addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0x80000000 + 0, 0x80000000 + 0], + }, + ]; +} From 5ab0359e2098be09f4838122b8f78a7511a00dac Mon Sep 17 00:00:00 2001 From: Minimoi Date: Thu, 15 Jan 2026 21:08:30 +0100 Subject: [PATCH 3/4] fix: remove duplicate aptosWallet.ts file - Keep only aptos.ts file with all Aptos types and interfaces - Remove duplicate aptosWallet.ts file that was causing confusion - Aptos wallet types are properly exported from aptos.ts --- packages/hdwallet-core/src/aptosWallet.ts | 84 ----------------------- 1 file changed, 84 deletions(-) delete mode 100644 packages/hdwallet-core/src/aptosWallet.ts diff --git a/packages/hdwallet-core/src/aptosWallet.ts b/packages/hdwallet-core/src/aptosWallet.ts deleted file mode 100644 index 071cdda8c..000000000 --- a/packages/hdwallet-core/src/aptosWallet.ts +++ /dev/null @@ -1,84 +0,0 @@ -import * as core from "./wallet"; - -export interface AptosGetAddress { - addressNList: core.BIP32Path; - showDisplay?: boolean; -} - -export interface AptosSignTx { - addressNList: core.BIP32Path; - /** Transaction bytes to sign */ - transactionBytes: Uint8Array; -} - -export interface AptosSignedTx { - signature: string; - publicKey: string; -} - -export interface AptosGetAccountPaths { - accountIdx: number; -} - -export interface AptosAccountPath { - addressNList: core.BIP32Path; -} - -export interface AptosWalletInfo extends core.HDWalletInfo { - readonly _supportsAptosInfo: boolean; - - /** - * Returns a list of bip32 paths for a given account index in preferred order - * from most to least preferred. - */ - aptosGetAccountPaths(msg: AptosGetAccountPaths): Array; - - /** - * Returns "next" account path, if any. - */ - aptosNextAccountPath(msg: AptosAccountPath): AptosAccountPath | undefined; -} - -export interface AptosWallet extends AptosWalletInfo, core.HDWallet { - readonly _supportsAptos: boolean; - - aptosGetAddress(msg: AptosGetAddress): Promise; - aptosGetAddresses?(msgs: AptosGetAddress[]): Promise; - aptosSignTx(msg: AptosSignTx): Promise; -} - -export function aptosDescribePath(path: core.BIP32Path): core.PathDescription { - const pathStr = core.addressNListToBIP32(path); - const unknown: core.PathDescription = { - verbose: pathStr, - coin: "Aptos", - isKnown: false, - }; - - if (path.length != 5) return unknown; - if (path[0] != 0x80000000 + 44) return unknown; - if (path[1] != 0x80000000 + core.slip44ByCoin("Aptos")) return unknown; - if ((path[2] & 0x80000000) >>> 0 !== 0x80000000) return unknown; - if ((path[3] & 0x80000000) >>> 0 !== 0x80000000) return unknown; - if ((path[4] & 0x80000000) >>> 0 !== 0x80000000) return unknown; - - const index = path[2] & 0x7fffffff; - return { - verbose: `Aptos Account #${index}`, - accountIdx: index, - wholeAccount: true, - coin: "Aptos", - isKnown: true, - }; -} - -// The standard derivation path for Aptos is: m/44'/637'/'/0'/0' -// Aptos uses SLIP-0010 which requires all derivation to be hardened for Ed25519 -export function aptosGetAccountPaths(msg: AptosGetAccountPaths): Array { - const slip44 = core.slip44ByCoin("Aptos"); - return [ - { - addressNList: [0x80000000 + 44, 0x80000000 + slip44, 0x80000000 + msg.accountIdx, 0x80000000 + 0, 0x80000000 + 0], - }, - ]; -} From c89ae3a4126446bbca1aa54a7fbfcb460556ba7e Mon Sep 17 00:00:00 2001 From: Minimoi Date: Fri, 16 Jan 2026 14:01:15 +0100 Subject: [PATCH 4/4] feat: add Aptos wallet support - Add supportsAptos and infoAptos type guards - Add AptosWallet import to wallet.ts --- packages/hdwallet-core/src/wallet.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/hdwallet-core/src/wallet.ts b/packages/hdwallet-core/src/wallet.ts index c55a629ba..0138fd402 100644 --- a/packages/hdwallet-core/src/wallet.ts +++ b/packages/hdwallet-core/src/wallet.ts @@ -1,6 +1,7 @@ import isObject from "lodash/isObject"; import { ArkeoWallet, ArkeoWalletInfo } from "./arkeo"; +import { AptosWallet, AptosWalletInfo } from "./aptos"; import { BinanceWallet, BinanceWalletInfo } from "./binance"; import { BTCInputScriptType, BTCWallet, BTCWalletInfo } from "./bitcoin"; import { CosmosWallet, CosmosWalletInfo } from "./cosmos"; @@ -304,6 +305,14 @@ export function supportsDebugLink(wallet: HDWallet): wallet is DebugLinkWallet { return isObject(wallet) && (wallet as any)._supportsDebugLink; } +export function supportsAptos(wallet: HDWallet): wallet is AptosWallet { + return isObject(wallet) && (wallet as any)._supportsAptos; +} + +export function infoAptos(info: HDWalletInfo): info is AptosWalletInfo { + return isObject(info) && (info as any)._supportsAptosInfo; +} + export interface HDWalletInfo { /** * Retrieve the wallet's vendor string.