Skip to content

Commit 834cce5

Browse files
authored
Merge branch 'main' into adam/add-authentications-functions
2 parents e0d1c76 + 2af188f commit 834cce5

File tree

30 files changed

+643
-124
lines changed

30 files changed

+643
-124
lines changed

v4-client-js/__native__/__ios__/v4-native-client.js

Lines changed: 59 additions & 10 deletions
Large diffs are not rendered by default.

v4-client-js/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

v4-client-js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@dydxprotocol/v4-client-js",
3-
"version": "1.3.15",
3+
"version": "1.3.16",
44
"description": "General client library for the new dYdX system (v4 decentralized)",
55
"main": "build/src/index.js",
66
"scripts": {

v4-client-js/src/clients/constants.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ export const TYPE_URL_MSG_UPDATE_CLOB_PAIR = '/dydxprotocol.clob.MsgUpdateClobPa
8686
export const TYPE_URL_MSG_DELAY_MESSAGE = '/dydxprotocol.delaymsg.MsgDelayMessage';
8787

8888
// x/listing
89-
export const TYPE_URL_MSG_CREATE_MARKET_PERMISSIONLESS = '/dydxprotocol.listing.MsgCreateMarketPermissionless';
89+
export const TYPE_URL_MSG_CREATE_MARKET_PERMISSIONLESS =
90+
'/dydxprotocol.listing.MsgCreateMarketPermissionless';
9091

9192
// x/perpetuals
9293
export const TYPE_URL_MSG_CREATE_PERPETUAL = '/dydxprotocol.perpetuals.MsgCreatePerpetual';
@@ -210,6 +211,11 @@ export enum AuthenticatorType {
210211
SUBACCOUNT_FILTER = 'SubaccountFilter',
211212
}
212213

214+
export enum TradingRewardAggregationPeriod {
215+
DAILY = 'DAILY',
216+
WEEKLY = 'WEEKLY',
217+
MONTHLY = 'MONTHLY',
218+
}
213219

214220
// ------------ API Defaults ------------
215221
export const DEFAULT_API_TIMEOUT: number = 3_000;

v4-client-js/src/clients/modules/account.ts

Lines changed: 110 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
import { OrderSide, OrderStatus, OrderType, PositionStatus, TickerType } from '../constants';
1+
import {
2+
OrderSide,
3+
OrderStatus,
4+
OrderType,
5+
PositionStatus,
6+
TickerType,
7+
TradingRewardAggregationPeriod,
8+
} from '../constants';
29
import { Data } from '../types';
310
import RestClient from './rest';
411

512
/**
613
* @description REST endpoints for data related to a particular address.
714
*/
815
export default class AccountClient extends RestClient {
16+
// ------ Subaccount ------ //
17+
918
async getSubaccounts(address: string, limit?: number): Promise<Data> {
1019
const uri = `/v4/addresses/${address}`;
1120
return this.get(uri, { limit });
@@ -16,6 +25,13 @@ export default class AccountClient extends RestClient {
1625
return this.get(uri);
1726
}
1827

28+
async getParentSubaccount(address: string, parentSubaccountNumber: number): Promise<Data> {
29+
const uri = `/v4/addresses/${address}/subaccountNumber/${parentSubaccountNumber}`;
30+
return this.get(uri);
31+
}
32+
33+
// ------ Positions ------ //
34+
1935
async getSubaccountPerpetualPositions(
2036
address: string,
2137
subaccountNumber: number,
@@ -54,6 +70,27 @@ export default class AccountClient extends RestClient {
5470
});
5571
}
5672

73+
// ------ Transfers ------ //
74+
75+
async getTransfersBetween(
76+
sourceAddress: string,
77+
sourceSubaccountNumber: string,
78+
recipientAddress: string,
79+
recipientSubaccountNumber: string,
80+
createdBeforeOrAtHeight?: number | null,
81+
createdBeforeOrAt?: string | null,
82+
): Promise<Data> {
83+
const uri = '/v4/transfers/between';
84+
return this.get(uri, {
85+
sourceAddress,
86+
sourceSubaccountNumber,
87+
recipientAddress,
88+
recipientSubaccountNumber,
89+
createdBeforeOrAtHeight,
90+
createdBeforeOrAt,
91+
});
92+
}
93+
5794
async getSubaccountTransfers(
5895
address: string,
5996
subaccountNumber: number,
@@ -92,6 +129,8 @@ export default class AccountClient extends RestClient {
92129
});
93130
}
94131

132+
// ------ Orders ------ //
133+
95134
async getSubaccountOrders(
96135
address: string,
97136
subaccountNumber: number,
@@ -121,11 +160,40 @@ export default class AccountClient extends RestClient {
121160
});
122161
}
123162

