> ## Documentation Index
> Fetch the complete documentation index at: https://www.dynamic.xyz/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Adding Custom Networks

> Extend your Flutter app to support any blockchain that shares secp256k1 or Ed25519 key material with Dynamic embedded wallets

## Overview

Dynamic embedded wallets give you cryptographic key material for two curves:

* **secp256k1** via the EVM wallet — used by Bitcoin, Ethereum, Tron, Cosmos, XRP, Starknet, and many others
* **Ed25519** via the Solana wallet — used by Solana, NEAR, Cardano, Aptos, Mavryk, Algorand, and others

Any blockchain that uses one of these curves can be supported without creating new wallets or prompting the user to re-authenticate. This page explains how to wire up a new chain yourself.

## Decision: which root wallet to use?

| Your chain uses | Root wallet   | Signing method                                                          |
| --------------- | ------------- | ----------------------------------------------------------------------- |
| secp256k1       | EVM wallet    | `DynamicSDK.instance.wallets.signRawMessage(...)`                       |
| Ed25519         | Solana wallet | `DynamicSDK.instance.solana.createSigner(wallet: ...).signMessage(...)` |

If your chain uses a derived curve (e.g., Starknet derives its Stark key from secp256k1), start from the EVM wallet and apply the key-derivation step locally in Dart.

## Step 1: Add Dart crypto dependencies

Choose the packages you need based on the chain's address encoding and hash functions:

```yaml theme={"system"}
# pubspec.yaml
dependencies:
  # Core cryptography (covers most chains)
  pointycastle: ^3.9.1     # secp256k1, SHA-256, SHA-3, Keccak, RIPEMD-160

  # Extended hashing
  hashlib: ^1.19.2         # Blake2b (Cardano, Mavryk, NEAR transactions)

  # Encoding
  bs58: ^1.0.2             # Base58 / base58check (Bitcoin, Tron, XRP, Mavryk, Solana)
  bech32: ^0.2.2           # Bech32 (Cosmos, Cardano)
  convert: ^3.1.1          # Hex utilities

  # Networking
  http: ^1.2.2
```

Run `flutter pub get` after updating `pubspec.yaml`.

## Step 2: Derive the address

### secp256k1 chains

secp256k1 chains need the compressed 33-byte public key. You recover it once per session by signing a deterministic message and running ecrecover:

```dart theme={"system"}
import 'dart:typed_data';
import 'package:pointycastle/pointycastle.dart';
import 'package:pointycastle/ecc/curves/secp256k1.dart';
import 'package:pointycastle/ecc/ecc_fp.dart' as fp;

// Cache this per session — recovery only needs to happen once
Uint8List? _cachedPubkey;

Future<Uint8List> getEvmCompressedPubkey() async {
  if (_cachedPubkey != null) return _cachedPubkey!;

  const recoveryMessage = 'MY_CHAIN_PUBKEY_RECOVERY';
  final evmWallet = DynamicSDK.instance.wallets.userWallets
      .firstWhere((w) => w.chain == 'EVM');

  // Sign EIP-191 prefixed message to get a recoverable signature
  final signature = await DynamicSDK.instance.wallets.signMessage(
    walletId: evmWallet.id,
    accountAddress: evmWallet.address,
    message: recoveryMessage,
  );

  // Reconstruct the hash that was actually signed
  final prefix = '\x19Ethereum Signed Message:\n${recoveryMessage.length}';
  final combined = Uint8List.fromList([...prefix.codeUnits, ...recoveryMessage.codeUnits]);
  final msgHash = Digest('Keccak/256').process(combined);

  // ecrecover → compressed pubkey
  final sigBytes = hexToBytes(strip0x(signature));
  var v = sigBytes[64];
  if (v >= 27) v -= 27;
  _cachedPubkey = _ecRecover(
    msgHash,
    BigInt.parse(bytesToHex(sigBytes.sublist(0, 32)), radix: 16),
    BigInt.parse(bytesToHex(sigBytes.sublist(32, 64)), radix: 16),
    v,
  );
  return _cachedPubkey!;
}
```

Once you have the compressed public key, derive the address using your chain's rules (SHA-256 + RIPEMD-160 for Bitcoin/Cosmos, keccak for Ethereum/Tron, etc.).

### Ed25519 chains

Ed25519 chains use the Solana wallet's public key directly. The Solana address is base58 encoding of the 32-byte Ed25519 public key:

```dart theme={"system"}
import 'package:bs58/bs58.dart';

Uint8List getSolanaPublicKey() {
  final solWallet = DynamicSDK.instance.wallets.userWallets
      .firstWhere((w) => w.chain == 'SOL');
  return Uint8List.fromList(base58.decode(solWallet.address));
}
```

## Step 3: Sign payloads

The signing flow is the same for all chains on the same curve — only the hash/serialization before calling the SDK differs.

### secp256k1 signing

