npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@polymeshassociation/eth-signing-manager

v1.0.1

Published

Polymesh SDK signing manager backed by Ethereum wallets (EIP-1193 providers and local accounts)

Readme

js-semistandard-style semantic-release

Eth Signing Manager

Polymesh SDK signing manager backed by an Ethereum key — MetaMask and other EIP-1193 providers, plus local (in process) accounts such as a viem LocalAccount or an ethers Wallet.

Background

Polymesh's revive pallet lets an Ethereum key dispatch any Polymesh runtime call as the Account

AccountId32 = <20-byte H160> ++ [0xEE; 12]

by putting the SCALE-encoded call in the data field of an Ethereum transaction addressed to a sentinel address. The Polymesh SDK builds that transaction entirely — gas, nonce, the sentinel address and the chain ID all come from the Substrate connection. This package's only job is to enumerate accounts and get the transaction signed, for the SDK to broadcast, or signed and broadcast by the wallet itself.

| | Sign only — the SDK broadcasts | The wallet signs and broadcasts | |---|---|---| | Method | signTransaction returns raw signed bytes | sendTransaction returns the Ethereum tx hash | | Who broadcasts | the SDK, over its existing Substrate connection | the wallet, via eth_sendTransaction | | Nonce control | the SDK | the wallet | | Used by | viem/ethers local accounts, Ledger, Fireblocks, WalletConnect wallets that implement eth_signTransaction | MetaMask, Rabby, Coinbase Wallet and most injected wallets, which do not implement eth_signTransaction |

Signing only is strictly better, and the SDK prefers it whenever the signer implements signTransaction; wallet broadcast exists because MetaMask — the wallet most users reach for — does not support eth_signTransaction.

This package never estimates gas, fetches a nonce, or knows the sentinel address or chain ID. It only signs (or signs and broadcasts) whatever EthTransactionRequest the SDK hands it.

Install

yarn add @polymeshassociation/eth-signing-manager

Usage — MetaMask (or any injected EIP-1193 provider)

import { EthSigningManager } from '@polymeshassociation/eth-signing-manager';
import { Polymesh } from '@polymeshassociation/polymesh-sdk';

// prompts the user to connect, via eth_requestAccounts
const signingManager = await EthSigningManager.create({
  provider: window.ethereum,
});

const polymesh = await Polymesh.connect({
  nodeUrl,
  signingManager,
});

// the SS58 encoded 0xEE Account(s) — set as the signing Account automatically by `connect`
const accounts = await signingManager.getAccounts();

polymesh.setSigningAccount(accounts[0]);

// react to the user switching Accounts or networks in the wallet
signingManager.onAccountChange((newAccounts) => {
  // update UI, re-set the signing Account, etc.
});

signingManager.onNetworkChange((networkInfo) => {
  // networkInfo.chainId is the 0x-prefixed hex chain ID
});

MetaMask does not implement eth_signTransaction, so EthSigningManager defaults an injected provider to wallet broadcast. That decision is made once, at create() time, and is expressed by which methods the returned EthSigner defines: the Polymesh SDK picks its submission strategy by looking at whether signTransaction or sendTransaction is present. No further configuration is required.

If you know the wallet supports eth_signTransaction

Some WalletConnect-reachable wallets, Ledger-via-WalletConnect, and similar do implement eth_signTransaction. Opt into signing explicitly — it is never auto-detected, because probing would mean sending a speculative request to the user's wallet:

const signingManager = await EthSigningManager.create({
  provider: someEip1193Provider,
  capabilities: { signTransaction: true },
});

If the override turns out to be wrong, the resulting signTransaction call throws a clear error naming the problem. It never silently falls back to wallet broadcast — handing control of the nonce from the SDK to the wallet mid-flight would be a worse failure mode than a loud error.

Usage — a local account (viem / ethers)

viem and ethers are not dependencies of this package. Adapt an account from either with fromViemAccount / fromEthersWallet, which convert the transaction into that library's native types before signing.

Do not pass a viem or ethers account straight through: an EthTransactionRequest is EIP-1193 wire format (hex strings, gas rather than gasLimit), which viem rejects and ethers silently signs with a zero gas limit.

viem

import { privateKeyToAccount } from 'viem/accounts';
import { EthSigningManager, fromViemAccount } from '@polymeshassociation/eth-signing-manager';
import { Polymesh } from '@polymeshassociation/polymesh-sdk';

const account = fromViemAccount(privateKeyToAccount('0x...'));

const signingManager = await EthSigningManager.create({ accounts: [account] });

const polymesh = await Polymesh.connect({ nodeUrl, signingManager });

ethers

import { Wallet } from 'ethers';
import { EthSigningManager, fromEthersWallet } from '@polymeshassociation/eth-signing-manager';

const account = fromEthersWallet(new Wallet('0x...'));

const signingManager = await EthSigningManager.create({ accounts: [account] });

Any other in-process signer can implement EthLocalAccount directly, converting the request itself:

interface EthLocalAccount {
  address: string;
  signTransaction(tx: EthTransactionRequest): Promise<HexString>; // raw signed (RLP) bytes
}

toViemTransaction(tx) and toEthersTransaction(tx) are exported for that case.

Local accounts always sign only (signTransaction: true, sendTransaction: false) — they sign in process and have no way to broadcast a transaction. Multiple accounts can be passed; the SDK selects the one matching the current signing Account's from address.

Address derivation

Each Ethereum address (H160) is exposed to the Polymesh SDK as the SS58 encoding of <h160> ++ [0xEE; 12]. Helpers for converting between the two encodings are also exported:

import {
  ss58FromEthAddress,
  ethAddressFromSs58,
  isEthDerivedAddress,
} from '@polymeshassociation/eth-signing-manager';