163+
async getParentSubaccountNumberOrders(
164+
address: string,
165+
parentSubaccountNumber: number,
166+
ticker?: string | null,
167+
side?: OrderSide | null,
168+
status?: OrderStatus | null,
169+
type?: OrderType | null,
170+
limit?: number | null,
171+
goodTilBlockBeforeOrAt?: number | null,
172+
goodTilBlockTimeBeforeOrAt?: string | null,
173+
returnLatestOrders?: boolean | null,
174+
): Promise<Data> {
175+
const uri = '/v4/orders/parentSubaccountNumber';
176+
return this.get(uri, {
177+
address,
178+
parentSubaccountNumber,
179+
ticker,
180+
side,
181+
status,
182+
type,
183+
limit,
184+
goodTilBlockBeforeOrAt,
185+
goodTilBlockTimeBeforeOrAt,
186+
returnLatestOrders,
187+
});
188+
}
189+
124190
async getOrder(orderId: string): Promise<Data> {
125191
const uri = `/v4/orders/${orderId}`;
126192
return this.get(uri);
127193
}
128194

195+
// ------ Fills ------ //
196+
129197
async getSubaccountFills(
130198
address: string,
131199
subaccountNumber: number,
@@ -172,6 +240,8 @@ export default class AccountClient extends RestClient {
172240
});
173241
}
174242

243+
// ------ Pnl ------ //
244+
175245
async getSubaccountHistoricalPNLs(
176246
address: string,
177247
subaccountNumber: number,
@@ -195,22 +265,49 @@ export default class AccountClient extends RestClient {
195265
});
196266
}
197267

198-
async getTransfersBetween(
199-
sourceAddress: string,
200-
sourceSubaccountNumber: string,
201-
recipientAddress: string,
202-
recipientSubaccountNumber: string,
268+
async getParentSubaccountNumberHistoricalPNLs(
269+
address: string,
270+
parentSubaccountNumber: number,
203271
createdBeforeOrAtHeight?: number | null,
204-
createdBeforeOrAt?: string | null
205-
): Promise<Data> {
206-
const uri = '/v4/transfers/between';
272+
createdBeforeOrAt?: string | null,
273+
createdOnOrAfterHeight?: number | null,
274+
createdOnOrAfter?: string | null,
275+
limit?: number | null,
276+
page?: number | null,
277+
): Promise<Data> {
278+
const uri = '/v4//historical-pnl/parentSubaccount';
207279
return this.get(uri, {
208-
sourceAddress,
209-
sourceSubaccountNumber,
210-
recipientAddress,
211-
recipientSubaccountNumber,
280+
address,
281+
parentSubaccountNumber,
212282
createdBeforeOrAtHeight,
213283
createdBeforeOrAt,
284+
createdOnOrAfterHeight,
285+
createdOnOrAfter,
286+
limit,
287+
page,
214288
});
215289
}
290+
291+
// ------ Rewards ------ //
292+
293+
async getHistoricalTradingRewardsAggregations(
294+
address: string,
295+
period: TradingRewardAggregationPeriod,
296+
limit?: number,
297+
startingBeforeOrAt?: string,
298+
startingBeforeOrAtHeight?: string,
299+
): Promise<Data> {
300+
const uri = `/v4/historicalTradingRewardAggregations/${address}`;
301+
return this.get(uri, { period, limit, startingBeforeOrAt, startingBeforeOrAtHeight });
302+
}
303+
304+
async getHistoricalBlockTradingRewards(
305+
address: string,
306+
limit?: number,
307+
startingBeforeOrAt?: string,
308+
startingBeforeOrAtHeight?: string,
309+
): Promise<Data> {
310+
const uri = `/v4/historicalBlockTradingRewards/${address}`;
311+
return this.get(uri, { limit, startingBeforeOrAt, startingBeforeOrAtHeight });
312+
}
216313
}

