dsh-hyperliquid 0.5.7 → 0.5.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +1 -1
- package/lib/remote.js +72 -41
- package/lib/tools.js +2 -2
- package/package.json +1 -1
- package/skills/hyperliquid-trading/SKILL.md +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.8 — 2026-10-17
|
|
4
|
+
|
|
5
|
+
- 账户互转补全:现货、主永续、各 HIP-3 交易所之间任意两两划转。现货 ↔ HIP-3、HIP-3 ↔ HIP-3 自动经主永续分两步签名提交,半托管只批准一次;第二步前重新确认主永续可划余额;第二步被拒、被取消或余额不足时报错说明资金在主永续;第二步已提交但未收到确认时报「结果未知,先对账,不要直接重试」,防止重复划转。两端都校验 USDC 保证金,非 USDC 交易所在签名前拒绝。
|
|
6
|
+
- 修复(0.5.7 重写时引入):钱包签名期间被取消后,签名不再提交到交易所。
|
|
7
|
+
|
|
3
8
|
## 0.5.7 — 2026-10-17
|
|
4
9
|
|
|
5
10
|
- `hyper_account action=transfer` 支持现货 ↔ 永续:`from` / `to` 取 `spot`、`perp` 或 HIP-3 交易所名。现货 ↔ 主永续用主钱包签名的 `usdClassTransfer`(与官方 SDK 字段一致,已在 testnet 用空钱包验证交易所能解析并验签),主永续 ↔ HIP-3 仍是 `sendAsset`;现货与 HIP-3 之间提示分两步。转出前核对可划余额(现货 = 总额 − 冻结,永续 = withdrawable);统一账户 / 组合保证金模式下拒绝并说明无需划转。旧参数 `dex` + `direction` 不变。
|
package/README.md
CHANGED
|
@@ -114,7 +114,7 @@ dsh plugin --profile web add dsh-web3
|
|
|
114
114
|
| `hyper_decide` | 实盘预检:提交交易意图与依据,返回 30 秒内有效的一次性 `decisionId` |
|
|
115
115
|
| `hyper_order` | place / cancel / leverage;实盘下单必须带匹配的 `decisionId`,可附带止盈止损(目前仅测试网) |
|
|
116
116
|
| `hyper_session` | status / bind,同步用户当前查看的币对 |
|
|
117
|
-
| `hyper_account` 托管动作 | `action=limits`(全托管时 agent 可调整允许币对与各项上限)、`action=transfer`(`from` / `to` 取 spot、perp 或 HIP-3
|
|
117
|
+
| `hyper_account` 托管动作 | `action=limits`(全托管时 agent 可调整允许币对与各项上限)、`action=transfer`(`from` / `to` 取 spot、perp 或 HIP-3 交易所名,自己的任意两个账户之间划转 USDC:现货 ↔ 主永续(usdClassTransfer)、主永续 ↔ HIP-3(sendAsset)一步完成,现货 ↔ HIP-3 与 HIP-3 ↔ HIP-3 自动经主永续两步、只批准一次;半托管每次需批准,全托管直接执行;主钱包签名;统一账户无需现货 ↔ 永续划转) |
|
|
118
118
|
| 交易体检与市场雷达 | `hyper_account action=analyze`:按完整往返统计近 7/30/90 天的胜率、盈亏比、期望、持仓时长、按币种/时段/星期的表现、手续费与资金费,并按金额列出习惯问题和建议规则(样本少于 10 笔往返时不下结论);`hyper_market action=scan`:扫描流动性最好的约 20 个可交易永续,给出贴近/突破区间、资金费极值、24h 涨跌和溢价的前几名 |
|
|
119
119
|
| `hyper_journal` | 交易员日志(跨会话保留):交易员档案(风险偏好、持仓周期、打扰偏好等)、策略库(版本与每次修订原因,按 paper / live 分开统计笔数、胜率、期望 R)、交易计划 / 结果 / 复盘 / 教训记录;摘要自动带入每个会话的系统提示 |
|
|
120
120
|
|
package/lib/remote.js
CHANGED
|
@@ -901,9 +901,10 @@ export class HyperRemote extends TypertRemoteService {
|
|
|
901
901
|
}
|
|
902
902
|
|
|
903
903
|
/**
|
|
904
|
-
* Move USDC between the spot balance ('spot'), the core perp
|
|
905
|
-
* spot ↔ perp is a
|
|
906
|
-
*
|
|
904
|
+
* Move USDC between any two of the trader's own accounts: the spot balance ('spot'), the core perp
|
|
905
|
+
* account ('perp') and each HIP-3 dex (e.g. 'xyz'). spot ↔ perp is a `usdClassTransfer`, perp ↔ HIP-3 a
|
|
906
|
+
* `sendAsset`; spot ↔ HIP-3 and HIP-3 ↔ HIP-3 go through perp in two signed legs under one approval.
|
|
907
|
+
* All legs need the main wallet. 半托管 asks the trader once per transfer; 全托管 goes straight through.
|
|
907
908
|
* Accepts { from, to, amount } or the original { dex, direction: 'in' | 'out', amount }.
|
|
908
909
|
*/
|
|
909
910
|
async transferCollateral(request, exec) {
|
|
@@ -921,22 +922,20 @@ export class HyperRemote extends TypertRemoteService {
|
|
|
921
922
|
}
|
|
922
923
|
if (from === to) throw new Error('from 和 to 不能相同')
|
|
923
924
|
if ([from, to].some((name) => name.includes(':'))) throw new Error('交易所名不含冒号,如 xyz')
|
|
924
|
-
// Every transfer has the core perp account on one side: spot ↔ perp or perp ↔ one HIP-3 dex.
|
|
925
|
-
if (from !== 'perp' && to !== 'perp') {
|
|
926
|
-
throw new Error(from === 'spot' || to === 'spot'
|
|
927
|
-
? '现货与 HIP-3 交易所之间请分两步:先在 spot 与 perp 之间划转,再在 perp 与该交易所之间划转'
|
|
928
|
-
: 'HIP-3 交易所之间请分两步:先转回 perp,再从 perp 转入目标交易所')
|
|
929
|
-
}
|
|
930
|
-
const dex = [from, to].find((name) => name !== 'spot' && name !== 'perp') ?? null
|
|
931
925
|
const amount = Number(request?.amount)
|
|
932
926
|
if (!Number.isFinite(amount) || amount <= 0) throw new Error('amount 必须为正数(USDC)')
|
|
933
927
|
if (Number(amount.toFixed(6)) !== amount) throw new Error('USDC 划转金额最多 6 位小数')
|
|
934
928
|
if (amount > this.cfg.maxPositionUsd) throw new Error(`单次划转不超过仓位上限 ${this.cfg.maxPositionUsd} USD`)
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
929
|
+
// Every leg has the core perp account on one side; a route without it goes through perp.
|
|
930
|
+
const legs = from === 'perp' || to === 'perp' ? [[from, to]] : [[from, 'perp'], ['perp', to]]
|
|
931
|
+
const dexes = [...new Set([from, to].filter((name) => name !== 'spot' && name !== 'perp'))]
|
|
932
|
+
if (dexes.length) {
|
|
933
|
+
const known = await this.perpDexList()
|
|
934
|
+
for (const dex of dexes) {
|
|
935
|
+
if (!known.some((row) => row?.name === dex)) throw new Error(`未知的 HIP-3 交易所 ${dex}`)
|
|
936
|
+
const meta = await this.client.info('meta', { dex })
|
|
937
|
+
if (meta?.collateralToken !== 0) throw new Error(`${dex} 不使用 USDC 保证金,暂不支持划转`)
|
|
938
|
+
}
|
|
940
939
|
}
|
|
941
940
|
const address = await this.resolveAddress()
|
|
942
941
|
if (!address) throw new Error('live 划转需要钱包地址')
|
|
@@ -944,46 +943,78 @@ export class HyperRemote extends TypertRemoteService {
|
|
|
944
943
|
if (signerAddress.toLowerCase() !== address.toLowerCase()) throw new Error('资金划转需要交易主钱包签名;当前签名钱包与 tradingAddress 不一致,API wallet 不能用于划转')
|
|
945
944
|
const label = (name) => name === 'spot' ? '现货账户' : name === 'perp' ? '主永续账户' : name
|
|
946
945
|
const labelEn = (name) => name === 'spot' ? 'spot' : name === 'perp' ? 'core perps' : name
|
|
947
|
-
//
|
|
946
|
+
// Unified / portfolio-margin accounts already share spot and perp USDC: no spot ↔ perp leg. The mode
|
|
947
|
+
// must be known before signing: a failed lookup stops the transfer.
|
|
948
|
+
if (from === 'spot' || to === 'spot') {
|
|
949
|
+
const mode = await this.accountMode(address, true)
|
|
950
|
+
if (mode === 'unifiedAccount' || mode === 'portfolioMargin') throw new Error(`账户模式为 ${mode},现货 USDC 已直接作为保证金,现货与永续之间无需划转`)
|
|
951
|
+
}
|
|
952
|
+
// What can leave the source account now (the later leg spends what the first one brought into perp).
|
|
948
953
|
let available
|
|
949
954
|
if (from === 'spot') {
|
|
950
|
-
// Unified / portfolio-margin accounts already use spot USDC as margin: there is nothing to move.
|
|
951
|
-
// The mode must be known before signing: a failed lookup stops the transfer.
|
|
952
|
-
const mode = await this.accountMode(address, true)
|
|
953
|
-
if (mode === 'unifiedAccount' || mode === 'portfolioMargin') throw new Error(`账户模式为 ${mode},现货 USDC 已直接作为保证金,无需划转`)
|
|
954
955
|
const spot = await this.client.spotClearinghouseState(address)
|
|
955
956
|
const usdc = (spot?.balances ?? []).find((row) => row.coin === 'USDC')
|
|
956
957
|
available = Number(usdc?.total ?? 0) - Number(usdc?.hold ?? 0)
|
|
957
958
|
} else {
|
|
958
|
-
if (to === 'spot') {
|
|
959
|
-
const mode = await this.accountMode(address, true)
|
|
960
|
-
if (mode === 'unifiedAccount' || mode === 'portfolioMargin') throw new Error(`账户模式为 ${mode},现货与永续共用余额,无需划转`)
|
|
961
|
-
}
|
|
962
959
|
const state = from === 'perp' ? await this.client.clearinghouseState(address) : await this.client.info('clearinghouseState', { user: address, dex: from })
|
|
963
960
|
available = Number(state?.withdrawable)
|
|
964
961
|
}
|
|
965
962
|
if (!Number.isFinite(available)) throw new Error('无法确认可划转余额')
|
|
966
963
|
if (amount > available + 1e-9) throw new Error(`${label(from)}可划转 ${Math.max(0, available).toFixed(2)} USDC,不足 ${amount}`)
|
|
967
|
-
const
|
|
968
|
-
|
|
969
|
-
if (
|
|
970
|
-
|
|
964
|
+
const route = legs.length > 1 ? `${label(from)} → 主永续账户 → ${label(to)}(两步)` : `${label(from)} → ${label(to)}`
|
|
965
|
+
const what = `${amount} USDC:${route}`
|
|
966
|
+
if (!this.isManaged()) await this.authorizeToolAction(`Hyperliquid ${this.cfg.network} 划转 ${what}?`, exec,
|
|
967
|
+
`Hyperliquid ${this.cfg.network}: transfer ${amount} USDC ${[from, ...legs.map(([, next]) => next)].map(labelEn).join(' → ')}?`)
|
|
971
968
|
const isMainnet = this.cfg.network === 'mainnet'
|
|
972
969
|
const amountText = String(Number(amount.toFixed(6)))
|
|
973
|
-
let
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const
|
|
978
|
-
|
|
979
|
-
|
|
970
|
+
let usdcToken = null
|
|
971
|
+
const responses = []
|
|
972
|
+
for (const [index, [source, target]] of legs.entries()) {
|
|
973
|
+
// Everything before the second leg is submitted can stop it; the first leg's money then sits in perp.
|
|
974
|
+
const parked = `第一步已完成(${amount} USDC 已到主永续账户)`
|
|
975
|
+
let submitted = false
|
|
976
|
+
try {
|
|
977
|
+
if (transferEpoch !== this.walletEpoch) throw new Error('钱包已切换,划转已取消')
|
|
978
|
+
exec.signal?.throwIfAborted()
|
|
979
|
+
if (index > 0) {
|
|
980
|
+
// The money that just arrived must be free to leave perp (positions may have taken it as margin).
|
|
981
|
+
const state = await this.client.clearinghouseState(address)
|
|
982
|
+
const free = Number(state?.withdrawable)
|
|
983
|
+
if (!Number.isFinite(free) || free + 1e-9 < amount) throw new Error(`主永续账户可划转 ${Number.isFinite(free) ? free.toFixed(2) : '未知'} USDC,不足 ${amount}`)
|
|
984
|
+
}
|
|
985
|
+
const nonce = this.client.nextNonce()
|
|
986
|
+
let built
|
|
987
|
+
if (source === 'spot' || target === 'spot') built = buildUsdClassTransfer({ amount: amountText, toPerp: target === 'perp', nonce, isMainnet })
|
|
988
|
+
else {
|
|
989
|
+
if (!usdcToken) {
|
|
990
|
+
const spotMeta = await this.client.info('spotMeta')
|
|
991
|
+
usdcToken = (spotMeta?.tokens ?? []).find((token) => token.index === 0 && token.name === 'USDC')?.tokenId
|
|
992
|
+
if (!usdcToken) throw new Error('无法读取 USDC 代币信息')
|
|
993
|
+
}
|
|
994
|
+
built = buildSendAsset({ destination: address, sourceDex: source === 'perp' ? '' : source, destinationDex: target === 'perp' ? '' : target, token: `USDC:${usdcToken}`, amount: amountText, nonce, isMainnet })
|
|
995
|
+
}
|
|
996
|
+
const signature = await this.signUserTyped(built.typed, `Hyperliquid ${this.cfg.network} 资金划转(${index + 1}/${legs.length}):${amount} USDC ${label(source)} → ${label(target)}`, exec, signerAddress)
|
|
997
|
+
// Cancelled while the wallet was signing: never submit that signature.
|
|
998
|
+
exec.signal?.throwIfAborted()
|
|
999
|
+
if (transferEpoch !== this.walletEpoch) throw new Error('钱包已切换,划转已取消')
|
|
1000
|
+
submitted = true
|
|
1001
|
+
const res = await this.client.exchangeUserSigned(built.action, signature, nonce)
|
|
1002
|
+
submitted = false
|
|
1003
|
+
assertExchangeAccepted(res)
|
|
1004
|
+
responses.push(res)
|
|
1005
|
+
} catch (error) {
|
|
1006
|
+
if (index === 0) throw error
|
|
1007
|
+
const reason = error?.message ?? String(error)
|
|
1008
|
+
this.store.recordEvent('live.transfer', { from: source, to: target, amount, failed: true, unknown: submitted, reason })
|
|
1009
|
+
// Submitted but no answer (timeout, network): it may have gone through. Never suggest a blind retry.
|
|
1010
|
+
if (submitted) throw new Error(`${parked};第二步 主永续账户 → ${label(target)} 已提交但未收到确认(${reason}),结果未知:先用 hyper_account action=state 核对主永续与 ${label(target)} 的余额,不要直接重试`)
|
|
1011
|
+
throw new Error(`${parked},第二步 主永续账户 → ${label(target)} 未完成:${reason}。资金在主永续账户,可稍后 from=perp to=${target} 补划`)
|
|
1012
|
+
}
|
|
1013
|
+
this.store.recordEvent('live.transfer', { from: source, to: target, amount, dex: [source, target].find((name) => name !== 'spot' && name !== 'perp'), toDex: target !== 'spot' && target !== 'perp' })
|
|
980
1014
|
}
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
assertExchangeAccepted(res)
|
|
985
|
-
this.store.recordEvent('live.transfer', { from, to, amount, dex: dex ?? undefined, toDex: dex ? to === dex : undefined })
|
|
986
|
-
return { ok: true, from, to, amount, dex: dex ?? undefined, direction: dex ? (to === dex ? 'in' : 'out') : undefined, response: res }
|
|
1015
|
+
const dex = dexes.length === 1 ? dexes[0] : undefined
|
|
1016
|
+
return { ok: true, from, to, amount, legs: legs.map(([source, target]) => ({ from: source, to: target })), dex,
|
|
1017
|
+
direction: dex && legs.length === 1 ? (to === dex ? 'in' : 'out') : undefined, response: responses.at(-1), responses }
|
|
987
1018
|
}
|
|
988
1019
|
|
|
989
1020
|
/** Sign a user-signed action's EIP-712 payload with the main wallet (dsh-web3 or the local key). */
|
package/lib/tools.js
CHANGED
|
@@ -356,7 +356,7 @@ function buildTools(remote) {
|
|
|
356
356
|
description: '查询交易账户。action=state 返回权益/持仓;orders 返回挂单;fills 返回成交;orderStatus 用 oid 或 cloid 核对某笔 live 订单,尤其在网络超时后。review 返回最近 7/30/90 天成交复盘、订单数量核对、费用和资金费覆盖缺口;不是完整账户收益率。' +
|
|
357
357
|
'action=analyze 交易体检(默认 90 天):按完整往返统计胜率、盈亏比、期望、持仓时长、按币种/时段/星期的表现、手续费与资金费,并给出按金额排序的习惯问题和建议规则——第一次见面或用户问“我做得怎么样”时用它,不要自己从成交里手算。' +
|
|
358
358
|
'action=limits 修改允许币对(addCoins/removeCoins)与单笔/每日/仓位/杠杆上限——仅全托管模式可用,半托管时由用户在 设置 → 交易限额与签名规则 修改。' +
|
|
359
|
-
'action=transfer
|
|
359
|
+
'action=transfer 在自己的任意两个账户之间划转 USDC:from / to 取 spot(现货)、perp(主永续)或 HIP-3 交易所名(如 xyz),amount 为数量;spot↔perp、perp↔HIP-3 一步完成,spot↔HIP-3 与 HIP-3↔HIP-3 自动经 perp 两步完成(只需批准一次)。下单前永续保证金不足而现货有 USDC 时,先 spot→perp。统一账户 / 组合保证金模式无需 spot↔perp 划转。全托管直接执行,半托管每次需用户批准;仅 live,需交易主钱包签名。旧参数 dex + direction(in/out)仍可用。',
|
|
360
360
|
parameters: {
|
|
361
361
|
type: 'object',
|
|
362
362
|
properties: {
|
|
@@ -397,7 +397,7 @@ function buildTools(remote) {
|
|
|
397
397
|
if (action === 'transfer') {
|
|
398
398
|
const res = await remote.transferCollateral(args, exec)
|
|
399
399
|
const name = (value) => value === 'spot' ? '现货账户' : value === 'perp' ? '主永续账户' : value
|
|
400
|
-
return { text: `已划转 ${res.amount} USDC:${
|
|
400
|
+
return { text: `已划转 ${res.amount} USDC:${[res.from, ...res.legs.map((leg) => leg.to)].map(name).join(' → ')}${res.legs.length > 1 ? '(两步)' : ''}`, ...res }
|
|
401
401
|
}
|
|
402
402
|
if (action === 'state') {
|
|
403
403
|
const a = await remote.account({ coin: remote.desk?.coin })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-hyperliquid",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.8",
|
|
4
4
|
"description": "Hyperliquid trading desk for the DeepSeek Harness (dsh) - market workspace, native agent conversation and guarded live / paper trading over the Hyperliquid API.",
|
|
5
5
|
"icon": "./icon.svg",
|
|
6
6
|
"type": "module",
|
|
@@ -34,7 +34,7 @@ description: 作为用户的交易副手,在 DSH 交易会话中用 Hyperliqui
|
|
|
34
34
|
|---|---|---|
|
|
35
35
|
| 实盘下单、撤单、改杠杆 | 每笔都需要用户批准 | 在限额内直接执行 |
|
|
36
36
|
| 允许币对与单笔/每日/仓位/杠杆上限 | 只能由用户在 **设置 → 交易限额与签名规则** 修改 | 你可以用 `hyper_account action=limits` 调整(`addCoins` / `removeCoins` / `maxOrderUsd` / `maxDailyUsd` / `maxPositionUsd` / `maxLeverage`) |
|
|
37
|
-
| USDC
|
|
37
|
+
| USDC 划转(现货、主永续、各 HIP-3 交易所之间任意互转) | `hyper_account action=transfer`,每次需要用户批准 | 直接执行 |
|
|
38
38
|
|
|
39
39
|
两种模式下 dsh-web3 钱包自己的签名策略都仍然生效:钱包拒签就停止,不要绕过。
|
|
40
40
|
|
|
@@ -48,7 +48,7 @@ description: 作为用户的交易副手,在 DSH 交易会话中用 Hyperliqui
|
|
|
48
48
|
- 先确认:`hyper_account action=state` 看永续可用保证金;现货余额在 Hyperliquid 现货账户里。
|
|
49
49
|
- 不够时用 `hyper_account action=transfer from=spot to=perp amount=…` 把这笔交易需要的保证金加一点缓冲划到永续;不再交易时可 `from=perp to=spot` 划回。半托管需要用户批准,全托管直接执行。不要让用户去网页手动划转。
|
|
50
50
|
- 统一账户(`unifiedAccount`)/ 组合保证金(`portfolioMargin`)模式下现货 USDC 直接作保证金,不需要也不能这样划转。
|
|
51
|
-
-
|
|
51
|
+
- 现货 ↔ HIP-3、HIP-3 ↔ HIP-3 直接写 `from=spot to=xyz` / `from=xyz to=abc`,插件自动经主永续两步完成,只需批准一次;如果第二步失败,报错会说明钱已停在主永续,再单独 `from=perp to=…` 补一步即可。
|
|
52
52
|
|
|
53
53
|
### HIP-3 的保证金是独立的
|
|
54
54
|
每个 HIP-3 交易所(如 xyz)都有独立的保证金账户,和主永续账户的余额不互通:
|