mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-27 14:02:43 +08:00
The trading account could only be funded outside NOFX: no way to move
spot ('main wallet') USDC into the perp trading account, and no deposit
address/QR shown anywhere.
- API: /hyperliquid/account now also returns spot USDC (total/available)
alongside the perp summary; /hyperliquid/submit-exchange accepts a
validated usdClassTransfer action (positive plain amount, boolean
toPerp, Mainnet chain) relayed like the existing approval actions
- Web: new HyperliquidFundsPanel in the exchange config modal with a
deposit tab (wallet address QR + Arbitrum/spot routing notes) and a
transfer tab (spot<->perp, signed by the user's own wallet via EIP-712
UsdClassTransfer — user-signed actions derive the account from the
signature, so the agent key NOFX holds cannot and does not move funds)
- Shared EIP-712/provider helpers extracted from HyperliquidWalletConnect
into lib/hyperliquidWallet.ts
68 lines
2.1 KiB
Go
68 lines
2.1 KiB
Go
package api
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func usdClassTransferAction(overrides map[string]any) map[string]any {
|
|
action := map[string]any{
|
|
"type": "usdClassTransfer",
|
|
"signatureChainId": "0x66eee",
|
|
"hyperliquidChain": "Mainnet",
|
|
"amount": "21.5",
|
|
"toPerp": true,
|
|
"nonce": float64(1784900000000),
|
|
}
|
|
for k, v := range overrides {
|
|
if v == nil {
|
|
delete(action, k)
|
|
continue
|
|
}
|
|
action[k] = v
|
|
}
|
|
return action
|
|
}
|
|
|
|
func TestValidateUsdClassTransferActionAcceptsValidTransfer(t *testing.T) {
|
|
if err := validateUsdClassTransferAction(usdClassTransferAction(nil)); err != nil {
|
|
t.Fatalf("expected valid usdClassTransfer to pass, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateUsdClassTransferActionRejectsBadAmounts(t *testing.T) {
|
|
cases := map[string]any{
|
|
"zero": "0",
|
|
"negative": "-5",
|
|
"not number": "abc",
|
|
"empty": "",
|
|
// The SDK's subaccount suffix must not be relayable from the browser.
|
|
"subaccount": "21.5 subaccount:0x1234",
|
|
}
|
|
for name, amount := range cases {
|
|
err := validateUsdClassTransferAction(usdClassTransferAction(map[string]any{"amount": amount}))
|
|
if err == nil {
|
|
t.Fatalf("%s: expected amount %q to be rejected", name, amount)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateUsdClassTransferActionRequiresBooleanToPerp(t *testing.T) {
|
|
if err := validateUsdClassTransferAction(usdClassTransferAction(map[string]any{"toPerp": nil})); err == nil {
|
|
t.Fatal("expected missing toPerp to be rejected")
|
|
}
|
|
if err := validateUsdClassTransferAction(usdClassTransferAction(map[string]any{"toPerp": "true"})); err == nil {
|
|
t.Fatal("expected non-boolean toPerp to be rejected")
|
|
}
|
|
if err := validateUsdClassTransferAction(usdClassTransferAction(map[string]any{"toPerp": false})); err != nil {
|
|
t.Fatalf("expected toPerp=false (perp->spot) to be valid, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateUsdClassTransferActionRequiresMainnetChain(t *testing.T) {
|
|
err := validateUsdClassTransferAction(usdClassTransferAction(map[string]any{"hyperliquidChain": "Testnet"}))
|
|
if err == nil || !strings.Contains(err.Error(), "hyperliquidChain") {
|
|
t.Fatalf("expected Testnet chain to be rejected, got %v", err)
|
|
}
|
|
}
|