2022-07-12 18:23:43 +00:00
|
|
|
import eth/keys
|
|
|
|
import eth/rlp
|
|
|
|
import eth/common
|
|
|
|
import eth/common/transaction as ct
|
2022-07-21 00:04:05 +00:00
|
|
|
import ./provider
|
2023-03-09 09:58:54 +00:00
|
|
|
import ./transaction as tx
|
2022-07-12 18:23:43 +00:00
|
|
|
import ./signer
|
2023-07-04 08:40:22 +00:00
|
|
|
import ./wallet/error
|
2022-07-12 18:23:43 +00:00
|
|
|
|
|
|
|
export keys
|
2023-07-04 08:40:22 +00:00
|
|
|
export WalletError
|
2022-07-12 18:23:43 +00:00
|
|
|
|
2022-07-21 00:00:57 +00:00
|
|
|
var rng {.threadvar.}: ref HmacDrbgContext
|
|
|
|
|
|
|
|
proc getRng: ref HmacDrbgContext =
|
2022-07-14 08:22:54 +00:00
|
|
|
if rng.isNil:
|
2022-07-21 00:00:57 +00:00
|
|
|
rng = newRng()
|
2022-07-14 08:22:54 +00:00
|
|
|
rng
|
2022-07-12 18:23:43 +00:00
|
|
|
|
|
|
|
type SignableTransaction = common.Transaction
|
|
|
|
|
|
|
|
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 =
|
2022-07-14 09:24:21 +00:00
|
|
|
without provider =? wallet.provider:
|
2023-07-04 08:40:22 +00:00
|
|
|
raiseWalletError "Wallet has no provider"
|
2022-07-14 09:24:21 +00:00
|
|
|
provider
|
2022-07-12 19:13:34 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
proc signTransaction(tr: var SignableTransaction, pk: PrivateKey) =
|
2023-07-03 13:51:42 +00:00
|
|
|
# Temporary V value, used to signal to the hashing function the
|
|
|
|
# chain id that we'd like to use for an EIP-155 signature
|
|
|
|
tr.V = int64(uint64(tr.chainId)) * 2 + 35
|
|
|
|
|
2022-07-12 18:23:43 +00:00
|
|
|
let h = tr.txHashNoSignature
|
|
|
|
let s = sign(pk, SkMessage(h.data))
|
|
|
|
|
|
|
|
let r = toRaw(s)
|
|
|
|
let v = r[64]
|
|
|
|
|
2023-03-09 09:58:54 +00:00
|
|
|
tr.R = fromBytesBE(UInt256, r.toOpenArray(0, 31))
|
2022-07-12 18:23:43 +00:00
|
|
|
tr.S = fromBytesBE(UInt256, r.toOpenArray(32, 63))
|
|
|
|
|
|
|
|
case tr.txType:
|
|
|
|
of TxLegacy:
|
2023-07-03 13:51:42 +00:00
|
|
|
tr.V = int64(v) + int64(uint64(tr.chainId))*2 + 35
|
2022-07-12 18:23:43 +00:00
|
|
|
of TxEip1559:
|
|
|
|
tr.V = int64(v)
|
|
|
|
else:
|
2023-07-04 08:40:22 +00:00
|
|
|
raiseWalletError "Transaction type not supported"
|
2022-07-12 18:23:43 +00:00
|
|
|
|
2023-03-09 09:58:54 +00:00
|
|
|
proc signTransaction*(wallet: Wallet, tx: tx.Transaction): Future[seq[byte]] {.async.} =
|
2022-07-14 09:24:21 +00:00
|
|
|
if sender =? tx.sender and sender != wallet.address:
|
2023-07-04 08:40:22 +00:00
|
|
|
raiseWalletError "from address mismatch"
|
2022-07-14 09:24:21 +00:00
|
|
|
|
|
|
|
without nonce =? tx.nonce and chainId =? tx.chainId and gasLimit =? tx.gasLimit:
|
2023-07-04 08:40:22 +00:00
|
|
|
raiseWalletError "Transaction is not properly populated"
|
2022-07-14 09:24:21 +00:00
|
|
|
|
2022-07-12 18:23:43 +00:00
|
|
|
var s: SignableTransaction
|
2022-07-14 09:24:21 +00:00
|
|
|
|
|
|
|
if maxFee =? tx.maxFee and maxPriorityFee =? tx.maxPriorityFee:
|
2022-07-12 18:23:43 +00:00
|
|
|
s.txType = TxEip1559
|
2022-07-14 09:24:21 +00:00
|
|
|
s.maxFee = GasInt(maxFee.truncate(uint64))
|
|
|
|
s.maxPriorityFee = GasInt(maxPriorityFee.truncate(uint64))
|
|
|
|
elif gasPrice =? tx.gasPrice:
|
2022-07-12 18:23:43 +00:00
|
|
|
s.txType = TxLegacy
|
2022-07-14 09:24:21 +00:00
|
|
|
s.gasPrice = GasInt(gasPrice.truncate(uint64))
|
|
|
|
else:
|
2023-07-04 08:40:22 +00:00
|
|
|
raiseWalletError "Transaction is not properly populated"
|
2022-07-14 09:24:21 +00:00
|
|
|
|
|
|
|
s.chainId = ChainId(chainId.truncate(uint64))
|
|
|
|
s.gasLimit = GasInt(gasLimit.truncate(uint64))
|
2023-07-03 13:50:24 +00:00
|
|
|
s.value = tx.value
|
2022-07-14 09:24:21 +00:00
|
|
|
s.nonce = nonce.truncate(uint64)
|
2022-07-12 18:23:43 +00:00
|
|
|
s.to = some EthAddress(tx.to)
|
|
|
|
s.payload = tx.data
|
|
|
|
signTransaction(s, wallet.privateKey)
|
2023-03-09 09:58:54 +00:00
|
|
|
|
2022-07-21 00:00:57 +00:00
|
|
|
return rlp.encode(s)
|
2022-07-12 19:21:35 +00:00
|
|
|
|
2023-03-09 09:58:54 +00:00
|
|
|
method sendTransaction*(wallet: Wallet, tx: tx.Transaction): Future[TransactionResponse] {.async.} =
|
2022-07-21 00:00:57 +00:00
|
|
|
let rawTX = await signTransaction(wallet, tx)
|
2022-07-14 09:51:24 +00:00
|
|
|
return await provider(wallet).sendTransaction(rawTX)
|
2022-07-12 19:21:35 +00:00
|
|
|
|
|
|
|
#TODO add functionality to sign messages
|
|
|
|
|
2022-07-14 08:22:54 +00:00
|
|
|
#TODO add functionality to create wallets from Mnemoniks or Keystores
|