You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I'm not exactly a pro programmer but it is nice to be able to ditch BigNumber and just use JS BigInt's nowadays thanks to to ethers.js v6. (I believe the web3.js crew have moved to this also. Don't quote me.)
No expectations. take it or leave it. I was just messing about and not actually writing anything with it (yet). Also, too lazy to fork and PR :P
/* ether v6 com[atible version of https://www.npmjs.com/package/@nxqbao/eth-signer-trezor*/import{ethers,AbstractSigner,Provider,Signature,Transaction,TypedDataDomain,TypedDataField,TransactionRequest,computeAddress,getAddress,hexlify,toNumber,toQuantity,resolveProperties,}from'ethers'importTrezorConnect,{Response,Unsuccessful,EthereumSignTransaction,}from'@trezor/connect';import{HDNodeResponse}from'@trezor/connect/lib/types/api/getPublicKey';import{transformTypedData}from"@trezor/connect-plugin-ethereum";import{ConnectError}from'./error';importHDkeyfrom'hdkey';constmanifest={email: '[email protected]',appUrl: 'https://hmterm.gruvin.me/'}constconfig={
manifest,popup: false,webusb: false,debug: false,lazyLoad: false// env: "node"}constHD_WALLET_PATH_BASE=`m`constDEFAULT_HD_PATH_STRING="m/44'/60'/0'/0"// TODO: handle <chainId>constDEFAULT_SESSION_NAME='trezor-signer'asyncfunctionhandleResponse<T>(p: Response<T>){constresponse=awaitp;if(response.success){returnresponse.payload;}throw{message: (responseasUnsuccessful).payload.error,code: (responseasUnsuccessful).payload.code}}exportclassTrezorSignerextendsAbstractSigner{private_derivePath: stringprivate_address?: string|undefinedprivate_isInitialized: booleanprivate_isLoggedIn: booleanprivate_isPrepared: booleanprivate_sessionName: stringprivate_hdk: HDkeyprivate_pathTable: {[key: string]: any}readonly_reqIndex?: string|numberreadonly_reqAddress?: stringconstructor(provider?: Provider,derivePath?: string,index?: number,address?: string,sessionName?: string){super(provider);if(index&&address){thrownewError("Specify account by either wallet index or address. Default index is 0.")}if(!index&&!address){index=0;}this._reqIndex=indexthis._reqAddress=addressthis._sessionName=sessionName||DEFAULT_SESSION_NAME;this._derivePath=derivePath||DEFAULT_HD_PATH_STRING;this._hdk=newHDkey();this._isInitialized=falsethis._isLoggedIn=falsethis._isPrepared=falsethis._pathTable={}}publicasyncprepare(): Promise<any>{if(this._isPrepared){return}this._isPrepared=true;awaitthis.init();awaitthis.login();awaitthis.getAccountsFromDevice()if(this._reqAddress!==undefined){this._address=this._reqAddressthis._derivePath=this.pathFromAddress(this._reqAddress)}if(this._reqIndex!==undefined){this._derivePath=this.concatWalletPath(this._reqIndex)this._address=this.addressFromIndex(HD_WALLET_PATH_BASE,this._reqIndex)}}publicasyncinit(): Promise<any>{if(this._isInitialized){return}console.log("Init trezor...")this._isInitialized=true;returnTrezorConnect.init(config)}publicasynclogin(): Promise<any>{if(this._isLoggedIn){return}console.log("Login to trezor...")this._isLoggedIn=true;// TODO: change to random handshake infoconstloginInfo=awaitTrezorConnect.requestLogin({challengeHidden: "0123456789abcdef",challengeVisual: `Login to ${this._sessionName}`})returnloginInfo}privateasyncgetAccountsFromDevice(fromIndex: number=0,toIndex: number=10): Promise<any>{if(toIndex<0||fromIndex<0){thrownewError('Invalid from and to')}awaitthis.setHdKey()constresult: string[]=[]for(leti=fromIndex;i<toIndex;i++){constaddress=this.addressFromIndex(HD_WALLET_PATH_BASE,i)result.push(address.toLowerCase());this._pathTable[getAddress(address)]=i}returnresult}privateasyncsetHdKey(): Promise<any>{if(this._hdk.publicKey&&this._hdk.chainCode){return}constresult=awaitthis.getDerivePublicKey()this._hdk.publicKey=Buffer.from(result.publicKey,'hex')this._hdk.chainCode=Buffer.from(result.chainCode,'hex')returnthis._hdk}privateasyncgetDerivePublicKey(): Promise<HDNodeResponse>{returnnewPromise(async(resolve,reject)=>{constresponse=awaitTrezorConnect.getPublicKey({path: this._derivePath})if(response.success)resolve(response.payload)elsereject(response.payload.error)})}publicasyncgetAddress(): Promise<string>{if(!this._address){constresult=awaitthis.makeRequest(()=>(TrezorConnect.ethereumGetAddress({path: this._derivePath})))this._address=result.address ? ethers.getAddress(result.address) : ''}returnthis._address;}publicasyncsignMessage(message: string|Uint8Array): Promise<string>{const_message=(messageinstanceofUint8Array) ? hexlify(message) : messageconstresult=awaitthis.makeRequest(()=>TrezorConnect.ethereumSignMessage({path: this._derivePath,message: _message}))returnresult.signature}publicasyncsignTransaction(transaction: TransactionRequest): Promise<string>{consttx=newTransaction()// TODO: handle tx.type// EIP-1559; Type 2if(tx.maxPriorityFeePerGas)tx.maxPriorityFeePerGas=tx.maxPriorityFeePerGasif(tx.maxFeePerGas)tx.maxFeePerGas=tx.maxFeePerGasconsttrezorTx: EthereumSignTransaction={path: this._derivePath,transaction: {to: (tx.to||'0x').toString(),value: toQuantity(tx.value||0),gasPrice: toQuantity(tx.gasPrice||0),gasLimit: toQuantity(tx.gasLimit||0),nonce: toQuantity(tx.nonce||0),data: hexlify(tx.data||'0x'),chainId: toNumber(tx.chainId||0),}}tx.signature=Signature.from(awaitthis.makeRequest(()=>TrezorConnect.ethereumSignTransaction(trezorTx),1))returntx.serialized}publicconnect(provider: Provider): TrezorSigner{returnnewTrezorSigner(provider,this._derivePath)}// TODO: This could probably all be done more easily using ethers::TypedDataEncoderpublicasyncsignTypedData(domain: TypedDataDomain,types: Record<string,Array<TypedDataField>>,value: Record<string,any>): Promise<string>{constdomainProps: {[key: string]: any}=resolveProperties(domain)constEIP712Domain: TypedDataField[]=[];constdomainPropertyTypes=['string','uint256','bytes32','address','string']constdomainProperties=['name','chainId','salt','verifyingContract','version']domainProperties.forEach((property,index)=>{if(domainProps[property]){EIP712Domain.push({type: domainPropertyTypes[index],name: property})}})consteip712Data={
domain,types: {
EIP712Domain,
...types},message: value,primaryType: Object.keys(types)[0]}asParameters<typeoftransformTypedData>[0]console.log("EIP712 Data: ",JSON.stringify(eip712Data,null,4))const{ domain_separator_hash, message_hash }=transformTypedData(eip712Data,true)console.log("Domain separator hash: ",domain_separator_hash)console.log("Message hash: ",message_hash)constresult=awaitthis.makeRequest(()=>TrezorConnect.ethereumSignTypedData({path: this._derivePath,metamask_v4_compat: true,data: eip712Data,
domain_separator_hash,message_hash: (message_hash===null ? undefined : message_hash)}));returnresult.signature;}privateaddressFromIndex(pathBase: string,index: number|string): string{constderivedKey=this._hdk.derive(`${pathBase}/${index}`)constaddress=computeAddress(hexlify(derivedKey.publicKey))returngetAddress(address)}privatepathFromAddress(address: string): string{constchecksummedAddress=getAddress(address)letindex=this._pathTable[checksummedAddress]if(typeofindex==='undefined'){for(leti=0;i<1000;i++){if(checksummedAddress===this.addressFromIndex(HD_WALLET_PATH_BASE,i)){index=ibreak}}}if(typeofindex==='undefined'){thrownewError('Unknown address in trezor');}returnthis.concatWalletPath(index);}privateconcatWalletPath(index: string|number){return`${this._derivePath}/${index.toString(10)}`}privateasyncmakeRequest<T>(fn: ()=>Response<T>,retries=20){try{awaitthis.prepare()constresult=awaithandleResponse(fn());returnresult}catch(e: unknown){if(retries===0){thrownewError('Trezor unreachable, please try again')}consterr=easConnectErrorif(err.code==='Device_CallInProgress'){returnnewPromise<T>(resolve=>{setTimeout(()=>{console.warn('request conflict, trying again in 400ms',err)resolve(this.makeRequest(fn,retries-1))},400)})}else{throwerr}}}}
The text was updated successfully, but these errors were encountered:
Thanks for your work on this repo! :-)
I'm not exactly a pro programmer but it is nice to be able to ditch BigNumber and just use JS BigInt's nowadays thanks to to ethers.js v6. (I believe the web3.js crew have moved to this also. Don't quote me.)
No expectations. take it or leave it. I was just messing about and not actually writing anything with it (yet). Also, too lazy to fork and PR :P
The text was updated successfully, but these errors were encountered: