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
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ jobs:

- run: bun install
- run: bun run deps:check
- name: Typecheck (observe)
continue-on-error: true
run: bun run typecheck
- run: bun run test:coverage:ci
- run: bun run build:openclaw:check

Expand Down
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ Reference file: [`mcp-config.example.json`](./mcp-config.example.json)
"command": "bun",
"args": ["run", "/ABSOLUTE/PATH/TO/src/mcp/server.ts"],
"env": {
"AELF_PRIVATE_KEY": "your_private_key_here"
"AELF_PRIVATE_KEY": "optional_env_fallback_private_key",
"PORTKEY_WALLET_PASSWORD": "optional_wallet_password",
"PORTKEY_CA_KEYSTORE_PASSWORD": "optional_keystore_password"
}
}
}
Expand All @@ -108,8 +110,13 @@ Copy and edit:
cp .env.example .env
```

- `AELF_PRIVATE_KEY`: required for write operations
- `AELF_PRIVATE_KEY` is read from environment only in MCP mode (no private key tool input)
- `AELF_PRIVATE_KEY`: optional env fallback for write operations (highest env priority)
- `PORTKEY_PRIVATE_KEY`: optional secondary env fallback for shared-skill compatibility
- Write tools (`aelf_send_contract_transaction`, `aelf_estimate_transaction_fee`) resolve signer as `explicit -> context -> env`
- `PORTKEY_WALLET_PASSWORD`: optional password cache for EOA wallet context
- `PORTKEY_CA_KEYSTORE_PASSWORD`: optional password cache for CA keystore context
- `PORTKEY_SKILL_WALLET_CONTEXT_PATH`: optional override for active context path (`~/.portkey/skill-wallet/context.v1.json`)
- `signerMode=daemon` is reserved and currently returns `SIGNER_DAEMON_NOT_IMPLEMENTED`
- `AELF_NODE_AELF_RPC_URL`: optional override for AELF node
- `AELF_NODE_TDVV_RPC_URL`: optional override for tDVV node
- `AELF_NODE_REGISTRY_PATH`: optional custom registry path
Expand Down Expand Up @@ -153,6 +160,7 @@ bun run test:coverage:ci
## Security

- Never put `AELF_PRIVATE_KEY` in prompts or channel outputs.
- Active wallet context must not contain plaintext private keys.
- Use environment variables for all secrets.

## License
Expand Down
14 changes: 11 additions & 3 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ aelf-node-setup claude
"command": "bun",
"args": ["run", "/ABSOLUTE/PATH/TO/src/mcp/server.ts"],
"env": {
"AELF_PRIVATE_KEY": "your_private_key_here"
"AELF_PRIVATE_KEY": "可选_env_回退私钥",
"PORTKEY_WALLET_PASSWORD": "可选钱包密码",
"PORTKEY_CA_KEYSTORE_PASSWORD": "可选keystore密码"
}
}
}
Expand All @@ -108,8 +110,13 @@ aelf-node-setup claude
cp .env.example .env
```

- `AELF_PRIVATE_KEY`:写操作必填
- MCP 模式仅从环境变量读取 `AELF_PRIVATE_KEY`(不接受 tool 入参传私钥)
- `AELF_PRIVATE_KEY`:写操作的 env 回退私钥(可选,env 优先级最高)
- `PORTKEY_PRIVATE_KEY`:共享 skill 兼容的次级 env 回退私钥(可选)
- 写操作工具(`aelf_send_contract_transaction`、`aelf_estimate_transaction_fee`)按 `explicit -> context -> env` 解析 signer
- `PORTKEY_WALLET_PASSWORD`:EOA wallet context 的密码缓存(可选)
- `PORTKEY_CA_KEYSTORE_PASSWORD`:CA keystore context 的密码缓存(可选)
- `PORTKEY_SKILL_WALLET_CONTEXT_PATH`:active context 路径覆盖(默认 `~/.portkey/skill-wallet/context.v1.json`)
- `signerMode=daemon` 仅预埋接口,本轮返回 `SIGNER_DAEMON_NOT_IMPLEMENTED`
- `AELF_NODE_AELF_RPC_URL`:可选,覆盖 AELF 节点
- `AELF_NODE_TDVV_RPC_URL`:可选,覆盖 tDVV 节点
- `AELF_NODE_REGISTRY_PATH`:可选,自定义节点注册表路径
Expand Down Expand Up @@ -153,6 +160,7 @@ bun run test:coverage:ci
## 安全

