@dydxprotocol/v4-client-js 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,117 @@
1
+ import { EncodeObject } from '@cosmjs/proto-signing';
2
+ import Long from 'long';
3
+
4
+ import { Network } from '../src/clients/constants';
5
+ import LocalWallet from '../src/clients/modules/local-wallet';
6
+ import { NobleClient } from '../src/clients/noble-client';
7
+ import { ValidatorClient } from '../src/clients/validator-client';
8
+ import { BECH32_PREFIX, NOBLE_BECH32_PREFIX } from '../src/lib/constants';
9
+ import { sleep } from '../src/lib/utils';
10
+ import { DYDX_TEST_MNEMONIC } from './constants';
11
+
12
+ async function test(): Promise<void> {
13
+ const dydxClient = await ValidatorClient.connect(
14
+ Network.testnet().validatorConfig,
15
+ );
16
+
17
+ const dydxWallet = await LocalWallet.fromMnemonic(
18
+ DYDX_TEST_MNEMONIC,
19
+ BECH32_PREFIX,
20
+ );
21
+ const nobleWallet = await LocalWallet.fromMnemonic(
22
+ DYDX_TEST_MNEMONIC,
23
+ NOBLE_BECH32_PREFIX,
24
+ );
25
+
26
+ const client = await NobleClient.connect('https://rpc.testnet.noble.strange.love', nobleWallet);
27
+
28
+ if (nobleWallet.address === undefined || dydxWallet.address === undefined) {
29
+ throw new Error('Wallet not found');
30
+ }
31
+
32
+ // IBC to noble
33
+
34
+ const ibcToNobleMsg: EncodeObject = {
35
+ typeUrl: '/ibc.applications.transfer.v1.MsgTransfer',
36
+ value: {
37
+ sourcePort: 'transfer',
38
+ sourceChannel: 'channel-0',
39
+ token: {
40
+ denom:
41
+ 'ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5',
42
+ amount: '1000000',
43
+ },
44
+ sender: dydxWallet.address,
45
+ receiver: nobleWallet.address,
46
+ timeoutTimestamp: Long.fromNumber(
47
+ Math.floor(Date.now() / 1000) * 1e9 + 10 * 60 * 1e9,
48
+ ),
49
+ },
50
+ };
51
+
52
+ const msgs = [ibcToNobleMsg];
53
+ const encodeObjects: Promise<EncodeObject[]> = new Promise((resolve) => resolve(msgs),
54
+ );
55
+
56
+ await dydxClient.post.send(
57
+ dydxWallet,
58
+ () => {
59
+ return encodeObjects;
60
+ },
61
+ false,
62
+ undefined,
63
+ undefined,
64
+ );
65
+
66
+ await sleep(30000);
67
+
68
+ try {
69
+ const coins = await client.getAccountBalances(nobleWallet.address);
70
+ console.log('Balances');
71
+ console.log(JSON.stringify(coins));
72
+
73
+ // IBC from noble
74
+
75
+ const ibcFromNobleMsg: EncodeObject = {
76
+ typeUrl: '/ibc.applications.transfer.v1.MsgTransfer',
77
+ value: {
78
+ sourcePort: 'transfer',
79
+ sourceChannel: 'channel-21',
80
+ token: {
81
+ denom: 'uusdc',
82
+ amount: coins[0].amount,
83
+ },
84
+ sender: nobleWallet.address,
85
+ receiver: dydxWallet.address,
86
+ timeoutTimestamp: Long.fromNumber(
87
+ Math.floor(Date.now() / 1000) * 1e9 + 10 * 60 * 1e9,
88
+ ),
89
+ },
90
+ };
91
+ const fee = await client.simulateTransaction([ibcFromNobleMsg]);
92
+
93
+ ibcFromNobleMsg.value.token.amount = (parseInt(ibcFromNobleMsg.value.token.amount, 10) -
94
+ Math.floor(parseInt(fee.amount[0].amount, 10) * 1.4)).toString();
95
+
96
+ await client.send([ibcFromNobleMsg]);
97
+ } catch (error) {
98
+ console.log(JSON.stringify(error.message));
99
+ }
100
+
101
+ await sleep(30000);
102
+
103
+ try {
104
+ const coin = await client.getAccountBalance(nobleWallet.address, 'uusdc');
105
+ console.log('Balance');
106
+ console.log(JSON.stringify(coin));
107
+ } catch (error) {
108
+ console.log(JSON.stringify(error.message));
109
+ }
110
+ }
111
+
112
+ test()
113
+ .then(() => {})
114
+ .catch((error) => {
115
+ console.log(error.message);
116
+ console.log(error);
117
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dydxprotocol/v4-client-js",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "General client library for the new dYdX system (v4 decentralized)",
5
5
  "main": "build/src/index.js",
6
6
  "scripts": {
@@ -48,7 +48,13 @@ export function calculateTimeInForce(
48
48
  ): Order_TimeInForce {
49
49
  switch (type) {
50
50
  case OrderType.MARKET:
51
- return Order_TimeInForce.TIME_IN_FORCE_IOC;
51
+ switch (timeInForce) {
52
+ case OrderTimeInForce.IOC:
53
+ return Order_TimeInForce.TIME_IN_FORCE_IOC;
54
+
55
+ default:
56
+ return Order_TimeInForce.TIME_IN_FORCE_FILL_OR_KILL;
57
+ }
52
58
 
53
59
  case OrderType.LIMIT:
54
60
  switch (timeInForce) {
@@ -28,6 +28,7 @@ export default class LocalWallet {
28
28
  address?: string;
29
29
  pubKey?: Secp256k1Pubkey;
30
30
  signer?: TransactionSigner;
31
+ offlineSigner?: OfflineSigner;
31
32
 
32
33
  static async fromOfflineSigner(signer:OfflineSigner): Promise<LocalWallet> {
33
34
  const wallet = new LocalWallet();
@@ -42,6 +43,7 @@ export default class LocalWallet {
42
43
  }
43
44
 
44
45
  async setSigner(signer: OfflineSigner): Promise<void> {
46
+ this.offlineSigner = signer;
45
47
  const stargateClient = await SigningStargateClient.offline(
46
48
  signer,
47
49
  {
@@ -8,7 +8,7 @@ import { Order_Side, Order_TimeInForce } from '@dydxprotocol/v4-proto/src/codege
8
8
  import * as AuthModule from 'cosmjs-types/cosmos/auth/v1beta1/query';
9
9
  import Long from 'long';
10
10
 
11
- import { BECH32_PREFIX } from '../lib/constants';
11
+ import { BECH32_PREFIX, GAS_MULTIPLIER, NOBLE_BECH32_PREFIX } from '../lib/constants';
12
12
  import { UserError } from '../lib/errors';
13
13
  import { ByteArrayEncoding, encodeJson } from '../lib/helpers';
14
14
  import { deriveHDKeyFromEthereumSignature } from '../lib/onboarding';
@@ -19,6 +19,7 @@ import {
19
19
  } from './constants';
20
20
  import { FaucetClient } from './faucet-client';
21
21
  import LocalWallet from './modules/local-wallet';
22
+ import { NobleClient } from './noble-client';
22
23
  import { SubaccountInfo } from './subaccount';
23
24
  import { OrderFlags } from './types';
24
25
 
@@ -29,6 +30,27 @@ declare global {
29
30
  var faucetClient: FaucetClient | null;
30
31
  // eslint-disable-next-line vars-on-top, no-var
31
32
  var wallet: LocalWallet;
33
+
34
+ // eslint-disable-next-line vars-on-top, no-var
35
+ var nobleValidatorUrl: string | undefined;
36
+ // eslint-disable-next-line vars-on-top, no-var
37
+ var nobleWallet: LocalWallet | undefined;
38
+ // eslint-disable-next-line vars-on-top, no-var
39
+ var nobleClient: NobleClient | undefined;
40
+ }
41
+
42
+ async function getNobleClient(): Promise<NobleClient | undefined> {
43
+ if (
44
+ globalThis.nobleClient === undefined &&
45
+ globalThis.nobleValidatorUrl !== undefined &&
46
+ globalThis.nobleWallet !== undefined
47
+ ) {
48
+ globalThis.nobleClient = await NobleClient.connect(
49
+ globalThis.nobleValidatorUrl,
50
+ globalThis.nobleWallet,
51
+ );
52
+ }
53
+ return Promise.resolve(globalThis.nobleClient);
32
54
  }
33
55
 
34
56
  export async function connectClient(
@@ -53,6 +75,7 @@ export async function connectNetwork(
53
75
  validatorUrl,
54
76
  chainId,
55
77
  faucetUrl,
78
+ nobleValidatorUrl,
56
79
  USDC_DENOM,
57
80
  USDC_DECIMALS,
58
81
  USDC_GAS_DENOM,
@@ -90,6 +113,8 @@ export async function connectNetwork(
90
113
  } else {
91
114
  globalThis.faucetClient = null;
92
115
  }
116
+ globalThis.nobleValidatorUrl = nobleValidatorUrl;
117
+ globalThis.nobleClient = undefined;
93
118
  return encodeJson(config);
94
119
  } catch (e) {
95
120
  return wrappedError(e);
@@ -101,6 +126,10 @@ export async function connectWallet(
101
126
  ): Promise<string> {
102
127
  try {
103
128
  globalThis.wallet = await LocalWallet.fromMnemonic(mnemonic, BECH32_PREFIX);
129
+ globalThis.nobleWallet = await LocalWallet.fromMnemonic(
130
+ mnemonic,
131
+ NOBLE_BECH32_PREFIX,
132
+ );
104
133
  const address = globalThis.wallet.address!;
105
134
  return encodeJson({ address });
106
135
  } catch (e) {
@@ -988,3 +1017,71 @@ export async function getMarketPrice(
988
1017
  return wrappedError(e);
989
1018
  }
990
1019
  }
1020
+
1021
+ export function getNobleAddress(): Promise<String> {
1022
+ try {
1023
+ if (globalThis.nobleWallet?.address === undefined) {
1024
+ throw new UserError('wallet is not set. Call connectWallet() first');
1025
+ }
1026
+ return Promise.resolve(encodeJson(globalThis.nobleWallet.address));
1027
+ } catch (error) {
1028
+ return Promise.resolve(wrappedError(error));
1029
+ }
1030
+ }
1031
+
1032
+ export async function getNobleBalance(): Promise<String> {
1033
+ try {
1034
+ const client = await getNobleClient();
1035
+ if (client === undefined) {
1036
+ throw new UserError(
1037
+ 'client is not connected.',
1038
+ );
1039
+ }
1040
+ if (globalThis.nobleWallet?.address === undefined) {
1041
+ throw new UserError('wallet is not set. Call connectWallet() first');
1042
+ }
1043
+ const coin = await client.getAccountBalance(globalThis.nobleWallet.address, 'uusdc');
1044
+ return encodeJson(coin);
1045
+ } catch (error) {
1046
+ return wrappedError(error);
1047
+ }
1048
+ }
1049
+
1050
+ export async function sendNobleIBC(squidPayload: string): Promise<String> {
1051
+ try {
1052
+ const client = await getNobleClient();
1053
+ if (client === undefined) {
1054
+ throw new UserError(
1055
+ 'client is not connected.',
1056
+ );
1057
+ }
1058
+ if (globalThis.nobleWallet?.address === undefined) {
1059
+ throw new UserError('wallet is not set. Call connectWallet() first');
1060
+ }
1061
+
1062
+ const decode = (str: string): string => Buffer.from(str, 'base64').toString('binary');
1063
+ const decoded = decode(squidPayload);
1064
+
1065
+ const json = JSON.parse(decoded);
1066
+
1067
+ const ibcMsg: EncodeObject = {
1068
+ typeUrl: json.msgTypeUrl, // '/ibc.applications.transfer.v1.MsgTransfer',
1069
+ value: json.msg,
1070
+ };
1071
+ const fee = await client.simulateTransaction([ibcMsg]);
1072
+
1073
+ // take out fee from amount before sweeping
1074
+ const amount = parseInt(ibcMsg.value.token.amount, 10) -
1075
+ Math.floor(parseInt(fee.amount[0].amount, 10) * GAS_MULTIPLIER);
1076
+
1077
+ if (amount <= 0) {
1078
+ throw new UserError('noble balance does not cover fees');
1079
+ }
1080
+
1081
+ ibcMsg.value.token.amount = amount.toString();
1082
+ const tx = await client.send([ibcMsg]);
1083
+ return encodeJson(tx);
1084
+ } catch (error) {
1085
+ return wrappedError(error);
1086
+ }
1087
+ }
@@ -0,0 +1,100 @@
1
+ import { EncodeObject, Registry, Coin } from '@cosmjs/proto-signing';
2
+ import {
3
+ calculateFee,
4
+ DeliverTxResponse,
5
+ GasPrice,
6
+ StdFee,
7
+ defaultRegistryTypes,
8
+ SigningStargateClient,
9
+ } from '@cosmjs/stargate';
10
+
11
+ import { GAS_MULTIPLIER } from './constants';
12
+ import LocalWallet from './modules/local-wallet';
13
+
14
+ export class NobleClient {
15
+ private stargateClient?: SigningStargateClient;
16
+ private wallet?: LocalWallet;
17
+
18
+ static async connect(
19
+ restEndpoint: string,
20
+ wallet: LocalWallet,
21
+ ): Promise<NobleClient> {
22
+ const client = new NobleClient();
23
+ await client.initialize(restEndpoint, wallet);
24
+ return client;
25
+ }
26
+
27
+ private async initialize(
28
+ restEndpoint: string,
29
+ wallet: LocalWallet,
30
+ ): Promise<void> {
31
+ if (wallet?.offlineSigner === undefined) {
32
+ throw new Error('Wallet signer not found');
33
+ }
34
+ this.wallet = wallet;
35
+ this.stargateClient = await SigningStargateClient.connectWithSigner(
36
+ restEndpoint,
37
+ wallet.offlineSigner,
38
+ { registry: new Registry(defaultRegistryTypes) },
39
+ );
40
+ }
41
+
42
+ getAccountBalances(address: string): Promise<readonly Coin[]> {
43
+ if (!this.stargateClient) {
44
+ throw new Error('stargateClient not initialized');
45
+ }
46
+ return this.stargateClient.getAllBalances(address);
47
+ }
48
+
49
+ getAccountBalance(address: string, denom: string): Promise<Coin> {
50
+ if (!this.stargateClient) {
51
+ throw new Error('stargateClient not initialized');
52
+ }
53
+ return this.stargateClient.getBalance(address, denom);
54
+ }
55
+
56
+ async send(
57
+ messages: EncodeObject[],
58
+ gasPrice: GasPrice = GasPrice.fromString('0.025uusdc'),
59
+ memo?: string,
60
+ ): Promise<DeliverTxResponse> {
61
+ if (!this.stargateClient) {
62
+ throw new Error('NobleClient stargateClient not initialized');
63
+ }
64
+ if (this.wallet?.address === undefined) {
65
+ throw new Error('NobleClient wallet not initialized');
66
+ }
67
+ // Simulate to get the gas estimate
68
+ const fee = await this.simulateTransaction(messages, gasPrice, memo);
69
+
70
+ // Sign and broadcast the transaction
71
+ return this.stargateClient.signAndBroadcast(
72
+ this.wallet.address,
73
+ messages,
74
+ fee,
75
+ memo ?? '',
76
+ );
77
+ }
78
+
79
+ async simulateTransaction(
80
+ messages: readonly EncodeObject[],
81
+ gasPrice: GasPrice = GasPrice.fromString('0.025uusdc'),
82
+ memo?: string,
83
+ ): Promise<StdFee> {
84
+ if (!this.stargateClient) {
85
+ throw new Error('NobleClient stargateClient not initialized');
86
+ }
87
+ if (this.wallet?.address === undefined) {
88
+ throw new Error('NobleClient wallet not initialized');
89
+ }
90
+ // Get simulated response
91
+ const gasEstimate = await this.stargateClient.simulate(
92
+ this.wallet?.address,
93
+ messages,
94
+ memo,
95
+ );
96
+
97
+ // Calculate and return the fee
98
+ return calculateFee(Math.floor(gasEstimate * GAS_MULTIPLIER), gasPrice);
99
+ }
100
+ }
@@ -2,6 +2,7 @@ import { StdFee } from '@cosmjs/stargate';
2
2
 
3
3
  // Bech32 Prefix
4
4
  export const BECH32_PREFIX = 'dydx';
5
+ export const NOBLE_BECH32_PREFIX = 'noble';
5
6
 
6
7
  // Broadcast Defaults
7
8
  export const BROADCAST_POLL_INTERVAL_MS: number = 300;