Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bitcoinAddress): Multiple bitcoin address types and testnet #2922

Merged
merged 45 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
098d49a
feat(bitcoinAddress): :sparkles: Add support for different address ty…
madoke May 25, 2024
6056a0b
Update src/modules/finance/index.ts
madoke May 25, 2024
6583feb
Update src/modules/finance/index.ts
madoke May 25, 2024
6b14c5f
Update src/modules/finance/index.ts
madoke May 25, 2024
fd6d989
Update src/modules/finance/index.ts
madoke May 25, 2024
6855ea6
Update src/modules/finance/index.ts
madoke May 25, 2024
7a7f540
Update src/modules/finance/index.ts
madoke May 25, 2024
7ec403f
remove optional parameter
madoke May 25, 2024
57729ca
change type parameter to random and add more tests
madoke May 25, 2024
790bc4b
Update test/modules/finance.spec.ts
madoke May 26, 2024
9907c90
Update test/modules/finance.spec.ts
madoke May 26, 2024
faa206d
Merge branch 'next' into multiple-bitcoin-address-types
madoke May 26, 2024
d6663e8
change address type specs to inline instead of locales and constants …
madoke May 26, 2024
03fe78f
Update bech32 validator regular expression
madoke May 26, 2024
35faabb
Merge branch 'next' into multiple-bitcoin-address-types
ST-DDT May 27, 2024
125c2bc
Update src/modules/finance/index.ts
madoke May 27, 2024
1dda0d5
Update src/modules/finance/index.ts
madoke May 27, 2024
dcf37b4
Update src/modules/finance/bitcoin.ts
madoke May 27, 2024
4de5b7d
change test format to tuples
madoke May 27, 2024
533bfe1
declare BitcoinAddressOptions type
madoke May 27, 2024
69a4758
remove obsolete debug logs
madoke May 27, 2024
291d7d8
Update Formatting
madoke May 27, 2024
d87cb1c
test each address type with a dedicated regex
madoke May 27, 2024
4f6f03b
export enums as strings + update @example + fix formatting issue
madoke May 28, 2024
84c5b0e
Update all tests to use regex and toMatch
madoke May 28, 2024
9a4bf48
Merge branch 'next' into multiple-bitcoin-address-types
madoke May 30, 2024
1b4d5c8
Merge branch 'next' into multiple-bitcoin-address-types
madoke May 31, 2024
b71b600
Update src/modules/finance/index.ts
madoke Jun 1, 2024
c160d87
Update src/modules/finance/index.ts
madoke Jun 2, 2024
1454e32
Update src/modules/finance/index.ts
madoke Jun 2, 2024
b351478
Update src/modules/finance/index.ts
madoke Jun 2, 2024
3efd52a
Update src/modules/finance/index.ts
madoke Jun 3, 2024
581fa07
Update test/modules/finance.spec.ts
madoke Jun 3, 2024
48e6e0a
Update test/modules/finance.spec.ts
madoke Jun 3, 2024
96ee762
Update test/modules/finance.spec.ts
madoke Jun 3, 2024
25a1de5
Update test/modules/finance.spec.ts
madoke Jun 3, 2024
0ea52ce
Merge branch 'faker-js:next' into multiple-bitcoin-address-types
madoke Jun 3, 2024
2d638d3
fix prettier issue
madoke Jun 3, 2024
777c8a6
fix typecheck issue
madoke Jun 3, 2024
7a54432
Update src/modules/finance/bitcoin.ts
madoke Jun 3, 2024
3a58d96
Update src/modules/finance/bitcoin.ts
madoke Jun 3, 2024
6dc78f7
Update src/modules/finance/bitcoin.ts
madoke Jun 3, 2024
6ab023b
Update src/modules/finance/bitcoin.ts
madoke Jun 3, 2024
e6a2a9f
Update src/index.ts
madoke Jun 3, 2024
857a2c8
swap import order to fix linting error
madoke Jun 4, 2024
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
8 changes: 8 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@
export type { DatatypeModule } from './modules/datatype';
export type { DateModule, SimpleDateModule } from './modules/date';
export type { Currency, FinanceModule } from './modules/finance';
export type {

Check failure on line 58 in src/index.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

Delete `type·`
BitcoinAddressFamilyType,

Check failure on line 59 in src/index.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

Delete `Type`
BitcoinNetworkType,

Check failure on line 60 in src/index.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

Delete `Type`
} from './modules/finance/bitcoin';
madoke marked this conversation as resolved.
Show resolved Hide resolved
export {

Check failure on line 62 in src/index.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

Insert `type·`
BitcoinAddressFamily,

Check failure on line 63 in src/index.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

Insert `Type`
BitcoinNetwork,

Check failure on line 64 in src/index.ts

View workflow job for this annotation

GitHub Actions / Lint: node-22, ubuntu-latest

Insert `Type`
} from './modules/finance/bitcoin';
ST-DDT marked this conversation as resolved.
Show resolved Hide resolved
export type { FoodModule } from './modules/food';
export type { GitModule } from './modules/git';
export type { HackerModule } from './modules/hacker';
Expand Down
72 changes: 72 additions & 0 deletions src/modules/finance/bitcoin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { Casing } from '../string';

