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
29 changes: 15 additions & 14 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## OVERVIEW

TypeScript library providing a unified interface for key generation, address derivation, wallet creation, and message signing across 8 blockchains (Bitcoin, Ethereum, Base, Solana, Aptos, Cardano, SUI, TRON). Built entirely on the @noble/@scure audited crypto ecosystem.
TypeScript library providing a unified interface for key generation, address derivation, wallet creation, and message signing across 9 blockchains (Bitcoin, Litecoin, Ethereum, Base, Solana, Aptos, Cardano, SUI, TRON). Built entirely on the @noble/@scure audited crypto ecosystem.

## STRUCTURE

Expand Down Expand Up @@ -30,19 +30,20 @@ keys/

## WHERE TO LOOK

| Task | Location | Notes |
| ------------------ | --------------------------------------------------------------------------------- | ---------------------------------------------------------- |
| Add new blockchain | `src/blockchains/` + `src/_blockchains.ts` | Extend the appropriate base class, register in lazy loader |
| Add address format | `src/utils/address.ts` | Shared across chains (legacy, segwit, hex, base58) |
| Add EVM chain | `src/utils/evm.ts` → `AbstractEVMBlockchain` | Minimal subclass with `name` and `bip44` |
| Fix signing | `src/utils/signing.ts` (generic) or `evm.ts`/`ed25519-chains.ts` (chain-specific) | EVM uses preamble hash, ed25519 signs raw |
| Change public API | `src/index.ts` | Re-exports only, never add logic here |
| Change agent tools | `src/tool-operations.ts`, `src/mcp.ts`, `packages/pi/extensions/keys.ts` | Executors are shared; schemas stay aligned |
| Add BIP/derivation | `src/utils/bip32/`, `bip39/`, `bip44/`, `slip10/` | Subdirs with index.ts |
| Mnemonic to wallet | `src/blockchain.ts` → `deriveHDWallet` + `src/utils/hd.ts` | Bitcoin and Sui override it, Cardano throws (CIP-1852) |
| Write tests | `test/` mirroring `src/` path | Use fixtures from `test/fixtures.ts` |
| Integration test | `test-integration/` | Separate pnpm package, manual execution |
| Run demos | `playground/*.ts` | Execute via `pnpm playground <file>` |
| Task | Location | Notes |
| ------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Add new blockchain | `src/blockchains/` + `src/_blockchains.ts` | Extend the appropriate base class, register in lazy loader |
| Add address format | `src/utils/address.ts` | Shared across chains (legacy, segwit, hex, base58) |
| Add Bitcoin family | `src/utils/bitcoin.ts` → `AbstractBitcoinBlockchain` | Reuse transparent address and HD behavior; keep chain signing rules explicit |
| Add EVM chain | `src/utils/evm.ts` → `AbstractEVMBlockchain` | Minimal subclass with `name` and `bip44` |
| Fix signing | `src/utils/signing.ts` (generic) or `evm.ts`/`ed25519-chains.ts` (chain-specific) | EVM uses preamble hash, ed25519 signs raw |
| Change public API | `src/index.ts` | Re-exports only, never add logic here |
| Change agent tools | `src/tool-operations.ts`, `src/mcp.ts`, `packages/pi/extensions/keys.ts` | Executors are shared; schemas stay aligned |
| Add BIP/derivation | `src/utils/bip32/`, `bip39/`, `bip44/`, `slip10/` | Subdirs with index.ts |
| Mnemonic to wallet | `src/blockchain.ts` → `deriveHDWallet` + `src/utils/hd.ts` | Bitcoin/Litecoin infer the address type; Sui overrides it, Cardano throws (CIP-1852) |
| Write tests | `test/` mirroring `src/` path | Use fixtures from `test/fixtures.ts` |
| Integration test | `test-integration/` | Separate pnpm package, manual execution |
| Run demos | `playground/*.ts` | Execute via `pnpm playground <file>` |

## CONVENTIONS

