2022-07-12 18:23:43 +00:00
import eth / keys
import eth / rlp
import eth / common
import eth / common / transaction as ct
import stew / byteutils
2022-07-21 00:04:05 +00:00
import . / provider
2022-07-12 18:23:43 +00:00
import . / transaction
import . / signer
export keys
2022-07-21 00:00:57 +00:00
var rng {. threadvar . } : ref HmacDrbgContext
proc getRng : ref HmacDrbgContext =
if rng . isnil :
rng = newRng ( )
return rng
2022-07-12 18:23:43 +00:00
type SignableTransaction = common . Transaction
type WalletError * = object of EthersError
type Wallet * = ref object of Signer
privateKey * : PrivateKey
publicKey * : PublicKey
address * : Address
2022-07-21 00:04:05 +00:00
provider * : ? Provider
2022-07-12 18:23:43 +00:00
2022-07-21 00:04:05 +00:00
proc new * ( _ : type Wallet , pk : string , provider : Provider ) : Wallet =
2022-07-12 18:23:43 +00:00
result = Wallet ( )
result . privateKey = PrivateKey . fromHex ( pk ) . value
result . publicKey = result . privateKey . toPublicKey ( )
result . address = Address . init ( result . publicKey . toCanonicalAddress ( ) )
result . provider = some provider
proc new * ( _ : type Wallet , pk : string ) : Wallet =
result = Wallet ( )
result . privateKey = PrivateKey . fromHex ( pk ) . value
result . publicKey = result . privateKey . toPublicKey ( )
result . address = Address . init ( result . publicKey . toCanonicalAddress ( ) )
2022-07-21 00:04:05 +00:00
proc connect * ( wallet : Wallet , provider : Provider ) =
2022-07-12 18:23:43 +00:00
wallet . provider = some provider
proc createRandom * ( _ : type Wallet ) : Wallet =
result = Wallet ( )
2022-07-21 00:00:57 +00:00
result . privateKey = PrivateKey . random ( getRng ( ) [ ] )
2022-07-12 18:23:43 +00:00
result . publicKey = result . privateKey . toPublicKey ( )
result . address = Address . init ( result . publicKey . toCanonicalAddress ( ) )
2022-07-21 00:04:05 +00:00
proc createRandom * ( _ : type Wallet , provider : Provider ) : Wallet =
2022-07-12 18:23:43 +00:00
result = Wallet ( )
2022-07-21 00:00:57 +00:00
result . privateKey = PrivateKey . random ( getRng ( ) [ ] )
2022-07-12 18:23:43 +00:00
result . publicKey = result . privateKey . toPublicKey ( )
result . address = Address . init ( result . publicKey . toCanonicalAddress ( ) )
result . provider = some provider
2022-07-12 19:13:34 +00:00
method provider * ( wallet : Wallet ) : Provider =
if wallet . provider . isSome :
return wallet . provider . get
else :
raise newException ( WalletError , " Wallet has no provider " )
2022-07-21 00:00:57 +00:00
method getAddress ( wallet : Wallet ) : Future [ Address ] {. async . } =
return wallet . address
2022-07-12 18:23:43 +00:00
func isPopulated ( tx : transaction . Transaction ) =
if tx . nonce . isNone or
tx . chainId . isNone or
tx . gasLimit . isNone or
( tx . gasPrice . isNone and ( tx . maxFee . isNone or tx . maxPriorityFee . isNone ) ) :
raise newException ( WalletError , " Transaction is not properly populated " )
proc signTransaction ( tr : var SignableTransaction , pk : PrivateKey ) =
let h = tr . txHashNoSignature
let s = sign ( pk , SkMessage ( h . data ) )
let r = toRaw ( s )
let v = r [ 64 ]
tr . R = fromBytesBe ( UInt256 , r . toOpenArray ( 0 , 31 ) )
tr . S = fromBytesBE ( UInt256 , r . toOpenArray ( 32 , 63 ) )
case tr . txType :
of TxLegacy :
#tr.V = int64(v) + int64(tr.chainId)*2 + 35 #TODO does not work, not sure why. Sending the tx results in error of too little funds. Maybe something wrong with signature and a wrong sender gets encoded?
tr . V = int64 ( v ) + 27
of TxEip1559 :
tr . V = int64 ( v )
else :
raise newException ( WalletError , " Transaction type not supported " )
2022-07-21 00:00:57 +00:00
proc signTransaction * ( wallet : Wallet , tx : transaction . Transaction ) : Future [ seq [ byte ] ] {. async . } =
2022-07-12 18:23:43 +00:00
if tx . sender . isSome :
doAssert tx . sender . get = = wallet . address , " from Address mismatch "
isPopulated ( tx )
var s : SignableTransaction
if tx . maxFee . isSome and tx . maxPriorityFee . isSome :
s . txType = TxEip1559
s . maxFee = GasInt ( tx . maxFee . get . truncate ( uint64 ) )
s . maxPriorityFee = GasInt ( tx . maxPriorityFee . get . truncate ( uint64 ) )
else :
s . txType = TxLegacy
s . gasPrice = GasInt ( tx . gasPrice . get . truncate ( uint64 ) )
s . chainId = ChainId ( tx . chainId . get . truncate ( uint64 ) )
s . gasLimit = GasInt ( tx . gasLimit . get . truncate ( uint64 ) )
s . nonce = tx . nonce . get . truncate ( uint64 )
s . to = some EthAddress ( tx . to )
s . payload = tx . data
signTransaction ( s , wallet . privateKey )
2022-07-21 00:00:57 +00:00
return rlp . encode ( s )
2022-07-12 19:21:35 +00:00
2022-07-21 00:00:57 +00:00
method sendTransaction * ( wallet : Wallet , tx : transaction . Transaction ) : Future [ TransactionResponse ] {. async . } =
let rawTX = await signTransaction ( wallet , tx )
2022-07-14 09:45:37 +00:00
return await wallet . provider . get . sendTransaction ( rawTX )
2022-07-12 19:21:35 +00:00
#TODO add functionality to sign messages
#TODO add functionality to create wallets from Mnemoniks or Keystores