Skip to content

Commit cdb476c

Browse files
Merge pull request #124 from InjectiveLabs/f/refactor_order_hash
feat: add multi-subaccount support
2 parents 11904a5 + 311e7d0 commit cdb476c

File tree

2 files changed

+146
-55
lines changed

2 files changed

+146
-55
lines changed

examples/chain_client/0_LocalOrderHash.py

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14-
1514
import asyncio
1615
import logging
1716

@@ -20,8 +19,7 @@
2019
from pyinjective.transaction import Transaction
2120
from pyinjective.constant import Network
2221
from pyinjective.wallet import PrivateKey
23-
from pyinjective.orderhash import OrderHashes
24-
22+
from pyinjective.orderhash import OrderHashManager
2523

2624
async def main() -> None:
2725
# select network: local, testnet, mainnet
@@ -37,7 +35,13 @@ async def main() -> None:
3735
pub_key = priv_key.to_public_key()
3836
address = await pub_key.to_address().async_init_num_seq(network.lcd_endpoint)
3937
subaccount_id = address.get_subaccount_id(index=0)
40-
OrderHashes.get_subaccount_nonce(network=network, subaccount_id=subaccount_id)
38+
subaccount_id_2 = address.get_subaccount_id(index=1)
39+
40+
order_hash_manager = OrderHashManager(
41+
address=address,
42+
network=network,
43+
subaccount_indexes=[0,1,2,7]
44+
)
4145

4246
# prepare trade info
4347
spot_market_id = "0xa508cb32923323679f29a032c70342c147c17d0145625922b0ef22e955c844c0"
@@ -49,10 +53,10 @@ async def main() -> None:
4953
market_id=spot_market_id,
5054
subaccount_id=subaccount_id,
5155
fee_recipient=fee_recipient,
52-
price=1.524,
56+
price=0.524,
5357
quantity=0.01,
5458
is_buy=True,
55-
is_po=True
59+
is_po=False
5660
),
5761
composer.SpotOrder(
5862
market_id=spot_market_id,
@@ -70,7 +74,7 @@ async def main() -> None:
7074
market_id=deriv_market_id,
7175
subaccount_id=subaccount_id,
7276
fee_recipient=fee_recipient,
73-
price=25111,
77+
price=10500,
7478
quantity=0.01,
7579
leverage=1.5,
7680
is_buy=True,
@@ -100,8 +104,7 @@ async def main() -> None:
100104
)
101105

102106
# compute order hashes
103-
104-
order_hashes = OrderHashes.compute_order_hashes(spot_orders=spot_orders, derivative_orders=derivative_orders)
107+
order_hashes = order_hash_manager.compute_order_hashes(spot_orders=spot_orders, derivative_orders=derivative_orders, subaccount_index=0)
105108

106109
print("computed spot order hashes", order_hashes.spot)
107110
print("computed derivative order hashes", order_hashes.derivative)
@@ -145,8 +148,7 @@ async def main() -> None:
145148

146149

147150
# compute order hashes
148-
149-
order_hashes = OrderHashes.compute_order_hashes(spot_orders=spot_orders, derivative_orders=derivative_orders)
151+
order_hashes = order_hash_manager.compute_order_hashes(spot_orders=spot_orders, derivative_orders=derivative_orders, subaccount_index=0)
150152

151153
print("computed spot order hashes", order_hashes.spot)
152154
print("computed derivative order hashes", order_hashes.derivative)
@@ -188,6 +190,104 @@ async def main() -> None:
188190
print("gas wanted: {}".format(gas_limit))
189191
print("gas fee: {} INJ".format(gas_fee))
190192

