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
5 changes: 3 additions & 2 deletions bindings/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Local, policy-gated signing and wallet management for every chain.
## Why OWS

- **Local key custody.** Private keys stay encrypted at rest and are decrypted only inside the OWS signing path after the relevant checks pass. Current implementations harden in-process memory handling and wipe key material after use.
- **Every chain, one interface.** EVM, Solana, XRPL, Sui, Bitcoin, Cosmos, Tron, TON, Spark, Filecoin — all first-class. CAIP-2/CAIP-10 addressing abstracts away chain-specific details.
- **Every chain, one interface.** EVM, Solana, XRPL, Sui, Bitcoin, Cosmos, Tron, TON, Spark, Filecoin, Nano, NEAR — all first-class. CAIP-2/CAIP-10 addressing abstracts away chain-specific details.
- **Policy before signing.** A pre-signing policy engine gates agent (API key) operations before decryption — chain allowlists, expiry, and optional custom executables.
- **Built for agents.** Native SDK and CLI today. A wallet created by one tool works in every other.

Expand All @@ -31,7 +31,7 @@ Using viem, `@solana/web3.js`, or the Tether WDK? Install [`@open-wallet-standar
import { createWallet, signMessage } from "@open-wallet-standard/core";

const wallet = createWallet("agent-treasury");
// => accounts for EVM, Solana, Bitcoin, Cosmos, Tron, TON, Filecoin, Sui, and XRPL
// => accounts for EVM, Solana, Bitcoin, Cosmos, Tron, TON, Spark, Filecoin, Sui, XRPL, Nano, and NEAR

const sig = signMessage("agent-treasury", "evm", "hello");
console.log(sig.signature);
Expand Down Expand Up @@ -64,6 +64,7 @@ ows sign tx --wallet agent-treasury --chain evm --tx "deadbeef..."
| XRPL | secp256k1 | Base58Check (`r...`) | `m/44'/144'/0'/0/0` |
| Spark (Bitcoin L2) | secp256k1 | spark: prefixed | `m/84'/0'/0'/0/0` |
| Filecoin | secp256k1 | f1 base32 | `m/44'/461'/0'/0/0` |
| NEAR | Ed25519 | implicit hex (64 chars) | `m/44'/397'/0'` |

## CLI Reference

Expand Down
30 changes: 19 additions & 11 deletions bindings/node/__test__/index.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,18 @@ describe('@open-wallet-standard/core', () => {

it('derives addresses for all chains', () => {
const phrase = generateMnemonic(12);
for (const chain of ['evm', 'solana', 'sui', 'bitcoin', 'cosmos', 'tron', 'ton', 'filecoin', 'xrpl', 'nano']) {
for (const chain of ['evm', 'solana', 'sui', 'bitcoin', 'cosmos', 'tron', 'ton', 'filecoin', 'xrpl', 'nano', 'near']) {
const addr = deriveAddress(phrase, chain);
assert.ok(addr.length > 0, `address should be non-empty for ${chain}`);
}
});

// ---- Universal wallet lifecycle ----

it('creates a universal wallet with 10 accounts', () => {
it('creates a universal wallet with 12 accounts', () => {
const wallet = createWallet('lifecycle-test', undefined, 12, vaultDir);
assert.equal(wallet.name, 'lifecycle-test');
assert.equal(wallet.accounts.length, 10);
assert.equal(wallet.accounts.length, 12);

const chainIds = wallet.accounts.map((a) => a.chainId);
assert.ok(chainIds.some((c) => c.startsWith('eip155:')));
Expand All @@ -75,9 +75,11 @@ describe('@open-wallet-standard/core', () => {
assert.ok(chainIds.some((c) => c.startsWith('cosmos:')));
assert.ok(chainIds.some((c) => c.startsWith('tron:')));
assert.ok(chainIds.some((c) => c.startsWith('ton:')));
assert.ok(chainIds.some((c) => c.startsWith('spark:')));
assert.ok(chainIds.some((c) => c.startsWith('fil:')));
assert.ok(chainIds.some((c) => c.startsWith('xrpl:')));
assert.ok(chainIds.some((c) => c.startsWith('nano:')));
assert.ok(chainIds.some((c) => c.startsWith('near:')));

// List
const wallets = listWallets(vaultDir);
Expand Down Expand Up @@ -111,7 +113,7 @@ describe('@open-wallet-standard/core', () => {

const wallet = importWalletMnemonic('mn-import', phrase, undefined, undefined, vaultDir);
assert.equal(wallet.name, 'mn-import');
assert.equal(wallet.accounts.length, 10);
assert.equal(wallet.accounts.length, 12);

const evmAcct = wallet.accounts.find((a) => a.chainId.startsWith('eip155:'));
assert.equal(evmAcct.address, expectedEvm);
Expand All @@ -124,12 +126,12 @@ describe('@open-wallet-standard/core', () => {

// ---- Private key import (secp256k1) ----

it('imports a secp256k1 private key with all 10 accounts', () => {
it('imports a secp256k1 private key with all 12 accounts', () => {
const privkey = '4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318';
const wallet = importWalletPrivateKey('pk-secp', privkey, undefined, vaultDir, 'evm');

assert.equal(wallet.name, 'pk-secp');
assert.equal(wallet.accounts.length, 10, 'should have all 10 chain accounts');
assert.equal(wallet.accounts.length, 12, 'should have all 12 chain accounts');

// Sign on EVM (provided key's curve)
const evmSig = signMessage('pk-secp', 'evm', 'hello', undefined, undefined, undefined, vaultDir);
Expand All @@ -149,11 +151,11 @@ describe('@open-wallet-standard/core', () => {

// ---- Private key import (ed25519) ----

it('imports an ed25519 private key with all 10 accounts', () => {
it('imports an ed25519 private key with all 12 accounts', () => {
const privkey = '9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60';
const wallet = importWalletPrivateKey('pk-ed', privkey, undefined, vaultDir, 'solana');

assert.equal(wallet.accounts.length, 10);
assert.equal(wallet.accounts.length, 12);

// Sign on Solana (provided key)
const solSig = signMessage('pk-ed', 'solana', 'hello', undefined, undefined, undefined, vaultDir);
Expand Down Expand Up @@ -181,7 +183,7 @@ describe('@open-wallet-standard/core', () => {
);

assert.equal(wallet.name, 'pk-both');
assert.equal(wallet.accounts.length, 10, 'should have all 10 chain accounts');
assert.equal(wallet.accounts.length, 12, 'should have all 12 chain accounts');

// Sign on EVM (secp256k1 key)
const evmSig = signMessage('pk-both', 'evm', 'hello', undefined, undefined, undefined, vaultDir);
Expand All @@ -206,7 +208,8 @@ describe('@open-wallet-standard/core', () => {

// XRPL and Nano are excluded here because their signers explicitly do not
// support generic off-chain message signing without a defined convention.
for (const chain of ['evm', 'solana', 'sui', 'bitcoin', 'cosmos', 'tron', 'ton', 'filecoin']) {
// NEAR's V1 sign_message is raw ed25519 over the bytes (NEP-413 is a follow-up).
for (const chain of ['evm', 'solana', 'sui', 'bitcoin', 'cosmos', 'tron', 'ton', 'filecoin', 'near']) {
const result = signMessage('all-chain-signer', chain, 'test', undefined, undefined, undefined, vaultDir);
assert.ok(result.signature.length > 0, `signature should be non-empty for ${chain}`);
}
Expand All @@ -232,12 +235,17 @@ describe('@open-wallet-standard/core', () => {
'00000000033b2e3c9fd0803ce8000000' +
`${'03'.repeat(32)}`;

// NEAR transactions have no envelope; signer hashes via sha256 then ed25519
// signs the digest. Any non-empty bytes verify the signing pipeline.
const nearTxHex = '42'.repeat(80);

const txHexByChain = {
solana: solTxHex,
nano: nanoTxHex,
near: nearTxHex,
};

for (const chain of ['evm', 'solana', 'sui', 'bitcoin', 'cosmos', 'tron', 'ton', 'filecoin', 'xrpl', 'nano']) {
for (const chain of ['evm', 'solana', 'sui', 'bitcoin', 'cosmos', 'tron', 'ton', 'filecoin', 'xrpl', 'nano', 'near']) {
const hex = txHexByChain[chain] ?? txHex;
const result = signTransaction('tx-signer', chain, hex, undefined, undefined, vaultDir);
assert.ok(result.signature.length > 0, `signature should be non-empty for ${chain}`);
Expand Down
5 changes: 3 additions & 2 deletions bindings/python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Local, policy-gated signing and wallet management for every chain.
## Why OWS

- **Local key custody.** Private keys stay encrypted at rest and are decrypted only inside the OWS signing path after the relevant checks pass. Current implementations harden in-process memory handling and wipe key material after use.
- **Every chain, one interface.** EVM, Solana, XRPL, Sui, Bitcoin, Cosmos, Tron, TON, Spark, Filecoin — all first-class. CAIP-2/CAIP-10 addressing abstracts away chain-specific details.
- **Every chain, one interface.** EVM, Solana, XRPL, Sui, Bitcoin, Cosmos, Tron, TON, Spark, Filecoin, Nano, NEAR — all first-class. CAIP-2/CAIP-10 addressing abstracts away chain-specific details.
- **Policy before signing.** A pre-signing policy engine gates agent (API key) operations before decryption — chain allowlists, expiry, and optional custom executables.
- **Built for agents.** Native SDK and CLI today. A wallet created by one tool works in every other.

Expand All @@ -28,7 +28,7 @@ The package is **fully self-contained** — it embeds the Rust core via native F
from ows import create_wallet, sign_message

wallet = create_wallet("agent-treasury")
# => accounts for EVM, Solana, Bitcoin, Cosmos, Tron, TON, Filecoin, Sui, and XRPL
# => accounts for EVM, Solana, Bitcoin, Cosmos, Tron, TON, Spark, Filecoin, Sui, XRPL, Nano, and NEAR

sig = sign_message("agent-treasury", "evm", "hello")
print(sig["signature"])
Expand Down Expand Up @@ -76,6 +76,7 @@ print(sig["signature"])
| XRPL | secp256k1 | Base58Check (`r...`) | `m/44'/144'/0'/0/0` |
| Spark (Bitcoin L2) | secp256k1 | spark: prefixed | `m/84'/0'/0'/0/0` |
| Filecoin | secp256k1 | f1 base32 | `m/44'/461'/0'/0/0` |
| NEAR | Ed25519 | implicit hex (64 chars) | `m/44'/397'/0'` |

## Architecture

Expand Down
8 changes: 5 additions & 3 deletions bindings/python/tests/test_bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def test_derive_address_ethereum():

def test_derive_address_all_supported_chains():
phrase = ows.generate_mnemonic(12)
for chain in ["evm", "solana", "sui", "bitcoin", "cosmos", "tron", "ton", "filecoin", "nano"]:
for chain in ["evm", "solana", "sui", "bitcoin", "cosmos", "tron", "ton", "filecoin", "xrpl", "nano", "near"]:
address = ows.derive_address(phrase, chain)
assert len(address) > 0

Expand All @@ -49,7 +49,7 @@ def test_create_and_list_wallets(vault_dir):
wallet = ows.create_wallet("test-wallet", vault_path_opt=vault_dir)
assert wallet["name"] == "test-wallet"
assert isinstance(wallet["accounts"], list)
assert len(wallet["accounts"]) == 10
assert len(wallet["accounts"]) == 12

# Verify each chain family is present
chain_ids = [a["chain_id"] for a in wallet["accounts"]]
Expand All @@ -60,9 +60,11 @@ def test_create_and_list_wallets(vault_dir):
assert any(c.startswith("cosmos:") for c in chain_ids)
assert any(c.startswith("tron:") for c in chain_ids)
assert any(c.startswith("ton:") for c in chain_ids)
assert any(c.startswith("spark:") for c in chain_ids)
assert any(c.startswith("fil:") for c in chain_ids)
assert any(c.startswith("xrpl:") for c in chain_ids)
assert any(c.startswith("nano:") for c in chain_ids)
assert any(c.startswith("near:") for c in chain_ids)

wallets = ows.list_wallets(vault_path_opt=vault_dir)
assert len(wallets) == 1
Expand Down Expand Up @@ -109,7 +111,7 @@ def test_import_wallet_mnemonic(vault_dir):
"imported", phrase, vault_path_opt=vault_dir
)
assert wallet["name"] == "imported"
assert len(wallet["accounts"]) == 10
assert len(wallet["accounts"]) == 12

# EVM account should match derived address
evm_account = next(a for a in wallet["accounts"] if a["chain_id"].startswith("eip155:"))
Expand Down
1 change: 1 addition & 0 deletions docs/02-signing-interface.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ Message signing follows chain-specific conventions:
- **Sui**: Intent-prefixed (scope=3) BLAKE2b-256 digest, Ed25519 signature
- **Cosmos**: ADR-036 off-chain signing
- **Filecoin**: Blake2b-256 hash then secp256k1 signing
- **NEAR**: V1 Ed25519 signature over the raw message bytes (parity with Solana). [NEP-413](https://github.com/near/NEPs/blob/master/neps/nep-0413.md) prefixed message signing — `tag 2147484061 || borsh({message, nonce, recipient, callbackUrl?})` — is tracked as a follow-up so callers can opt in via a structured payload. Transaction signing is `Ed25519(SHA-256(borsh(Transaction)))`; `encode_signed_transaction` returns `borsh(Transaction) || 0x00 || sig64` (the canonical `borsh(SignedTransaction)`).

### `signTypedData(request: SignTypedDataRequest): Promise<SignMessageResult>`

Expand Down
8 changes: 7 additions & 1 deletion docs/07-supported-chains.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ OWS groups chains into families that share a cryptographic curve and address der
| XRPL | secp256k1 | 144 | `m/44'/144'/0'/0/{index}` | Base58Check (`r...`) | `xrpl` |
| Spark | secp256k1 | 8797555 | `m/84'/0'/0'/0/{index}` | `spark:` + compressed pubkey hex | `spark` |
| Filecoin | secp256k1 | 461 | `m/44'/461'/0'/0/{index}` | `f1` + base32(blake2b-160) | `fil` |
| NEAR | ed25519 | 397 | `m/44'/397'/{index}'` | 64-char lowercase hex of pubkey (implicit account) | `near` |

## Known Networks

Expand Down Expand Up @@ -73,6 +74,8 @@ Each network has a canonical chain identifier. Endpoint discovery and transport
| XRPL | `xrpl:mainnet` |
| Spark | `spark:mainnet` |
| Filecoin | `fil:mainnet` |
| NEAR | `near:mainnet` |
| NEAR (testnet) | `near:testnet` |

Implementations MAY ship convenience endpoint defaults, but those defaults are deployment choices rather than OWS interoperability requirements.

Expand Down Expand Up @@ -104,6 +107,8 @@ xrpl-testnet → xrpl:testnet
xrpl-devnet → xrpl:devnet
spark → spark:mainnet
filecoin → fil:mainnet
near → near:mainnet
near-testnet → near:testnet
```

Aliases MUST be resolved to full CAIP-2 identifiers before any processing. They MUST NOT appear in wallet files, policy files, or audit logs.
Expand All @@ -127,7 +132,8 @@ Master Seed (512 bits via PBKDF2)
├── m/44'/784'/0'/0'/0' → Sui Account 0
├── m/44'/144'/0'/0/0 → XRPL Account 0
├── m/84'/0'/0'/0/0 → Spark Account 0
└── m/44'/461'/0'/0/0 → Filecoin Account 0
├── m/44'/461'/0'/0/0 → Filecoin Account 0
└── m/44'/397'/0' → NEAR Account 0
```

For mnemonic-based wallets, a single mnemonic derives accounts across all supported chains. Those wallet files store the encrypted mnemonic, and the signer derives the appropriate private key using each chain's coin type and derivation path. Wallets imported from raw private keys instead store encrypted curve-key material directly.
Expand Down
5 changes: 3 additions & 2 deletions ows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ The bindings are **standalone** — they embed the Rust core via native FFI. No
import { createWallet, signMessage } from "@open-wallet-standard/core";

const wallet = createWallet("my-wallet");
console.log(wallet.accounts); // addresses for EVM, Solana, Bitcoin, Cosmos, Tron, TON, Filecoin, Sui, and XRPL
console.log(wallet.accounts); // addresses for EVM, Solana, Bitcoin, Cosmos, Tron, TON, Spark, Filecoin, Sui, XRPL, Nano, and NEAR

const sig = signMessage("my-wallet", "evm", "hello");
console.log(sig.signature);
Expand All @@ -68,7 +68,7 @@ console.log(sig.signature);
| Crate | Description |
|-------|-------------|
| `ows-core` | Types, CAIP-2/10 parsing, errors, config. Zero crypto dependencies. |
| `ows-signer` | ChainSigner trait, HD derivation, address derivation for EVM, Solana, XRPL, Sui, Bitcoin, Cosmos, Tron, TON, Spark, and Filecoin. |
| `ows-signer` | ChainSigner trait, HD derivation, address derivation for EVM, Solana, XRPL, Sui, Bitcoin, Cosmos, Tron, TON, Spark, Filecoin, Nano, and NEAR. |
| `ows-lib` | Library interface used by language bindings and the CLI. |
| `ows-pay` | x402 payment flows, service discovery, and funding helpers. |
| `ows-cli` | The `ows` command-line tool. |
Expand All @@ -85,6 +85,7 @@ console.log(sig.signature);
- **Spark** (Bitcoin L2) — secp256k1, spark: prefixed addresses
- **XRPL** — secp256k1, Base58Check r-addresses
- **Filecoin** — secp256k1, f1 base32 addresses
- **NEAR** — Ed25519, implicit hex (64 chars), Borsh-serialized transactions

## License

Expand Down
5 changes: 3 additions & 2 deletions ows/crates/ows-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ The bindings are **standalone** — they embed the Rust core via native FFI. No
import { createWallet, signMessage } from "@open-wallet-standard/core";

const wallet = createWallet("my-wallet");
console.log(wallet.accounts); // addresses for EVM, Solana, Bitcoin, Cosmos, Tron, TON, Filecoin, Sui, and XRPL
console.log(wallet.accounts); // addresses for EVM, Solana, Bitcoin, Cosmos, Tron, TON, Spark, Filecoin, Sui, XRPL, Nano, and NEAR

const sig = signMessage("my-wallet", "evm", "hello");
console.log(sig.signature);
Expand All @@ -68,7 +68,7 @@ console.log(sig.signature);
| Crate | Description |
|-------|-------------|
| `ows-core` | Types, CAIP-2/10 parsing, errors, config. Zero crypto dependencies. |
| `ows-signer` | ChainSigner trait, HD derivation, address derivation for EVM, Solana, XRPL, Sui, Bitcoin, Cosmos, Tron, TON, Spark, and Filecoin. |
| `ows-signer` | ChainSigner trait, HD derivation, address derivation for EVM, Solana, XRPL, Sui, Bitcoin, Cosmos, Tron, TON, Spark, Filecoin, Nano, and NEAR. |
| `ows-lib` | Library interface used by language bindings and the CLI. |
| `ows-pay` | x402 payment flows, service discovery, and funding helpers. |
| `ows-cli` | The `ows` command-line tool. |
Expand All @@ -85,6 +85,7 @@ console.log(sig.signature);
- **Spark** (Bitcoin L2) — secp256k1, spark: prefixed addresses
- **XRPL** — secp256k1, Base58Check r-addresses
- **Filecoin** — secp256k1, f1 base32 addresses
- **NEAR** — Ed25519, implicit hex (64 chars), Borsh-serialized transactions

## License

Expand Down
5 changes: 3 additions & 2 deletions ows/crates/ows-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ The bindings are **standalone** — they embed the Rust core via native FFI. No
import { createWallet, signMessage } from "@open-wallet-standard/core";

const wallet = createWallet("my-wallet");
console.log(wallet.accounts); // addresses for EVM, Solana, Bitcoin, Cosmos, Tron, TON, Filecoin, Sui, and XRPL
console.log(wallet.accounts); // addresses for EVM, Solana, Bitcoin, Cosmos, Tron, TON, Spark, Filecoin, Sui, XRPL, Nano, and NEAR

const sig = signMessage("my-wallet", "evm", "hello");
console.log(sig.signature);
Expand All @@ -68,7 +68,7 @@ console.log(sig.signature);
| Crate | Description |
|-------|-------------|
| `ows-core` | Types, CAIP-2/10 parsing, errors, config. Zero crypto dependencies. |
| `ows-signer` | ChainSigner trait, HD derivation, address derivation for EVM, Solana, XRPL, Sui, Bitcoin, Cosmos, Tron, TON, Spark, and Filecoin. |
| `ows-signer` | ChainSigner trait, HD derivation, address derivation for EVM, Solana, XRPL, Sui, Bitcoin, Cosmos, Tron, TON, Spark, Filecoin, Nano, and NEAR. |
| `ows-lib` | Library interface used by language bindings and the CLI. |
| `ows-pay` | x402 payment flows, service discovery, and funding helpers. |
| `ows-cli` | The `ows` command-line tool. |
Expand All @@ -85,6 +85,7 @@ console.log(sig.signature);
- **Spark** (Bitcoin L2) — secp256k1, spark: prefixed addresses
- **XRPL** — secp256k1, Base58Check r-addresses
- **Filecoin** — secp256k1, f1 base32 addresses
- **NEAR** — Ed25519, implicit hex (64 chars), Borsh-serialized transactions

## License

Expand Down
Loading
Loading