- 不要在对话输出中暴露 `AELF_PRIVATE_KEY`。
- Active wallet context 不存明文私钥。
- 所有密钥均通过环境变量管理。

## License
Expand Down
4 changes: 4 additions & 0 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,25 @@ description: "AElf node querying and contract execution skill for agents."
- Chain reads: status, block, transaction result, metadata
- Contract operations: view call and transaction sending
- Node registry import/list with REST-first and SDK fallback strategy
- Shared signer resolution for write operations: `explicit -> context -> env`
- Supports SDK, CLI, MCP, and OpenClaw integration from one codebase.

## Safe usage rules
- Never print private keys, mnemonics, or tokens in channel outputs.
- For write operations, require explicit user confirmation and validate parameters before sending transactions.
- Prefer `simulate` or read-only queries first when available.
- Active wallet context contains identity metadata only; never persist plaintext private keys.

## Command recipes
- Start MCP server: `bun run mcp`
- Run CLI entry: `bun run cli`
- Generate OpenClaw config: `bun run build:openclaw`
- Verify OpenClaw config: `bun run build:openclaw:check`
- Run CI coverage gate: `bun run test:coverage:ci`
- For write calls, pass optional `signerContext` with `signerMode=auto`.

## Limits / Non-goals
- This skill focuses on domain operations and adapters; it is not a full wallet custody system.
- Do not hardcode environment secrets in source code or docs.
- Avoid bypassing validation for external service calls.
- `signerMode=daemon` is reserved and returns `SIGNER_DAEMON_NOT_IMPLEMENTED` in this release.
8 changes: 6 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ export { importNode, listNodes } from './src/core/node-registry.js';

export { resolveNode, listAvailableNodes } from './lib/node-router.js';
export { clearSdkCaches, clearSdkCacheForRpc } from './lib/sdk-client.js';
export { resolvePrivateKeyContext, SignerContextError } from './lib/signer-context.js';
export {
readWalletContext,
writeWalletContext,
getActiveWalletProfile,
setActiveWalletProfile,
} from './lib/wallet-context.js';
export type {
SkillResponse,
SkillError,
Expand All @@ -27,4 +34,13 @@ export type {
EstimateTransactionFeeInput,
ImportNodeInput,
NodeProfile,
SignerMode,
SignerProvider,
SignerContextInput,
} from './lib/types.js';
export type {
WalletType,
WalletSource,
ActiveWalletProfile,
WalletContextFile,
} from './lib/wallet-context.js';
19 changes: 19 additions & 0 deletions lib/aelf-sdk.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
declare module 'aelf-sdk' {
export interface AelfWallet {
address: string;
privateKey: string;
mnemonic?: string;
[key: string]: unknown;
}

Expand Down Expand Up @@ -36,6 +38,8 @@ declare module 'aelf-sdk' {
export interface AelfWalletApi {
createNewWallet(): AelfWallet;
getWalletByPrivateKey(privateKey: string): AelfWallet;
AESEncrypt(privateKey: string, password: string): string;
AESDecrypt(encrypted: string, password: string): string;
}

export interface AelfStaticApi {
Expand All @@ -53,3 +57,18 @@ declare module 'aelf-sdk' {

export default AElf;
}

declare module "aelf-sdk/src/util/keyStore.js" {
export function getKeystore(
account: { privateKey: string; mnemonic?: string; address?: string; nickName?: string },
password: string,
option?: Record<string, unknown>,
): unknown;
export function unlockKeystore(keystore: unknown, password: string): {
privateKey: string;
mnemonic?: string;
address?: string;
nickName?: string;
};
export function checkPassword(keystore: unknown, password: string): boolean;
}
11 changes: 9 additions & 2 deletions lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,17 @@ export function normalizeError(input: unknown, fallbackCode = 'UNKNOWN_ERROR'):
}

if (input instanceof Error) {
const maybeCode =
typeof (input as { code?: unknown }).code === 'string'
? String((input as { code?: unknown }).code)
: '';
const maybeDetails = (input as { details?: unknown }).details;
const maybeRaw = (input as { raw?: unknown }).raw;
return {
code: fallbackCode,
code: maybeCode || fallbackCode,
message: input.message,
raw: { name: input.name, stack: input.stack },
details: maybeDetails !== undefined ? stringifyRaw(maybeDetails) : '',
raw: maybeRaw !== undefined ? maybeRaw : { name: input.name, stack: input.stack },
};
}

Expand Down
Loading