Bu sayfa otomatik olarak çevrilmiştir. İngilizce orijinal kanonik versiyondur. İngilizce oku
Ana içeriğe geç

EIP-712 İmzalama

Bu doküman, zincir üstü işlemler için kullanılan üç EIP-712 imzalama domainini açıklar: Agent İstekleri, Manager İşlemleri ve RSM Komutları.

Genel Bakış

Çoğu protokol işlemi, EIP-712 typed data imzaları gerektirir. İmzalayan şunlardan biri olmalıdır:

  1. Agent (API Cüzdanı): Exchange.addApiWallet aracılığıyla yetkilendirilir - alım satım isteklerini imzalar
  2. Manager: Hesap sahibi - para çekme ve varlık transferlerini imzalar
  3. RSM İmzalayıcısı: Protokol tarafından kontrol edilir - likidasyon/rebalance komutlarını imzalar

Exchange kontratı imzaları doğrular ve işlemleri, bunları ActionCaster mesajları olarak kodlayan Processor'a iletir.

İmzasız Fonlama Giriş Noktaları

Her fonlama çağrısı bir EIP-712 işlemi değildir. Bu metodlar, ödemeyi yapan cüzdan veya router tarafından gönderilen doğrudan işlemlerdir:

function depositUsdcFor(address account, uint256 amount) external;
function depositOption(address account, address token, uint256 amount) external;

depositUsdcFor kasıtlı olarak imzasızdır çünkü msg.sender yalnızca USDC ödeyicisidir. Alacaklandırılan Hypercall hesabı, açıkça belirtilen account argümanı ve UsdcDeposit.account event alanıdır. Routerlar ve zap'ler bu metodu çağırabileceğinden, indexerlar ve backend servisleri alacak atfetmek için msg.sender'ı kullanmamalıdır.

depositOption, opsiyon tokenlarını msg.sender'dan yakar ve RSM indexer'ı için Deposit(account, msg.sender, token, amount) eventini yayınlar. Opsiyon alacaklandırma yolu event tabanlıdır ve yatırım yapandan bir manager, agent veya RSM imzası kullanmaz.

EIP-712 Domain Separator'ları

Üç domainin tümü aynı yapıyı ancak farklı isimleri kullanır:

{
"name": "<DomainName>",
"version": "1",
"chainId": <chainId>,
"verifyingContract": "0x0000000000000000000000000000000000000000"
}

Chain ID'ler:

  • Testnet: 998
  • Ana ağ: 999

Domain 1: Agent İstekleri (HypercallAgentSign)

Domain Adı: "HypercallAgentSign"

Önceden Hesaplanmış Domain Separator'lar:

  • Testnet: 0x8f0a44075cd4e0c79e5bd379a6fad5fa1329a4ea76d74e4edfa1138933d35e8a
  • Ana ağ: chain ID 999 ve aktif ortam için deploy edilmiş verifier yapılandırmasını kullanın.

İmzalayan: API Cüzdanı (Exchange.addApiWallet aracılığıyla yetkilendirilmiş olmalıdır)

Nonce: İmzalayan bazında replay koruması. Engine, imzalayan başına en yüksek 100 nonce'u saklar. Yeni bir nonce, kümedeki en küçük değerden büyük olmalı ve daha önce kullanılmamış olmalıdır. Nonce'lar sunucu zaman damgasının (T - 2 gün, T + 1 gün) aralığında olmalıdır. Zincir üstünde, Exchange.isNonceUsed(signer, nonce) kullanımı bir bitmap aracılığıyla takip eder

HLRequestOrder

HyperLiquid perp/spot emirleri verir.

Struct:

struct HLOrder {
uint32 asset; // HyperLiquid asset ID
bool isBuy; // true = buy, false = sell
uint64 limitPx; // Limit price (fixed-point)
uint64 sz; // Size (fixed-point)
bool reduceOnly; // true = reduce-only order
uint8 encodedTif; // Time-in-force encoding
uint128 cloid; // Client order ID (0 = auto-generate)
}