/**
* The bitcoin address families.
*/
export enum BitcoinAddressFamily {
Legacy = 'legacy',
Segwit = 'segwit',
Bech32 = 'bech32',
Taproot = 'taproot',
}

/**
* The bitcoin address families.
*/
export type BitcoinAddressFamilyType = `${BitcoinAddressFamily}`;

/**
* The different bitcoin networks.
*/
export enum BitcoinNetwork {
Mainnet = 'mainnet',
Testnet = 'testnet',
}

/**
* The different bitcoin networks.
*/
export type BitcoinNetworkType = `${BitcoinNetwork}`;

type BitcoinAddressOptions = {
prefix: Record<BitcoinNetworkType, string>;
length: { min: number; max: number };
casing: Casing;
exclude: string;
};

export const BitcoinAddressSpecs: Record<
BitcoinAddressFamilyType,
BitcoinAddressOptions
> = {
[BitcoinAddressFamily.Legacy]: {
prefix: { [BitcoinNetwork.Mainnet]: '1', [BitcoinNetwork.Testnet]: 'm' },
length: { min: 26, max: 34 },
casing: 'mixed',
exclude: '0OIl',
},
[BitcoinAddressFamily.Segwit]: {
prefix: { [BitcoinNetwork.Mainnet]: '3', [BitcoinNetwork.Testnet]: '2' },
length: { min: 26, max: 34 },
casing: 'mixed',
exclude: '0OIl',
},
[BitcoinAddressFamily.Bech32]: {
prefix: {
[BitcoinNetwork.Mainnet]: 'bc1',
[BitcoinNetwork.Testnet]: 'tb1',
},
length: { min: 42, max: 42 },
casing: 'lower',
exclude: '1bBiIoO',
},
[BitcoinAddressFamily.Taproot]: {
prefix: {
[BitcoinNetwork.Mainnet]: 'bc1p',
[BitcoinNetwork.Testnet]: 'tb1p',
},
length: { min: 62, max: 62 },
casing: 'lower',
exclude: '1bBiIoO',
},
};
53 changes: 42 additions & 11 deletions src/modules/finance/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { FakerError } from '../../errors/faker-error';
import { ModuleBase } from '../../internal/module-base';
import type { BitcoinAddressFamilyType, BitcoinNetworkType } from './bitcoin';
import {
BitcoinAddressFamily,
BitcoinAddressSpecs,
BitcoinNetwork,
} from './bitcoin';
import iban from './iban';

