Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
{
"name": "Eterna Trading",
"description": "Trade on Eterna from your openclaw agent using the eterna CLI",
"skills": ["skills/trade", "skills/onboard"],
"skills": [
"skills/trade",
"skills/market-scan",
"skills/deposit",
"skills/withdraw",
"skills/open-position",
"skills/close-position"
],
"hooks": []
}
11 changes: 8 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@ jobs:
- name: Check SKILL.md files exist
run: |
test -f skills/trade/SKILL.md
test -f skills/onboard/SKILL.md
test -f skills/deposit/SKILL.md
test -f skills/market-scan/SKILL.md
test -f skills/open-position/SKILL.md
test -f skills/close-position/SKILL.md
test -f skills/withdraw/SKILL.md
- name: Check SKILL.md frontmatter
run: |
grep -q "^name:" skills/trade/SKILL.md
grep -q "^name:" skills/onboard/SKILL.md
for skill in trade deposit market-scan open-position close-position withdraw; do
grep -q "^name:" "skills/$skill/SKILL.md"
done
32 changes: 20 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Eterna Trading — Openclaw Plugin & Skill
# Eterna Trading — Openclaw Plugin

Let your openclaw AI agent trade on [Eterna](https://eterna.exchange) using the `eterna` CLI.

Expand All @@ -13,7 +13,7 @@ eterna login

## Installation

**As a plugin** (recommended — more versatile):
**As a plugin** (recommended):

```shell
openclaw plugins install @eterna-hybrid-exchange/openclaw-plugin
Expand All @@ -27,21 +27,29 @@ openclaw skills install @eterna-hybrid-exchange/eterna-trading-skill

## Skills included

### `eterna_trading` — always active
### `eterna_trading` — always active (router)

Teaches the agent to use the `eterna` CLI for trading operations:
Detects user state on first message and routes to the right skill. New users get an automatic market scan and guided onboarding flow. Returning traders see their positions.

- `eterna balance` — check account balance
- `eterna positions` — view open positions
- `eterna orders` — view active orders
- `eterna execute <file>` — execute TypeScript trading code
- `eterna sdk --search <query>` — browse SDK method docs
### `market_scan` — market analysis and trade ideas

### `onboarding` — explicit invocation only
Live market briefings, TA scanning, deep-dives on specific symbols, and trade idea generation with entry/stop/target levels.

A guided 5-phase onboarding flow for new end users (Discovery → Trust Building → First Deposit → First Trade → Preferences). Activate with `/eterna-trading:onboarding` or by asking the agent to onboard you.
### `deposit` — deposit and fund trading account

Not suitable for fully autonomous trading agents — it assumes a human user is present.
Guides through deposit address, chain selection, deposit monitoring via `getDepositRecords()`, transfer from Funding to Trading wallet, and balance confirmation.

### `withdraw` — withdraw funds

Check withdrawable balance, submit withdrawal, and track status.

### `open_position` — place trades

Pre-trade checks (balance, existing positions, instrument specs), trade proposals with clear margin/notional breakdown, and execution with proper TP/SL.

### `close_position` — close positions and manage orders

Close positions, cancel orders, and modify TP/SL on existing positions.

## Releasing a new version

Expand Down
19 changes: 18 additions & 1 deletion openclaw.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,24 @@
"id": "eterna-trading",
"name": "Eterna Trading",
"description": "Trade on Eterna from your openclaw agent using the eterna CLI",
"skills": ["./skills/trade", "./skills/onboard"],
"skills": [
"./skills/trade",
"./skills/market-scan",
"./skills/deposit",
"./skills/withdraw",
"./skills/open-position",
"./skills/close-position"
],
"dependencies": {
"bins": {
"eterna": {
"package": "@eterna-hybrid-exchange/cli",
"install": "npm install -g @eterna-hybrid-exchange/cli",
"postInstall": "eterna login",
"docs": "https://github.com/EternaHybridExchange/eterna-cli"
}
}
},
"configSchema": {
"type": "object",
"additionalProperties": false
Expand Down
88 changes: 88 additions & 0 deletions skills/close-position/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
name: close_position
description: Close positions and cancel orders on Eterna
metadata.openclaw.requires.bins:
- name: eterna
install: npm install -g @eterna-hybrid-exchange/cli
postInstall: eterna login
---

# Close Position Skill

Close positions and cancel orders.

## Show current positions

```typescript
const [positions, orders] = await Promise.all([
eterna.getPositions(),
eterna.getOrders(),
]);

return {
positions: positions.list.filter((p) => parseFloat(p.size) > 0).map((p) => ({
symbol: p.symbol, side: p.side, size: p.size,
entryPrice: p.avgPrice, markPrice: p.markPrice,
unrealisedPnl: p.unrealisedPnl, leverage: p.leverage,
})),
activeOrders: orders.list.map((o) => ({
symbol: o.symbol, orderId: o.orderId, side: o.side,
type: o.orderType, qty: o.qty, price: o.price, status: o.orderStatus,
})),
};
```

## Close a position

**Confirm with user before closing.** Show current P&L so they know what they're locking in.

```typescript
const result = await eterna.closePosition("LINKUSDT");
return result;
```

Returns `{ orderId, closedSize, side, entryPrice, markPrice, unrealisedPnl }`.

## Cancel orders

Single order:

```typescript
const result = await eterna.cancelOrder("LINKUSDT", "orderId123");
return result;
```

All orders on a symbol:

```typescript
const result = await eterna.cancelAllOrders("LINKUSDT");
return result;
```

All orders across all symbols:

```typescript
const result = await eterna.cancelAllOrders();
return result;
```

## Modify TP/SL on existing position

```typescript
await eterna.setTradingStop({
symbol: "LINKUSDT",
takeProfit: "9.80",
stopLoss: "9.10",
});
```

## SDK methods used

| Method | Returns |
|--------|---------|
| `eterna.getPositions(symbol?)` | `{ list: [{ symbol, side, size, avgPrice, markPrice, unrealisedPnl, leverage, takeProfit, stopLoss }] }` |
| `eterna.getOrders(symbol?)` | `{ list: [{ symbol, orderId, side, orderType, qty, price, orderStatus }] }` |
| `eterna.closePosition(symbol)` | `{ orderId, closedSize, side, entryPrice, markPrice, unrealisedPnl }` |
| `eterna.cancelOrder(symbol, orderId)` | `{ orderId, orderLinkId }` |
| `eterna.cancelAllOrders(symbol?)` | `{ list: [{ orderId }] }` |
| `eterna.setTradingStop({ symbol, takeProfit?, stopLoss?, trailingStop?, ... })` | `{}` |
113 changes: 113 additions & 0 deletions skills/deposit/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
name: deposit
description: Guide user through depositing crypto and transferring funds to the trading wallet
metadata.openclaw.requires.bins:
- name: eterna
install: npm install -g @eterna-hybrid-exchange/cli
postInstall: eterna login
---

# Deposit Skill

Guide the user through depositing funds into their Eterna trading account.

**Critical flow:** Deposit address → send crypto → monitor with `getDepositRecords()` → transfer to trading wallet → confirm balance.

## Important context

- Deposits arrive in the **Funding wallet**, NOT the trading wallet.
- After deposit confirms, you MUST call `transferToTrading()` before funds are usable.
- `getBalance()` checks the **trading wallet** — it will show zero until you transfer. **Do NOT use `getBalance()` to check if a deposit has arrived.**
- Use `getDepositRecords()` to monitor incoming deposits.
- Recommend **Arbitrum** for USDT deposits — cheapest and fastest.

## Step 1 — Show deposit options

```typescript
const coins = await eterna.getAllowedDepositCoins("USDT");
return coins.configList.map((c) => ({
coin: c.coin, chain: c.chain, chainName: c.chainType,
minDeposit: c.minDepositAmount, confirmations: c.blockConfirmNumber,
}));
```

Present as a simple choice: "I'd recommend **Arbitrum** — fast and cheap. Which chain works for your wallet?"

If the user wants a different coin (BTC, ETH, USDC), run `getAllowedDepositCoins` for that coin.

## Step 2 — Get deposit address

```typescript
// Replace coin/chain based on user's choice
const addr = await eterna.getDepositAddress("USDT", "ARBI");
return addr;
```

Show address clearly. If there's a tag/memo, emphasize it — missing tags can lose funds.

## Step 3 — Monitor deposit

Once the user says they've sent funds, check `getDepositRecords()`:

```typescript
const records = await eterna.getDepositRecords("USDT");
const pending = records.rows.filter((r) => r.status !== 3 && r.status !== 4);
const confirmed = records.rows.filter((r) => r.status === 3);
return { pending, confirmed };
```

**Status codes:**
- 0 = unknown
- 1 = waiting for confirmations — tell user: "I can see it, waiting for blockchain confirmations."
- 2 = processing — "Almost there, Eterna is processing it."
- **3 = success** — proceed to Step 4 immediately.
- 4 = failed — "Something went wrong. Check the tx on a block explorer."

Check when the user asks. If they ask you to poll, check every minute.

## Step 4 — Transfer to trading wallet

**This step is mandatory.** Funds sit in the Funding wallet until transferred.

If they deposited USDT:

```typescript
const transfer = await eterna.transferToTrading("USDT", "ALL_BALANCE");
return transfer;
```

If they deposited a non-USDT coin, swap first:

```typescript
const swap = await eterna.swapToUsdt("ETH"); // omit amount for full balance
const transfer = await eterna.transferToTrading("USDT", "ALL_BALANCE");
return { swap, transfer };
```

## Step 5 — Confirm balance is ready

```typescript
const balance = await eterna.getBalance();
const account = balance.list[0];
return {
totalEquity: account.totalEquity,
availableBalance: account.totalAvailableBalance,
coins: account.coin.filter((c) => parseFloat(c.equity) > 0).map((c) => ({
coin: c.coin, equity: c.equity, usdValue: c.usdValue,
})),
};
```

Celebrate: "You're funded! $X ready to trade." Then suggest looking at trade ideas.

## SDK methods used

| Method | Returns |
|--------|---------|
| `eterna.getAllowedDepositCoins(coin?, chain?)` | `{ configList: [{ coin, chain, chainType, minDepositAmount, blockConfirmNumber }] }` |
| `eterna.getDepositAddress(coin, chainType)` | `{ coin, chains: { chainType, addressDeposit, tagDeposit } }` |
| `eterna.getDepositRecords(coin?)` | `{ rows: [{ coin, chain, amount, txID, status, confirmations }] }` — status: 0-4 |
| `eterna.transferToTrading(coin, amount)` | `{ transferId: string }` — use `"ALL_BALANCE"` for full transfer |
| `eterna.swapToUsdt(coin, amount?)` | `{ coin, orderId, qty, success, message }` — omit amount for full balance |
| `eterna.getBalance()` | `{ list: [{ totalEquity, totalAvailableBalance, coin: [...] }] }` — **trading wallet only** |
| `eterna.getAllCoinsBalance(accountType)` | Balance by account type: `"FUND"`, `"UNIFIED"`, `"SPOT"` |
Loading
Loading