struct HLRequestOrder {
HLOrder[] orders;
uint64 nonce;
}

Type Hash:

  • HL_ORDER_TYPE_HASH: keccak256("HLOrder(uint32 asset,bool isBuy,uint64 limitPx,uint64 sz,bool reduceOnly,uint8 encodedTif,uint128 cloid)")
  • HL_ORDER_REQUEST_TYPE_HASH: keccak256("HLRequestOrder(HLOrder[] orders,uint64 nonce)HLOrder(...)")

Kodlama:

  1. Her HLOrderstructHash(HLOrder) ile hash'leyin
  2. Emir hash'lerini paketleyin: keccak256(abi.encodePacked(orderHashes))
  3. İsteği hash'leyin: keccak256(abi.encode(HL_ORDER_REQUEST_TYPE_HASH, packedOrderHashes, nonce))
  4. EIP-712 digest: MessageHashUtils.toTypedDataHash(domainSeparator, structHash)

Örnek (ethers.js):

const domain = {
name: "HypercallAgentSign",
version: "1",
chainId: 998, // testnet
verifyingContract: ethers.ZeroAddress
};

const types = {
HLOrder: [
{ name: "asset", type: "uint32" },
{ name: "isBuy", type: "bool" },
{ name: "limitPx", type: "uint64" },
{ name: "sz", type: "uint64" },
{ name: "reduceOnly", type: "bool" },
{ name: "encodedTif", type: "uint8" },
{ name: "cloid", type: "uint128" }
],
HLRequestOrder: [
{ name: "orders", type: "HLOrder[]" },
{ name: "nonce", type: "uint64" }
]
};

const message = {
orders: [{
asset: 0, // BTC perp
isBuy: true,
limitPx: 50000000000, // $50,000 (fixed-point)
sz: 1000000, // 0.001 BTC (fixed-point)
reduceOnly: false,
encodedTif: 0, // GTC
cloid: 0 // auto-generate
}],
nonce: 1
};

const signature = await apiWalletSigner.signTypedData(domain, types, message);

Zincir Üstü Giriş Noktası: Exchange.hlRequestOrder(HLRequestOrder memory request, bytes memory signature)

Processor Çıktısı: Her emri ActionCasterEncoder.limitOrder(...) olarak kodlar ve bytes[] işlemleri döndürür.

HLRequestCancel

Emirleri emir ID'sine göre iptal eder.

Struct:

struct HLCancel {
uint32 asset;
uint64 oid; // Order ID from HyperLiquid
}

struct HLRequestCancel {
HLCancel[] cancels;
uint64 nonce;
}

Type Hash:

  • HL_CANCEL_TYPE_HASH: keccak256("HLCancel(uint32 asset,uint64 oid)")
  • HL_CANCEL_REQUEST_TYPE_HASH: keccak256("HLRequestCancel(HLCancel[] cancels,uint64 nonce)HLCancel(...)")

Örnek:

const message = {
cancels: [{
asset: 0,
oid: 12345
}],
nonce: 2
};

const signature = await apiWalletSigner.signTypedData(domain, types, message);

Zincir Üstü Giriş Noktası: Exchange.hlRequestCancel(HLRequestCancel memory request, bytes memory signature)

HLRequestCancelByCloid

Emirleri müşteri emir ID'sine (client order ID) göre iptal eder.

Struct:

struct HLCancelByCloid {
uint32 asset;
uint128 cloid; // Client order ID
}

struct HLRequestCancelByCloid {
HLCancelByCloid[] cancels;
uint64 nonce;
}

Type Hash:

  • HL_CANCEL_BY_CLOID_TYPE_HASH: keccak256("HLCancelByCloid(uint32 asset,uint128 cloid)")
  • HL_CANCEL_BY_CLOID_REQUEST_TYPE_HASH: keccak256("HLRequestCancelByCloid(HLCancelByCloid[] cancels,uint64 nonce)HLCancelByCloid(...)")

Örnek:

const message = {
cancels: [{
asset: 0,
cloid: 9876543210
}],
nonce: 3
};

