diff --git a/packages/addresses/src/__tests__/coercions-test.ts b/packages/addresses/src/__tests__/coercions-test.ts new file mode 100644 index 000000000000..cbc78a93464e --- /dev/null +++ b/packages/addresses/src/__tests__/coercions-test.ts @@ -0,0 +1,17 @@ +import { address, Base58EncodedAddress } from '../base58'; + +describe('coercions', () => { + describe('address', () => { + it('can coerce to `Base58EncodedAddress`', () => { + // See scripts/fixtures/GQE2yjns7SKKuMc89tveBDpzYHwXfeuB2PGAbGaPWc6G.json + const raw = + 'GQE2yjns7SKKuMc89tveBDpzYHwXfeuB2PGAbGaPWc6G' as Base58EncodedAddress<'GQE2yjns7SKKuMc89tveBDpzYHwXfeuB2PGAbGaPWc6G'>; + const coerced = address('GQE2yjns7SKKuMc89tveBDpzYHwXfeuB2PGAbGaPWc6G'); + expect(coerced).toBe(raw); + }); + it('throws on invalid `Base58EncodedAddress`', () => { + const thisThrows = () => address('3333333333333333'); + expect(thisThrows).toThrow('`3333333333333333` is not a base-58 encoded address'); + }); + }); +}); diff --git a/packages/addresses/src/__typetests__/coercions-typetests.ts b/packages/addresses/src/__typetests__/coercions-typetests.ts new file mode 100644 index 000000000000..684741436930 --- /dev/null +++ b/packages/addresses/src/__typetests__/coercions-typetests.ts @@ -0,0 +1,3 @@ +import { address, Base58EncodedAddress } from '../base58'; + +address('555555555555555555555555') satisfies Base58EncodedAddress<'555555555555555555555555'>; diff --git a/packages/addresses/src/base58.ts b/packages/addresses/src/base58.ts index b8d84e6b0a6b..566706b216ef 100644 --- a/packages/addresses/src/base58.ts +++ b/packages/addresses/src/base58.ts @@ -30,6 +30,13 @@ export function assertIsBase58EncodedAddress( } } +export function address( + putativeBase58EncodedAddress: TAddress +): Base58EncodedAddress { + assertIsBase58EncodedAddress(putativeBase58EncodedAddress); + return putativeBase58EncodedAddress as Base58EncodedAddress; +} + export function getBase58EncodedAddressCodec( config?: Readonly<{ description: string; diff --git a/packages/rpc-core/src/__tests__/coercions-test.ts b/packages/rpc-core/src/__tests__/coercions-test.ts new file mode 100644 index 000000000000..b1654bc1496b --- /dev/null +++ b/packages/rpc-core/src/__tests__/coercions-test.ts @@ -0,0 +1,67 @@ +import { lamports, LamportsUnsafeBeyond2Pow53Minus1 } from '../lamports'; +import { StringifiedBigInt, stringifiedBigInt } from '../stringified-bigint'; +import { StringifiedNumber, stringifiedNumber } from '../stringified-number'; +import { TransactionSignature, transactionSignature } from '../transaction-signature'; +import { UnixTimestamp, unixTimestamp } from '../unix-timestamp'; + +describe('coercions', () => { + describe('lamports', () => { + it('can coerce to `LamportsUnsafeBeyond2Pow53Minus1`', () => { + const raw = 1234n as LamportsUnsafeBeyond2Pow53Minus1; + const coerced = lamports(1234n); + expect(coerced).toBe(raw); + }); + it('throws on invalid `LamportsUnsafeBeyond2Pow53Minus1`', () => { + const thisThrows = () => lamports(-5n); + expect(thisThrows).toThrow('Input for 64-bit unsigned integer cannot be negative'); + }); + }); + describe('stringifiedBigInt', () => { + it('can coerce to `StringifiedBigInt`', () => { + const raw = '1234' as StringifiedBigInt; + const coerced = stringifiedBigInt('1234'); + expect(coerced).toBe(raw); + }); + it('throws on invalid `StringifiedBigInt`', () => { + const thisThrows = () => stringifiedBigInt('test'); + expect(thisThrows).toThrow('`test` cannot be parsed as a BigInt'); + }); + }); + describe('stringifiedNumber', () => { + it('can coerce to `StringifiedNumber`', () => { + const raw = '1234' as StringifiedNumber; + const coerced = stringifiedNumber('1234'); + expect(coerced).toBe(raw); + }); + it('throws on invalid `StringifiedNumber`', () => { + const thisThrows = () => stringifiedNumber('test'); + expect(thisThrows).toThrow('`test` cannot be parsed as a Number'); + }); + }); + describe('transactionSignature', () => { + it('can coerce to `TransactionSignature`', () => { + // Randomly generated + const raw = + '3bwsNoq6EP89sShUAKBeB26aCC3KLGNajRm5wqwr6zRPP3gErZH7erSg3332SVY7Ru6cME43qT35Z7JKpZqCoPaL' as TransactionSignature; + const coerced = transactionSignature( + '3bwsNoq6EP89sShUAKBeB26aCC3KLGNajRm5wqwr6zRPP3gErZH7erSg3332SVY7Ru6cME43qT35Z7JKpZqCoPaL' + ); + expect(coerced).toBe(raw); + }); + it('throws on invalid `TransactionSignature`', () => { + const thisThrows = () => transactionSignature('test'); + expect(thisThrows).toThrow('`test` is not a transaction signature'); + }); + }); + describe('unixTimestamp', () => { + it('can coerce to `UnixTimestamp`', () => { + const raw = 1234 as UnixTimestamp; + const coerced = unixTimestamp(1234); + expect(coerced).toBe(raw); + }); + it('throws on invalid `UnixTimestamp`', () => { + const thisThrows = () => unixTimestamp(8.75e15); + expect(thisThrows).toThrow('`8750000000000000` is not a timestamp'); + }); + }); +}); diff --git a/packages/rpc-core/src/__typetests__/coercions-typetests.ts b/packages/rpc-core/src/__typetests__/coercions-typetests.ts new file mode 100644 index 000000000000..53077f9a81e0 --- /dev/null +++ b/packages/rpc-core/src/__typetests__/coercions-typetests.ts @@ -0,0 +1,11 @@ +import { lamports, LamportsUnsafeBeyond2Pow53Minus1 } from '../lamports'; +import { StringifiedBigInt, stringifiedBigInt } from '../stringified-bigint'; +import { StringifiedNumber, stringifiedNumber } from '../stringified-number'; +import { TransactionSignature, transactionSignature } from '../transaction-signature'; +import { UnixTimestamp, unixTimestamp } from '../unix-timestamp'; + +lamports(50_000_000_000_000n) satisfies LamportsUnsafeBeyond2Pow53Minus1; +stringifiedBigInt('50_000_000_000_000') satisfies StringifiedBigInt; +stringifiedNumber('50_000_000_000_000') satisfies StringifiedNumber; +transactionSignature('x') satisfies TransactionSignature; +unixTimestamp(0) satisfies UnixTimestamp; diff --git a/packages/rpc-core/src/lamports.ts b/packages/rpc-core/src/lamports.ts index 9c2d1f445b24..c36d90ce0833 100644 --- a/packages/rpc-core/src/lamports.ts +++ b/packages/rpc-core/src/lamports.ts @@ -19,3 +19,8 @@ export function assertIsLamports( throw new Error('Input number is too large to be represented as a 64-bit unsigned integer'); } } + +export function lamports(putativeLamports: bigint): LamportsUnsafeBeyond2Pow53Minus1 { + assertIsLamports(putativeLamports); + return putativeLamports; +} diff --git a/packages/rpc-core/src/stringified-bigint.ts b/packages/rpc-core/src/stringified-bigint.ts index 571256107045..d4d2c056d3b4 100644 --- a/packages/rpc-core/src/stringified-bigint.ts +++ b/packages/rpc-core/src/stringified-bigint.ts @@ -9,3 +9,8 @@ export function assertIsStringifiedBigInt(putativeBigInt: string): asserts putat }); } } + +export function stringifiedBigInt(putativeBigInt: string): StringifiedBigInt { + assertIsStringifiedBigInt(putativeBigInt); + return putativeBigInt; +} diff --git a/packages/rpc-core/src/stringified-number.ts b/packages/rpc-core/src/stringified-number.ts index 4e43cb6db827..4bf0367b617d 100644 --- a/packages/rpc-core/src/stringified-number.ts +++ b/packages/rpc-core/src/stringified-number.ts @@ -5,3 +5,8 @@ export function assertIsStringifiedNumber(putativeNumber: string): asserts putat throw new Error(`\`${putativeNumber}\` cannot be parsed as a Number`); } } + +export function stringifiedNumber(putativeNumber: string): StringifiedNumber { + assertIsStringifiedNumber(putativeNumber); + return putativeNumber; +} diff --git a/packages/rpc-core/src/transaction-signature.ts b/packages/rpc-core/src/transaction-signature.ts index 10fcf3e5b026..7baf7f9d2c64 100644 --- a/packages/rpc-core/src/transaction-signature.ts +++ b/packages/rpc-core/src/transaction-signature.ts @@ -27,3 +27,8 @@ export function assertIsTransactionSignature( }); } } + +export function transactionSignature(putativeTransactionSignature: string): TransactionSignature { + assertIsTransactionSignature(putativeTransactionSignature); + return putativeTransactionSignature; +} diff --git a/packages/rpc-core/src/unix-timestamp.ts b/packages/rpc-core/src/unix-timestamp.ts index 9cae6023c2e8..982b215fc194 100644 --- a/packages/rpc-core/src/unix-timestamp.ts +++ b/packages/rpc-core/src/unix-timestamp.ts @@ -12,3 +12,8 @@ export function assertIsUnixTimestamp(putativeTimestamp: number): asserts putati }); } } + +export function unixTimestamp(putativeTimestamp: number): UnixTimestamp { + assertIsUnixTimestamp(putativeTimestamp); + return putativeTimestamp; +}