193+
spot_orders = [
194+
composer.SpotOrder(
195+
market_id=spot_market_id,
196+
subaccount_id=subaccount_id_2,
197+
fee_recipient=fee_recipient,
198+
price=1.524,
199+
quantity=0.01,
200+
is_buy=True,
201+
is_po=True
202+
),
203+
composer.SpotOrder(
204+
market_id=spot_market_id,
205+
subaccount_id=subaccount_id_2,
206+
fee_recipient=fee_recipient,
207+
price=27.92,
208+
quantity=0.01,
209+
is_buy=False,
210+
is_po=False
211+
),
212+
]
213+
214+
derivative_orders = [
215+
composer.DerivativeOrder(
216+
market_id=deriv_market_id,
217+
subaccount_id=subaccount_id_2,
218+
fee_recipient=fee_recipient,
219+
price=25111,
220+
quantity=0.01,
221+
leverage=1.5,
222+
is_buy=True,
223+
is_po=False
224+
),
225+
composer.DerivativeOrder(
226+
market_id=deriv_market_id,
227+
subaccount_id=subaccount_id_2,
228+
fee_recipient=fee_recipient,
229+
price=65111,
230+
quantity=0.01,
231+
leverage=2,
232+
is_buy=False,
233+
is_reduce_only=False
234+
),
235+
]
236+
237+
# prepare tx msg
238+
spot_msg = composer.MsgBatchCreateSpotLimitOrders(
239+
sender=address.to_acc_bech32(),
240+
orders=spot_orders
241+
)
242+
243+
deriv_msg = composer.MsgBatchCreateDerivativeLimitOrders(
244+
sender=address.to_acc_bech32(),
245+
orders=derivative_orders
246+
)
247+
248+
# compute order hashes
249+
order_hashes = order_hash_manager.compute_order_hashes(spot_orders=spot_orders, derivative_orders=derivative_orders, subaccount_index=1)
250+
251+
print("computed spot order hashes", order_hashes.spot)
252+
print("computed derivative order hashes", order_hashes.derivative)
253+
254+
# build sim tx 3
255+
tx = (
256+
Transaction()
257+
.with_messages(spot_msg, deriv_msg)
258+
.with_sequence(address.get_sequence())
259+
.with_account_num(address.get_number())
260+
.with_chain_id(network.chain_id)
261+
)
262+
sim_sign_doc = tx.get_sign_doc(pub_key)
263+
sim_sig = priv_key.sign(sim_sign_doc.SerializeToString())
264+
sim_tx_raw_bytes = tx.get_tx_data(sim_sig, pub_key)
265+
266+
# simulate tx
267+
(sim_res, success) = await client.simulate_tx(sim_tx_raw_bytes)
268+
if not success:
269+
print(sim_res)
270+
return
271+
272+
# build tx
273+
gas_price = 500000000
274+
gas_limit = sim_res.gas_info.gas_used + 20000 # add 20k for gas, fee computation
275+
gas_fee = '{:.18f}'.format((gas_price * gas_limit) / pow(10, 18)).rstrip('0')
276+
fee = [composer.Coin(
277+
amount=gas_price * gas_limit,
278+
denom=network.fee_denom,
279+
)]
280+
tx = tx.with_gas(gas_limit).with_fee(fee).with_memo('').with_timeout_height(client.timeout_height)
281+
sign_doc = tx.get_sign_doc(pub_key)
282+
sig = priv_key.sign(sign_doc.SerializeToString())
283+
tx_raw_bytes = tx.get_tx_data(sig, pub_key)
284+
285+
# broadcast tx: send_tx_async_mode, send_tx_sync_mode, send_tx_block_mode
286+
res = await client.send_tx_sync_mode(tx_raw_bytes)
287+
print(res)
288+
print("gas wanted: {}".format(gas_limit))
289+
print("gas fee: {} INJ".format(gas_fee))
290+
191291
if __name__ == "__main__":
192292
logging.basicConfig(level=logging.INFO)
193293
asyncio.get_event_loop().run_until_complete(main())

pyinjective/orderhash.py

Lines changed: 35 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -35,69 +35,56 @@ class DerivativeOrder(EIP712Struct):
3535
domain_separator = EIP712_domain.hash_struct()
3636
order_type_dict = {0: '\x00', 1: '\x01', 2: '\x02', 3: '\x03', 4: '\x04', 5: '\x05', 6: '\x06', 7: '\x07', 8: '\x08'}
3737