v4-client-py-v2/dydx_v4_client/indexer/rest/noble_client.py

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
from typing import List, Optional
22

33
import grpc
4-
from ecdsa.util import sigencode_string_canonize
54
from v4_proto.cosmos.auth.v1beta1 import query_pb2_grpc as auth
65
from v4_proto.cosmos.auth.v1beta1.auth_pb2 import BaseAccount
76
from v4_proto.cosmos.auth.v1beta1.query_pb2 import QueryAccountRequest
87
from v4_proto.cosmos.bank.v1beta1 import query_pb2 as bank_query
98
from v4_proto.cosmos.bank.v1beta1 import query_pb2_grpc as bank_query_grpc
109
from v4_proto.cosmos.base.abci.v1beta1.abci_pb2 import TxResponse
1110
from v4_proto.cosmos.base.v1beta1.coin_pb2 import Coin
12-
from v4_proto.cosmos.crypto.secp256k1.keys_pb2 import PubKey
1311
from v4_proto.cosmos.tx.signing.v1beta1.signing_pb2 import SignMode
1412
from v4_proto.cosmos.tx.v1beta1 import service_pb2_grpc
1513
from v4_proto.cosmos.tx.v1beta1.service_pb2 import (
@@ -29,7 +27,8 @@
2927

3028
from dydx_v4_client.config import GAS_MULTIPLIER
3129
from dydx_v4_client.node.builder import as_any
32-
from dydx_v4_client.wallet import from_mnemonic
30+
from dydx_v4_client.key_pair import KeyPair
31+
from dydx_v4_client.wallet import Wallet
3332

3433

3534
class NobleClient:
@@ -72,8 +71,8 @@ async def connect(self, mnemonic: str):
7271
"""
7372
if not mnemonic:
7473
raise ValueError("Mnemonic not provided")
75-
private_key = from_mnemonic(mnemonic)
76-
self.wallet = private_key
74+
key_pair = KeyPair.from_mnemonic(mnemonic)
75+
self.wallet = Wallet(key_pair, 0, 0)
7776
self.channel = grpc.secure_channel(
7877
self.rest_endpoint,
7978
grpc.ssl_channel_credentials(),
@@ -178,26 +177,19 @@ async def send(
178177

179178
# Sign and broadcast the transaction
180179
signer_info = SignerInfo(
181-
public_key=as_any(
182-
PubKey(key=self.wallet.get_verifying_key().to_string("compressed"))
183-
),
180+
public_key=as_any(self.wallet.public_key),
184181
mode_info=ModeInfo(single=ModeInfo.Single(mode=SignMode.SIGN_MODE_DIRECT)),
185-
sequence=self.get_account(
186-
self.wallet.get_verifying_key().to_string()
187-
).sequence,
182+
sequence=self.get_account(self.wallet.address).sequence,
188183
)
189184
body = TxBody(messages=messages, memo=memo or self.default_client_memo)
190185
auth_info = AuthInfo(signer_infos=[signer_info], fee=fee)
191-
signature = self.wallet.sign(
186+
signature = self.wallet.key.sign(
192187
SignDoc(
193188
body_bytes=body.SerializeToString(),
194189
auth_info_bytes=auth_info.SerializeToString(),
195-
account_number=self.get_account(
196-
self.wallet.get_verifying_key().to_string()
197-
).account_number,
190+
account_number=self.get_account(self.wallet.address).account_number,
198191
chain_id=self.chain_id,
199-
).SerializeToString(),
200-
sigencode=sigencode_string_canonize,
192+
).SerializeToString()
201193
)
202194

203195
tx = Tx(body=body, auth_info=auth_info, signatures=[signature])
@@ -233,13 +225,9 @@ async def simulate_transaction(
233225

234226
# Get simulated response
235227
signer_info = SignerInfo(
236-
public_key=as_any(
237-
PubKey(key=self.wallet.get_verifying_key().to_string("compressed"))
238-
),
228+
public_key=as_any(self.wallet.public_key),
239229
mode_info=ModeInfo(single=ModeInfo.Single(mode=SignMode.SIGN_MODE_DIRECT)),
240-
sequence=self.get_account(
241-
self.wallet.get_verifying_key().to_string()
242-
).sequence,
230+
sequence=self.get_account(self.wallet.address).sequence,
243231
)
244232
body = TxBody(messages=messages, memo=memo or self.default_client_memo)
245233
auth_info = AuthInfo(signer_infos=[signer_info], fee=Fee(gas_limit=0))
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""
2+
This module implements ECDSA (Elliptic Curve Digital Signature Algorithm) key pair wrapper class. Initially `ecdsa.SigningKey` was directly used. However due to security concerns and to avoid direct dependency on specific implementation, `KeyPair class was introduced. This class provides a wrapper around the `coincurve.PrivateKey` and mimics how `ecdsa` was used before.
3+
"""
4+
5+
from dataclasses import dataclass
6+
from typing import Tuple
7+
8+
from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins
9+
from coincurve import PrivateKey
10+
from coincurve.utils import GROUP_ORDER_INT, int_to_bytes
11+
12+
13+
def bytes_from_mnemonic(mnemonic: str) -> bytes:
14+
seed = Bip39SeedGenerator(mnemonic).Generate()
15+
return (
16+
Bip44.FromSeed(seed, Bip44Coins.COSMOS)
17+
.DeriveDefaultPath()
18+
.PrivateKey()
19+
.Raw()
20+
.ToBytes()
21+
)
22+
23+
24+
@dataclass
25+
class KeyPair:
26+
"""
27+
Wrapper class around `coincurve.PrivateKey` to mimic `ecdsa.SigningKey` behavior.
28+
"""
29+
30+
key: PrivateKey
31+
32+
@staticmethod
33+
def from_mnemonic(mnemonic: str) -> "KeyPair":
34+
"""
35+
Creates a private key from a mnemonic.
36+
"""
37+
return KeyPair(PrivateKey(bytes_from_mnemonic(mnemonic)))
38+
39+
@staticmethod
40+
def from_hex(hex_key: str) -> "KeyPair":
41+
"""
42+
Creates a private key from a hex string.
43+
"""
44+
return KeyPair(PrivateKey.from_hex(hex_key))
45+
46+
def sign(self, message: bytes) -> bytes:
47+
"""
48+
Signs a message using the private key. Signature is encoded the same way as `ecdsa.util.sigencode_string_canonize`, to cointains 64-bytes.
49+
"""
50+
signature = self.key.sign_recoverable(message)
51+
return coinsign_canonize(signature)
52+
53+
@property
54+
def public_key_bytes(self) -> bytes:
55+
"""
56+
Returns the public key bytes of the key pair in compressed format.
57+
"""
58+
return self.key.public_key.format(compressed=True)
59+
60+
61+
def coinsign_extract(signature: bytes) -> Tuple[int, int]:
62+
assert len(signature) == 65
63+
64+
r = int.from_bytes(signature[:32], "big")
65+
s = int.from_bytes(signature[32:64], "big")
66+
67+
return r, s
68+
69+
70+
def coinsign_canonize(signature: bytes) -> bytes:
71+
r, s = coinsign_extract(signature)
72+
73+
if s > GROUP_ORDER_INT // 2:
74+
s = GROUP_ORDER_INT - s
75+
76+
r_bytes = int_to_bytes(r)
77+
s_bytes = int_to_bytes(s)
78+
return r_bytes.rjust(32, b"\x00") + s_bytes.rjust(32, b"\x00")

0 commit comments

Comments
 (0)