UNPKG

@kaiachain/ethers-ext

Version:
402 lines (356 loc) 11.1 kB
import { getAddress } from "@ethersproject/address"; import { BigNumber } from "@ethersproject/bignumber"; import { JsonRpcProvider } from "@ethersproject/providers"; import { SigningKey, computePublicKey } from "@ethersproject/signing-key"; import { computeAddress } from "@ethersproject/transactions"; import { HexStr } from "@kaiachain/js-ext-core"; import { Wallet } from "./signer.js"; function isSameAddress(a: string, b: string) { return getAddress(a) == getAddress(b); } function isSamePrivateKey(a: string, b: string) { return computeAddress(a) == computeAddress(b); } // Accounts is array of Wallet in ethers.js Ext export class Accounts { public wallets: Wallet[]; constructor(provider: JsonRpcProvider, list: [[string, string?]] | Wallet[]) { this.wallets = []; for (let i = 0; i < list.length; i++) { if (list[i] instanceof Wallet) { // @ts-ignore this.wallets.push(list[i]); } else if (Array.isArray(list[i])) { // @ts-ignore if (list[i].length == 1) { // @ts-ignore this.add([list[i][0]], provider); // @ts-ignore } else if (list[i].length == 2) { // @ts-ignore this.add([list[i][0], list[i][1]], provider); } else { throw new Error( "Input has to be the array of [address, privateKey] or [privateKey]" ); } } else { throw new Error( "Input has to be Wallet, [address, privateKey], or [privateKey]" ); } } } async add( account: [string, string?], provider: JsonRpcProvider ): Promise<boolean> { let addr: string; let priv: string; if (account.length == 1) { const signingKey = new SigningKey(account[0]); addr = computeAddress(signingKey.compressedPublicKey); priv = account[0]; } else if (account.length == 2 && account[1] != undefined) { addr = account[0]; priv = account[1]; } else { throw new Error("Input has to be [address, privateKey] or [privateKey]"); } for (let i = 0; i < this.wallets.length; i++) { if ( isSameAddress(await this.wallets[i].getAddress(), addr) && isSamePrivateKey(this.wallets[i].privateKey, priv) ) { return false; } } this.wallets.push(new Wallet(addr, priv, provider)); return true; } async remove(account: [string, string?]): Promise<boolean> { let addr: string; let priv: string; if (account.length == 1) { const signingKey = new SigningKey(account[0]); addr = computeAddress(signingKey.compressedPublicKey); priv = account[0]; } else if (account.length == 2 && account[1] != undefined) { addr = account[0]; priv = account[1]; } else { throw new Error("Input has to be [address, privateKey] or [privateKey]"); } for (let i = 0; i < this.wallets.length; i++) { if ( isSameAddress(await this.wallets[i].getAddress(), addr) && // @ts-ignore isSamePrivateKey(await this.wallets[i].privateKey, priv) ) { delete this.wallets[i]; this.wallets.splice(i, 1); return true; } } return false; } removeAll() { if (this.wallets.length == 0) { return; } for ( let i = this.wallets.length - 1; i >= 0 && i < this.wallets.length; i-- ) { delete this.wallets[i]; this.wallets.splice(i, 1); } } accountByKey(privateKey: string): Wallet[] { const ret: Wallet[] = []; for (let i = 0; i < this.wallets.length; i++) { if (isSameAddress(this.wallets[i].privateKey, privateKey)) { ret.push(this.wallets[i]); } } return ret; } async accountByAddress(address: string): Promise<Wallet[]> { const ret: Wallet[] = []; for (let i = 0; i < this.wallets.length; i++) { if (isSameAddress(await this.wallets[i].getAddress(), address)) { ret.push(this.wallets[i]); } } return ret; } } // AccountInfo is filled with the result of getAccountKey() RPC call type AccountInfo = { address: string; nonce: number; balance: string | BigNumber; key: any; }; export class AccountStore { private provider: JsonRpcProvider | undefined; public accounts: Accounts | undefined; public accountInfos: AccountInfo[] | undefined; private signableKeyList: string[] = []; async refresh( provider: JsonRpcProvider, list: [[string, string?]] | Wallet[] ) { this.provider = provider; if (this.accounts != undefined) { this.accounts.removeAll(); } this.accounts = new Accounts(provider, list); const wallets = this.accounts.wallets; this.signableKeyList = []; await this.updateSignableKeyList(); this.accountInfos = []; let accInfo; for (let i = 0; i < wallets.length; i++) { if (this.provider instanceof JsonRpcProvider) { const addr: string = await wallets[i].getAddress(); if (this.hasAccountInfos(addr)) { continue; } const klaytn_account = await this.provider.send("klay_getAccount", [ addr, "latest", ]); const klaytn_accountKey = klaytn_account.account.key; accInfo = { address: addr, nonce: klaytn_account.account.nonce, balance: klaytn_account.account.balance, key: {}, }; if (klaytn_accountKey.keyType == 1) { // AccountKeyLegacy accInfo.key = { type: 1, key: {}, }; } else if (klaytn_accountKey.keyType == 2) { // AccountKeyPublic accInfo.key = { type: 2, key: { pubkey: this.getPubkeyInfo( klaytn_accountKey.key.x, klaytn_accountKey.key.y ), }, }; } else if (klaytn_accountKey.keyType == 4) { // AccountKeyWeightedMultiSig accInfo.key = { type: 4, key: { threshold: klaytn_accountKey.key.threshold, keys: [], }, }; for (let i = 0; i < klaytn_accountKey.key.keys.length; i++) { // @ts-ignore accInfo.key.key.keys.push({ weight: klaytn_accountKey.key.keys[i].weight, pubkey: this.getPubkeyInfo( klaytn_accountKey.key.keys[i].key.x, klaytn_accountKey.key.keys[i].key.y ), }); } } else if (klaytn_accountKey.keyType == 5) { // AccountKeyRoleBased accInfo.key = { type: 5, key: { RoleTransaction: {}, RoleAccountUpdate: {}, RoleFeePayer: {}, }, }; const roleKeys = []; for (let i = 0; i < klaytn_accountKey.key.length; i++) { if (klaytn_accountKey.key[i].keyType == 1) { // AccountKeyLegacy in the role-based key roleKeys.push({ type: 1, key: {}, }); } else if (klaytn_accountKey.key[i].keyType == 2) { // AccountKeyPublic in the role-based key roleKeys.push({ type: 2, key: { pubkey: this.getPubkeyInfo( klaytn_accountKey.key[i].key.x, klaytn_accountKey.key[i].key.y ), }, }); } else if (klaytn_accountKey.key[i].keyType == 4) { // AccountKeyWeightedMultiSig in the role-based key const multiKeys = { type: 4, key: { threshold: klaytn_accountKey.key[i].key.threshold, keys: [], }, }; // add mult-keys for ( let j = 0; j < klaytn_accountKey.key[i].key.keys.length; j++ ) { // @ts-ignore multiKeys.key.keys.push({ weight: klaytn_accountKey.key[i].key.keys[j].weight, pubkey: this.getPubkeyInfo( klaytn_accountKey.key[i].key.keys[j].key.x, klaytn_accountKey.key[i].key.keys[j].key.y ), }); } roleKeys.push(multiKeys); } } // @ts-ignore accInfo.key.key = { RoleTransaction: roleKeys[0], RoleAccountUpdate: roleKeys[1], RoleFeePayer: roleKeys[2], }; } this.accountInfos.push(accInfo); } else { throw new Error( "Klaytn typed transaction can only be broadcasted to a Klaytn JSON-RPC server" ); } } } async updateSignableKeyList() { let i: number; for ( i = 0; this.accounts != undefined && i < this.accounts.wallets.length; i++ ) { let hashedKey = await this.accounts.wallets[i].getEtherAddress(); hashedKey = String(hashedKey).toLocaleLowerCase(); if (this.hasInSignableKeyList(hashedKey) == false) { this.signableKeyList.push(hashedKey); } } } hasInSignableKeyList(address: string): boolean { const hashedKey = String(address).toLocaleLowerCase(); return this.signableKeyList.indexOf(hashedKey) != -1; } hasAccountInfos(address: string): boolean { let i: number; for ( i = 0; this.accountInfos != undefined && i < this.accountInfos.length; i++ ) { if (isSameAddress(this.accountInfos[i].address, address)) { return true; } } return false; } getType(address: string): number | null { let i: number; for ( i = 0; this.accountInfos != undefined && i < this.accountInfos.length; i++ ) { if (isSameAddress(this.accountInfos[i].address, address)) { return this.accountInfos[i].key.type; } } return null; } getAccountInfo(address: string): AccountInfo | null { let i: number; for ( i = 0; this.accountInfos != undefined && i < this.accountInfos.length; i++ ) { if (isSameAddress(this.accountInfos[i].address, address)) { return this.accountInfos[i]; } } return null; } getAccountInfos(): AccountInfo[] | undefined { return this.accountInfos; } getPubkeyInfo(x: string, y: string): any { const zeroPadX = HexStr.zeroPad(x, 32); const zeroPadY = HexStr.zeroPad(y, 32); const stripedX = String(zeroPadX).substring(2); const stripedY = String(zeroPadY).substring(2); const compressedKey = computePublicKey( HexStr.concat("0x04" + stripedX + stripedY), true ); const hashedKey = computeAddress(compressedKey); const hasPrivateKey = this.hasInSignableKeyList(hashedKey); return { compressed: compressedKey, hashed: hashedKey, hasPrivateKey: hasPrivateKey, }; } }