1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! Permissioned keys/authenticators example.
//! For more information see [the docs](https://docs.dydx.xyz/interaction/permissioned-keys).
mod support;
use anyhow::{Error, Result};
use bigdecimal::BigDecimal;
use dydx::config::ClientConfig;
use dydx::indexer::{IndexerClient, Subaccount};
use dydx::node::{
Account, Authenticator, NodeClient, OrderBuilder, OrderSide, PublicAccount, Wallet,
};
use dydx_proto::dydxprotocol::clob::order::TimeInForce;
use std::str::FromStr;
use support::constants::TEST_MNEMONIC;
use tokio::time::{sleep, Duration};
const ETH_USD_TICKER: &str = "ETH-USD";
pub struct Trader {
client: NodeClient,
indexer: IndexerClient,
account: Account,
}
impl Trader {
pub async fn connect(index: u32) -> Result<Self> {
// Initialize rustls crypto provider
support::crypto::init_crypto_provider();
let config = ClientConfig::from_file("client/tests/testnet.toml").await?;
let mut client = NodeClient::connect(config.node).await?;
let indexer = IndexerClient::new(config.indexer);
let wallet = Wallet::from_mnemonic(TEST_MNEMONIC)?;
let account = wallet.account(index, &mut client).await?;
Ok(Self {
client,
indexer,
account,
})
}
}
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::fmt().try_init().map_err(Error::msg)?;
#[cfg(feature = "telemetry")]
support::telemetry::metrics_dashboard().await?;
// We will just create two (isolated) accounts using the same mnemonic.
// In a more realistic setting each user would have its own mnemonic/wallet.
let mut master = Trader::connect(0).await?;
let master_address = master.account.address().clone();
let mut permissioned = Trader::connect(1).await?;
// -- Permissioning account actions --
log::info!("[master] Creating the authenticator.");
// For permissioned trading, the permissioned account needs an associated authenticator ID,
// created by the permissioning account.
// An authenticator declares the conditions/permissions that allow the permissioned account to
// trade under.
let authenticator = Authenticator::AllOf(vec![
// The permissioned account needs to share its public key with the permissioning account.
// Through other channels, users can share their public keys using hex strings, e.g.,
// let keystring = hex::encode(&account.public_key().to_bytes())
// let bytes = hex::decode(&keystring);
Authenticator::SignatureVerification(permissioned.account.public_key().to_bytes()),
// The allowed actions. Several message types are allowed to be defined, separated by commas.
Authenticator::MessageFilter("/dydxprotocol.clob.MsgPlaceOrder".into()),
// The allowed markets. Several IDs allowed to be defined.
Authenticator::ClobPairIdFilter("0,1".into()),
// The allowed subaccounts. Several subaccounts allowed to be defined.
Authenticator::SubaccountFilter("0".into()),
// A transaction will only be accepted if all conditions above are satisfied.
// Alternatively, `Authenticator::AnyOf` can be used.
// If only one condition was declared (if so, it must be a `Authenticator::SignatureVerification`),
// `AllOf` or `AnyOf` should not be used.
]);
// Broadcast the built authenticator.
master
.client
.authenticators()
.add_authenticator(&mut master.account, master_address.clone(), authenticator)
.await?;
sleep(Duration::from_secs(3)).await;
// -- Permissioned account actions --
log::info!("[trader] Fetching the authenticator.");
// The permissioned account needs then to acquire the ID associated with the authenticator.
// Here, we will just grab the last authenticator ID pushed under the permissioning account.
let id = permissioned
.client
.authenticators()
.get_authenticators(master_address.clone())
.await?
.last()
.unwrap()
.id;
// The permissioned account then adds that ID.
// An updated `PublicAccount` account, representing the permissioner, needs to be created.
let external_account =
PublicAccount::updated(master_address.clone(), &mut permissioned.client).await?;
permissioned
.account
.authenticators_mut()
.add(external_account, id);
let master_subaccount = Subaccount {
address: master_address.clone(),
number: 0.try_into()?,
};
log::info!("[trader] Creating the order. Using authenticator ID {id}.");
// Create an order as usual, however for the permissioning account's subaccount.
let market = permissioned
.indexer
.markets()
.get_perpetual_market(Ð_USD_TICKER.into())
.await?;
let current_block_height = permissioned.client.latest_block_height().await?;
let size = BigDecimal::from_str("0.02")?;
let (_id, order) = OrderBuilder::new(market, master_subaccount)
.market(OrderSide::Buy, size)
.reduce_only(false)
.price(100) // market-order slippage protection price
.time_in_force(TimeInForce::Unspecified)
.until(current_block_height.ahead(10))
.build(123456)?;
let tx_hash = permissioned
.client
.place_order(&mut permissioned.account, order)
.await?;
tracing::info!("Broadcast transaction hash: {:?}", tx_hash);
// -- Permissioning account actions --
log::info!("[master] Removing the authenticator.");
// Authenticators can also be removed when not needed anymore
master
.client
.authenticators()
.remove_authenticator(&mut master.account, master_address, id)
.await?;
Ok(())
}