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