-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtransaction.py
132 lines (115 loc) · 4.01 KB
/
transaction.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import base64
import hashlib
import json
from typing import Any, Dict, List
import sys
if sys.version_info < (3, 8):
from typing_extensions import Literal, TypedDict
else:
from typing import Literal, TypedDict
# Valid transaction broadcast modes for the `POST /txs` endpoint of the
# Cosmos REST API.
SyncMode = Literal["sync", "async", "block"]
class Wallet(TypedDict):
seed: str
derivation_path: str
private_key: bytes
public_key: bytes
address: str
import ecdsa
from wallet import privkey_to_address, privkey_to_pubkey
# from typing import SyncMode
SyncMode = Literal["sync", "async", "block"]
class Transaction:
"""A Cosmos transaction.
After initialization, one or more token transfers can be added by
calling the `add_transfer()` method. Finally, call `get_pushable()`
to get a signed transaction that can be pushed to the `POST /txs`
endpoint of the Cosmos REST API.
"""
def __init__(
self,
*,
privkey: bytes,
account_num: int,
sequence: int,
fee: int,
gas: int,
fee_denom: str = "eul",
memo: str = "test",
chain_id: str = "euler-6",
sync_mode: SyncMode = "sync",
) -> None:
self._privkey = privkey
self._account_num = account_num
self._sequence = sequence
self._fee = fee
self._fee_denom = fee_denom
self._gas = gas
self._memo = memo
self._chain_id = chain_id
self._sync_mode = sync_mode
self._msgs: List[dict] = []
def add_cyberlink(self, cid_from: str, cid_to: str) -> None:
cyberlink = {
"type": "cyber/Link",
"value": {
"address": privkey_to_address(self._privkey),
"links": [{"from": cid_from, "to": cid_to}]
}
}
self._msgs.append(cyberlink)
def add_transfer(self, recipient: str, amount: int, denom: str = "eul") -> None:
transfer = {
"type": "cosmos-sdk/MsgSend",
"value": {
"from_address": privkey_to_address(self._privkey),
"to_address": recipient,
"amount": [{"denom": denom, "amount": str(amount)}],
},
}
self._msgs.append(transfer)
def get_pushable(self) -> str:
pubkey = privkey_to_pubkey(self._privkey)
base64_pubkey = base64.b64encode(pubkey).decode("utf-8")
pushable_tx = {
"tx": {
"msg": self._msgs,
"fee": {
"gas": "200000",
"amount": [],
},
"memo": self._memo,
"signatures": [
{
"signature": self._sign(),
"pub_key": {"type": "tendermint/PubKeySecp256k1", "value": base64_pubkey},
"account_number": str(self._account_num),
"sequence": str(self._sequence),
}
],
},
"mode": self._sync_mode,
}
return json.dumps(pushable_tx, separators=(",", ":"))
def _sign(self) -> str:
message_str = json.dumps(self._get_sign_message(), separators=(",", ":"), sort_keys=True)
message_bytes = message_str.encode("utf-8")
privkey = ecdsa.SigningKey.from_string(self._privkey, curve=ecdsa.SECP256k1)
signature_compact = privkey.sign_deterministic(
message_bytes, hashfunc=hashlib.sha256, sigencode=ecdsa.util.sigencode_string_canonize
)
signature_base64_str = base64.b64encode(signature_compact).decode("utf-8")
return signature_base64_str
def _get_sign_message(self) -> Dict[str, Any]:
return {
"chain_id": self._chain_id,
"account_number": str(self._account_num),
"fee": {
"gas": "200000",
"amount": [],
},
"memo": self._memo,
"sequence": str(self._sequence),
"msgs": self._msgs,
}