Mit dem rasanten Aufstieg dezentraler Börsen (DEX) im Bereich des Kryptowährungshandels haben sich quantitative Händler allmählich für einen effizienten automatisierten Handel an diese Plattformen gewandt. Als eine der beliebtesten dezentralen Handelsplattformen bietet dYdX leistungsstarke Handelsfunktionen und unterstützt den Handel mit Futures perpetual contract.
In diesem Artikel wird erläutert, wie man den quantitativen Handel auf dYdX v4 praktiziert, einschließlich der Verwendung seiner API zum Handel, zur Erfassung von Marktdaten und zur Verwaltung von Konten.
dYdX v3
Mit Ethereum generiert der Handel Belohnungen, die Belohnung sinddYdX
tokens.Der vorherige dYdX v3 Protokoll DEX Austausch war offline. Die aktuelle dYdX v4 App-Adresse ist:
Nach dem Öffnen der App-Seite befindet sich in der oberen rechten Ecke eine Schaltfläche zur Verbindung zur Brieftasche.
Wenn Sie sich zunächst mit der Testnetzwerkumgebung vertraut machen möchten, können Sie das Testnetzwerk verwenden:
Außerdem klicken Sie auf die Schaltfläche "Connect Wallet" in der oberen rechten Ecke, scannen den Code, um die Brieftasche zu verbinden, und überprüfen Sie die Signatur. Nachdem die Brieftasche erfolgreich verbunden ist, wird automatisch eine dydx v4-Adresse generiert. Diese Adresse wird in der oberen rechten Ecke der App-Seite angezeigt. Klicken Sie darauf und ein Menü erscheint. Es gibt Operationen wie Aufladen, Abheben und Übertragen. Einer der Unterschiede zwischen dem dYdX-Mainnet (Produktionsumfeld) und dem Testnet besteht darin, dass, wenn Sie auf das Testnet klicken, 300 USDC-Assets automatisch mit dem Wasserhahn für das Testen aufgeladen werden. Wenn Sie echte Transaktionen auf dYdX durchführen möchten, müssen Sie USDC-Assets aufladen. Recharge ist auch sehr praktisch und mit mehreren Assets und Ketten für die Aufladung kompatibel.
dYdX v4-Kontoadresse
Die dYdX v4-Kontoadresse wird aus der Wallet-Adresse abgeleitet.dydx1xxxxxxxxxxxxxxxxxxxxq2ge5jr4nzfeljxxxx
, die eine Adresse ist, die mit dydx1 beginnt. Diese Adresse kann in Blockchain-Explorern abgerufen werden.
Mnemonik
Sie können die Schaltfläche
Mnemonik kann direkt auf der FMZ-Plattform konfiguriert oder lokal auf dem Docker gespeichert werden.
Die Testnet-Umgebung unterscheidet sich in einigen Aspekten von der Mainnet-Umgebung.
subAccountNumber >= 128
, wenn das Teilkonto der ID keine Positionen hat, werden die Vermögenswerte automatisch auf das Teilkonto, dessen Teilkontonummer 0 ist, abgebucht.
Bei der Prüfung wurde festgestellt, dass das Prüfnetz dieses Mechanismus nicht besitzt (oder die Auslösungsbedingungen sind unterschiedlich und es wurde im Prüfnetz nicht ausgelöst).DYDX
, TestnetzDv4TNT
Hauptnetz:
Indexadresse:https://indexer.dydx.trade
Kette-ID:dydx-mainnet-1
REST-Knoten:https://dydx-dao-api.polkachu.com:443
Testnetz:
Indexadresse:https://indexer.v4testnet.dydx.exchange
Kette-ID:dydx-testnet-4
REST-Knoten:https://dydx-testnet-api.polkachu.com
Das dYdX v4-Protokoll basiert auf dem Kosmos-Ökosystem. Das dYdX v4-DEX-System besteht hauptsächlich aus zwei Teilen:
Der Indexerdienst stellt REST- und Websocket-Protokolle bereit.
REST-Protokoll Die REST-Protokollschnittstelle unterstützt Marktinformationsanfragen, Kontoinformationen, Positionsinformationen, Auftragsinformationen und andere Anfragen und wurde als einheitliche API-Schnittstelle auf der FMZ-Plattform eingekapselt.
WebSocket-Protokoll Auf der FMZ-Plattform können Sie die Dial-Funktion verwenden, um eine Websocket-Verbindung zu erstellen und Marktinformationen zu abonnieren.
Es sollte beachtet werden, dass der Indexer von dydx v4 das gleiche Problem wie zentrale Börsen hat, dh Datenaktualisierungen sind nicht so zeitnah. Zum Beispiel können Sie manchmal die Bestellung nicht finden, wenn Sie sie sofort nach der Bestellung abfragen.Sleep(n)
) bestimmte Vorgänge vor der Abfrage.
Hier ist ein Beispiel für die Verwendung der Dial-Funktion zur Erstellung einer Websocket-API-Verbindung und zum Abonnieren von Bestellbuchdaten:
function dYdXIndexerWSconnManager(streamingPoint) {
var self = {}
self.base = streamingPoint
self.wsThread = null
// subscription
self.CreateWsThread = function (msgSubscribe) {
self.wsThread = threading.Thread(function (streamingPoint, msgSubscribe) {
// Order book
var orderBook = null
// Update order book
var updateOrderbook = function(orderbook, update) {
// Update bids
if (update.bids) {
update.bids.forEach(([price, size]) => {
const priceFloat = parseFloat(price)
const sizeFloat = parseFloat(size)
if (sizeFloat === 0) {
// Delete the buy order with price
orderbook.bids = orderbook.bids.filter(bid => parseFloat(bid.price) !== priceFloat)
} else {
// Update or add a buy order
orderbook.bids = orderbook.bids.filter(bid => parseFloat(bid.price) !== priceFloat)
orderbook.bids.push({price: price, size: size})
// Sort by price descending
orderbook.bids.sort((a, b) => parseFloat(b.price) - parseFloat(a.price))
}
})
}
// Update asks
if (update.asks) {
update.asks.forEach(([price, size]) => {
const priceFloat = parseFloat(price)
const sizeFloat = parseFloat(size)
if (sizeFloat === 0) {
// Delete the sell order with price
orderbook.asks = orderbook.asks.filter(ask => parseFloat(ask.price) !== priceFloat)
} else {
// Update or add a sell order
orderbook.asks = orderbook.asks.filter(ask => parseFloat(ask.price) !== priceFloat)
orderbook.asks.push({price: price, size: size})
// Sort by price ascending
orderbook.asks.sort((a, b) => parseFloat(a.price) - parseFloat(b.price))
}
})
}
return orderbook
}
var conn = Dial(`${streamingPoint}|reconnect=true&payload=${JSON.stringify(msgSubscribe)}`)
if (!conn) {
Log("createWsThread failed.")
return
}
while (true) {
var data = conn.read()
if (data) {
var msg = null
try {
msg = JSON.parse(data)
if (msg["type"] == "subscribed") {
orderBook = msg["contents"]
threading.currentThread().postMessage(orderBook)
} else if (msg["type"] == "channel_data") {
orderBook = updateOrderbook(orderBook, msg["contents"])
threading.currentThread().postMessage(orderBook)
}
} catch (e) {
Log("e.name:", e.name, "e.stack:", e.stack, "e.message:", e.message)
}
}
}
}, streamingPoint, msgSubscribe)
}
// monitor
self.Peek = function () {
return self.wsThread.peekMessage()
}
return self
}
function main() {
// real : wss://indexer.dydx.trade/v4/ws
// simulate : wss://indexer.v4testnet.dydx.exchange/v4/ws
var symbol = "ETH-USD"
var manager = dYdXIndexerWSconnManager("wss://indexer.dydx.trade/v4/ws")
manager.CreateWsThread({"type": "subscribe", "channel": "v4_orderbook", "id": symbol})
var redCode = "#FF0000"
var greenCode = "#006400"
while (true) {
var depthTbl = {type: "table", title: symbol + " / depth", cols: ["level", "price", "amount"], rows: []}
var depth = manager.Peek()
if (depth) {
for (var i = 0; i < depth.asks.length; i++) {
if (i > 9) {
break
}
var ask = depth.asks[i]
depthTbl.rows.push(["asks " + (i + 1) + greenCode, ask.price + greenCode, ask.size + greenCode])
}
depthTbl.rows.reverse()
for (var i = 0; i < depth.bids.length; i++) {
if (i > 9) {
break
}
var bid = depth.bids[i]
depthTbl.rows.push(["bids " + (i + 1) + redCode, bid.price + redCode, bid.size + redCode])
}
}
LogStatus(_D(), "\n`" + JSON.stringify(depthTbl) + "`")
}
}
Die am häufigsten verwendeten Nachrichten in Transaktionen sind Auftragsnachrichten, Auftragsunterbrechungsnachrichten und Überweisungsnachrichten.
{
"@type": "/dydxprotocol.clob.MsgPlaceOrder",
"order": {
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": xxx,
"orderFlags": 64,
"clobPairId": 1
},
"side": "SIDE_BUY",
"quantums": "2000000",
"subticks": "3500000000",
"goodTilBlockTime": 1742295981
}
}
Grenzfolge:
In der auf der FMZ-Plattform verkapselten Orderfunktion beträgt der für Limitorders verwendete Wert orderFlags:ORDER_FLAGS_LONG_TERM = 64 # Long-term order
Gemäß den Einschränkungen des DYDX v4-Protokolls wird die längste Bestelldauer von 90 Tagen verwendet (alle Bestelltypen auf DYDX v4 haben Gültigkeitsfristen).
Marktordnung:
In der auf der FMZ-Plattform verkapselten Auftragsfunktion beträgt der für Marktordern verwendete Wert von orderFlags:ORDER_FLAGS_SHORT_TERM = 0 # Short-term order
, gemäß den Empfehlungen des dydx v4-Protokolls:
// empfehlen Sie auf Oracle Preis - 5% oder niedriger für SELL, Oracle Preis + 5% für BUY
Da es sich nicht um einen echten Markt Auftrag handelt, wird der Oracle-Preis verwendet, plus oder minus 5% Slippage als Markt Auftrag. Die Gültigkeitsdauer von kurzfristigen Aufträgen unterscheidet sich auch von der von langfristigen Aufträgen. Kurzfristige Aufträge verwenden die Blockhöhe Gültigkeitsdauer, die gemäß der Empfehlung von dydx v4 auf den aktuellen Block + 10 Blockhöhen festgelegt ist.
Handelspaare DYDX-Kontoadresse Unterkontonummer (Unterkontonummer) ClientId (zufällig generiert) clobPairId (Id des Transaktionssymbols) AnordnungFlaggen goodTilData (Millisekunden)
{
"@type": "/dydxprotocol.clob.MsgCancelOrder",
"orderId": {
"subaccountId": {
"owner": "xxx"
},
"clientId": 2585872024,
"orderFlags": 64,
"clobPairId": 1
},
"goodTilBlockTime": 1742295981
}
Die von der FMZ-Plattform-Order-Schnittstelle zurückgegebene Order-ID muss eingegeben werden.
{
"@type": "/dydxprotocol.sending.MsgCreateTransfer",
"transfer": {
"sender": {
"owner": "xxx"
},
"recipient": {
"owner": "xxx",
"number": 128
},
"amount": "10000000"
}
}
Der Sub-Account mit SubAccountNumber 0 ist der erste automatisch erstellte Sub-Account. Die Sub-Account-ID mit SubAccountNumber größer oder gleich 128 wird für den isolierten Positionshandel verwendet, der mindestens 20 USDC-Assets erfordert. Sie können beispielsweise von SubAccountNumber 0 -> 128 oder von SubAccountNumber 128 -> 0 gehen. Überweisungen erfordern eine Gasgebühr. Die Gasgebühr kann USDC oder dydx Token sein.
Der obige Inhalt erläutert einige Packaging-Details kurz. Als nächstes werden wir die spezifische Verwendung üben. Hier verwenden wir das dYdX v4 Testnetzwerk zur Demonstration. Das Testnetzwerk ist im Grunde das gleiche wie das Hauptnetzwerk und es gibt einen automatischen Wasserhahn zum Empfangen von Testvermögen. Der Docker-Bereitstellungsvorgang wird nicht wiederholt. Erstellen Sie einen Live-Trading-Test auf FMZ.
Nach erfolgreicher Verbindung mit der dYdX v4-App mit einer Kryptowährungsgeldbörse (hier verwende ich die imToken-Geldbörse) fordern Sie Ihre Testwerte an und exportieren Sie dann die Mnemonik für Ihr aktuelles dYdX v4-Konto (abgeleitet aus Ihrer Brieftasche).
Hier verwenden wir die lokale Datei-Methode, um sie zu konfigurieren (Sie können sie auch direkt ausfüllen und auf die Plattform konfigurieren).
Natürlich kann es auch in andere Verzeichnisse platziert werden (der spezifische Pfad muss während der Konfiguration geschrieben werden).
Füllen Sie das mnemonische Bearbeitungsfeld aus:file:///mnemonic.txt
, ist der entsprechende tatsächliche Weg:docker directory/logs/storage/594291
.
function main() {
// Switch the indexer address of the test chain
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Switch the ChainId of the test chain
exchange.IO("chainId", "dydx-testnet-4")
// Switch the REST node address of the test chain
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")
// Read account information test
Log(exchange.GetAccount())
}
Lesen Sie die Testnetzwerk-Kontoinformationen:
{
"Info": {
"subaccounts": [{
"address": "dydx1fzsndj35a26maujxff88q2ge5jr4nzfeljn2ez",
"subaccountNumber": 0,
"equity": "300.386228",
"latestProcessedBlockHeight": "28193227",
"freeCollateral": "300.386228",
"openPerpetualPositions": {},
"assetPositions": {
"USDC": {
"subaccountNumber": 0,
"size": "300.386228",
"symbol": "USDC",
"side": "LONG",
"assetId": "0"
}
},
"marginEnabled": true,
"updatedAtHeight": "28063818"
}, {
"address": "dydx1fzsndj35a26maujxff88q2ge5jr4nzfeljn2ez",
"equity": "0",
"freeCollateral": "0",
"openPerpetualPositions": {},
"marginEnabled": true,
"subaccountNumber": 1,
"assetPositions": {},
"updatedAtHeight": "27770289",
"latestProcessedBlockHeight": "28193227"
}, {
"equity": "0",
"openPerpetualPositions": {},
"marginEnabled": true,
"updatedAtHeight": "28063818",
"latestProcessedBlockHeight": "28193227",
"subaccountNumber": 128,
"freeCollateral": "0",
"assetPositions": {},
"address": "dydx1fzsndj35a26maujxff88q2ge5jr4nzfeljn2ez"
}],
"totalTradingRewards": "0.021744179376211564"
},
"Stocks": 0,
"FrozenStocks": 0,
"Balance": 300.386228,
"FrozenBalance": 0,
"Equity": 300.386228,
"UPnL": 0
}
Nicht auf das Testnetz umgestellt, mit dem Hauptnetz getestet
function main() {
var markets = exchange.GetMarkets()
if (!markets) {
throw "get markets error"
}
var tbl = {type: "table", title: "test markets", cols: ["key", "Symbol", "BaseAsset", "QuoteAsset", "TickSize", "AmountSize", "PricePrecision", "AmountPrecision", "MinQty", "MaxQty", "MinNotional", "MaxNotional", "CtVal"], rows: []}
for (var symbol in markets) {
var market = markets[symbol]
tbl.rows.push([symbol, market.Symbol, market.BaseAsset, market.QuoteAsset, market.TickSize, market.AmountSize, market.PricePrecision, market.AmountPrecision, market.MinQty, market.MaxQty, market.MinNotional, market.MaxNotional, market.CtVal])
}
LogStatus("`" + JSON.stringify(tbl) + "`")
}
function main() {
// Switch the indexer address of the test chain
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Switch the ChainId of the test chain
exchange.IO("chainId", "dydx-testnet-4")
// Switch the REST node address of the test chain
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")
// Limit order, pending order
var idSell = exchange.CreateOrder("ETH_USD.swap", "sell", 4000, 0.002)
var idBuy = exchange.CreateOrder("ETH_USD.swap", "buy", 3000, 0.003)
// Market order
var idMarket = exchange.CreateOrder("ETH_USD.swap", "buy", -1, 0.01)
Log("idSell:", idSell)
Log("idBuy:", idBuy)
Log("idMarket:", idMarket)
}
dYdX v4 App Seite:
Das Testnetzwerk legt zwei Aufträge im Voraus auf, testet die aktuellen ausstehenden Aufträge und storniert die Aufträge.
function main() {
// Switch the indexer address of the test chain
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Switch the ChainId of the test chain
exchange.IO("chainId", "dydx-testnet-4")
// Switch the REST node address of the test chain
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")
var orders = exchange.GetOrders()
Log("orders:", orders)
for (var order of orders) {
exchange.CancelOrder(order.Id, order)
Sleep(2000)
}
var tbl = {type: "table", title: "test GetOrders", cols: ["Id", "Price", "Amount", "DealAmount", "AvgPrice", "Status", "Type", "Offset", "ContractType"], rows: []}
for (var order of orders) {
tbl.rows.push([order.Id, order.Price, order.Amount, order.DealAmount, order.AvgPrice, order.Status, order.Type, order.Offset, order.ContractType])
}
LogStatus("`" + JSON.stringify(tbl) + "`")
}
function main() {
// Switch the indexer address of the test chain
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Switch the ChainId of the test chain
exchange.IO("chainId", "dydx-testnet-4")
// Switch the REST node address of the test chain
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")
var p1 = exchange.GetPositions("USD.swap")
var p2 = exchange.GetPositions("ETH_USD.swap")
var p3 = exchange.GetPositions()
var p4 = exchange.GetPositions("SOL_USD.swap")
var tbls = []
for (var positions of [p1, p2, p3, p4]) {
var tbl = {type: "table", title: "test GetPosition/GetPositions", cols: ["Symbol", "Amount", "Price", "FrozenAmount", "Type", "Profit", "Margin", "ContractType", "MarginLevel"], rows: []}
for (var p of positions) {
tbl.rows.push([p.Symbol, p.Amount, p.Price, p.FrozenAmount, p.Type, p.Profit, p.Margin, p.ContractType, p.MarginLevel])
}
tbls.push(tbl)
}
LogStatus("`" + JSON.stringify(tbls) + "`")
}
function main() {
// Switch the indexer address of the test chain
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Switch the ChainId of the test chain
exchange.IO("chainId", "dydx-testnet-4")
// Switch the REST node address of the test chain
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")
// subAccountNumber 0 -> 128 : 20 USDC, Gas Fee is adv4tnt, i.e. dydx token
// var ret = exchange.IO("transferUSDCToSubaccount", 0, 128, "adv4tnt", 20)
// Log("ret:", ret)
// Switch to subaccount subAccountNumber 128 and read account information to check
exchange.IO("subAccountNumber", 128)
var account = exchange.GetAccount()
Log("account:", account)
}
Daten des Protokolls:
Wechseln Sie zum Unterkonto, dessen Unterkontonummer 128 ist, und die von GetAccount zurückgegebenen Daten sind:
{
"Info": {
"subaccounts": [{
"subaccountNumber": 0,
"assetPositions": {
"USDC": {
"size": "245.696892",
"symbol": "USDC",
"side": "LONG",
"assetId": "0",
"subaccountNumber": 0
}
},
"updatedAtHeight": "28194977",
"latestProcessedBlockHeight": "28195008",
"address": "dydx1fzsndj35a26maujxff88q2ge5jr4nzfeljn2ez",
"freeCollateral": "279.5022142346",
"openPerpetualPositions": {
"ETH-USD": {
"closedAt": null,
"size": "0.01",
"maxSize": "0.01",
"exitPrice": null,
"unrealizedPnl": "-0.17677323",
"subaccountNumber": 0,
"status": "OPEN",
"createdAt": "2024-12-26T03:36:09.264Z",
"createdAtHeight": "28194494",
"sumClose": "0",
"netFunding": "0",
"market": "ETH-USD",
"side": "LONG",
"entryPrice": "3467.2",
"realizedPnl": "0",
"sumOpen": "0.01"
}
},
"marginEnabled": true,
"equity": "280.19211877"
}, {
"openPerpetualPositions": {},
"assetPositions": {},
"marginEnabled": true,
"latestProcessedBlockHeight": "28195008",
"address": "dydx1fzsndj35a26maujxff88q2ge5jr4nzfeljn2ez",
"subaccountNumber": 1,
"equity": "0",
"freeCollateral": "0",
"updatedAtHeight": "27770289"
}, {
"openPerpetualPositions": {},
"updatedAtHeight": "28194977",
"latestProcessedBlockHeight": "28195008",
"address": "dydx1fzsndj35a26maujxff88q2ge5jr4nzfeljn2ez",
"subaccountNumber": 128,
"assetPositions": {
"USDC": {
"assetId": "0",
"subaccountNumber": 128,
"size": "20",
"symbol": "USDC",
"side": "LONG"
}
},
"marginEnabled": true,
"equity": "20",
"freeCollateral": "20"
}],
"totalTradingRewards": "0.021886899964446858"
},
"Stocks": 0,
"FrozenStocks": 0,
"Balance": 20,
"FrozenBalance": 0,
"Equity": 20,
"UPnL": 0
}
Man sieht, dass das Unterkonto mit Unterkonto Nummer 128 20 USDC überwiesen hat.
Entsprechend der Reihenfolge, erhalten TxHash und testen Sie die Methode der IO REST Knoten rufen
Wie bekommt man den TxHash einer Bestellung? Das Exchange-Objekt dydx speichert den TxHash, der mit der Order-ID abgerufen werden kann.
function main() {
// Switch the indexer address of the test chain
exchange.SetBase("https://indexer.v4testnet.dydx.exchange")
// Switch the ChainId of the test chain
exchange.IO("chainId", "dydx-testnet-4")
// Switch the REST node address of the test chain
exchange.IO("restApiBase", "https://dydx-testnet-api.polkachu.com")
var id1 = exchange.CreateOrder("ETH_USD.swap", "buy", 3000, 0.002)
var hash1 = exchange.IO("getTxHash", id1)
Log("id1:", id1, "hash1:", hash1)
var id2 = exchange.CreateOrder("ETH_USD.swap", "buy", 2900, 0.003)
var hash2 = exchange.IO("getTxHash", id2)
Log("id2:", id2, "hash2:", hash2)
// To clear the mapping table, use: exchange.IO("getTxHash", "")
var arr = [hash1, hash2]
Sleep(10000)
for (var txHash of arr) {
// GET https://docs.cosmos.network /cosmos/tx/v1beta1/txs/{hash}
var ret = exchange.IO("api", "GET", "/cosmos/tx/v1beta1/txs/" + txHash)
Log("ret:", ret)
}
}
Nachrichten, die über TxHash abgefragt wurden:
VAR RET =exchange.IO(Api,
GET , /cosmos/tx/v1beta1/txs/ + txHash)
Der Inhalt ist zu lang, also hier einige Auszüge zur Demonstration:
{
"tx_response": {
"codespace": "",
"code": 0,
"logs": [],
"info": "",
"height": "28195603",
"data": "xxx",
"raw_log": "",
"gas_wanted": "-1",
"gas_used": "0",
"tx": {
"@type": "/cosmos.tx.v1beta1.Tx",
"body": {
"messages": [{
"@type": "/dydxprotocol.clob.MsgPlaceOrder",
"order": {
"good_til_block_time": 1742961542,
"condition_type": "CONDITION_TYPE_UNSPECIFIED",
"order_id": {
"clob_pair_id": 1,
"subaccount_id": {
"owner": "xxx",
"number": 0
},
"client_id": 2999181974,
"order_flags": 64
},
"side": "SIDE_BUY",
"quantums": "3000000",
"client_metadata": 0,
"conditional_order_trigger_subticks": "0",
"subticks": "2900000000",
"time_in_force": "TIME_IN_FORCE_UNSPECIFIED",
"reduce_only": false
}
}],
"memo": "FMZ",
"timeout_height": "0",
"extension_options": [],
"non_critical_extension_options": []
},
...
Die oben genannten Tests basieren auf dem neuesten Docker. Sie müssen den neuesten Docker herunterladen, um dYdX v4 DEX zu unterstützen
Vielen Dank für Ihre Unterstützung und für das Lesen.