Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
60 changes: 60 additions & 0 deletions src.ts/address/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,66 @@ export function getAddress(address: string): string {
* getIcapAddress("XE65GB6LDNXYOFTX0NSV3FUWKOWIXAMJK37");
* //_error:
*/
/**
* Returns a normalized and checksumed address for %%address%%,
* or ``null`` if %%address%% is not a valid address.
*
* This is a safe alternative to [[getAddress]] that does not throw.
*
* @example:
* safeGetAddress("0x8ba1f109551bd432803012645ac136ddd64dba72")
* //_result:
*
* safeGetAddress("not-an-address")
* //_result:
*/
export function safeGetAddress(address: string): string | null {
try {
return getAddress(address);
} catch (e) {
return null;
}
}

/**
* Compares two addresses for equality, ignoring case and checksum.
*
* Returns ``false`` if either address is invalid.
*
* @example:
* addressEquals("0x8ba1f109551bD432803012645Ac136ddd64DBA72", "0x8BA1F109551BD432803012645AC136DDD64DBA72")
* //_result:
*
* addressEquals("0x8ba1f109551bD432803012645Ac136ddd64DBA72", "0x0000000000000000000000000000000000000000")
* //_result:
*/
export function addressEquals(a: string, b: string): boolean {
try {
return getAddress(a) === getAddress(b);
} catch (e) {
return false;
}
}

/**
* Returns ``true`` if %%address%% is the zero address
* (``0x0000000000000000000000000000000000000000``).
*
* @example:
* isZeroAddress("0x0000000000000000000000000000000000000000")
* //_result:
*
* isZeroAddress("0x8ba1f109551bD432803012645Ac136ddd64DBA72")
* //_result:
*/
export function isZeroAddress(address: string): boolean {
try {
return getAddress(address) === "0x0000000000000000000000000000000000000000";
} catch (e) {
return false;
}
}

export function getIcapAddress(address: string): string {
//let base36 = _base16To36(getAddress(address).substring(2)).toUpperCase();
let base36 = BigInt(getAddress(address)).toString(36).toUpperCase();
Expand Down
36 changes: 36 additions & 0 deletions src.ts/address/contract-address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,42 @@ export function getCreateAddress(tx: { from: string, nonce: BigNumberish }): str
* getCreate2Address(from, salt, initCodeHash)
* //_result:
*/
/**
* Returns the addresses that would result from ``CREATE`` operations
* for the given %%from%% address starting at %%startNonce%% for
* %%count%% sequential nonces.
*
* This is useful for predicting multiple contract deployment addresses.
*
* @example
* from = "0x8ba1f109551bD432803012645Ac136ddd64DBA72";
* getCreateAddressRange({ from, startNonce: 0, count: 3 });
* //_result:
*/
export function getCreateAddressRange(tx: { from: string, startNonce: BigNumberish, count: number }): Array<string> {
const from = getAddress(tx.from);
const startNonce = getBigInt(tx.startNonce, "tx.startNonce");
const count = tx.count;

assertArgument(Number.isInteger(count) && count > 0, "count must be a positive integer", "tx.count", count);
assertArgument(startNonce >= BigInt(0), "startNonce must be non-negative", "tx.startNonce", tx.startNonce);

const addresses: Array<string> = [];
for (let i = 0; i < count; i++) {
const nonce = startNonce + BigInt(i);
let nonceHex = nonce.toString(16);
if (nonceHex === "0") {
nonceHex = "0x";
} else if (nonceHex.length % 2) {
nonceHex = "0x0" + nonceHex;
} else {
nonceHex = "0x" + nonceHex;
}
addresses.push(getAddress(dataSlice(keccak256(encodeRlp([ from, nonceHex ])), 12)));
}
return addresses;
}

export function getCreate2Address(_from: string, _salt: BytesLike, _initCodeHash: BytesLike): string {
const from = getAddress(_from);
const salt = getBytes(_salt, "salt");
Expand Down
4 changes: 2 additions & 2 deletions src.ts/address/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,9 @@ export interface NameResolver {
resolveName(name: string): Promise<null | string>;
}

export { getAddress, getIcapAddress } from "./address.js";
export { getAddress, getIcapAddress, safeGetAddress, addressEquals, isZeroAddress } from "./address.js";

export { getCreateAddress, getCreate2Address } from "./contract-address.js";
export { getCreateAddress, getCreateAddressRange, getCreate2Address } from "./contract-address.js";


export { isAddressable, isAddress, resolveAddress } from "./checks.js";
39 changes: 39 additions & 0 deletions src.ts/crypto/signature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,45 @@ export class Signature {
return (bv & BN_1) ? 27: 28;
}

/**
* Returns ``true`` if %%sig%% is a valid signature format that
* can be parsed by [[Signature.from]].
*
* This does not validate the signature against a specific digest,
* only that the format is correct.
*
* @example:
* Signature.isSignatureLike("0x" + "ab".repeat(65))
* //_result:
*
* Signature.isSignatureLike("0x1234")
* //_result:
*/
static isSignatureLike(sig: any): sig is SignatureLike {
try {
Signature.from(sig);
return true;
} catch (e) {
return false;
}
}

/**
* Returns ``true`` if %%sig%% is a valid [[link-eip-2098]] compact
* signature (64 bytes).
*/
static isCompactSignature(sig: any): boolean {
if (typeof sig !== "string") { return false; }
try {
const bytes = getBytes(sig, "signature");
if (bytes.length !== 64) { return false; }
Signature.from(sig);
return true;
} catch (e) {
return false;
}
}

/**
* Creates a new [[Signature]].
*
Expand Down
11 changes: 9 additions & 2 deletions src.ts/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ export {

export { resolveProperties, defineProperties} from "./properties.js";

export { decodeRlp } from "./rlp-decode.js";
export { decodeRlp, isValidRlp, rlpItemCount } from "./rlp-decode.js";
export { encodeRlp } from "./rlp-encode.js";

export { formatEther, parseEther, formatUnits, parseUnits } from "./units.js";
export { formatEther, parseEther, formatGwei, parseGwei, formatUnits, parseUnits, convertUnits, isValidDecimalString } from "./units.js";

export {
toUtf8Bytes,
Expand All @@ -50,6 +50,13 @@ export {

export { uuidV4 } from "./uuid.js";

export {
isValidPrivateKey, isValidBytes32, isValidTransactionHash,
isValidBlockTag, normalizeHexData, isValidSolidityType,
clampBigInt, getIntegerRange, fitsInType,
assertDataLength, safeToNumber, safeToBigInt
} from "./validators.js";

/////////////////////////////
// Types

Expand Down
48 changes: 48 additions & 0 deletions src.ts/utils/rlp-decode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,54 @@ function _decode(data: Uint8Array, offset: number): { consumed: number, result:
return { consumed: 1, result: hexlifyByte(data[offset]) };
}

/**
* Returns ``true`` if %%data%% is valid RLP-encoded data.
*
* This attempts to decode the data and returns ``false`` if
* decoding fails for any reason (malformed data, trailing bytes, etc.).
*
* @example:
* isValidRlp("0xc0")
* //_result:
*
* isValidRlp("0xff")
* //_result:
*/
export function isValidRlp(_data: BytesLike): boolean {
try {
const data = getBytes(_data, "data");
const decoded = _decode(data, 0);
return decoded.consumed === data.length;
} catch (e) {
return false;
}
}

/**
* Returns the number of top-level items in the RLP-encoded %%data%%,
* or ``-1`` if the data is not a valid RLP list.
*
* If the data is a single item (not a list), returns ``0``.
*
* @example:
* // An empty list
* rlpItemCount("0xc0")
* //_result:
*/
export function rlpItemCount(_data: BytesLike): number {
try {
const data = getBytes(_data, "data");
const decoded = _decode(data, 0);
if (decoded.consumed !== data.length) { return -1; }
if (Array.isArray(decoded.result)) {
return decoded.result.length;
}
return 0;
} catch (e) {
return -1;
}
}

/**
* Decodes %%data%% into the structured data it represents.
*/
Expand Down
73 changes: 73 additions & 0 deletions src.ts/utils/units.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,76 @@ export function formatEther(wei: BigNumberish): string {
export function parseEther(ether: string): bigint {
return parseUnits(ether, 18);
}

/**
* Converts %%value%% into a //decimal string// using the number of
* decimal places for the given %%unit%% name (e.g. ``"gwei"``).
*
* This is a convenience function that resolves unit names.
*
* @example:
* formatGwei(1000000000n)
* //_result:
*
* formatGwei(1500000000n)
* //_result:
*/
export function formatGwei(wei: BigNumberish): string {
return formatUnits(wei, "gwei");
}

/**
* Converts the //decimal string// %%value%% to a BigInt, using 9
* decimal places (gwei).
*
* @example:
* parseGwei("1.0")
* //_result:
*
* parseGwei("20.5")
* //_result:
*/
export function parseGwei(value: string): bigint {
return parseUnits(value, "gwei");
}

/**
* Converts %%value%% from one unit to another.
*
* The %%fromUnit%% and %%toUnit%% can be unit names (e.g. ``"gwei"``,
* ``"ether"``) or decimal counts.
*
* @example:
* // Convert 1 ether to gwei
* convertUnits("1.0", "ether", "gwei")
* //_result:
*
* // Convert 1000000000 gwei to ether
* convertUnits("1000000000", "gwei", "ether")
* //_result:
*/
export function convertUnits(value: string, fromUnit: string | Numeric, toUnit: string | Numeric): string {
assertArgument(typeof value === "string", "value must be a string", "value", value);
// Parse from fromUnit to base (wei), then format in toUnit
const baseValue = parseUnits(value, fromUnit);
return formatUnits(baseValue, toUnit);
}

/**
* Returns ``true`` if %%value%% is a valid decimal string that
* can be parsed by [[parseUnits]].
*
* @example:
* isValidDecimalString("1.5")
* //_result:
*
* isValidDecimalString("abc")
* //_result:
*
* isValidDecimalString("1.2.3")
* //_result:
*/
export function isValidDecimalString(value: string): boolean {
if (typeof value !== "string") { return false; }
return !!value.match(/^-?[0-9]+(\.[0-9]+)?$/);
}
Loading