const signature = await apiWalletSigner.signTypedData(domain, types, message);

Zincir Üstü Giriş Noktası: Exchange.hlRequestCancelByCloid(HLRequestCancelByCloid memory request, bytes memory signature)

Domain 2: Manager İşlemleri (HypercallManagerSign)

Domain Adı: "HypercallManagerSign"

Önceden Hesaplanmış Domain Separator'lar:

  • Testnet: 0xd1f76b6138be892c14b71b0569bdb049cb44f239d34c78ef1ffaacd2466f9f18
  • Ana ağ: Henüz belirlenmedi (TBD)

İmzalayan: Hesap Manager'ı (hesabı oluşturan EOA)

Nonce: Manager bazında replay koruması. Agent nonce'ları ile aynı sınırlı küme modeli: en yüksek 100 nonce saklanır, yeni nonce kümenin minimumunu aşmalı ve tekrar olmamalıdır. Zincir üstünde Exchange.isNonceUsed(manager, nonce) ile takip edilir

HLActionSendAsset

Hesaptan bir hedef adrese ActionCaster aracılığıyla varlık gönderir.

Struct:

struct HLActionSendAsset {
address account;
uint64 nonce;
address destination;
uint32 srcDex; // Source DEX (type(uint32).max = HyperCore)
uint32 dstDex; // Destination DEX (type(uint32).max = HyperCore)
uint64 token; // Token ID
uint64 amountWei; // Amount in wei
}

Type Hash: keccak256("HLActionSendAsset(address account,uint64 nonce,address destination,uint32 srcDex,uint32 dstDex,uint64 token,uint64 amountWei)")

Gereksinimler:

  • signer == managers[account] (zincir üstünde doğrulanır)
  • Eğer destination == Exchange ise, token desteklenen bir token olmalıdır (_checkExchangeToken)

Örnek:

const domain = {
name: "HypercallManagerSign",
version: "1",
chainId: 998,
verifyingContract: ethers.ZeroAddress
};

const types = {
HLActionSendAsset: [
{ name: "account", type: "address" },
{ name: "nonce", type: "uint64" },
{ name: "destination", type: "address" },
{ name: "srcDex", type: "uint32" },
{ name: "dstDex", type: "uint32" },
{ name: "token", type: "uint64" },
{ name: "amountWei", type: "uint64" }
]
};

const message = {
account: accountAddress,
nonce: 1,
destination: recipientAddress,
srcDex: 0xFFFFFFFF, // HyperCore
dstDex: 0xFFFFFFFF, // HyperCore
token: 0, // USDC
amountWei: 1000000 // 1 USDC (6 decimals)
};

const signature = await managerSigner.signTypedData(domain, types, message);

Zincir Üstü Giriş Noktası: Exchange.hlActionSendAsset(HLActionSendAsset memory action, bytes memory signature)

Processor Çıktısı: ActionCasterEncoder.sendAsset(...) olarak kodlar.

HCActionWithdrawToken

Exchange'den Hesaba token çeker.

Struct:

struct HCActionWithdrawToken {
address account;
uint64 nonce;
uint32 srcDex;
uint32 dstDex;
uint64 token;
uint64 amountWei;
}

Type Hash: keccak256("HCActionWithdrawToken(address account,uint64 nonce,uint32 srcDex,uint32 dstDex,uint64 token,uint64 amountWei)")

Gereksinimler:

  • signer == managers[account]
  • Token desteklenen bir token olmalıdır (_checkExchangeToken - şu anda yalnızca spot USDC)
  • Hesap HyperCore üzerinde aktive edilmiş olmalıdır (ActionCasterUtils.checkAccountActivated)

Davranış:

  • ActionCaster işlemlerini Exchange başlatır (Account değil)
  • Tokeni HyperCore üzerinde Exchange'den Account'a transfer eder

Örnek:

const message = {
account: accountAddress,
nonce: 2,
srcDex: 0xFFFFFFFF, // Exchange
dstDex: 0xFFFFFFFF, // HyperCore
token: 0, // USDC
amountWei: 5000000 // 5 USDC
};