/**
Expand Down Expand Up @@ -486,23 +492,48 @@ export class FinanceModule extends ModuleBase {
/**
* Generates a random Bitcoin address.
*
* @param options An optional options object.
* @param options.type The bitcoin address type (`'legacy'`, `'sewgit'`, `'bech32'` or `'taproot'`). Defaults to a random address type.
* @param options.network The bitcoin network (`'mainnet'` or `'testnet'`). Defaults to `'mainnet'`.
*
* @example
* faker.finance.bitcoinAddress() // '3ySdvCkTLVy7gKD4j6JfSaf5d'
* faker.finance.bitcoinAddress() // '1TeZEFLmGPLEQrSRdAcnZLoWwYeiHwmRog'
* faker.finance.bitcoinAddress({ type: 'bech32' }) // 'bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4'
* faker.finance.bitcoinAddress({ type: 'bech32', network: 'testnet' }) // 'tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx'
*
* @since 3.1.0
*/
bitcoinAddress(): string {
const addressLength = this.faker.number.int({ min: 25, max: 39 });

let address = this.faker.helpers.arrayElement(['1', '3']);

address += this.faker.string.alphanumeric({
length: addressLength,
casing: 'mixed',
exclude: '0OIl',
bitcoinAddress(
options: {
/**
* The bitcoin address type (`'legacy'`, `'sewgit'`, `'bech32'` or `'taproot'`).
*
* @default faker.helpers.arrayElement(['legacy','sewgit','bech32','taproot'])
*/
type?: BitcoinAddressFamilyType;
/**
* The bitcoin network (`'mainnet'` or `'testnet'`).
*
* @default 'mainnet'
*/
network?: BitcoinNetworkType;
} = {}
): string {
const {
type = this.faker.helpers.enumValue(BitcoinAddressFamily),
network = BitcoinNetwork.Mainnet,
} = options;
const addressSpec = BitcoinAddressSpecs[type];
const addressPrefix = addressSpec.prefix[network];
const addressLength = this.faker.number.int(addressSpec.length);

const address = this.faker.string.alphanumeric({
length: addressLength - addressPrefix.length,
casing: addressSpec.casing,
exclude: addressSpec.exclude,
});

return address;
return addressPrefix + address;
}

/**
Expand Down
18 changes: 15 additions & 3 deletions test/modules/__snapshots__/finance.spec.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ exports[`finance > 42 > bic > noArgs 1`] = `"YTPECC2VXXX"`;

exports[`finance > 42 > bic > with branch code 1`] = `"JYTPCD52XXX"`;

exports[`finance > 42 > bitcoinAddress 1`] = `"3JAaa4SAH2YQdbbiwrhB9hnsMcvA3Ba"`;
exports[`finance > 42 > bitcoinAddress > noArgs 1`] = `"3JAaa4SAH2YQdbbiwrhB9hnsMcvA3Ba4XY"`;

exports[`finance > 42 > bitcoinAddress > with type and network option 1`] = `"1XJAaa4SAH2YQdbbiwrhB9hnsMcvA"`;

exports[`finance > 42 > bitcoinAddress > with type option 1`] = `"1XJAaa4SAH2YQdbbiwrhB9hnsMcvA"`;

exports[`finance > 42 > creditCardCVV 1`] = `"397"`;

Expand Down Expand Up @@ -108,7 +112,11 @@ exports[`finance > 1211 > bic > noArgs 1`] = `"XFZROMRC"`;

exports[`finance > 1211 > bic > with branch code 1`] = `"YXFZNPOROTR"`;

exports[`finance > 1211 > bitcoinAddress 1`] = `"3eZEFLmGPLEQrSRdAcnZLoWwYeiHwmRogjbyG9G"`;
exports[`finance > 1211 > bitcoinAddress > noArgs 1`] = `"bc1pw8zppsdqusnufvv7l7dzsexkz8aqjdve9a6kq5qh8f7vlh2q6q9sjg7mv4"`;

exports[`finance > 1211 > bitcoinAddress > with type and network option 1`] = `"1TeZEFLmGPLEQrSRdAcnZLoWwYeiHwmRog"`;

exports[`finance > 1211 > bitcoinAddress > with type option 1`] = `"1TeZEFLmGPLEQrSRdAcnZLoWwYeiHwmRog"`;

exports[`finance > 1211 > creditCardCVV 1`] = `"982"`;

Expand Down Expand Up @@ -192,7 +200,11 @@ exports[`finance > 1337 > bic > noArgs 1`] = `"EHLILK9ZXXX"`;

exports[`finance > 1337 > bic > with branch code 1`] = `"GEHLGGI9XXX"`;

exports[`finance > 1337 > bitcoinAddress 1`] = `"1hsjwgYJ7oC8ZrMNmqzLbhEubpcwQ"`;
exports[`finance > 1337 > bitcoinAddress > noArgs 1`] = `"3hsjwgYJ7oC8ZrMNmqzLbhEubpc"`;

exports[`finance > 1337 > bitcoinAddress > with type and network option 1`] = `"1ahsjwgYJ7oC8ZrMNmqzLbhEubpc"`;

exports[`finance > 1337 > bitcoinAddress > with type option 1`] = `"1ahsjwgYJ7oC8ZrMNmqzLbhEubpc"`;

exports[`finance > 1337 > creditCardCVV 1`] = `"212"`;

Expand Down
106 changes: 98 additions & 8 deletions test/modules/finance.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import isValidBtcAddress from 'validator/lib/isBtcAddress';
import isCreditCard from 'validator/lib/isCreditCard';
import { describe, expect, it } from 'vitest';
import { faker, fakerZH_CN } from '../../src';
import { FakerError } from '../../src/errors/faker-error';
import {
BitcoinAddressFamily,
BitcoinNetwork,
} from '../../src/modules/finance/bitcoin';
import ibanLib from '../../src/modules/finance/iban';
import { luhnCheck } from '../../src/modules/helpers/luhn-check';
import { seededTests } from '../support/seeded-runs';
Expand All @@ -21,7 +24,6 @@ describe('finance', () => {
'currencyCode',
'currencyName',
'currencySymbol',
'bitcoinAddress',
'litecoinAddress',
'creditCardCVV',
'ethereumAddress',
Expand Down Expand Up @@ -91,6 +93,15 @@ describe('finance', () => {
ellipsis: true,
});
});

t.describe('bitcoinAddress', (t) => {
t.it('noArgs')
.it('with type option', { type: BitcoinAddressFamily.Legacy })
.it('with type and network option', {
type: BitcoinAddressFamily.Legacy,
network: BitcoinNetwork.Mainnet,
});
});
});

describe.each(times(NON_SEEDED_BASED_RUN).map(() => faker.seed()))(
Expand Down Expand Up @@ -313,17 +324,96 @@ describe('finance', () => {
});

describe('bitcoinAddress()', () => {
const m_legacy = /^1[A-HJ-NP-Za-km-z1-9]{25,39}$/;
const t_legacy = /^m[A-HJ-NP-Za-km-z1-9]{25,39}$/;
const m_segwit = /^3[A-HJ-NP-Za-km-z1-9]{25,39}$/;
const t_segwit = /^2[A-HJ-NP-Za-km-z1-9]{25,39}$/;
const m_bech32 = /^bc1[ac-hj-np-z02-9]{39,39}$/;
const t_bech32 = /^tb1[ac-hj-np-z02-9]{39,39}$/;
const m_taproot = /^bc1p[ac-hj-np-z02-9]{58,58}$/;
const t_taproot = /^tb1p[ac-hj-np-z02-9]{58,58}$/;

const isBtcAddress = (address: string) =>
[
m_legacy,
t_legacy,
m_segwit,
t_segwit,
m_bech32,
t_bech32,
m_taproot,
t_taproot,
].some((r) => r.test(address));

it('should return a valid bitcoin address', () => {
const bitcoinAddress = faker.finance.bitcoinAddress();
/**
* Note: Although the total length of a Bitcoin address can be 25-33 characters, regex quantifiers only check the preceding token
* Therefore we take one from the total length of the address not including the first character ([13])
*/

expect(bitcoinAddress).toBeTruthy();
expect(bitcoinAddress).toBeTypeOf('string');
expect(bitcoinAddress).toSatisfy(isValidBtcAddress);
});
expect(bitcoinAddress).toSatisfy(isBtcAddress);
});

