Skip to content

Portkey-Wallet/aelf-signer

Repository files navigation

@portkey/aelf-signer

Unified signer interface for the aelf blockchain — supports both EOA (direct signing) and CA (Portkey Contract Account via ManagerForwardCall).

中文文档


Why

DApp skills on aelf (Awaken DEX, eForest NFT, etc.) currently only work with EOA wallets. Portkey CA wallet users cannot use these skills because CA transactions require wrapping every contract call in ManagerForwardCall.

@portkey/aelf-signer solves this by providing a common AelfSigner interface that DApp skills can accept instead of raw wallet: any. The signer handles signing details internally — EOA signs directly, CA wraps in ManagerForwardCall — making the wallet type completely transparent to the DApp skill.

Install

bun add @portkey/aelf-signer
# or
npm install @portkey/aelf-signer

Quick Start

Auto-detect from Environment

import { createSignerFromEnv, callViewMethod } from '@portkey/aelf-signer';

// Automatically creates EoaSigner or CaSigner based on env vars
const signer = createSignerFromEnv();

console.log(`Identity: ${signer.address}`);       // EOA address or CA address
console.log(`Signing key: ${signer.keyAddress}`);  // Same for EOA, Manager address for CA
console.log(`Is CA: ${signer.isCa}`);

// Send a contract call (transparent: EOA direct or CA via ManagerForwardCall)
const result = await signer.sendContractCall(
  'https://aelf-public-node.aelf.io',
  tokenContractAddress,
  'Transfer',
  { to: recipient, symbol: 'ELF', amount: '100000000', memo: '' },
);
console.log(`TX: ${result.transactionId}`);

// View calls don't need a signer
const balance = await callViewMethod(rpcUrl, tokenContract, 'GetBalance', {
  symbol: 'ELF',
  owner: signer.address,
});

EOA Mode

import { createEoaSigner } from '@portkey/aelf-signer';

const signer = createEoaSigner(process.env.AELF_PRIVATE_KEY!);
// signer.address === signer.keyAddress === wallet address

CA Mode

import { createCaSigner } from '@portkey/aelf-signer';

const signer = createCaSigner({
  managerPrivateKey: process.env.PORTKEY_PRIVATE_KEY!,
  caHash: process.env.PORTKEY_CA_HASH!,
  caAddress: process.env.PORTKEY_CA_ADDRESS!,
  // Optional: provide directly to avoid API lookup
  caContractAddress: process.env.PORTKEY_CA_CONTRACT_ADDRESS,
  // Optional: defaults to mainnet
  portkeyApiUrl: 'https://aa-portkey.portkey.finance',
});

// signer.address = CA address (on-chain identity)
// signer.keyAddress = Manager address (signing key)

Environment Variables

EOA Mode

# Set one of:
AELF_PRIVATE_KEY=<64-char hex private key>
# or
PORTKEY_PRIVATE_KEY=<64-char hex private key>

CA Mode

# All three required:
PORTKEY_PRIVATE_KEY=<manager private key hex>
PORTKEY_CA_HASH=<CA hash>
PORTKEY_CA_ADDRESS=<CA address>

# Optional:
PORTKEY_CA_CONTRACT_ADDRESS=<CA contract address on target chain>
PORTKEY_API_URL=<Portkey API URL, defaults to mainnet>

Detection priority:

  1. If PORTKEY_CA_HASH + PORTKEY_CA_ADDRESS + PORTKEY_PRIVATE_KEY all set -> CA mode
  2. If AELF_PRIVATE_KEY set -> EOA mode
  3. If only PORTKEY_PRIVATE_KEY set -> EOA mode (fallback)

API Reference

AelfSigner Interface

interface AelfSigner {
  readonly address: string;      // Identity address
  readonly keyAddress: string;   // Signing key address
  readonly isCa: boolean;        // Whether CA mode

  sendContractCall(
    rpcUrl: string,
    contractAddress: string,
    methodName: string,
    params: Record<string, unknown>,
  ): Promise<SendResult>;

  signMessage(message: string): string;
}
  • address: The identity on chain. For EOA: wallet address. For CA: CA address. Use for owner, from fields.
  • keyAddress: The address of the actual signing key. For EOA: same as address. For CA: Manager address. Use for API auth.
  • sendContractCall(): Send a state-changing contract call. EOA signs directly; CA wraps in ManagerForwardCall with protobuf encoding.
  • signMessage(): SHA256-hash and sign a message. Returns hex signature.

Factory Functions

Function Description
createSignerFromEnv() Auto-detect signer type from env vars
createEoaSigner(privateKey) Create EOA signer
createCaSigner(config) Create CA signer
isEoaSigner(signer) Type guard for EoaSigner
isCaSigner(signer) Type guard for CaSigner
getSigningAddress(signer) Get keyAddress

Utility Functions

Function Description
callViewMethod(rpcUrl, addr, method, params?) Read-only contract call (no signer needed)
pollTxResult(rpcUrl, txId) Poll for transaction result
getAelfInstance(rpcUrl) Get cached AElf SDK instance
fetchChainInfo(apiUrl?) Fetch chain info from Portkey API
clearCaches() Clear all caches (for testing)

For DApp Skill Developers

Before (wallet-only)

// awaken-agent-skills/src/core/trade.ts (before)
export async function executeSwap(config, wallet: any, params) {
  const account = wallet.address;  // Only works with EOA
  await approveToken(config, wallet, ...);
  await callSendMethod(rpcUrl, contract, 'Swap', wallet, args);
}

After (signer-compatible)

// awaken-agent-skills/src/core/trade.ts (after)
import type { AelfSigner } from '@portkey/aelf-signer';

export async function executeSwap(config, signer: AelfSigner, params) {
  const account = signer.address;  // caAddress for CA, walletAddress for EOA
  await signer.sendContractCall(rpcUrl, tokenContract, 'Approve', { ... });
  await signer.sendContractCall(rpcUrl, swapContract, 'Swap', { ... });
}

MCP Server Integration

// awaken-agent-skills/src/mcp/server.ts
import { createSignerFromEnv } from '@portkey/aelf-signer';

// In tool handler:
const signer = createSignerFromEnv(); // Auto-detects EOA or CA
const result = await executeSwap(config, signer, params);

Architecture

@portkey/aelf-signer
├── src/
│   ├── types.ts          # AelfSigner interface, result types
│   ├── utils.ts          # AElf SDK wrappers, TX polling, view calls
│   ├── eoa-signer.ts     # EoaSigner: direct private key signing
│   ├── ca-signer.ts      # CaSigner: ManagerForwardCall + protobuf encoding
│   ├── factory.ts        # createSignerFromEnv, type guards
│   └── index.ts          # Re-exports
└── __tests__/
    └── unit/
        └── signer.test.ts

How CaSigner Works

When CaSigner.sendContractCall(rpcUrl, contractAddr, method, params) is called:

  1. Resolve CA contract address — from config or auto-discovered via Portkey API
  2. Fetch protobuf descriptorsgetContractFileDescriptorSet for the target contract
  3. Find input type — locate the method's input message type in the protobuf schema
  4. Encode params — apply aelf-sdk transforms + protobuf encoding to bytes
  5. Call ManagerForwardCall — on the CA contract with { caHash, contractAddress, methodName, args }
  6. Poll TX result — wait for mining, return result

License

MIT

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors