Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
a1fdefa
feat: support address creation with BIP44
shahin-hq Jan 27, 2026
8226705
feat: support address creation with BIP44
shahin-hq Jan 27, 2026
f46ff51
feat: support address creation with BIP44
shahin-hq Jan 27, 2026
81c4de9
feat: support address creation with BIP44
shahin-hq Jan 27, 2026
b6dbc87
feat: support address creation with BIP44
shahin-hq Jan 27, 2026
9a0d75e
feat: support address creation with BIP44
shahin-hq Jan 28, 2026
bba9dae
feat: support address creation with BIP44
shahin-hq Jan 28, 2026
d7e091b
style: resolve style guide violations
shahin-hq Jan 28, 2026
feb6e40
feat: support address creation with BIP44
shahin-hq Jan 28, 2026
23d6753
Merge remote-tracking branch 'origin/feat/implement-bip-44-derivation…
shahin-hq Jan 28, 2026
3df4a6b
feat: support address creation with BIP44
shahin-hq Jan 28, 2026
fabd36c
feat: support address creation with BIP44
shahin-hq Jan 28, 2026
0f2af81
feat: support address creation with BIP44
shahin-hq Jan 28, 2026
c0f74df
feat: support address creation with BIP44
shahin-hq Jan 28, 2026
e749e0b
feat: support address creation with BIP44
shahin-hq Jan 28, 2026
df9ca7a
feat: support address creation with BIP44
shahin-hq Jan 28, 2026
b444521
feat: support address creation with BIP44
shahin-hq Jan 28, 2026
532ffff
style: resolve style guide violations
shahin-hq Jan 28, 2026
da8f603
feat: support address creation with BIP44
shahin-hq Jan 28, 2026
a48affc
style: resolve style guide violations
shahin-hq Jan 28, 2026
701af68
feat: support address creation with BIP44
shahin-hq Jan 29, 2026
04b7680
Merge remote-tracking branch 'origin/feat/implement-bip-44-derivation…
shahin-hq Jan 29, 2026
1356e53
feat: support address creation with BIP44
shahin-hq Jan 29, 2026
0838e86
feat: support address creation with BIP44
shahin-hq Jan 29, 2026
db2163a
style: resolve style guide violations
shahin-hq Jan 29, 2026
2c67de7
feat: support address creation with BIP44
shahin-hq Jan 29, 2026
19b9ce1
Merge remote-tracking branch 'origin/feat/implement-bip-44-derivation…
shahin-hq Jan 29, 2026
c684406
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
425d235
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
8fd218f
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
e043565
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
816c67f
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
8340727
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
14fd269
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
8c646fc
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
c796a17
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
455bc73
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
fdcc31d
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
439f656
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
9b2fc0e
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
0f4b115
feat: support address creation with BIP44
shahin-hq Feb 2, 2026
55d91e9
style: resolve style guide violations
shahin-hq Feb 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,28 +11,28 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
node-version: [18.x]
concurrency:
group: ${{ github.head_ref }}-publish
cancel-in-progress: true
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- name: Cache pnpm modules
uses: actions/cache@v2
uses: actions/cache@v3
env:
cache-name: cache-pnpm-modules
with:
path: ~/.pnpm-store
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.node-version }}-${{ hashFiles('**/package.json') }}
restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}-${{ matrix.node-version }}-
- name: Install pnpm
uses: pnpm/action-setup@v2
uses: pnpm/action-setup@v3
with:
version: 6.20.4
version: 8.15.9
- name: Install dependencies
run: pnpm install
- name: Publish
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ jobs:
group: ${{ github.head_ref }}-test
cancel-in-progress: true
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v3
with:
version: 8.8.0
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18"
cache: "pnpm"
- name: Build
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/label.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:

steps:
- name: Checkout code
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
ref: ${{ github.head_ref }}

Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/validators.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ jobs:
compile:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: pnpm/action-setup@v2
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v3
with:
version: 8.8.0
- uses: actions/setup-node@v2
- uses: actions/setup-node@v3
with:
node-version: "16"
node-version: "18"
cache: "pnpm"
- name: Build
run: |
Expand Down
12 changes: 12 additions & 0 deletions packages/ark/source/address.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ export class AddressService extends Services.AbstractAddressService {
};
}

public override async fromBip44Mnemonic(
mnemonic: string,
path: string,
): Promise<Services.AddressDataTransferObject> {
abort_unless(BIP39.compatible(mnemonic), "The given value is not BIP39 compliant.");

return {
address: BaseAddress.fromBip44Mnemonic(mnemonic, path, this.#config.network),
type: "bip44",
};
}

public override async fromMultiSignature({
min,
publicKeys,
Expand Down
4 changes: 4 additions & 0 deletions packages/ark/source/crypto/identities/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export class Address {
return Address.fromPublicKey(PublicKey.fromPassphrase(passphrase), network);
}

public static fromBip44Mnemonic(mnemonic: string, path: string, network?: Network): string {
return Address.fromPublicKey(PublicKey.fromBip44Mnemonic(mnemonic, path), network);
}

public static fromPublicKey(publicKey: string, network?: Network): string {
if (!PublicKey.verify(publicKey)) {
throw new PublicKeyError(publicKey);
Expand Down
11 changes: 10 additions & 1 deletion packages/ark/source/crypto/identities/keys.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Hash, secp256k1, WIF } from "@ardenthq/sdk-cryptography";
import { Hash, secp256k1, WIF, BIP39, HDKey } from "@ardenthq/sdk-cryptography";

import { Network } from "../interfaces/networks.js";
import { KeyPair } from "./contracts.js";
Expand All @@ -10,6 +10,15 @@ export class Keys {
return Keys.fromPrivateKey(Hash.sha256(Buffer.from(passphrase, "utf8")), compressed);
}

public static fromBip44Mnemonic(mnemonic: string, path: string, compressed = true): KeyPair {
const seed = BIP39.toSeed(mnemonic);

const hd = HDKey.fromSeed(Buffer.from(seed));
const child = hd.derive(path);

return Keys.fromPrivateKey(child.privateKey, compressed);
}

public static fromPrivateKey(privateKey: Buffer | string, compressed = true): KeyPair {
privateKey = privateKey instanceof Buffer ? privateKey : Buffer.from(privateKey, "hex");

Expand Down
4 changes: 4 additions & 0 deletions packages/ark/source/crypto/identities/private-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export class PrivateKey {
return Keys.fromPassphrase(passphrase).privateKey;
}

public static fromBip44Mnemonic(passphrase: string, path: string): string {
return Keys.fromBip44Mnemonic(passphrase, path).privateKey;
}

public static fromWIF(wif: string, network?: Network): string {
return Keys.fromWIF(wif, network).privateKey;
}
Expand Down
4 changes: 4 additions & 0 deletions packages/ark/source/crypto/identities/public-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export class PublicKey {
return Keys.fromPassphrase(passphrase).publicKey;
}

public static fromBip44Mnemonic(passphrase: string, path: string): string {
return Keys.fromBip44Mnemonic(passphrase, path).publicKey;
}

public static fromWIF(wif: string, network?: Network): string {
return Keys.fromWIF(wif, network).publicKey;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,11 @@ export abstract class TransactionBuilder<TBuilder extends TransactionBuilder<TBu
return this.signWithKeyPair(keys);
}

public signWithBip44Mnemonic(mnemonic: string, path: string): TBuilder {
const keys: IKeyPair = Keys.fromBip44Mnemonic(mnemonic, path);
return this.signWithKeyPair(keys);
}

public signWithWif(wif: string, networkWif?: number): TBuilder {
const keys: IKeyPair = Keys.fromWIF(wif);

Expand Down
11 changes: 11 additions & 0 deletions packages/ark/source/private-key.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ export class PrivateKeyService extends Services.AbstractPrivateKeyService {
};
}

public override async fromBip44Mnemonic(
mnemonic: string,
path: string,
): Promise<Services.PrivateKeyDataTransferObject> {
abort_unless(BIP39.compatible(mnemonic), "The given value is not BIP39 compliant.");

return {
privateKey: BasePrivateKey.fromBip44Mnemonic(mnemonic, path),
};
}

public override async fromSecret(secret: string): Promise<Services.PrivateKeyDataTransferObject> {
abort_if(BIP39.compatible(secret), "The given value is BIP39 compliant. Please use [fromMnemonic] instead.");

Expand Down
11 changes: 11 additions & 0 deletions packages/ark/source/public-key.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ export class PublicKeyService extends Services.AbstractPublicKeyService {
};
}

public override async fromBip44Mnemonic(
mnemonic: string,
path: string,
): Promise<Services.PublicKeyDataTransferObject> {
abort_unless(BIP39.compatible(mnemonic), "The given value is not BIP39 compliant.");

return {
publicKey: BasePublicKey.fromBip44Mnemonic(mnemonic, path),
};
}

public override async fromMultiSignature(
min: number,
publicKeys: string[],
Expand Down
14 changes: 14 additions & 0 deletions packages/ark/source/transaction.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,19 @@ export class TransactionService extends Services.AbstractTransactionService {

if (input.signatory.actsWithMnemonic() || input.signatory.actsWithConfirmationMnemonic()) {
address = (await this.#addressService.fromMnemonic(input.signatory.signingKey())).address;

senderPublicKey = (await this.#publicKeyService.fromMnemonic(input.signatory.signingKey())).publicKey;
}

if (input.signatory.actsWithBip44Mnemonic()) {
const path = input.signatory.path();

address = (await this.#addressService.fromBip44Mnemonic(input.signatory.signingKey(), path)).address;

senderPublicKey = (await this.#publicKeyService.fromBip44Mnemonic(input.signatory.signingKey(), path))
.publicKey;
}

if (input.signatory.actsWithSecret() || input.signatory.actsWithConfirmationSecret()) {
address = (await this.#addressService.fromSecret(input.signatory.signingKey())).address;
senderPublicKey = (await this.#publicKeyService.fromSecret(input.signatory.signingKey())).publicKey;
Expand Down Expand Up @@ -315,6 +325,10 @@ export class TransactionService extends Services.AbstractTransactionService {
transaction.sign(input.signatory.signingKey());
}

if (input.signatory.actsWithBip44Mnemonic()) {
transaction.signWithBip44Mnemonic(input.signatory.signingKey(), input.signatory.path());
}

if (input.signatory.actsWithConfirmationMnemonic()) {
transaction.sign(input.signatory.signingKey());
transaction.secondSign(input.signatory.confirmKey());
Expand Down
1 change: 1 addition & 0 deletions packages/profiles/source/serialiser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export class WalletSerialiser {
[WalletData.LedgerModel]: this.#wallet.data().get(WalletData.LedgerModel),
[WalletData.Status]: this.#wallet.data().get(WalletData.Status),
[WalletData.IsPrimary]: this.#wallet.data().get(WalletData.IsPrimary, false),
[WalletData.AddressIndex]: this.#wallet.data().get(WalletData.AddressIndex),
},
id: this.#wallet.id(),
settings: this.#wallet.settings().all(),
Expand Down
1 change: 1 addition & 0 deletions packages/profiles/source/signatory.factory.contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface SignatoryInput {
secondSecret?: string;
wif?: string;
privateKey?: string;
path?: string;
}

export interface ISignatoryFactory {
Expand Down
11 changes: 11 additions & 0 deletions packages/profiles/source/signatory.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,16 @@ export class SignatoryFactory implements ISignatoryFactory {
secondSecret,
wif,
privateKey,
path,
}: SignatoryInput): Promise<Signatories.Signatory> {
if (mnemonic && secondMnemonic) {
return this.#wallet.signatory().confirmationMnemonic(mnemonic, secondMnemonic);
}

if (mnemonic && path) {
return this.#wallet.signatory().bip44Mnemonic(mnemonic, path);
}

if (mnemonic) {
return this.#wallet.signatory().mnemonic(mnemonic);
}
Expand Down Expand Up @@ -50,6 +55,12 @@ export class SignatoryFactory implements ISignatoryFactory {
return this.#wallet.signatory().secret(await this.#wallet.signingKey().get(encryptionPassword));
}

if (this.#wallet.actsWithBip44MnemonicWithEncryption()) {
return this.#wallet
.signatory()
.bip44Mnemonic(await this.#wallet.signingKey().get(encryptionPassword), path as string);
}

return this.#wallet.signatory().mnemonic(await this.#wallet.signingKey().get(encryptionPassword));
}

Expand Down
16 changes: 16 additions & 0 deletions packages/profiles/source/wallet.contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,22 @@ export interface IReadWriteWallet {
*/
actsWithMnemonic(): boolean;

/**
* Determines if the wallet has been imported with a BIP44 mnemonic
*
* @return {*} {boolean}
* @memberof IReadWriteWallet
*/
actsWithBip44Mnemonic(): boolean;

/**
* Determines if the wallet has been imported with a BIP44 mnemonic with encryption
*
* @return {*} {boolean}
* @memberof IReadWriteWallet
*/
actsWithBip44MnemonicWithEncryption(): boolean;

/**
* Determines if the wallet has been imported with a address.
*
Expand Down
1 change: 1 addition & 0 deletions packages/profiles/source/wallet.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export enum WalletData {
LedgerModel = "LEDGER_MODEL",
Status = "STATUS",
IsPrimary = "IS_PRIMARY",
AddressIndex = "ADDRESS_INDEX",
}

/**
Expand Down
34 changes: 27 additions & 7 deletions packages/profiles/source/wallet.factory.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* istanbul ignore file */

import { Enums } from "@ardenthq/sdk";
import { BIP38, BIP39, UUID } from "@ardenthq/sdk-cryptography";
import { BIP38, BIP39, UUID, secp256k1, HDKey } from "@ardenthq/sdk-cryptography";

import {
IAddressOptions,
Expand Down Expand Up @@ -75,12 +75,32 @@ export class WalletFactory implements IWalletFactory {

/** {@inheritDoc IWalletFactory.fromMnemonicWithBIP44} */
public async fromMnemonicWithBIP44(options: IMnemonicDerivativeOptions): Promise<IReadWriteWallet> {
return this.#fromMnemonicWithDerivative({
derivationType: "bip44",
featureFlag: Enums.FeatureFlag.AddressMnemonicBip44,
importMethod: WalletImportMethod.BIP44.MNEMONIC,
options,
});
const wallet: IReadWriteWallet = new Wallet(UUID.random(), {}, this.#profile);
wallet.data().set(WalletData.ImportMethod, WalletImportMethod.BIP44.MNEMONIC);
wallet.data().set(WalletData.Status, WalletFlag.Cold);

await wallet.mutator().coin(options.coin, options.network);

const slip = wallet.config().get("network.constants.slip44");
const account = options.levels.account ?? 0;
const change = options.levels.change ?? 0;
const addressIndex = options.levels.addressIndex ?? 0;

const path = `m/44'/${slip}'/${account}'/${change}/${addressIndex}`;

wallet.data().set(WalletData.AddressIndex, addressIndex);
wallet.data().set(WalletData.DerivationPath, path);

const address = (await wallet.coin().address().fromBip44Mnemonic(options.mnemonic, path)).address;

await wallet.mutator().address({ address });

if (options.password) {
wallet.data().set(WalletData.ImportMethod, WalletImportMethod.BIP44.MNEMONIC_WITH_ENCRYPTION);
await wallet.signingKey().set(options.mnemonic, options.password);
}

return wallet;
}

/** {@inheritDoc IWalletFactory.fromMnemonicWithBIP49} */
Expand Down
4 changes: 2 additions & 2 deletions packages/profiles/source/wallet.identifier.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ export class WalletIdentifierFactory {
return this.#address(wallet);
}

if (wallet.actsWithMnemonic()) {
if (wallet.actsWithMnemonic() || wallet.actsWithBip44Mnemonic()) {
return this.#addressOrPublicKey(wallet);
}

if (wallet.actsWithPublicKey()) {
return this.#addressOrPublicKey(wallet);
}

if (wallet.actsWithMnemonicWithEncryption()) {
if (wallet.actsWithMnemonicWithEncryption() || wallet.actsWithBip44MnemonicWithEncryption()) {
return this.#addressOrPublicKey(wallet);
}

Expand Down
1 change: 1 addition & 0 deletions packages/profiles/source/wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ describe("Wallet", ({ beforeAll, beforeEach, loader, nock, assert, stub, it }) =
VOTES_AVAILABLE: 0,
VOTES_USED: 0,
IS_PRIMARY: false,
ADDRESS_INDEX: undefined,
});
assert.object(actual.settings);
assert.string(actual.settings.AVATAR);
Expand Down
Loading