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 9 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
20 changes: 19 additions & 1 deletion src/definitions/finance.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { Currency } from '../modules/finance';
import type {
BitcoinAddressType,
BitcoinNetwork,
Currency,
} from '../modules/finance';
import type { Casing } from '../modules/string';
import type { LocaleEntry } from './definitions';
/**
* The possible definitions related to finance.
Expand Down Expand Up @@ -26,4 +31,17 @@ export type FinanceDefinition = LocaleEntry<{
* Types of transactions (e.g. `deposit`).
*/
transaction_type: string[];

/**
* Specifications for generating different types of bitcoin addresses (e.g. `bech32`).
*/
bitcoin_address_specs: Record<
xDivisionByZerox marked this conversation as resolved.
Show resolved Hide resolved
BitcoinAddressType,
{
prefix: Record<BitcoinNetwork, string>;
length: { min: number; max: number };
casing: Casing;
exclude: string;
}
>;
}>;
8 changes: 8 additions & 0 deletions src/locales/base/finance/bitcoin_address_specs/bech32.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Casing } from '../../../../modules/string';

export default {
prefix: { mainnet: 'bc1', testnet: 'tb1' },
length: { min: 42, max: 42 },
casing: 'lower' as Casing,
exclude: '1bBiIoO',
};
18 changes: 18 additions & 0 deletions src/locales/base/finance/bitcoin_address_specs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* This file is automatically generated.
* Run 'pnpm run generate:locales' to update.
*/
import type { FinanceDefinition } from '../../../..';
import bech32 from './bech32';
import legacy from './legacy';
import segwit from './segwit';
import taproot from './taproot';

const bitcoin_address_specs: FinanceDefinition['bitcoin_address_specs'] = {
bech32,
legacy,
segwit,
taproot,
};

export default bitcoin_address_specs;
8 changes: 8 additions & 0 deletions src/locales/base/finance/bitcoin_address_specs/legacy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Casing } from '../../../../modules/string';

export default {
prefix: { mainnet: '1', testnet: 'm' },
length: { min: 26, max: 34 },
casing: 'mixed' as Casing,
exclude: '0OIl',
};
8 changes: 8 additions & 0 deletions src/locales/base/finance/bitcoin_address_specs/segwit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Casing } from '../../../../modules/color';

export default {
prefix: { mainnet: '3', testnet: '2' },
length: { min: 26, max: 34 },
casing: 'mixed' as Casing,
exclude: '0OIl',
};
8 changes: 8 additions & 0 deletions src/locales/base/finance/bitcoin_address_specs/taproot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Casing } from '../../../../modules/string';

export default {
prefix: { mainnet: 'bc1p', testnet: 'tb1p' },
length: { min: 62, max: 62 },
casing: 'lower' as Casing,
exclude: '1bBiIoO',
};
12 changes: 12 additions & 0 deletions src/locales/base/finance/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* This file is automatically generated.
* Run 'pnpm run generate:locales' to update.
*/
import type { FinanceDefinition } from '../../..';
import bitcoin_address_specs from './bitcoin_address_specs';

const finance: FinanceDefinition = {
bitcoin_address_specs,
};

export default finance;
2 changes: 2 additions & 0 deletions src/locales/base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import type { LocaleDefinition } from '../..';
import color from './color';
import database from './database';
import finance from './finance';
import hacker from './hacker';
import internet from './internet';
import location from './location';
Expand All @@ -14,6 +15,7 @@ import system from './system';
const base: LocaleDefinition = {
color,
database,
finance,
hacker,
internet,
location,
Expand Down
57 changes: 46 additions & 11 deletions src/modules/finance/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export interface Currency {
symbol: string;
}

export type BitcoinAddressType = 'legacy' | 'segwit' | 'bech32' | 'taproot';

export type BitcoinNetwork = 'mainnet' | 'testnet';
xDivisionByZerox marked this conversation as resolved.
Show resolved Hide resolved

/**
* Puts a space after every 4 characters.
*
Expand Down Expand Up @@ -486,23 +490,54 @@ export class FinanceModule extends ModuleBase {
/**
* Generates a random Bitcoin address.
*
* @param options An options object.
madoke marked this conversation as resolved.
Show resolved Hide resolved
* @param options.type The bitcoin address type (`'legacy'`, `'sewgit'`, `'bech32'` or `'taproot'`). Defaults to `'legacy'`.
madoke marked this conversation as resolved.
Show resolved Hide resolved
* @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', 'segwit', 'bech32', 'taproot'])
*/
type?: BitcoinAddressType;
/**
* The bitcoin network (`'mainnet'` or `'testnet'`).
*
* @default 'mainnet'
*/
network?: BitcoinNetwork;
} = {}
): string {
const {
type = this.faker.helpers.arrayElement([
'legacy',
'segwit',
'bech32',
'taproot',
]),
network = 'mainnet',
} = options;
const addressSpec =
this.faker.definitions.finance.bitcoin_address_specs[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
113 changes: 105 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 type {
BitcoinAddressType,
BitcoinNetwork,
} from '../../src/modules/finance';
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: 'legacy' })
.it('with type and network option', {
type: 'legacy',
network: 'mainnet',
});
});
});

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

