Skip to content

Commit

Permalink
Merge branch 'main' into adam/add-authentications-functions
Browse files Browse the repository at this point in the history
  • Loading branch information
adamfraser committed Jan 28, 2025
2 parents ca2c897 + 2af188f commit 52ce9d5
Show file tree
Hide file tree
Showing 30 changed files with 643 additions and 124 deletions.
69 changes: 59 additions & 10 deletions v4-client-js/__native__/__ios__/v4-native-client.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion v4-client-js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion v4-client-js/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dydxprotocol/v4-client-js",
"version": "1.3.15",
"version": "1.3.16",
"description": "General client library for the new dYdX system (v4 decentralized)",
"main": "build/src/index.js",
"scripts": {
Expand Down
8 changes: 7 additions & 1 deletion v4-client-js/src/clients/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ export const TYPE_URL_MSG_UPDATE_CLOB_PAIR = '/dydxprotocol.clob.MsgUpdateClobPa
export const TYPE_URL_MSG_DELAY_MESSAGE = '/dydxprotocol.delaymsg.MsgDelayMessage';

// x/listing
export const TYPE_URL_MSG_CREATE_MARKET_PERMISSIONLESS = '/dydxprotocol.listing.MsgCreateMarketPermissionless';
export const TYPE_URL_MSG_CREATE_MARKET_PERMISSIONLESS =
'/dydxprotocol.listing.MsgCreateMarketPermissionless';

// x/perpetuals
export const TYPE_URL_MSG_CREATE_PERPETUAL = '/dydxprotocol.perpetuals.MsgCreatePerpetual';
Expand Down Expand Up @@ -210,6 +211,11 @@ export enum AuthenticatorType {
SUBACCOUNT_FILTER = 'SubaccountFilter',
}

export enum TradingRewardAggregationPeriod {
DAILY = 'DAILY',
WEEKLY = 'WEEKLY',
MONTHLY = 'MONTHLY',
}

// ------------ API Defaults ------------
export const DEFAULT_API_TIMEOUT: number = 3_000;
Expand Down
123 changes: 110 additions & 13 deletions v4-client-js/src/clients/modules/account.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { OrderSide, OrderStatus, OrderType, PositionStatus, TickerType } from '../constants';
import {
OrderSide,
OrderStatus,
OrderType,
PositionStatus,
TickerType,
TradingRewardAggregationPeriod,
} from '../constants';
import { Data } from '../types';
import RestClient from './rest';

/**
* @description REST endpoints for data related to a particular address.
*/
export default class AccountClient extends RestClient {
// ------ Subaccount ------ //

async getSubaccounts(address: string, limit?: number): Promise<Data> {
const uri = `/v4/addresses/${address}`;
return this.get(uri, { limit });
Expand All @@ -16,6 +25,13 @@ export default class AccountClient extends RestClient {
return this.get(uri);
}

async getParentSubaccount(address: string, parentSubaccountNumber: number): Promise<Data> {
const uri = `/v4/addresses/${address}/subaccountNumber/${parentSubaccountNumber}`;
return this.get(uri);
}

// ------ Positions ------ //

async getSubaccountPerpetualPositions(
address: string,
subaccountNumber: number,
Expand Down Expand Up @@ -54,6 +70,27 @@ export default class AccountClient extends RestClient {
});
}

// ------ Transfers ------ //

async getTransfersBetween(
sourceAddress: string,
sourceSubaccountNumber: string,
recipientAddress: string,
recipientSubaccountNumber: string,
createdBeforeOrAtHeight?: number | null,
createdBeforeOrAt?: string | null,
): Promise<Data> {
const uri = '/v4/transfers/between';
return this.get(uri, {
sourceAddress,
sourceSubaccountNumber,
recipientAddress,
recipientSubaccountNumber,
createdBeforeOrAtHeight,
createdBeforeOrAt,
});
}

async getSubaccountTransfers(
address: string,
subaccountNumber: number,
Expand Down Expand Up @@ -92,6 +129,8 @@ export default class AccountClient extends RestClient {
});
}

// ------ Orders ------ //

async getSubaccountOrders(
address: string,
subaccountNumber: number,
Expand Down Expand Up @@ -121,11 +160,40 @@ export default class AccountClient extends RestClient {
});
}