const signature = await managerSigner.signTypedData(domain, types, message);

Zincir Üstü Giriş Noktası: Exchange.hcActionWithdrawToken(HCActionWithdrawToken memory action, bytes memory signature)

HCActionWithdrawOption

Exchange'den HyperEVM üzerindeki bir alıcıya opsiyon tokenları çeker.

Struct:

struct HCActionWithdrawOption {
address account;
uint64 nonce;
address recipient;
address option; // Option token address
uint256 amountWei; // Amount in wei
}

Type Hash: keccak256("HCActionWithdrawOption(address account,uint64 nonce,address recipient,address option,uint256 amountWei)")

Gereksinimler:

  • signer == managers[account]
  • option desteklenen bir opsiyon olmalıdır (optionRegistry.isSupportedOption(option))

Davranış:

  • ActionCaster işlemi yoktur (diğer para çekme işlemlerinin aksine)
  • Opsiyon tokenını IOptionToken(option).mint(recipient, amountWei) aracılığıyla recipient adresine mint eder
  • Withdraw(account, recipient, option, amountWei) eventini yayınlar

Örnek:

const message = {
account: accountAddress,
nonce: 3,
recipient: recipientAddress,
option: optionTokenAddress,
amountWei: ethers.parseEther("1.0") // 1 option token
};

const signature = await managerSigner.signTypedData(domain, types, message);

Zincir Üstü Giriş Noktası: Exchange.hcActionWithdrawOption(HCActionWithdrawOption memory action, bytes memory signature)

Domain 3: RSM Komutları (HypercallRsmSign)

Domain Adı: "HypercallRsmSign"

Önceden Hesaplanmış Domain Separator'lar:

  • Testnet: 0x650b282053fb61d3fd477bdc28f6434311fe905e27cc4ca643e87e802c45938c
  • Ana ağ: Henüz belirlenmedi (TBD)

İmzalayan: RSM İmzalayıcısı (Exchange.setRsmSigner aracılığıyla ayarlanır, zincir üstünde doğrulanır)

Nonce: RSM imzalayıcısı bazında nonce (Exchange.nextNonce[rsmSigner] ile takip edilir)

RSM komutları SEQUENCER_ROLE tarafından çağrılabilir; piyasa yapıcılar bunları doğrudan çağırmaz.

RsmCommandRebalance

Bir pozisyonu yeniden dengelemek için HyperCore üzerinde reduce-only bir IOC emri yürütür.

Struct:

struct RsmCommandRebalance {
address target; // Account to rebalance
uint64 nonce;
uint32 asset;
bool isBuy;
uint64 limitPx;
uint64 sz;
}

Type Hash: keccak256("RsmCommandRebalance(address target,uint64 nonce,uint32 asset,bool isBuy,uint64 limitPx,uint64 sz)")

Gereksinimler:

  • signer == rsmSigner (zincir üstünde doğrulanır)
  • Çağıran SEQUENCER_ROLE rolüne sahip olmalıdır

Davranış:

  • reduceOnly: true ve encodedTif: 3 (IOC) ile ActionCasterEncoder.limitOrder olarak kodlar
  • Hedef hesap üzerinde yürütülür

Zincir Üstü Giriş Noktası: Exchange.rsmCommandRebalance(RsmCommandRebalance memory cmd, bytes memory signature)

RsmCommandRepay

Bir hesap adına Exchange'e token yatırır (likidasyon geri ödemeleri için kullanılır).

Struct:

struct RsmCommandRepay {
address target;
uint64 nonce;
uint32 srcDex;
uint32 dstDex;
uint64 token;
uint64 amountWei;
}

Type Hash: keccak256("RsmCommandRepay(address target,uint64 nonce,uint32 srcDex,uint32 dstDex,uint64 token,uint64 amountWei)")

Gereksinimler:

  • signer == rsmSigner
  • Çağıran SEQUENCER_ROLE rolüne sahip olmalıdır
  • Token desteklenen bir token olmalıdır (_checkExchangeToken)

