1 | "use strict";
|
2 |
|
3 | import { arrayify, BytesLike, concat, hexDataLength, hexDataSlice, isHexString, stripZeros } from "@ethersproject/bytes";
|
4 | import { BigNumber, BigNumberish, _base16To36, _base36To16 } from "@ethersproject/bignumber";
|
5 | import { keccak256 } from "@ethersproject/keccak256";
|
6 | import { encode } from "@ethersproject/rlp";
|
7 |
|
8 | import { Logger } from "@ethersproject/logger";
|
9 | import { version } from "./_version";
|
10 | const logger = new Logger(version);
|
11 |
|
12 | function 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 |
|
41 | const MAX_SAFE_INTEGER: number = 0x1fffffffffffff;
|
42 |
|
43 | function log10(x: number): number {
|
44 | if (Math.log10) { return Math.log10(x); }
|
45 | return Math.log(x) / Math.LN10;
|
46 | }
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 | const ibanLookup: { [character: string]: string } = { };
|
53 | for (let i = 0; i < 10; i++) { ibanLookup[String(i)] = String(i); }
|
54 | for (let i = 0; i < 26; i++) { ibanLookup[String.fromCharCode(65 + i)] = String(10 + i); }
|
55 |
|
56 |
|
57 | const safeDigits = Math.floor(log10(MAX_SAFE_INTEGER));
|
58 |
|
59 | function 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 |
|
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 |
|
77 | export 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 |
|
87 | if (address.substring(0, 2) !== "0x") { address = "0x" + address; }
|
88 |
|
89 | result = getChecksumAddress(address);
|
90 |
|
91 |
|
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 |
|
97 | } else if (address.match(/^XE[0-9]{2}[0-9A-Za-z]{30,31}$/)) {
|
98 |
|
99 |
|
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 |
|
115 | export function isAddress(address: string): boolean {
|
116 | try {
|
117 | getAddress(address);
|
118 | return true;
|
119 | } catch (error) { }
|
120 | return false;
|
121 | }
|
122 |
|
123 | export 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 |
|
130 | export 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 |
|
143 | export 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 | }
|