describe('bitcoinAddress()', () => {
// Utility for validating Bitcoin addresses (validator lib doesn't support taproot and testnets yet)
const bech32 = /^(bc1|tb1|bc1p|tb1p)[a-z0-9]{39,58}$/;
const base58 = /^(1|2|3|m)[A-HJ-NP-Za-km-z1-9]{25,39}$/;
const isBtcAddress = (address: string) =>
bech32.test(address) || base58.test(address);
xDivisionByZerox marked this conversation as resolved.
Show resolved Hide resolved

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`
madoke marked this conversation as resolved.
Show resolved Hide resolved
type | expectedPrefix
${'legacy'} | ${'1'}
${'segwit'} | ${'3'}
${'bech32'} | ${'bc1'}
${'taproot'} | ${'bc1p'}
`(
xDivisionByZerox marked this conversation as resolved.
Show resolved Hide resolved
'should handle the type = $type argument',
({
type,
expectedPrefix,
}: {
type: BitcoinAddressType;
expectedPrefix: string;
}) => {
const bitcoinAddress = faker.finance.bitcoinAddress({
type,
});

expect(bitcoinAddress).toBeTruthy();
expect(bitcoinAddress).toBeTypeOf('string');
expect(bitcoinAddress).toSatisfy(isBtcAddress);
expect(bitcoinAddress.startsWith(expectedPrefix)).toBe(true);
}
);

it.each`
network | expectedPrefixes
${'mainnet'} | ${['1', '3', 'bc1', 'bc1p']}
${'testnet'} | ${['2', 'm', 'tb1', 'tb1p']}
`(
'should handle the network = $network argument',
({
network,
expectedPrefixes,
}: {
network: BitcoinNetwork;
expectedPrefixes: string[];
}) => {
const bitcoinAddress = faker.finance.bitcoinAddress({
network,
});

expect(bitcoinAddress).toBeTruthy();
expect(bitcoinAddress).toBeTypeOf('string');
expect(bitcoinAddress).toSatisfy(isBtcAddress);
expect(
expectedPrefixes.some((p: string) => bitcoinAddress.startsWith(p))
).toBe(true);
}
);

it.each`
type | network | expectedPrefix
${'legacy'} | ${'mainnet'} | ${'1'}
${'legacy'} | ${'testnet'} | ${'m'}
${'segwit'} | ${'mainnet'} | ${'3'}
${'segwit'} | ${'testnet'} | ${'2'}
${'bech32'} | ${'mainnet'} | ${'bc1'}
${'bech32'} | ${'testnet'} | ${'tb1'}
${'taproot'} | ${'mainnet'} | ${'bc1p'}
${'taproot'} | ${'testnet'} | ${'tb1p'}
`(
'should handle the type = $type and network = $network arguments',
({
type,
network,
expectedPrefix,
}: {
type: BitcoinAddressType;
network: BitcoinNetwork;
expectedPrefix: string;
}) => {
const bitcoinAddress = faker.finance.bitcoinAddress({
type,
network,
});

expect(bitcoinAddress).toBeTruthy();
expect(bitcoinAddress).toBeTypeOf('string');
expect(bitcoinAddress.startsWith(expectedPrefix)).toBe(true);
madoke marked this conversation as resolved.
Show resolved Hide resolved
expect(bitcoinAddress).toSatisfy(isBtcAddress);
}
);
});

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