Skip to content

Commit 61da18d

Browse files
committed
add chain client examples
1 parent eea77e1 commit 61da18d

9 files changed

+1507
-112
lines changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import asyncio
2+
import aiohttp
3+
import logging
4+
import json
5+
import base64
6+
import ecdsa
7+
import sha3
8+
import grpc
9+
10+
from typing import Any, Dict, List
11+
from injective.chain_client._wallet import (
12+
generate_wallet,
13+
privkey_to_address,
14+
privkey_to_pubkey,
15+
pubkey_to_address,
16+
seed_to_privkey,
17+
DEFAULT_BECH32_HRP,
18+
)
19+
from injective.chain_client._typings import SyncMode
20+
21+
22+
23+
MIN_GAS_PRICE = 500000000
24+
25+
class Transaction:
26+
27+
def __init__(
28+
self,
29+
*,
30+
privkey: bytes,
31+
account_num: int,
32+
sequence: int,
33+
fee: int,
34+
gas: int,
35+
fee_denom: str = "inj",
36+
memo: str = "",
37+
chain_id: str = "injective-888",
38+
hrp: str = DEFAULT_BECH32_HRP,
39+
sync_mode: SyncMode = "block",
40+
) -> None:
41+
self._privkey = privkey
42+
self._account_num = account_num
43+
self._sequence = sequence
44+
self._fee = fee
45+
self._fee_denom = fee_denom
46+
self._gas = gas
47+
self._memo = memo
48+
self._chain_id = chain_id
49+
self._hrp = hrp
50+
self._sync_mode = sync_mode
51+
self._msgs: List[dict] = []
52+
53+
def add_cosmos_bank_msg_send(self, recipient: str, amount: int, denom: str = "inj") -> None:
54+
msg = {
55+
"type": "cosmos-sdk/MsgSend",
56+
"value": {
57+
"from_address": privkey_to_address(self._privkey, hrp=self._hrp),
58+
"to_address": recipient,
59+
"amount": [{"denom": denom, "amount": str(amount)}],
60+
},
61+
}
62+
self._msgs.append(msg)
63+
64+
def get_signed(self) -> str:
65+
pubkey = privkey_to_pubkey(self._privkey)
66+
base64_pubkey = base64.b64encode(pubkey).decode("utf-8")
67+
signed_tx = {
68+
"tx": {
69+
"msg": self._msgs,
70+
"fee": {
71+
"gas": str(self._gas),
72+
"amount": [{"denom": self._fee_denom, "amount": str(self._fee)}],
73+
},
74+
"memo": self._memo,
75+
"signatures": [
76+
{
77+
"signature": self._sign(),
78+
"pub_key": {"type": "injective/PubKeyEthSecp256k1", "value": base64_pubkey},
79+
"account_number": str(self._account_num),
80+
"sequence": str(self._sequence),
81+
}
82+
],
83+
},
84+
"mode": self._sync_mode,
85+
}
86+
return json.dumps(signed_tx, separators=(",", ":"))
87+
88+
def _sign(self) -> str:
89+
message_str = json.dumps(
90+
self._get_sign_message(), separators=(",", ":"), sort_keys=True)
91+
message_bytes = message_str.encode("utf-8")
92+
93+
privkey = ecdsa.SigningKey.from_string(
94+
self._privkey, curve=ecdsa.SECP256k1)
95+
signature_compact_keccak = privkey.sign_deterministic(
96+
message_bytes, hashfunc=sha3.keccak_256, sigencode=ecdsa.util.sigencode_string_canonize
97+
)
98+
signature_base64_str = base64.b64encode(
99+
signature_compact_keccak).decode("utf-8")
100+
return signature_base64_str
101+
102+
def _get_sign_message(self) -> Dict[str, Any]:
103+
return {
104+
"chain_id": self._chain_id,
105+
"account_number": str(self._account_num),
106+
"fee": {
107+
"gas": str(self._gas),
108+
"amount": [{"amount": str(self._fee), "denom": self._fee_denom}],
109+
},
110+
"memo": self._memo,
111+
"sequence": str(self._sequence),
112+
"msgs": self._msgs,
113+
}
114+
115+
async def main() -> None:
116+
sender_pk = seed_to_privkey(
117+
"physical page glare junk return scale subject river token door mirror title"
118+
)
119+
sender_acc_addr = privkey_to_address(sender_pk)
120+
print("Sender Account:", sender_acc_addr)
121+
122+
acc_num, acc_seq = await get_account_num_seq(sender_acc_addr)
123+
124+
tx = Transaction(
125+
privkey=sender_pk,
126+
account_num=acc_num,
127+
sequence=acc_seq,
128+
gas=200000,
129+
fee=200000 * MIN_GAS_PRICE,
130+
sync_mode="block",
131+
)
132+
tx.add_cosmos_bank_msg_send(
133+
recipient="inj1qy69k458ppmj45c3vqwcd6wvlcuvk23x0hsz58",
134+
amount=10000000000000000,
135+
denom="inj",
136+
)
137+
138+
tx_json = tx.get_signed()
139+
140+
print("Signed Tx:", tx_json)
141+
print("Sent Tx:", await post_tx(tx_json))
142+
143+
async def get_account_num_seq(address: str) -> (int, int):
144+
async with aiohttp.ClientSession() as session:
145+
async with session.request(
146+
'GET', 'http://staking-lcd-testnet.injective.network/cosmos/auth/v1beta1/accounts/' + address,
147+
headers={'Accept-Encoding': 'application/json'},
148+
) as response:
149+
if response.status != 200:
150+
print(await response.text())
151+
raise ValueError("HTTP response status", response.status)
152+
153+
resp = json.loads(await response.text())
154+
acc = resp['account']['base_account']
155+
return acc['account_number'], acc['sequence']
156+
157+
async def post_tx(tx_json: str):
158+
async with aiohttp.ClientSession() as session:
159+
async with session.request(
160+
'POST', 'http://staking-lcd-testnet.injective.network/txs', data=tx_json,
161+
headers={'Content-Type': 'application/json'},
162+
) as response:
163+
if response.status != 200:
164+
print(await response.text())
165+
raise ValueError("HTTP response status", response.status)
166+
167+
resp = json.loads(await response.text())
168+
if 'code' in resp:
169+
print("Response:", resp)
170+
raise ValueError('sdk error %d: %s' % (resp['code'], resp['raw_log']))
171+
172+
return resp['txhash']
173+
174+
if __name__ == "__main__":
175+
logging.basicConfig(level=logging.INFO)
176+
asyncio.get_event_loop().run_until_complete(main())
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import asyncio
2+
import aiohttp
3+
import logging
4+
import base64
5+
import json
6+
import ecdsa
7+
import sha3
8+
import grpc
9+
10+
from typing import Any, Dict, List
11+
from injective.chain_client._wallet import (
12+
generate_wallet,
13+
privkey_to_address,
14+
privkey_to_pubkey,
15+
pubkey_to_address,
16+
seed_to_privkey,
17+
DEFAULT_BECH32_HRP,
18+
)
19+
from injective.chain_client._typings import SyncMode
20+
21+
22+
MIN_GAS_PRICE = 500000000
23+
24+
class Transaction:
25+
26+
def __init__(
27+
self,
28+
*,
29+
privkey: bytes,
30+
account_num: int,
31+
sequence: int,
32+
fee: int,
33+
gas: int,
34+
fee_denom: str = "inj",
35+
memo: str = "",
36+
chain_id: str = "injective-888",
37+
hrp: str = DEFAULT_BECH32_HRP,
38+
sync_mode: SyncMode = "block",
39+
) -> None:
40+
self._privkey = privkey
41+
self._account_num = account_num
42+
self._sequence = sequence
43+
self._fee = fee
44+
self._fee_denom = fee_denom
45+
self._gas = gas
46+
self._memo = memo
47+
self._chain_id = chain_id
48+
self._hrp = hrp
49+
self._sync_mode = sync_mode
50+
self._msgs: List[dict] = []
51+
52+
def add_exchange_msg_deposit(self, subaccount: str, amount: int, denom: str = "inj") -> None:
53+
msg = {
54+
"type": "exchange/MsgDeposit",
55+
"value": {
56+
"sender": privkey_to_address(self._privkey, hrp=self._hrp),
57+
"subaccount_id": subaccount,
58+
"amount": {"denom": denom, "amount": str(amount)},
59+
},
60+
}
61+
self._msgs.append(msg)
62+
63+
def get_signed(self) -> str:
64+
pubkey = privkey_to_pubkey(self._privkey)
65+
base64_pubkey = base64.b64encode(pubkey).decode("utf-8")
66+
signed_tx = {
67+
"tx": {
68+
"msg": self._msgs,
69+
"fee": {
70+
"gas": str(self._gas),
71+
"amount": [{"denom": self._fee_denom, "amount": str(self._fee)}],
72+
},
73+
"memo": self._memo,
74+
"signatures": [
75+
{
76+
"signature": self._sign(),
77+
"pub_key": {"type": "injective/PubKeyEthSecp256k1", "value": base64_pubkey},
78+
"account_number": str(self._account_num),
79+
"sequence": str(self._sequence),
80+
}
81+
],
82+
},
83+
"mode": self._sync_mode,
84+
}
85+
return json.dumps(signed_tx, separators=(",", ":"))
86+
87+
def _sign(self) -> str:
88+
message_str = json.dumps(
89+
self._get_sign_message(), separators=(",", ":"), sort_keys=True)
90+
message_bytes = message_str.encode("utf-8")
91+
92+
privkey = ecdsa.SigningKey.from_string(
93+
self._privkey, curve=ecdsa.SECP256k1)
94+
signature_compact_keccak = privkey.sign_deterministic(
95+
message_bytes, hashfunc=sha3.keccak_256, sigencode=ecdsa.util.sigencode_string_canonize
96+
)
97+
signature_base64_str = base64.b64encode(
98+
signature_compact_keccak).decode("utf-8")
99+
return signature_base64_str
100+
101+
def _get_sign_message(self) -> Dict[str, Any]:
102+
return {
103+
"chain_id": self._chain_id,
104+
"account_number": str(self._account_num),
105+
"fee": {
106+
"gas": str(self._gas),
107+
"amount": [{"amount": str(self._fee), "denom": self._fee_denom}],
108+
},
109+
"memo": self._memo,
110+
"sequence": str(self._sequence),
111+
"msgs": self._msgs,
112+
}
113+
114+
async def main() -> None:
115+
sender_pk = seed_to_privkey(
116+
"physical page glare junk return scale subject river token door mirror title"
117+
)
118+
sender_acc_addr = privkey_to_address(sender_pk)
119+
print("Sender Account:", sender_acc_addr)
120+
121+
acc_num, acc_seq = await get_account_num_seq(sender_acc_addr)
122+
123+
tx = Transaction(
124+
privkey=sender_pk,
125+
account_num=acc_num,
126+
sequence=acc_seq,
127+
gas=200000,
128+
fee=200000 * MIN_GAS_PRICE,
129+
sync_mode="block",
130+
)
131+
132+
tx.add_exchange_msg_deposit(
133+
subaccount= "0xbdaedec95d563fb05240d6e01821008454c24c36000000000000000000000000",
134+
amount=10000000000000000,
135+
denom="inj",
136+
)
137+
138+
tx_json = tx.get_signed()
139+
140+
print("Signed Tx:", tx_json)
141+
print("Sent Tx:", await post_tx(tx_json))
142+
143+
async def get_account_num_seq(address: str) -> (int, int):
144+
async with aiohttp.ClientSession() as session:
145+
async with session.request(
146+
'GET', 'http://staking-lcd-testnet.injective.network/cosmos/auth/v1beta1/accounts/' + address,
147+
headers={'Accept-Encoding': 'application/json'},
148+
) as response:
149+
if response.status != 200:
150+
print(await response.text())
151+
raise ValueError("HTTP response status", response.status)
152+
153+
resp = json.loads(await response.text())
154+
acc = resp['account']['base_account']
155+
return acc['account_number'], acc['sequence']
156+
157+
async def post_tx(tx_json: str):
158+
async with aiohttp.ClientSession() as session:
159+
async with session.request(
160+
'POST', 'http://staking-lcd-testnet.injective.network/txs', data=tx_json,
161+
headers={'Content-Type': 'application/json'},
162+
) as response:
163+
if response.status != 200:
164+
print(await response.text())
165+
raise ValueError("HTTP response status", response.status)
166+
167+
resp = json.loads(await response.text())
168+
if 'code' in resp:
169+
print("Response:", resp)
170+
raise ValueError('sdk error %d: %s' % (resp['code'], resp['raw_log']))
171+
172+
return resp['txhash']
173+
174+
if __name__ == "__main__":
175+
logging.basicConfig(level=logging.INFO)
176+
asyncio.get_event_loop().run_until_complete(main())

0 commit comments

Comments
 (0)