Davranış:

  • destination: EXCHANGE ile ActionCasterEncoder.sendAsset olarak kodlar
  • Hedef hesap üzerinde yürütülür

Zincir Üstü Giriş Noktası: Exchange.rsmCommandRepay(RsmCommandRepay memory cmd, bytes memory signature)

Nonce Yönetimi

Her imzalayan (API cüzdanı, manager, RSM imzalayıcısı) bağımsız bir nonce alanına sahiptir:

mapping(address signer => uint256 nonce) public nextNonce;
mapping(address signer => BitMaps.BitMap) private _nonces; // Tracks used nonces

Kurallar:

  1. Nonce'lar kesin olarak artan olmalıdır (boşluk olabilir, ancak nextNonce korunur)
  2. Bir kez kullanıldıktan sonra, bir nonce yeniden kullanılamaz (isNonceUsed(signer, nonce) ile kontrol edilir)
  3. nextNonce[signer], garanti edilen minimum kullanılmamış nonce'dur (atlanmışsa daha düşük nonce'lar kullanılmamış olabilir)

Nonce Durumunu Sorgulama:

function isNonceUsed(address signer, uint256 nonce) external view returns (bool);

En İyi Uygulama: Nonce'ları zincir dışında takip edin ve atomik olarak artırın. nextNonce'u bir tutarlılık kontrolü olarak kullanın.

İmza Doğrulama Akışı

  1. Zincir Dışı: İmzalayan, EIP-712 digest'ini oluşturur ve özel anahtarıyla imzalar
  2. Zincir Üstü: Exchange imzalı mesajı alır ve Processor.process* çağrısını yapar
  3. Processor: İmzayı doğrular, imzalayanı kurtarır (recover), ActionCaster işlemlerini kodlar
  4. Exchange: Nonce'u kontrol eder, yetkilendirmeyi doğrular (manager/API cüzdanı/RSM), işlemleri yürütür

Örnek Akış (HLRequestOrder):

1. API Wallet signs HLRequestOrder with nonce=1
2. RSM Sequencer calls Exchange.hlRequestOrder(request, signature)
3. Processor.hlRequestOrder verifies signature, recovers API wallet
4. Exchange._useNonce(apiWallet, 1) checks and marks nonce as used
5. Exchange._getAccountByApiWallet(apiWallet) returns Account
6. Account.performCoreActions(orderActions) executes ActionCaster calls

Kullanımdan Kaldırılan Fonksiyonlar

Aşağıdaki fonksiyonlar kullanımdan kaldırılmıştır, ancak geriye dönük uyumluluk için hâlâ mevcuttur:

  • placeCoreOrders (hlRequestOrder kullanın)
  • cancelCoreOrders (hlRequestCancel kullanın)
  • cancelCoreOrdersByCloid (hlRequestCancelByCloid kullanın)

Bunlar, eski bir MsgPack kodlama şeması ve CoreSignatures domainini ("Exchange", chainId 1337) kullanır. Yeni entegrasyonlar için kullanmayın.

Güvenlik Değerlendirmeleri

  1. Özel Anahtar Saklama: API cüzdanı ve manager anahtarlarını güvenli bir şekilde saklayın (manager için donanım cüzdanı, API cüzdanları için şifreli depolama).

  2. Nonce Replay: Nonce'ları asla yeniden kullanmayın. Nonce'ları zincir dışında takip edin ve atomik olarak artırın.

  3. Domain Separator: Her zaman doğru chain ID'yi kullanın (testnet için 998, ana ağ henüz belirlenmedi). Domain separator'ın kontrat sabitleriyle eşleştiğini doğrulayın.

  4. İmza Doğrulama: Kontrat, imzaları zincir üstünde doğrular. Kritik işlemler için zincir dışı imza doğrulamasına güvenmeyin.

  5. Manager ve API Cüzdanı Ayrımı: Manager'lar hesap sahipliğini ve para çekme işlemlerini kontrol eder. API cüzdanları yalnızca alım satım isteklerini imzalar. Ayrı anahtarlar kullanın.

Referanslar