38-
class OrderHashes:
38+
class OrderHashResponse:
3939
def __init__(
4040
self,
4141
spot: [str] = None,
4242
derivative: [str] = None,
43-
nonce: str = None
4443
):
4544
self.spot = spot
4645
self.derivative = derivative
47-
self.nonce = nonce
48-
49-
@classmethod
50-
def get_subaccount_nonce(self, network, subaccount_id) -> int:
51-
url = network.lcd_endpoint + '/injective/exchange/v1beta1/exchange/' + subaccount_id
52-
res = requests.get(url = url)
53-
nonce = res.json()["nonce"]
54-
self.nonce = nonce + 1
55-
return self.nonce
56-
57-
@classmethod
58-
def compute_order_hashes(self, spot_orders, derivative_orders) -> [str]:
46+
47+
class OrderHashManager:
48+
def __init__(
49+
self,
50+
address,
51+
network,
52+
subaccount_indexes: [int] = None,
53+
):
54+
self.address = address
55+
self.subacc_nonces = dict()
56+
57+
for i in subaccount_indexes:
58+
subaccount_id = address.get_subaccount_id(index=i)
59+
url = network.lcd_endpoint + '/injective/exchange/v1beta1/exchange/' + subaccount_id
60+
res = requests.get(url=url)
61+
nonce = res.json()["nonce"]
62+
self.subacc_nonces[i] = [subaccount_id, nonce + 1]
63+
64+
def compute_order_hashes(self, spot_orders, derivative_orders, subaccount_index) -> [str]:
5965
if len(spot_orders) + len(derivative_orders) == 0:
6066
return []
6167

62-
order_hashes = OrderHashes(spot=[], derivative=[])
63-
64-
subaccount_id = None
65-
if len(spot_orders) > 0:
66-
subaccount_id = spot_orders[0].order_info.subaccount_id
67-
else:
68-
subaccount_id = derivative_orders[0].order_info.subaccount_id
68+
order_hashes = OrderHashResponse(spot=[], derivative=[])
6969

7070
for o in spot_orders:
71-
msg = build_eip712_msg(o, self.nonce)
72-
typed_data_hash = msg.hash_struct()
73-
typed_bytes = b'\x19\x01' + domain_separator + typed_data_hash
74-
keccak256 = sha3.keccak_256()
75-
keccak256.update(typed_bytes)
76-
order_hash = keccak256.hexdigest()
77-
order_hashes.spot.append('0x' + order_hash)
78-
self.nonce += 1
71+
msg = build_eip712_msg(o, self.subacc_nonces[subaccount_index][1])
72+
order_hash = hash_order(msg)
73+
order_hashes.spot.append(order_hash)
74+
self.subacc_nonces[subaccount_index][1] += 1
7975

8076
for o in derivative_orders:
81-
msg = build_eip712_msg(o, self.nonce)
82-
typed_data_hash = msg.hash_struct()
83-
typed_bytes = b'\x19\x01' + domain_separator + typed_data_hash
84-
keccak256 = sha3.keccak_256()
85-
keccak256.update(typed_bytes)
86-
order_hash = keccak256.hexdigest()
87-
order_hashes.derivative.append('0x' + order_hash)
88-
self.nonce += 1
77+
msg = build_eip712_msg(o, self.subacc_nonces[subaccount_index][1])
78+
order_hash = hash_order(msg)
79+
order_hashes.derivative.append(order_hash)
80+
self.subacc_nonces[subaccount_index][1] += 1
8981

9082
return order_hashes
9183

9284
def param_to_backend_go(param) -> int:
9385
go_param = Decimal(param) / pow(10, 18)
9486
return format(go_param, '.18f')
9587

96-
def increment_nonce(self):
97-
current_nonce = self.nonce
98-
self.nonce += 1
99-
return current_nonce
100-
10188
def parse_order_type(order):
10289
return order_type_dict[order.order_type]
10390

@@ -139,5 +126,9 @@ def build_eip712_msg(order, nonce):
139126
Margin=go_margin
140127
)
141128

142-
# only support msgs from single subaccount
143-
129+
def hash_order(msg):
130+
typed_data_hash = msg.hash_struct()
131+
typed_bytes = b'\x19\x01' + domain_separator + typed_data_hash
132+
keccak256 = sha3.keccak_256()
133+
keccak256.update(typed_bytes)
134+
return '0x' + keccak256.hexdigest()

0 commit comments

Comments
 (0)