async getParentSubaccountNumberOrders(
address: string,
parentSubaccountNumber: number,
ticker?: string | null,
side?: OrderSide | null,
status?: OrderStatus | null,
type?: OrderType | null,
limit?: number | null,
goodTilBlockBeforeOrAt?: number | null,
goodTilBlockTimeBeforeOrAt?: string | null,
returnLatestOrders?: boolean | null,
): Promise<Data> {
const uri = '/v4/orders/parentSubaccountNumber';
return this.get(uri, {
address,
parentSubaccountNumber,
ticker,
side,
status,
type,
limit,
goodTilBlockBeforeOrAt,
goodTilBlockTimeBeforeOrAt,
returnLatestOrders,
});
}

async getOrder(orderId: string): Promise<Data> {
const uri = `/v4/orders/${orderId}`;
return this.get(uri);
}

// ------ Fills ------ //

async getSubaccountFills(
address: string,
subaccountNumber: number,
Expand Down Expand Up @@ -172,6 +240,8 @@ export default class AccountClient extends RestClient {
});
}

// ------ Pnl ------ //

async getSubaccountHistoricalPNLs(
address: string,
subaccountNumber: number,
Expand All @@ -195,22 +265,49 @@ export default class AccountClient extends RestClient {
});
}

async getTransfersBetween(
sourceAddress: string,
sourceSubaccountNumber: string,
recipientAddress: string,
recipientSubaccountNumber: string,
async getParentSubaccountNumberHistoricalPNLs(
address: string,
parentSubaccountNumber: number,
createdBeforeOrAtHeight?: number | null,
createdBeforeOrAt?: string | null
): Promise<Data> {
const uri = '/v4/transfers/between';
createdBeforeOrAt?: string | null,
createdOnOrAfterHeight?: number | null,
createdOnOrAfter?: string | null,
limit?: number | null,
page?: number | null,
): Promise<Data> {
const uri = '/v4//historical-pnl/parentSubaccount';
return this.get(uri, {
sourceAddress,
sourceSubaccountNumber,
recipientAddress,
recipientSubaccountNumber,
address,
parentSubaccountNumber,
createdBeforeOrAtHeight,
createdBeforeOrAt,
createdOnOrAfterHeight,
createdOnOrAfter,
limit,
page,
});
}

// ------ Rewards ------ //

async getHistoricalTradingRewardsAggregations(
address: string,
period: TradingRewardAggregationPeriod,
limit?: number,
startingBeforeOrAt?: string,
startingBeforeOrAtHeight?: string,
): Promise<Data> {
const uri = `/v4/historicalTradingRewardAggregations/${address}`;
return this.get(uri, { period, limit, startingBeforeOrAt, startingBeforeOrAtHeight });
}

async getHistoricalBlockTradingRewards(
address: string,
limit?: number,
startingBeforeOrAt?: string,
startingBeforeOrAtHeight?: string,
): Promise<Data> {
const uri = `/v4/historicalBlockTradingRewards/${address}`;
return this.get(uri, { limit, startingBeforeOrAt, startingBeforeOrAtHeight });
}
}
34 changes: 11 additions & 23 deletions v4-client-py-v2/dydx_v4_client/indexer/rest/noble_client.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
from typing import List, Optional