```dart theme={"system"}
Future<String> signWithEvmWallet(Uint8List digest32) async {
  final evmWallet = DynamicSDK.instance.wallets.userWallets
      .firstWhere((w) => w.chain == 'EVM');

  // digest32 must be exactly 32 bytes, passed as hex without 0x
  final signature = await DynamicSDK.instance.wallets.signRawMessage(
    walletId: evmWallet.id,
    accountAddress: evmWallet.address,
    message: bytesToHex(digest32),
  );

  // Returns 65-byte hex (r + s + v) — strip 0x prefix if present
  return strip0x(signature);
}
```

Many chains require low-S normalization. Apply this before encoding the signature:

```dart theme={"system"}
final _secp256k1N = BigInt.parse(
  'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141',
  radix: 16,
);

BigInt normalizeLowS(BigInt s) {
  final halfN = _secp256k1N >> 1;
  return s > halfN ? _secp256k1N - s : s;
}
```

### Ed25519 signing

```dart theme={"system"}
Future<Uint8List> signWithSolanaWallet(String hexDigest) async {
  final solWallet = DynamicSDK.instance.wallets.userWallets
      .firstWhere((w) => w.chain == 'SOL');

  final signer = DynamicSDK.instance.solana.createSigner(wallet: solWallet);
  final result = await signer.signMessage(message: hexDigest);

  // The result may be hex, base64, or base58 — normalize:
  return decodeSig(result);
}
```

The `decodeSig` helper handles all three encodings:

```dart theme={"system"}
import 'dart:convert';
import 'package:bs58/bs58.dart';

Uint8List decodeSig(String sig) {
  final clean = sig.startsWith('0x') ? sig.substring(2) : sig.trim();
  if (RegExp(r'^[0-9a-fA-F]{128}$').hasMatch(clean)) return hexToBytes(clean);
  try {
    final bin = base64.decode(clean);
    if (bin.length == 64) return Uint8List.fromList(bin);
  } catch (_) {}
  return Uint8List.fromList(base58.decode(clean));
}
```

## Step 4: Query balances and broadcast transactions

Use `package:http` to call the chain's RPC or REST API. The signing flow is chain-agnostic; only the serialization and endpoint differ.

```dart theme={"system"}
import 'dart:convert';
import 'package:http/http.dart' as http;

Future<Map<String, dynamic>> jsonRpc(
  String url,
  String method,
  dynamic params,
) async {
  final res = await http.post(
    Uri.parse(url),
    headers: {'Content-Type': 'application/json'},
    body: json.encode({'jsonrpc': '2.0', 'id': 1, 'method': method, 'params': params}),
  );
  final data = json.decode(res.body) as Map<String, dynamic>;
  if (data['error'] != null) throw Exception(data['error']['message']);
  return data['result'] as Map<String, dynamic>;
}
```

## Shared byte utilities

Add these to `lib/utils/bytes.dart`:

```dart theme={"system"}
import 'dart:typed_data';

String strip0x(String hex) => hex.startsWith('0x') ? hex.substring(2) : hex;

String bytesToHex(Uint8List bytes) =>
    bytes.map((b) => b.toRadixString(16).padLeft(2, '0')).join();

Uint8List hexToBytes(String hex) {
  final clean = strip0x(hex);
  final result = Uint8List(clean.length ~/ 2);
  for (var i = 0; i < clean.length; i += 2) {
    result[i ~/ 2] = int.parse(clean.substring(i, i + 2), radix: 16);
  }
  return result;
}

Uint8List concatBytes(List<Uint8List> arrays) {
  final total = arrays.fold(0, (s, a) => s + a.length);
  final result = Uint8List(total);
  var offset = 0;
  for (final arr in arrays) {
    result.setAll(offset, arr);
    offset += arr.length;
  }
  return result;
}
```

## Checklist for a new chain

1. Identify the curve (secp256k1 or Ed25519) and the root wallet to use
2. Look up the address encoding (bech32, base58check, hex, etc.) in the chain's spec
3. Implement `deriveAddress(String rootWalletAddress) → String`
4. Implement `digestForSigning(String message) → String` (hex of hash)
5. Call `signWithEvmWallet` or `signWithSolanaWallet` with the digest
6. Post-process the signature if needed (low-S normalization, DER encoding, etc.)
7. Serialize and broadcast the transaction via the chain's RPC

See the existing chain implementations for complete examples:

* [tron.dart](https://github.com/dynamic-labs/flutter-multichain-demo/blob/main/lib/chains/tron.dart) — secp256k1, base58check
* [cosmos.dart](https://github.com/dynamic-labs/flutter-multichain-demo/blob/main/lib/chains/cosmos.dart) — secp256k1, bech32, Protobuf
* [near.dart](https://github.com/dynamic-labs/flutter-multichain-demo/blob/main/lib/chains/near.dart) — Ed25519, Borsh
* [cardano.dart](https://github.com/dynamic-labs/flutter-multichain-demo/blob/main/lib/chains/cardano.dart) — Ed25519, CBOR
* [aptos.dart](https://github.com/dynamic-labs/flutter-multichain-demo/blob/main/lib/chains/aptos.dart) — Ed25519, REST API signing
