@portkey/ca-agent-skills 1.1.0
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/LICENSE +21 -0
- package/README.md +270 -0
- package/README.zh-CN.md +265 -0
- package/bin/platforms/claude.ts +36 -0
- package/bin/platforms/cursor.ts +39 -0
- package/bin/platforms/openclaw.ts +39 -0
- package/bin/platforms/utils.ts +144 -0
- package/bin/setup.ts +191 -0
- package/cli-helpers.ts +26 -0
- package/index.ts +145 -0
- package/lib/aelf-client.ts +281 -0
- package/lib/aelf-sdk.d.ts +103 -0
- package/lib/config.ts +46 -0
- package/lib/http.ts +197 -0
- package/lib/types.ts +492 -0
- package/mcp-config.example.json +12 -0
- package/openclaw.json +197 -0
- package/package.json +49 -0
- package/portkey_auth_skill.ts +173 -0
- package/portkey_query_skill.ts +147 -0
- package/portkey_tx_skill.ts +143 -0
- package/src/core/account.ts +230 -0
- package/src/core/assets.ts +175 -0
- package/src/core/auth.ts +310 -0
- package/src/core/contract.ts +118 -0
- package/src/core/guardian.ts +141 -0
- package/src/core/keystore.ts +319 -0
- package/src/core/transfer.ts +243 -0
- package/src/mcp/server.ts +756 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Portkey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# Portkey CA Agent Skills
|
|
2
|
+
|
|
3
|
+
> AI Agent toolkit for [Portkey Wallet](https://portkey.finance) on the [aelf](https://aelf.com) blockchain — Email registration, login, transfers, guardian management, and generic contract calls.
|
|
4
|
+
|
|
5
|
+
[中文文档](./README.zh-CN.md)
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
ca-agent-skills/
|
|
11
|
+
├── index.ts # SDK entry — direct import for LangChain / LlamaIndex
|
|
12
|
+
├── src/
|
|
13
|
+
│ ├── core/ # Pure business logic (no I/O side effects)
|
|
14
|
+
│ │ ├── account.ts # checkAccount, getGuardianList, getHolderInfo, getChainInfo
|
|
15
|
+
│ │ ├── auth.ts # sendVerificationCode, verifyCode, registerWallet, recoverWallet
|
|
16
|
+
│ │ ├── assets.ts # getTokenBalance, getTokenList, getNftCollections, getNftItems, getTokenPrice
|
|
17
|
+
│ │ ├── transfer.ts # sameChainTransfer, crossChainTransfer, recoverStuckTransfer
|
|
18
|
+
│ │ ├── guardian.ts # addGuardian, removeGuardian
|
|
19
|
+
│ │ ├── contract.ts # managerForwardCall, callContractViewMethod
|
|
20
|
+
│ │ └── keystore.ts # Encrypted wallet persistence (save, unlock, lock)
|
|
21
|
+
│ └── mcp/
|
|
22
|
+
│ └── server.ts # MCP adapter — for Claude Desktop, Cursor, GPT, etc.
|
|
23
|
+
├── portkey_query_skill.ts # CLI adapter — query commands
|
|
24
|
+
├── portkey_auth_skill.ts # CLI adapter — registration & login commands
|
|
25
|
+
├── portkey_tx_skill.ts # CLI adapter — transfer & guardian commands
|
|
26
|
+
├── cli-helpers.ts # CLI output helpers
|
|
27
|
+
├── bin/
|
|
28
|
+
│ └── setup.ts # One-command setup for AI platforms
|
|
29
|
+
├── lib/
|
|
30
|
+
│ ├── config.ts # Network config, env overrides
|
|
31
|
+
│ ├── types.ts # TypeScript interfaces & enums
|
|
32
|
+
│ ├── aelf-client.ts # aelf-sdk wrapper (wallet, contract, signing)
|
|
33
|
+
│ └── http.ts # HTTP client for Portkey backend API
|
|
34
|
+
└── __tests__/ # Unit / Integration / E2E tests
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Core + Adapters pattern:** Three adapters (MCP, CLI, SDK) call the same Core functions — zero duplicated logic.
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
| # | Category | Capability | MCP Tool | CLI Command | SDK Function |
|
|
42
|
+
|---|----------|-----------|----------|-------------|--------------|
|
|
43
|
+
| 1 | Account | Check email registration | `portkey_check_account` | `check-account` | `checkAccount` |
|
|
44
|
+
| 2 | Account | Get guardian list | `portkey_get_guardian_list` | `guardian-list` | `getGuardianList` |
|
|
45
|
+
| 3 | Account | Get CA holder info | `portkey_get_holder_info` | `holder-info` | `getHolderInfo` |
|
|
46
|
+
| 4 | Account | Get chain info | `portkey_get_chain_info` | `chain-info` | `getChainInfo` |
|
|
47
|
+
| 5 | Auth | Get verifier server | `portkey_get_verifier` | `get-verifier` | `getVerifierServer` |
|
|
48
|
+
| 6 | Auth | Send verification code | `portkey_send_code` | `send-code` | `sendVerificationCode` |
|
|
49
|
+
| 7 | Auth | Verify code | `portkey_verify_code` | `verify-code` | `verifyCode` |
|
|
50
|
+
| 8 | Auth | Register wallet | `portkey_register` | `register` | `registerWallet` |
|
|
51
|
+
| 9 | Auth | Recover wallet (login) | `portkey_recover` | `recover` | `recoverWallet` |
|
|
52
|
+
| 10 | Auth | Check status | `portkey_check_status` | `check-status` | `checkRegisterOrRecoveryStatus` |
|
|
53
|
+
| 11 | Assets | Token balance | `portkey_balance` | `balance` | `getTokenBalance` |
|
|
54
|
+
| 12 | Assets | Token list | `portkey_token_list` | `token-list` | `getTokenList` |
|
|
55
|
+
| 13 | Assets | NFT collections | `portkey_nft_collections` | `nft-collections` | `getNftCollections` |
|
|
56
|
+
| 14 | Assets | NFT items | `portkey_nft_items` | `nft-items` | `getNftItems` |
|
|
57
|
+
| 15 | Assets | Token price | `portkey_token_price` | `token-price` | `getTokenPrice` |
|
|
58
|
+
| 16 | Transfer | Same-chain transfer | `portkey_transfer` | `transfer` | `sameChainTransfer` |
|
|
59
|
+
| 17 | Transfer | Cross-chain transfer | `portkey_cross_chain_transfer` | `cross-chain-transfer` | `crossChainTransfer` |
|
|
60
|
+
| 18 | Transfer | Transaction result | `portkey_tx_result` | `tx-result` | `getTransactionResult` |
|
|
61
|
+
| 19 | Transfer | Recover stuck transfer | `portkey_recover_stuck_transfer` | `recover-stuck-transfer` | `recoverStuckTransfer` |
|
|
62
|
+
| 20 | Guardian | Add guardian | `portkey_add_guardian` | `add-guardian` | `addGuardian` |
|
|
63
|
+
| 21 | Guardian | Remove guardian | `portkey_remove_guardian` | `remove-guardian` | `removeGuardian` |
|
|
64
|
+
| 22 | Contract | ManagerForwardCall | `portkey_forward_call` | `forward-call` | `managerForwardCall` |
|
|
65
|
+
| 23 | Contract | View method call | `portkey_view_call` | `view-call` | `callContractViewMethod` |
|
|
66
|
+
| 24 | Wallet | Create wallet | `portkey_create_wallet` | `create-wallet` | `createWallet` |
|
|
67
|
+
| 25 | Wallet | Save keystore | `portkey_save_keystore` | `save-keystore` | `saveKeystore` |
|
|
68
|
+
| 26 | Wallet | Unlock wallet | `portkey_unlock` | `unlock` | `unlockWallet` |
|
|
69
|
+
| 27 | Wallet | Lock wallet | `portkey_lock` | `lock` | `lockWallet` |
|
|
70
|
+
| 28 | Wallet | Wallet status | `portkey_wallet_status` | `wallet-status` | `getWalletStatus` |
|
|
71
|
+
|
|
72
|
+
## Wallet Persistence (Keystore)
|
|
73
|
+
|
|
74
|
+
Manager private keys are encrypted and stored locally using aelf-sdk's keystore scheme (scrypt + AES-128-CTR).
|
|
75
|
+
|
|
76
|
+
**Storage location:** `~/.portkey/ca/{network}.keystore.json`
|
|
77
|
+
|
|
78
|
+
### First-time setup (after registration/recovery)
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# AI flow: create_wallet → register → check_status → save_keystore(password)
|
|
82
|
+
# The wallet is auto-unlocked after saving.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### New conversation
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# AI calls portkey_wallet_status to check if keystore exists
|
|
89
|
+
# If locked, asks user for password → portkey_unlock(password)
|
|
90
|
+
# Write operations now work automatically
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Manual CLI usage
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Save keystore
|
|
97
|
+
bun run portkey_auth_skill.ts save-keystore \
|
|
98
|
+
--password "your-password" \
|
|
99
|
+
--private-key "hex-key" \
|
|
100
|
+
--mnemonic "word1 word2 ..." \
|
|
101
|
+
--ca-hash "xxx" --ca-address "ELF_xxx_AELF"
|
|
102
|
+
|
|
103
|
+
# Unlock
|
|
104
|
+
bun run portkey_auth_skill.ts unlock --password "your-password"
|
|
105
|
+
|
|
106
|
+
# Check status
|
|
107
|
+
bun run portkey_auth_skill.ts wallet-status
|
|
108
|
+
|
|
109
|
+
# Lock
|
|
110
|
+
bun run portkey_auth_skill.ts lock
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### How it works
|
|
114
|
+
|
|
115
|
+
1. **Save** — encrypts the Manager private key + mnemonic with a user-provided password, writes to `~/.portkey/ca/`
|
|
116
|
+
2. **Unlock** — decrypts the keystore, loads the wallet into memory for the current process
|
|
117
|
+
3. **Lock** — clears the private key from memory
|
|
118
|
+
4. **Write operations** — automatically use the unlocked wallet; falls back to `PORTKEY_PRIVATE_KEY` env var if no keystore is unlocked
|
|
119
|
+
|
|
120
|
+
## Prerequisites
|
|
121
|
+
|
|
122
|
+
- [Bun](https://bun.sh) >= 1.0
|
|
123
|
+
- An aelf wallet private key or an unlocked keystore (for write operations only)
|
|
124
|
+
|
|
125
|
+
## Quick Start
|
|
126
|
+
|
|
127
|
+
### 1. Install
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
bun add @portkey/ca-agent-skills
|
|
131
|
+
|
|
132
|
+
# Or clone locally
|
|
133
|
+
git clone https://github.com/AwakenFinance/ca-agent-skills.git
|
|
134
|
+
cd ca-agent-skills
|
|
135
|
+
bun install
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### 2. Configure
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
cp .env.example .env
|
|
142
|
+
# Edit .env — add your PORTKEY_PRIVATE_KEY (only for write operations)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 3. One-Command Setup
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Claude Desktop
|
|
149
|
+
bun run bin/setup.ts claude
|
|
150
|
+
|
|
151
|
+
# Cursor (project-level)
|
|
152
|
+
bun run bin/setup.ts cursor
|
|
153
|
+
|
|
154
|
+
# Cursor (global)
|
|
155
|
+
bun run bin/setup.ts cursor --global
|
|
156
|
+
|
|
157
|
+
# OpenClaw — output config to stdout
|
|
158
|
+
bun run bin/setup.ts openclaw
|
|
159
|
+
|
|
160
|
+
# OpenClaw — merge into existing config
|
|
161
|
+
bun run bin/setup.ts openclaw --config-path ./my-openclaw.json
|
|
162
|
+
|
|
163
|
+
# Check status (Claude, Cursor, OpenClaw)
|
|
164
|
+
bun run bin/setup.ts list
|
|
165
|
+
|
|
166
|
+
# Remove
|
|
167
|
+
bun run bin/setup.ts uninstall claude
|
|
168
|
+
bun run bin/setup.ts uninstall cursor
|
|
169
|
+
bun run bin/setup.ts uninstall openclaw --config-path ./my-openclaw.json
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Usage
|
|
173
|
+
|
|
174
|
+
### MCP (Claude Desktop / Cursor)
|
|
175
|
+
|
|
176
|
+
Add to your MCP config (`mcp-config.example.json`):
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"mcpServers": {
|
|
181
|
+
"ca-agent-skills": {
|
|
182
|
+
"command": "bun",
|
|
183
|
+
"args": ["run", "/path/to/ca-agent-skills/src/mcp/server.ts"],
|
|
184
|
+
"env": {
|
|
185
|
+
"PORTKEY_PRIVATE_KEY": "your_private_key_here",
|
|
186
|
+
"PORTKEY_NETWORK": "mainnet"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### OpenClaw
|
|
194
|
+
|
|
195
|
+
The `openclaw.json` in the project root defines 13 CLI-based tools for OpenClaw. Use `bun run bin/setup.ts openclaw` to generate or merge the config.
|
|
196
|
+
|
|
197
|
+
### CLI
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
# Check if email is registered
|
|
201
|
+
bun run portkey_query_skill.ts check-account --email user@example.com
|
|
202
|
+
|
|
203
|
+
# Get chain info
|
|
204
|
+
bun run portkey_query_skill.ts chain-info
|
|
205
|
+
|
|
206
|
+
# Create wallet
|
|
207
|
+
bun run portkey_auth_skill.ts create-wallet
|
|
208
|
+
|
|
209
|
+
# Transfer tokens (requires PORTKEY_PRIVATE_KEY env)
|
|
210
|
+
bun run portkey_tx_skill.ts transfer --ca-hash xxx --token-contract xxx --symbol ELF --to xxx --amount 100000000 --chain-id AELF
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### SDK
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { getConfig, checkAccount, createWallet, getTokenBalance } from '@portkey/ca-agent-skills';
|
|
217
|
+
|
|
218
|
+
const config = getConfig({ network: 'mainnet' });
|
|
219
|
+
|
|
220
|
+
// Check account
|
|
221
|
+
const account = await checkAccount(config, { email: 'user@example.com' });
|
|
222
|
+
|
|
223
|
+
// Create wallet
|
|
224
|
+
const wallet = createWallet();
|
|
225
|
+
|
|
226
|
+
// Get balance
|
|
227
|
+
const balance = await getTokenBalance(config, {
|
|
228
|
+
caAddress: 'xxx',
|
|
229
|
+
chainId: 'AELF',
|
|
230
|
+
symbol: 'ELF',
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Network
|
|
235
|
+
|
|
236
|
+
| Network | Chain IDs | API URL |
|
|
237
|
+
|---------|-----------|---------|
|
|
238
|
+
| mainnet (default) | AELF, tDVV | `https://aa-portkey.portkey.finance` |
|
|
239
|
+
| testnet | AELF, tDVW | `https://aa-portkey-test.portkey.finance` |
|
|
240
|
+
|
|
241
|
+
## Environment Variables
|
|
242
|
+
|
|
243
|
+
| Variable | Required | Default | Description |
|
|
244
|
+
|----------|----------|---------|-------------|
|
|
245
|
+
| `PORTKEY_PRIVATE_KEY` | Fallback | — | Manager wallet private key (fallback if keystore not unlocked) |
|
|
246
|
+
| `PORTKEY_NETWORK` | No | `mainnet` | `mainnet` or `testnet` |
|
|
247
|
+
| `PORTKEY_API_URL` | No | Per network | Override API endpoint |
|
|
248
|
+
| `PORTKEY_GRAPHQL_URL` | No | Per network | Override GraphQL endpoint |
|
|
249
|
+
|
|
250
|
+
## Testing
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
bun test # All tests
|
|
254
|
+
bun run test:unit # Unit tests only
|
|
255
|
+
bun run test:integration # Integration (requires network)
|
|
256
|
+
bun run test:e2e # E2E (requires private key)
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Security
|
|
260
|
+
|
|
261
|
+
- Never commit your `.env` file (git-ignored by default)
|
|
262
|
+
- Private keys are only needed for write operations (transfer, guardian management, contract calls)
|
|
263
|
+
- **Keystore encryption**: Manager private keys are encrypted with scrypt (N=8192) + AES-128-CTR via aelf-sdk's keystore module. Files are stored with `0600` permissions.
|
|
264
|
+
- **In-memory lifecycle**: private keys exist in memory only while unlocked; `portkey_lock` clears them immediately
|
|
265
|
+
- When using MCP, the keystore password only exists in the AI conversation context — it is never written to disk
|
|
266
|
+
- `PORTKEY_PRIVATE_KEY` env var is supported as a fallback but keystore is the recommended approach
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
MIT
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# Portkey CA Agent Skills
|
|
2
|
+
|
|
3
|
+
> [Portkey Wallet](https://portkey.finance) 的 AI Agent 工具包,基于 [aelf](https://aelf.com) 区块链 — 支持 Email 注册/登录、转账、Guardian 管理和通用合约调用。
|
|
4
|
+
|
|
5
|
+
[English](./README.md)
|
|
6
|
+
|
|
7
|
+
## 架构
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
ca-agent-skills/
|
|
11
|
+
├── index.ts # SDK 入口 — 供 LangChain / LlamaIndex 直接 import
|
|
12
|
+
├── src/
|
|
13
|
+
│ ├── core/ # 纯业务逻辑(无副作用)
|
|
14
|
+
│ │ ├── account.ts # 账户查询
|
|
15
|
+
│ │ ├── auth.ts # 注册/登录/验证
|
|
16
|
+
│ │ ├── assets.ts # 资产查询(Token、NFT、价格)
|
|
17
|
+
│ │ ├── transfer.ts # 同链/跨链转账、卡单恢复
|
|
18
|
+
│ │ ├── guardian.ts # Guardian 管理
|
|
19
|
+
│ │ ├── contract.ts # 通用合约调用(ManagerForwardCall)
|
|
20
|
+
│ │ └── keystore.ts # 钱包加密持久化(save、unlock、lock)
|
|
21
|
+
│ └── mcp/
|
|
22
|
+
│ └── server.ts # MCP 适配器 — Claude Desktop / Cursor / GPT
|
|
23
|
+
├── portkey_query_skill.ts # CLI — 查询命令
|
|
24
|
+
├── portkey_auth_skill.ts # CLI — 注册/登录命令
|
|
25
|
+
├── portkey_tx_skill.ts # CLI — 交易/Guardian 命令
|
|
26
|
+
├── bin/setup.ts # 一键配置工具
|
|
27
|
+
└── lib/ # 基础设施(config、types、aelf-sdk 封装、HTTP 客户端)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**核心模式:** 三个适配器(MCP / CLI / SDK)调用同一套 Core 函数,零重复逻辑。
|
|
31
|
+
|
|
32
|
+
## 功能清单
|
|
33
|
+
|
|
34
|
+
| # | 分类 | 功能 | MCP Tool | SDK 函数 |
|
|
35
|
+
|---|------|------|----------|----------|
|
|
36
|
+
| 1 | 账户 | 检查 Email 是否注册 | `portkey_check_account` | `checkAccount` |
|
|
37
|
+
| 2 | 账户 | 获取 Guardian 列表 | `portkey_get_guardian_list` | `getGuardianList` |
|
|
38
|
+
| 3 | 账户 | 获取 CA Holder 信息 | `portkey_get_holder_info` | `getHolderInfo` |
|
|
39
|
+
| 4 | 账户 | 获取链信息 | `portkey_get_chain_info` | `getChainInfo` |
|
|
40
|
+
| 5 | 验证 | 获取 Verifier | `portkey_get_verifier` | `getVerifierServer` |
|
|
41
|
+
| 6 | 验证 | 发送验证码 | `portkey_send_code` | `sendVerificationCode` |
|
|
42
|
+
| 7 | 验证 | 校验验证码 | `portkey_verify_code` | `verifyCode` |
|
|
43
|
+
| 8 | 注册 | 注册 CA 钱包 | `portkey_register` | `registerWallet` |
|
|
44
|
+
| 9 | 登录 | 恢复/登录 CA 钱包 | `portkey_recover` | `recoverWallet` |
|
|
45
|
+
| 10 | 状态 | 查询注册/恢复状态 | `portkey_check_status` | `checkRegisterOrRecoveryStatus` |
|
|
46
|
+
| 11 | 资产 | 查询 Token 余额 | `portkey_balance` | `getTokenBalance` |
|
|
47
|
+
| 12 | 资产 | Token 列表 | `portkey_token_list` | `getTokenList` |
|
|
48
|
+
| 13 | 资产 | NFT 集合 | `portkey_nft_collections` | `getNftCollections` |
|
|
49
|
+
| 14 | 资产 | NFT 项目 | `portkey_nft_items` | `getNftItems` |
|
|
50
|
+
| 15 | 资产 | Token 价格 | `portkey_token_price` | `getTokenPrice` |
|
|
51
|
+
| 16 | 转账 | 同链转账 | `portkey_transfer` | `sameChainTransfer` |
|
|
52
|
+
| 17 | 转账 | 跨链转账 | `portkey_cross_chain_transfer` | `crossChainTransfer` |
|
|
53
|
+
| 18 | 转账 | 查询交易结果 | `portkey_tx_result` | `getTransactionResult` |
|
|
54
|
+
| 19 | 转账 | 跨链卡单恢复 | `portkey_recover_stuck_transfer` | `recoverStuckTransfer` |
|
|
55
|
+
| 20 | Guardian | 添加 Guardian | `portkey_add_guardian` | `addGuardian` |
|
|
56
|
+
| 21 | Guardian | 移除 Guardian | `portkey_remove_guardian` | `removeGuardian` |
|
|
57
|
+
| 22 | 合约 | 通用 ManagerForwardCall | `portkey_forward_call` | `managerForwardCall` |
|
|
58
|
+
| 23 | 合约 | 只读合约调用 | `portkey_view_call` | `callContractViewMethod` |
|
|
59
|
+
| 24 | 钱包 | 创建钱包 | `portkey_create_wallet` | `createWallet` |
|
|
60
|
+
| 25 | 钱包 | 保存 Keystore | `portkey_save_keystore` | `saveKeystore` |
|
|
61
|
+
| 26 | 钱包 | 解锁钱包 | `portkey_unlock` | `unlockWallet` |
|
|
62
|
+
| 27 | 钱包 | 锁定钱包 | `portkey_lock` | `lockWallet` |
|
|
63
|
+
| 28 | 钱包 | 钱包状态 | `portkey_wallet_status` | `getWalletStatus` |
|
|
64
|
+
|
|
65
|
+
## 钱包持久化(Keystore)
|
|
66
|
+
|
|
67
|
+
Manager 私钥使用 aelf-sdk 内置的 keystore 方案(scrypt + AES-128-CTR)加密存储到本地。
|
|
68
|
+
|
|
69
|
+
**存储路径:** `~/.portkey/ca/{network}.keystore.json`
|
|
70
|
+
|
|
71
|
+
### 首次设置(注册/恢复成功后)
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# AI 流程:create_wallet → register → check_status → save_keystore(密码)
|
|
75
|
+
# 保存后自动解锁,当前对话可直接使用。
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 新对话
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# AI 调用 portkey_wallet_status 检查是否存在 keystore
|
|
82
|
+
# 如果已锁定,向用户索要密码 → portkey_unlock(密码)
|
|
83
|
+
# 之后写操作自动生效
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### CLI 手动操作
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# 保存 keystore
|
|
90
|
+
bun run portkey_auth_skill.ts save-keystore \
|
|
91
|
+
--password "你的密码" \
|
|
92
|
+
--private-key "hex私钥" \
|
|
93
|
+
--mnemonic "助记词" \
|
|
94
|
+
--ca-hash "xxx" --ca-address "ELF_xxx_AELF"
|
|
95
|
+
|
|
96
|
+
# 解锁
|
|
97
|
+
bun run portkey_auth_skill.ts unlock --password "你的密码"
|
|
98
|
+
|
|
99
|
+
# 查看状态
|
|
100
|
+
bun run portkey_auth_skill.ts wallet-status
|
|
101
|
+
|
|
102
|
+
# 锁定
|
|
103
|
+
bun run portkey_auth_skill.ts lock
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 工作原理
|
|
107
|
+
|
|
108
|
+
1. **Save** — 用用户密码加密 Manager 私钥 + 助记词,写入 `~/.portkey/ca/`
|
|
109
|
+
2. **Unlock** — 解密 keystore,将钱包加载到进程内存
|
|
110
|
+
3. **Lock** — 清除内存中的私钥
|
|
111
|
+
4. **写操作** — 优先使用已解锁的钱包;如果没有解锁的 keystore,fallback 到 `PORTKEY_PRIVATE_KEY` 环境变量
|
|
112
|
+
|
|
113
|
+
## 前置条件
|
|
114
|
+
|
|
115
|
+
- [Bun](https://bun.sh) >= 1.0
|
|
116
|
+
- aelf 钱包私钥或已解锁的 keystore(仅写操作需要)
|
|
117
|
+
|
|
118
|
+
## 快速开始
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# 安装
|
|
122
|
+
bun add @portkey/ca-agent-skills
|
|
123
|
+
|
|
124
|
+
# 配置
|
|
125
|
+
cp .env.example .env
|
|
126
|
+
# 编辑 .env,添加 PORTKEY_PRIVATE_KEY(仅写操作需要)
|
|
127
|
+
|
|
128
|
+
# 一键配置到 AI 平台
|
|
129
|
+
bun run bin/setup.ts claude # Claude Desktop
|
|
130
|
+
bun run bin/setup.ts cursor # Cursor(项目级)
|
|
131
|
+
bun run bin/setup.ts cursor --global # Cursor(全局)
|
|
132
|
+
bun run bin/setup.ts openclaw # OpenClaw — 输出配置到 stdout
|
|
133
|
+
bun run bin/setup.ts openclaw --config-path ./my-openclaw.json # 合并到已有配置
|
|
134
|
+
|
|
135
|
+
# 查看配置状态(Claude / Cursor / OpenClaw)
|
|
136
|
+
bun run bin/setup.ts list
|
|
137
|
+
|
|
138
|
+
# 卸载
|
|
139
|
+
bun run bin/setup.ts uninstall claude
|
|
140
|
+
bun run bin/setup.ts uninstall cursor
|
|
141
|
+
bun run bin/setup.ts uninstall openclaw --config-path ./my-openclaw.json
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## SDK 使用示例
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { getConfig, checkAccount, createWallet, getTokenBalance } from '@portkey/ca-agent-skills';
|
|
148
|
+
|
|
149
|
+
const config = getConfig({ network: 'mainnet' });
|
|
150
|
+
|
|
151
|
+
// 检查账户
|
|
152
|
+
const account = await checkAccount(config, { email: 'user@example.com' });
|
|
153
|
+
console.log(account.isRegistered, account.originChainId);
|
|
154
|
+
|
|
155
|
+
// 创建钱包
|
|
156
|
+
const wallet = createWallet();
|
|
157
|
+
console.log(wallet.address, wallet.privateKey);
|
|
158
|
+
|
|
159
|
+
// 查询余额
|
|
160
|
+
const balance = await getTokenBalance(config, {
|
|
161
|
+
caAddress: 'xxx',
|
|
162
|
+
chainId: 'AELF',
|
|
163
|
+
symbol: 'ELF',
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## 注册流程示例(Email)
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import {
|
|
171
|
+
getConfig, createWallet, getVerifierServer,
|
|
172
|
+
sendVerificationCode, verifyCode, registerWallet,
|
|
173
|
+
checkRegisterOrRecoveryStatus, OperationType,
|
|
174
|
+
} from '@portkey/ca-agent-skills';
|
|
175
|
+
|
|
176
|
+
const config = getConfig({ network: 'mainnet' });
|
|
177
|
+
|
|
178
|
+
// 1. 获取 Verifier
|
|
179
|
+
const verifier = await getVerifierServer(config);
|
|
180
|
+
|
|
181
|
+
// 2. 发送验证码(注意:mainnet 已废弃 Register(0),统一使用 CommunityRecovery(1))
|
|
182
|
+
const { verifierSessionId } = await sendVerificationCode(config, {
|
|
183
|
+
email: 'user@example.com',
|
|
184
|
+
verifierId: verifier.id,
|
|
185
|
+
chainId: 'AELF',
|
|
186
|
+
operationType: OperationType.CreateCAHolder, // 注册用 1,登录用 SocialRecovery(2)
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// 3. 用户输入验证码后校验
|
|
190
|
+
const { signature, verificationDoc } = await verifyCode(config, {
|
|
191
|
+
email: 'user@example.com',
|
|
192
|
+
verificationCode: '123456',
|
|
193
|
+
verifierId: verifier.id,
|
|
194
|
+
verifierSessionId,
|
|
195
|
+
chainId: 'AELF',
|
|
196
|
+
operationType: OperationType.CreateCAHolder,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// 4. 创建 Manager 钱包
|
|
200
|
+
const wallet = createWallet();
|
|
201
|
+
|
|
202
|
+
// 5. 提交注册
|
|
203
|
+
const { sessionId } = await registerWallet(config, {
|
|
204
|
+
email: 'user@example.com',
|
|
205
|
+
manager: wallet.address,
|
|
206
|
+
verifierId: verifier.id,
|
|
207
|
+
verificationDoc,
|
|
208
|
+
signature,
|
|
209
|
+
chainId: 'AELF',
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// 6. 轮询状态
|
|
213
|
+
let status;
|
|
214
|
+
do {
|
|
215
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
216
|
+
status = await checkRegisterOrRecoveryStatus(config, { sessionId, type: 'register' });
|
|
217
|
+
} while (status.status === 'pending');
|
|
218
|
+
|
|
219
|
+
console.log('CA Address:', status.caAddress);
|
|
220
|
+
console.log('CA Hash:', status.caHash);
|
|
221
|
+
|
|
222
|
+
// 7. 保存 keystore(加密持久化 Manager 私钥)
|
|
223
|
+
import { saveKeystore } from '@portkey/ca-agent-skills';
|
|
224
|
+
saveKeystore({
|
|
225
|
+
password: 'user-chosen-password',
|
|
226
|
+
privateKey: wallet.privateKey,
|
|
227
|
+
mnemonic: wallet.mnemonic,
|
|
228
|
+
caHash: status.caHash!,
|
|
229
|
+
caAddress: status.caAddress!,
|
|
230
|
+
originChainId: 'AELF',
|
|
231
|
+
network: 'mainnet',
|
|
232
|
+
});
|
|
233
|
+
// 钱包已自动解锁,后续写操作无需再设置 PORTKEY_PRIVATE_KEY
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## 环境变量
|
|
237
|
+
|
|
238
|
+
| 变量 | 必需 | 默认值 | 说明 |
|
|
239
|
+
|------|------|--------|------|
|
|
240
|
+
| `PORTKEY_PRIVATE_KEY` | Fallback | — | Manager 钱包私钥(keystore 未解锁时的 fallback) |
|
|
241
|
+
| `PORTKEY_NETWORK` | 否 | `mainnet` | `mainnet` 或 `testnet` |
|
|
242
|
+
| `PORTKEY_API_URL` | 否 | 按网络 | 覆盖 API 地址 |
|
|
243
|
+
| `PORTKEY_GRAPHQL_URL` | 否 | 按网络 | 覆盖 GraphQL 地址 |
|
|
244
|
+
|
|
245
|
+
## 测试
|
|
246
|
+
|
|
247
|
+
```bash
|
|
248
|
+
bun test # 全部测试
|
|
249
|
+
bun run test:unit # 单元测试
|
|
250
|
+
bun run test:integration # 集成测试(需要网络)
|
|
251
|
+
bun run test:e2e # E2E 测试(需要私钥)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## 安全
|
|
255
|
+
|
|
256
|
+
- `.env` 文件已默认 git-ignore,不要提交
|
|
257
|
+
- 私钥仅写操作需要(转账、Guardian 管理、合约调用)
|
|
258
|
+
- **Keystore 加密**:Manager 私钥使用 scrypt(N=8192)+ AES-128-CTR 加密,文件权限 `0600`
|
|
259
|
+
- **内存生命周期**:私钥仅在 unlock 期间存在于内存,`portkey_lock` 立即清除
|
|
260
|
+
- MCP 模式下,keystore 密码仅存在于 AI 对话上下文,不会写入磁盘
|
|
261
|
+
- `PORTKEY_PRIVATE_KEY` 环境变量仍然支持作为 fallback,但推荐使用 keystore
|
|
262
|
+
|
|
263
|
+
## License
|
|
264
|
+
|
|
265
|
+
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getPlatformPaths,
|
|
3
|
+
readJsonFile,
|
|
4
|
+
writeJsonFile,
|
|
5
|
+
mergeMcpConfig,
|
|
6
|
+
generateMcpEntry,
|
|
7
|
+
SERVER_NAME,
|
|
8
|
+
} from './utils.js';
|
|
9
|
+
|
|
10
|
+
export interface ClaudeSetupOptions {
|
|
11
|
+
configPath?: string;
|
|
12
|
+
serverPath?: string;
|
|
13
|
+
force?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function setupClaude(options: ClaudeSetupOptions = {}): void {
|
|
17
|
+
const paths = getPlatformPaths();
|
|
18
|
+
const configPath = options.configPath || paths.claude;
|
|
19
|
+
|
|
20
|
+
const existing = readJsonFile(configPath);
|
|
21
|
+
const entry = generateMcpEntry(options.serverPath);
|
|
22
|
+
const { config, action } = mergeMcpConfig(existing, SERVER_NAME, entry, options.force);
|
|
23
|
+
|
|
24
|
+
if (action === 'skipped') {
|
|
25
|
+
console.log(`[SKIP] "${SERVER_NAME}" already exists in ${configPath}`);
|
|
26
|
+
console.log(' Use --force to overwrite.');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
writeJsonFile(configPath, config);
|
|
31
|
+
console.log(`[${action.toUpperCase()}] ${SERVER_NAME} in ${configPath}`);
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log('Next steps:');
|
|
34
|
+
console.log(' 1. Edit the config to replace <YOUR_PRIVATE_KEY> with your actual key');
|
|
35
|
+
console.log(' 2. Restart Claude Desktop');
|
|
36
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getPlatformPaths,
|
|
3
|
+
readJsonFile,
|
|
4
|
+
writeJsonFile,
|
|
5
|
+
mergeMcpConfig,
|
|
6
|
+
generateMcpEntry,
|
|
7
|
+
SERVER_NAME,
|
|
8
|
+
} from './utils.js';
|
|
9
|
+
|
|
10
|
+
export interface CursorSetupOptions {
|
|
11
|
+
configPath?: string;
|
|
12
|
+
serverPath?: string;
|
|
13
|
+
force?: boolean;
|
|
14
|
+
global?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function setupCursor(options: CursorSetupOptions = {}): void {
|
|
18
|
+
const paths = getPlatformPaths();
|
|
19
|
+
const configPath =
|
|
20
|
+
options.configPath || (options.global ? paths.cursorGlobal : paths.cursorProject);
|
|
21
|
+
|
|
22
|
+
const existing = readJsonFile(configPath);
|
|
23
|
+
const entry = generateMcpEntry(options.serverPath);
|
|
24
|
+
const { config, action } = mergeMcpConfig(existing, SERVER_NAME, entry, options.force);
|
|
25
|
+
|
|
26
|
+
if (action === 'skipped') {
|
|
27
|
+
console.log(`[SKIP] "${SERVER_NAME}" already exists in ${configPath}`);
|
|
28
|
+
console.log(' Use --force to overwrite.');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
writeJsonFile(configPath, config);
|
|
33
|
+
const scope = options.global ? 'global' : 'project-level';
|
|
34
|
+
console.log(`[${action.toUpperCase()}] ${SERVER_NAME} (${scope}) in ${configPath}`);
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log('Next steps:');
|
|
37
|
+
console.log(' 1. Edit the config to replace <YOUR_PRIVATE_KEY> with your actual key');
|
|
38
|
+
console.log(' 2. Restart Cursor or reload the MCP servers');
|
|
39
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import { getPackageRoot, readJsonFile, writeJsonFile } from './utils.js';
|
|
4
|
+
|
|
5
|
+
export interface OpenClawSetupOptions {
|
|
6
|
+
configPath?: string;
|
|
7
|
+
cwd?: string;
|
|
8
|
+
force?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function setupOpenClaw(options: OpenClawSetupOptions = {}): void {
|
|
12
|
+
const packageRoot = getPackageRoot();
|
|
13
|
+
const openclawSource = path.join(packageRoot, 'openclaw.json');
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(openclawSource)) {
|
|
16
|
+
console.log('[ERROR] openclaw.json not found at', openclawSource);
|
|
17
|
+
console.log(' Please create it first.');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const sourceConfig = readJsonFile(openclawSource);
|
|
22
|
+
|
|
23
|
+
// Replace cwd placeholders if needed
|
|
24
|
+
const cwd = options.cwd || packageRoot;
|
|
25
|
+
|
|
26
|
+
if (options.configPath) {
|
|
27
|
+
// Merge into existing config
|
|
28
|
+
const existing = readJsonFile(options.configPath);
|
|
29
|
+
const merged = {
|
|
30
|
+
...existing,
|
|
31
|
+
tools: [...((existing.tools as unknown[]) || []), ...((sourceConfig.tools as unknown[]) || [])],
|
|
32
|
+
};
|
|
33
|
+
writeJsonFile(options.configPath, merged);
|
|
34
|
+
console.log(`[MERGED] OpenClaw tools into ${options.configPath}`);
|
|
35
|
+
} else {
|
|
36
|
+
// Output to stdout for piping
|
|
37
|
+
console.log(JSON.stringify({ ...sourceConfig, cwd }, null, 2));
|
|
38
|
+
}
|
|
39
|
+
}
|