UNPKG

5.24 kBPlain TextView Raw
1"use strict";
2
3import { arrayify, BytesLike, concat, hexDataLength, hexDataSlice, isHexString, stripZeros } from "@ethersproject/bytes";
4import { BigNumber, BigNumberish, _base16To36, _base36To16 } from "@ethersproject/bignumber";
5import { keccak256 } from "@ethersproject/keccak256";
6import { encode } from "@ethersproject/rlp";
7
8import { Logger } from "@ethersproject/logger";
9import { version } from "./_version";
10const logger = new Logger(version);
11
12function getChecksumAddress(address: string): string {
13 if (!isHexString(address, 20)) {
14 logger.throwArgumentError("invalid address", "address", address);
15 }
16
17 address = address.toLowerCase();
18
19 const chars = address.substring(2).split("");
20
21 const expanded = new Uint8Array(40);
22 for (let i = 0; i < 40; i++) {
23 expanded[i] = chars[i].charCodeAt(0);
24 }
25
26 const hashed = arrayify(keccak256(expanded));
27
28 for (let i = 0; i < 40; i += 2) {
29 if ((hashed[i >> 1] >> 4) >= 8) {
30 chars[i] = chars[i].toUpperCase();
31 }
32 if ((hashed[i >> 1] & 0x0f) >= 8) {
33 chars[i + 1] = chars[i + 1].toUpperCase();
34 }
35 }
36
37 return "0x" + chars.join("");
38}
39
40// Shims for environments that are missing some required constants and functions
41const MAX_SAFE_INTEGER: number = 0x1fffffffffffff;
42
43function log10(x: number): number {
44 if (Math.log10) { return Math.log10(x); }
45 return Math.log(x) / Math.LN10;
46}
47
48
49// See: https://en.wikipedia.org/wiki/International_Bank_Account_Number
50
51// Create lookup table
52const ibanLookup: { [character: string]: string } = { };
53for (let i = 0; i < 10; i++) { ibanLookup[String(i)] = String(i); }
54for (let i = 0; i < 26; i++) { ibanLookup[String.fromCharCode(65 + i)] = String(10 + i); }
55
56// How many decimal digits can we process? (for 64-bit float, this is 15)
57const safeDigits = Math.floor(log10(MAX_SAFE_INTEGER));
58
59function ibanChecksum(address: string): string {
60 address = address.toUpperCase();
61 address = address.substring(4) + address.substring(0, 2) + "00";
62
63 let expanded = address.split("").map((c) => { return ibanLookup[c]; }).join("");
64
65 // Javascript can handle integers safely up to 15 (decimal) digits
66 while (expanded.length >= safeDigits){
67 let block = expanded.substring(0, safeDigits);
68 expanded = parseInt(block, 10) % 97 + expanded.substring(block.length);
69 }
70
71 let checksum = String(98 - (parseInt(expanded, 10) % 97));
72 while (checksum.length < 2) { checksum = "0" + checksum; }
73
74 return checksum;
75};
76
77export function getAddress(address: string): string {
78 let result = null;
79
80 if (typeof(address) !== "string") {
81 logger.throwArgumentError("invalid address", "address", address);
82 }
83
84 if (address.match(/^(0x)?[0-9a-fA-F]{40}$/)) {
85
86 // Missing the 0x prefix
87 if (address.substring(0, 2) !== "0x") { address = "0x" + address; }
88
89 result = getChecksumAddress(address);
90
91 // It is a checksummed address with a bad checksum
92 if (address.match(/([A-F].*[a-f])|([a-f].*[A-F])/) && result !== address) {
93 logger.throwArgumentError("bad address checksum", "address", address);
94 }
95
96 // Maybe ICAP? (we only support direct mode)
97 } else if (address.match(/^XE[0-9]{2}[0-9A-Za-z]{30,31}$/)) {
98
99 // It is an ICAP address with a bad checksum
100 if (address.substring(2, 4) !== ibanChecksum(address)) {
101 logger.throwArgumentError("bad icap checksum", "address", address);
102 }
103
104 result = _base36To16(address.substring(4));
105 while (result.length < 40) { result = "0" + result; }
106 result = getChecksumAddress("0x" + result);
107
108 } else {
109 logger.throwArgumentError("invalid address", "address", address);
110 }
111
112 return result;
113}
114
115export function isAddress(address: string): boolean {
116 try {
117 getAddress(address);
118 return true;
119 } catch (error) { }
120 return false;
121}
122
123export function getIcapAddress(address: string): string {
124 let base36 = _base16To36(getAddress(address).substring(2)).toUpperCase();
125 while (base36.length < 30) { base36 = "0" + base36; }
126 return "XE" + ibanChecksum("XE00" + base36) + base36;
127}
128
129// http://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed
130export function getContractAddress(transaction: { from: string, nonce: BigNumberish }) {
131 let from: string = null;
132 try {
133 from = getAddress(transaction.from);
134 } catch (error) {
135 logger.throwArgumentError("missing from address", "transaction", transaction);
136 }
137
138 const nonce = stripZeros(arrayify(BigNumber.from(transaction.nonce).toHexString()));
139
140 return getAddress(hexDataSlice(keccak256(encode([ from, nonce ])), 12));
141}
142
143export function getCreate2Address(from: string, salt: BytesLike, initCodeHash: BytesLike): string {
144 if (hexDataLength(salt) !== 32) {
145 logger.throwArgumentError("salt must be 32 bytes", "salt", salt);
146 }
147 if (hexDataLength(initCodeHash) !== 32) {
148 logger.throwArgumentError("initCodeHash must be 32 bytes", "initCodeHash", initCodeHash);
149 }
150 return getAddress(hexDataSlice(keccak256(concat([ "0xff", getAddress(from), salt, initCodeHash ])), 12))
151}