import grpc
from ecdsa.util import sigencode_string_canonize
from v4_proto.cosmos.auth.v1beta1 import query_pb2_grpc as auth
from v4_proto.cosmos.auth.v1beta1.auth_pb2 import BaseAccount
from v4_proto.cosmos.auth.v1beta1.query_pb2 import QueryAccountRequest
from v4_proto.cosmos.bank.v1beta1 import query_pb2 as bank_query
from v4_proto.cosmos.bank.v1beta1 import query_pb2_grpc as bank_query_grpc
from v4_proto.cosmos.base.abci.v1beta1.abci_pb2 import TxResponse
from v4_proto.cosmos.base.v1beta1.coin_pb2 import Coin
from v4_proto.cosmos.crypto.secp256k1.keys_pb2 import PubKey
from v4_proto.cosmos.tx.signing.v1beta1.signing_pb2 import SignMode
from v4_proto.cosmos.tx.v1beta1 import service_pb2_grpc
from v4_proto.cosmos.tx.v1beta1.service_pb2 import (
Expand All @@ -29,7 +27,8 @@

from dydx_v4_client.config import GAS_MULTIPLIER
from dydx_v4_client.node.builder import as_any
from dydx_v4_client.wallet import from_mnemonic
from dydx_v4_client.key_pair import KeyPair
from dydx_v4_client.wallet import Wallet


class NobleClient:
Expand Down Expand Up @@ -72,8 +71,8 @@ async def connect(self, mnemonic: str):
"""
if not mnemonic:
raise ValueError("Mnemonic not provided")
private_key = from_mnemonic(mnemonic)
self.wallet = private_key
key_pair = KeyPair.from_mnemonic(mnemonic)
self.wallet = Wallet(key_pair, 0, 0)
self.channel = grpc.secure_channel(
self.rest_endpoint,
grpc.ssl_channel_credentials(),
Expand Down Expand Up @@ -178,26 +177,19 @@ async def send(

# Sign and broadcast the transaction
signer_info = SignerInfo(
public_key=as_any(
PubKey(key=self.wallet.get_verifying_key().to_string("compressed"))
),
public_key=as_any(self.wallet.public_key),
mode_info=ModeInfo(single=ModeInfo.Single(mode=SignMode.SIGN_MODE_DIRECT)),
sequence=self.get_account(
self.wallet.get_verifying_key().to_string()
).sequence,
sequence=self.get_account(self.wallet.address).sequence,
)
body = TxBody(messages=messages, memo=memo or self.default_client_memo)
auth_info = AuthInfo(signer_infos=[signer_info], fee=fee)
signature = self.wallet.sign(
signature = self.wallet.key.sign(
SignDoc(
body_bytes=body.SerializeToString(),
auth_info_bytes=auth_info.SerializeToString(),
account_number=self.get_account(
self.wallet.get_verifying_key().to_string()
).account_number,
account_number=self.get_account(self.wallet.address).account_number,
chain_id=self.chain_id,
).SerializeToString(),
sigencode=sigencode_string_canonize,
).SerializeToString()
)

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

# Get simulated response
signer_info = SignerInfo(
public_key=as_any(
PubKey(key=self.wallet.get_verifying_key().to_string("compressed"))
),
public_key=as_any(self.wallet.public_key),
mode_info=ModeInfo(single=ModeInfo.Single(mode=SignMode.SIGN_MODE_DIRECT)),
sequence=self.get_account(
self.wallet.get_verifying_key().to_string()
).sequence,
sequence=self.get_account(self.wallet.address).sequence,
)
body = TxBody(messages=messages, memo=memo or self.default_client_memo)
auth_info = AuthInfo(signer_infos=[signer_info], fee=Fee(gas_limit=0))
Expand Down
78 changes: 78 additions & 0 deletions v4-client-py-v2/dydx_v4_client/key_pair.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""
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.
"""

from dataclasses import dataclass
from typing import Tuple

from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins
from coincurve import PrivateKey
from coincurve.utils import GROUP_ORDER_INT, int_to_bytes


def bytes_from_mnemonic(mnemonic: str) -> bytes:
seed = Bip39SeedGenerator(mnemonic).Generate()
return (
Bip44.FromSeed(seed, Bip44Coins.COSMOS)
.DeriveDefaultPath()
.PrivateKey()
.Raw()
.ToBytes()
)


@dataclass
class KeyPair:
"""
Wrapper class around `coincurve.PrivateKey` to mimic `ecdsa.SigningKey` behavior.
"""

key: PrivateKey

@staticmethod
def from_mnemonic(mnemonic: str) -> "KeyPair":
"""
Creates a private key from a mnemonic.
"""
return KeyPair(PrivateKey(bytes_from_mnemonic(mnemonic)))

@staticmethod
def from_hex(hex_key: str) -> "KeyPair":
"""
Creates a private key from a hex string.
"""
return KeyPair(PrivateKey.from_hex(hex_key))

def sign(self, message: bytes) -> bytes:
"""
Signs a message using the private key. Signature is encoded the same way as `ecdsa.util.sigencode_string_canonize`, to cointains 64-bytes.
"""
signature = self.key.sign_recoverable(message)
return coinsign_canonize(signature)

@property
def public_key_bytes(self) -> bytes:
"""
Returns the public key bytes of the key pair in compressed format.
"""
return self.key.public_key.format(compressed=True)


def coinsign_extract(signature: bytes) -> Tuple[int, int]:
assert len(signature) == 65

r = int.from_bytes(signature[:32], "big")
s = int.from_bytes(signature[32:64], "big")

return r, s


def coinsign_canonize(signature: bytes) -> bytes:
r, s = coinsign_extract(signature)

if s > GROUP_ORDER_INT // 2:
s = GROUP_ORDER_INT - s

r_bytes = int_to_bytes(r)
s_bytes = int_to_bytes(s)
return r_bytes.rjust(32, b"\x00") + s_bytes.rjust(32, b"\x00")
Loading

0 comments on commit 52ce9d5

Please sign in to comment.