ss58FromEthAddress('0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac', 42);
// '5HYRCKHYJN9z5xUtfFkyMj4JUhsAwWyvuU8vKB1FcnYTf9ZQ'

ethAddressFromSs58('5HYRCKHYJN9z5xUtfFkyMj4JUhsAwWyvuU8vKB1FcnYTf9ZQ');
// '0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac'

isEthDerivedAddress('5Ef2XHepJvTUJLhhx39Nf5iqu6AACrfFAmc6AW8a3hKF4Rdc'); // false — native address

EthSigningManager also exposes instance helpers getEthAccounts() (the raw H160 addresses, in the same order as getAccounts()), toEthAddress(ss58) and toSs58Address(h160).

SS58 format

setSs58Format(n) is called by the Polymesh SDK's Context.setSigningManager before getAccounts(), so it does not normally need to be called manually. The manager defaults to 42 so that constructing it standalone (before connecting to a chain) still produces valid addresses.

signingManager.setSs58Format(12); // e.g. Polymesh testnet

Capability resolution

eth_signTransaction support cannot be feature-detected without sending a request to the wallet, so EthSigningManager.create() resolves conservative defaults once, up front:

| Signer | signTransaction | sendTransaction | eip1559 | |---|---|---|---| | EIP-1193 provider (provider: ...) | false | true | true | | Local account (accounts: [...]) | true | false | true |

Pass capabilities to create() to override any of these explicitly:

await EthSigningManager.create({
  provider,
  capabilities: { signTransaction: true, eip1559: false },
});

These three booleans are a construction-time input, and are not what the SDK reads at runtime. signTransaction and sendTransaction decide which methods get attached to the EthSigner that getEthSigner() returns, and the SDK reads that method presence — so a signer can never advertise a capability it does not have. Only eip1559 survives onto the runtime EthSigner.capabilities, because it is the one thing the object's shape cannot express.

manager.resolvedCapabilities reports what create() decided, for diagnostics. To ask what the signer will actually do, check which methods getEthSigner() exposes.

API surface

SigningManager, EthSigningManager, EthSigner, EthSignerCapabilities and EthTransactionRequest are all defined by @polymeshassociation/signing-manager-types. This package re-exports them for convenience, so you can name them without adding a dependency of your own.

class EthSigningManager implements SigningManager, EthSigningManager /* eth-signer contract */ {
  static create(args: EthSigningManagerArgs): Promise<EthSigningManager>;

  // SigningManager
  getAccounts(): Promise<string[]>;
  setSs58Format(ss58Format: number): void;
  getExternalSigner(): PolkadotSigner; // signPayload/signRaw always throw — see below

  // Eth signer contract
  getEthSigner(): EthSigner; // { capabilities: { eip1559 }, signTransaction?, sendTransaction? }

  // Diagnostics: what `create()` resolved. Not the runtime contract — see "Capability resolution"
  resolvedCapabilities: ResolvedCapabilities;

  // Convenience
  getEthAccounts(): Promise<string[]>;
  toEthAddress(ss58Address: string): string;
  toSs58Address(h160: string): string;
  getCurrentNetwork(): Promise<NetworkInfo | null>;
  onAccountChange(cb: (accounts: string[]) => void, ethAddresses?: boolean): UnsubCallback;
  onNetworkChange(cb: (networkInfo: NetworkInfo) => void): UnsubCallback;
}

Network safety

Before asking the wallet to sign or broadcast, the manager reads eth_chainId and compares it to the chainId the Polymesh SDK put in the transaction request (which the SDK reads from the chain's consts.revive.chainId). If they disagree, it throws before the wallet is ever opened.

This matters because the destination is a sentinel address that only means something on Polymesh. A wallet left on Ethereum Mainnet would otherwise broadcast the SCALE-encoded call there, to an address with no contract code — spending real gas for no effect, while the Polymesh transaction never happens and the SDK waits for a result that will never arrive.

An unreadable chain ID is treated as a mismatch (fail closed). Local accounts skip the check entirely: they have no notion of a "current network" and sign exactly the request they are given.

Limitations

  • Off-chain signatures are not supported. getExternalSigner().signPayload / .signRaw always throw. The chain's verify_any_signature only accepts sr25519/ed25519 signatures over SCALE payloads, and an Ethereum key fundamentally cannot produce one. This blocks bulk identity.addSecondaryKeysWithAuthorization and off-chain settlement/investment receipts — onboarding a single Ethereum key into an existing Identity is unaffected, since that flow is two ordinary signed dispatches (inviteAccount + joinIdentityAsKey), not an off-chain signature.
  • MultiSig is out of scope. An Ethereum key acting as a MultiSig signer is not supported by the SDK in this release.
  • MetaMask shows an unavoidable warning. Because the sentinel address has no contract code, MetaMask always displays "You're sending call data to an address that isn't a contract." This cannot be suppressed from either the SDK or this package; it is safe to proceed.
  • Transaction history is not yet indexed for Ethereum accounts. The SubQuery-based middleware the Polymesh SDK's getTransactionHistory() relies on cannot currently attribute a revive extrinsic to the Ethereum Account, and records reverted revive transactions as successful. getTransactionHistory() throws NotSupported for an Ethereum derived Account rather than silently returning an empty (and misleading) list. Balance-changing activity driven by chain events (e.g. getPolyxTransactions) is unaffected.
  • Gas is never estimated by this package. It only signs/sends whatever EthTransactionRequest the Polymesh SDK supplies (see Background). Do not pass a hand-built transaction request to a raw EIP-1193 provider through this manager expecting gas or nonce inference.

License

ISC — see LICENSE.