it.each([
[BitcoinAddressFamily.Legacy, m_legacy],
[BitcoinAddressFamily.Segwit, m_segwit],
[BitcoinAddressFamily.Bech32, m_bech32],
[BitcoinAddressFamily.Taproot, m_taproot],
] as const)(
'should handle the type = $type argument',
(type, regex) => {
const bitcoinAddress = faker.finance.bitcoinAddress({
type,
});

expect(bitcoinAddress).toBeTruthy();
expect(bitcoinAddress).toBeTypeOf('string');
expect(bitcoinAddress).toSatisfy(isBtcAddress);
expect(bitcoinAddress).toMatch(regex);
}
);

it.each([
[BitcoinNetwork.Mainnet, [m_legacy, m_segwit, m_bech32, m_taproot]],
[BitcoinNetwork.Testnet, [t_legacy, t_segwit, t_bech32, t_taproot]],
] as const)(
'should handle the network = $network argument',
(network, regexes) => {
const bitcoinAddress = faker.finance.bitcoinAddress({
network,
});

expect(bitcoinAddress).toBeTruthy();
expect(bitcoinAddress).toBeTypeOf('string');
expect(bitcoinAddress).toSatisfy(isBtcAddress);
expect(bitcoinAddress).toSatisfy<string>((v) =>
regexes.some((r) => r.test(v))
);
}
);

it.each([
[BitcoinAddressFamily.Legacy, BitcoinNetwork.Mainnet, m_legacy],
[BitcoinAddressFamily.Legacy, BitcoinNetwork.Testnet, t_legacy],
[BitcoinAddressFamily.Segwit, BitcoinNetwork.Mainnet, m_segwit],
[BitcoinAddressFamily.Segwit, BitcoinNetwork.Testnet, t_segwit],
[BitcoinAddressFamily.Bech32, BitcoinNetwork.Mainnet, m_bech32],
[BitcoinAddressFamily.Bech32, BitcoinNetwork.Testnet, t_bech32],
[BitcoinAddressFamily.Taproot, BitcoinNetwork.Mainnet, m_taproot],
[BitcoinAddressFamily.Taproot, BitcoinNetwork.Testnet, t_taproot],
] as const)(
'should handle the type = $type and network = $network arguments',
(type, network, regex) => {
const bitcoinAddress = faker.finance.bitcoinAddress({
type,
network,
});

expect(bitcoinAddress).toBeTruthy();
expect(bitcoinAddress).toBeTypeOf('string');
expect(bitcoinAddress).toSatisfy(isBtcAddress);
expect(bitcoinAddress).toMatch(regex);
}
);
});

describe('litecoinAddress()', () => {
Expand Down
Loading