Expand Down
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![license](https://img.shields.io/github/license/agntn/keys?style=flat&colorA=130f40&colorB=474787)](https://github.com/agntn/keys/blob/main/LICENSE.md)
[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/agntn/keys)

Typed key generation, address derivation, and message signing across eight blockchains and two curves.
Typed key generation, address derivation, and message signing across nine blockchains and two curves.

> [!WARNING]
> **@agntn/keys is experimental.** The package name, public API, provider model, and tool surfaces may change before the first stable release. Pin exact versions if you build on it now.
Expand Down Expand Up @@ -68,6 +68,22 @@ const testnet = useBlockchain(await blockchains.bitcoin({ network: "testnet" })(
testnet.getAddress(publicKey, "segwit"); // tb1q...
```

### Litecoin

```ts
import { blockchains } from "@agntn/keys";

const mnemonic =
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
const ltc = await blockchains.litecoin()();
const wallet = ltc.deriveHDWallet(mnemonic, "m/84'/2'/0'/0/0");
const testnet = await blockchains.litecoin({ network: "testnet" })();
```

Litecoin uses the same five address types as Bitcoin, with `L`/`M`/`ltc1` on mainnet and `m` or `n`/`Q`/`tltc1` on testnet. Old P2SH prefixes (`3` and `2`) are accepted, not generated. Those old addresses overlap with Bitcoin, so validation alone cannot identify the chain. MWEB and regtest are outside this implementation.

Message signing uses the Litecoin Core message digest and returns a compact signature of 64 bytes as hex, not Core's recoverable base64 format. Verify against the public key with `verifyMessage`.

### Sign and verify messages

```ts
Expand Down Expand Up @@ -124,7 +140,7 @@ const sol = useBlockchain(await blockchains.solana()());
sol.deriveHDWallet(mnemonic, "m/44'/501'/0'/0'", { passphrase: "TREZOR" }).address;
```

secp256k1 chains walk BIP32 and ed25519 chains walk SLIP-10, which accepts hardened segments only. Bitcoin reads the address type off the purpose level (44, 49, 84, 86) unless one is passed. Cardano throws, because CIP-1852 starts from the entropy rather than the BIP39 seed.
secp256k1 chains walk BIP32 and ed25519 chains walk SLIP-10, which accepts hardened segments only. Bitcoin and Litecoin read the address type off the purpose level (44, 49, 84, 86) unless one is passed. Cardano throws, because CIP-1852 starts from the entropy rather than the BIP39 seed.

### Recover one missing BIP39 word

Expand Down Expand Up @@ -173,6 +189,7 @@ The server handles private keys, mnemonics, entropy, messages, and signatures as
| Chain | Curve | Address Formats | Testnet |
| ------------ | ------------------ | ------------------------------------ | ------- |
| **Bitcoin** | secp256k1 | legacy, p2sh, segwit, p2wsh, taproot | ✅ |
| **Litecoin** | secp256k1 | legacy, p2sh, segwit, p2wsh, taproot | ✅ |
| **Ethereum** | secp256k1 | EIP-55 checksum | - |
| **Base** | secp256k1 | EVM-compatible | - |
| **Solana** | ed25519 | base58 | - |
Expand Down
1 change: 1 addition & 0 deletions build.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export default defineBuildConfig({
"./src/blockchains/aptos.ts",
"./src/blockchains/base.ts",
"./src/blockchains/bitcoin.ts",
"./src/blockchains/litecoin.ts",
"./src/blockchains/cardano.ts",
"./src/blockchains/ethereum.ts",
"./src/blockchains/solana.ts",
Expand Down
10 changes: 5 additions & 5 deletions packages/pi/extensions/keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ export default function keysExtension(pi: ExtensionAPI) {
promptSnippet:
"Use to create a new wallet with keys and address for Bitcoin, Ethereum, Solana, etc.",
promptGuidelines: [
"Provide a chain name (bitcoin, ethereum, base, solana, aptos, tron, sui, cardano)",
"Provide a chain name (bitcoin, litecoin, ethereum, base, solana, aptos, tron, sui, cardano)",
"Optionally specify network (mainnet/testnet) and address type",
"Bitcoin address types: legacy, p2sh, segwit, p2wsh, taproot",
"Bitcoin and Litecoin address types: legacy, p2sh, segwit, p2wsh, taproot",
"Cardano address types: payment, stake, enterprise",
"Returns hex private key, hex public key, and address",
],
Expand Down Expand Up @@ -84,7 +84,7 @@ export default function keysExtension(pi: ExtensionAPI) {
promptGuidelines: [
"Provide a chain name and private key as hex",
"Optionally specify network and address type",
"Bitcoin address types: legacy, p2sh, segwit, p2wsh, taproot",
"Bitcoin and Litecoin address types: legacy, p2sh, segwit, p2wsh, taproot",
"For Sui, use ed25519 or secp256k1 as the address type",
],
parameters: Type.Object({
Expand Down Expand Up @@ -115,7 +115,7 @@ export default function keysExtension(pi: ExtensionAPI) {
promptGuidelines: [
"Provide a chain, an English BIP39 mnemonic, and a full derivation path",
"Common paths: Bitcoin m/44'/0'/0'/0/0 (legacy), m/49'/0'/0'/0/0 (p2sh), m/84'/0'/0'/0/0 (segwit), m/86'/0'/0'/0/0 (taproot); Ethereum m/44'/60'/0'/0/0; Solana m/44'/501'/0'/0'; Aptos m/44'/637'/0'/0'/0'; Sui m/44'/784'/0'/0'/0'",
"Bitcoin picks the address type from the path purpose unless addressType is set",
"Bitcoin and Litecoin pick the address type from the path purpose unless addressType is set",
"Optionally pass a BIP39 passphrase, a network, or an address type",
"Cardano is not supported because CIP-1852 derives from entropy, not from the BIP39 seed",
"Use only public or disposable mnemonics because tool arguments are saved in the transcript",
Expand Down Expand Up @@ -374,7 +374,7 @@ export default function keysExtension(pi: ExtensionAPI) {
promptGuidelines: [
"Provide chain, message text, and private key (hex)",
"Returns the signature as hex string",
"Bitcoin uses its own message preamble format",
"Bitcoin and Litecoin each use their own message preamble",
"Ethereum/Base use EIP-191 prefix",
],
parameters: Type.Object({
Expand Down
1 change: 1 addition & 0 deletions src/_blockchains.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function lazy<T extends AbstractBlockchain>(loader: () => Promise<BlockchainModu
*/
export const blockchains = {
bitcoin: lazy(() => import("./blockchains/bitcoin.ts")),
litecoin: lazy(() => import("./blockchains/litecoin.ts")),
solana: lazy(() => import("./blockchains/solana.ts")),
aptos: lazy(() => import("./blockchains/aptos.ts")),
tron: lazy(() => import("./blockchains/tron.ts")),
Expand Down
8 changes: 5 additions & 3 deletions src/blockchains/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ Lazy-loaded class modules. Each file exports a named concrete class and the same
| Family | Chains | Signing | Key Derivation |
| -------------------- | ---------------------- | ------------------------------------------------- | ----------------------------------- |
| **EVM** | ethereum, base | `evmSignMessage` (preamble + keccak256) | secp256k1 via `utils/secp256k1` |
| **secp256k1 custom** | bitcoin, tron | `evmSignMessage` (same signing, custom addresses) | secp256k1 via `utils/secp256k1` |
| **Bitcoin family** | bitcoin, litecoin | chain-specific message preamble | secp256k1 via `utils/secp256k1` |
| **secp256k1 custom** | tron | `evmSignMessage` (same signing, custom addresses) | secp256k1 via `utils/secp256k1` |
| **ed25519** | solana, aptos, cardano | `ed25519SignMessage` (raw, no prehash) | ed25519 via `utils/ed25519` |
| **dual-curve** | sui | both (selected via `options.scheme`) | ed25519 default, secp256k1 optional |

Expand All @@ -24,11 +25,12 @@ Lazy-loaded class modules. Each file exports a named concrete class and the same

## PATTERNS

- **Bitcoin base class** - `AbstractBitcoinBlockchain` in `utils/bitcoin.ts` shares address generation and HD purpose inference. Litecoin signs its Core digest with noble prehash disabled and accepts both P2SH prefix generations.
- **EVM base class** - `Ethereum` and `Base` extend `AbstractEVMBlockchain`, which owns their shared key, address, validation, and signing behavior
- **Network params** - Bitcoin and Cardano keep separate address parameters for each network in `NETWORK_PARAMS`; TRON uses `0x41` and `T` on mainnet, Shasta, and Nile
- **Network params** - Bitcoin, Litecoin, and Cardano keep separate address parameters for each network in `NETWORK_PARAMS`; TRON uses `0x41` and `T` on mainnet, Shasta, and Nile
- **BIP44 coin type** - every chain sets `bip44` from `BIP44` enum or SLIP-0044 number
- **SUI dual-curve** - `getKeyPublic` and `signMessage` check `options.scheme` to pick ed25519 or secp256k1
- **HD wallets** - `deriveHDWallet` on the base class walks BIP32 or SLIP-10 by curve; Bitcoin infers the address type from the path purpose, Sui takes the curve from the scheme, Cardano throws because CIP-1852 derives differently
- **HD wallets** - `deriveHDWallet` on the base class walks BIP32 or SLIP-10 by curve; Bitcoin and Litecoin infer the address type from the path purpose, Sui takes the curve from the scheme, Cardano throws because CIP-1852 derives differently

## COMPLEXITY

Expand Down
Loading
Loading