Giter Club home page Giter Club logo

micro-eth-signer's Introduction

micro-eth-signer

Minimal library for Ethereum transactions, addresses and smart contracts.

  • ๐Ÿ”“ Secure: 3 deps, audited noble cryptography
  • ๐Ÿ”ป Tree-shaking-friendly: use only what's necessary, other code won't be included
  • ๐ŸŒ No network code: simplified auditing and offline usage
  • ๐Ÿ” Reliable: 150MB of test vectors from EIPs, ethers and viem
  • โœ๏ธ Transactions: Create, sign and decode complex txs using human-readable hints
  • ๐Ÿ†Ž Call smart contracts: Chainlink and Uniswap APIs are included
  • ๐Ÿฆบ Typescript-friendly ABI, RLP and SSZ decoding
  • ๐Ÿชถ 1200 lines for core functionality

Check out article ZSTs, ABIs, stolen keys and broken legs about caveats of secure ABI parsing found during development of the library.

Check out all web3 utility libraries: ETH, BTC, SOL

Usage

npm install micro-eth-signer

We support all major platforms and runtimes. For Deno, ensure to use npm specifier. For React Native, you may need a polyfill for getRandomValues. If you don't like NPM, a standalone eth-signer.js is also available.

Create and sign transactions

import { addr, amounts, Transaction } from 'micro-eth-signer';
const privateKey = '0x6b911fd37cdf5c81d4c0adb1ab7fa822ed253ab0ad9aa18d77257c88b29b718e';
const senderAddr = addr.fromPrivateKey(privateKey);
const unsignedTx = Transaction.prepare({
  to: '0xdf90dea0e0bf5ca6d2a7f0cb86874ba6714f463e',
  maxFeePerGas: 100n * amounts.GWEI, // 100 gwei in wei
  value: 1n * amounts.ETHER, // 1 eth in wei
  nonce: 0n,
});
const tx = unsignedTx.signBy(privateKey); // Uint8Array is also accepted
console.log('signed tx', tx, tx.toHex());
console.log('need total wei', tx.calcAmounts().wei.amountWithFee);
console.log('address is same', tx.sender === senderAddr);

We support legacy, EIP2930, EIP1559 and EIP4844 (Dencun / Cancun) transactions.

Create and checksum addresses

import { addr } from 'micro-eth-signer';
const priv = '0x0687640ee33ef844baba3329db9e16130bd1735cbae3657bd64aed25e9a5c377';
const pub = '030fba7ba5cfbf8b00dd6f3024153fc44ddda93727da58c99326eb0edd08195cdb';
const nonChecksummedAddress = '0x0089d53f703f7e0843953d48133f74ce247184c2';
const checksummedAddress = addr.addChecksum(nonChecksummedAddress);
console.log(
  checksummedAddress, // 0x0089d53F703f7E0843953D48133f74cE247184c2
  addr.verifyChecksum(checksummedAddress), // true
  addr.verifyChecksum(nonChecksummedAddress), // also true
  addr.fromPrivateKey(priv),
  addr.fromPublicKey(pub)
);

Generate random keys and addresses

import { addr } from 'micro-eth-signer';
const random = addr.random(); // Secure: uses CSPRNG
console.log(random.privateKey, random.address);
// 0x17ed046e6c4c21df770547fad9a157fd17b48b35fe9984f2ff1e3c6a62700bae
// 0x26d930712fd2f612a107A70fd0Ad79b777cD87f6

Human-readable hints

Decoding transactions

The transaction sent ERC-20 USDT token between addresses. The library produces a following hint:

Transfer 22588 USDT to 0xdac17f958d2ee523a2206206994597c13d831ec7

import { decodeTx } from 'micro-eth-signer/abi';

const tx =
  '0xf8a901851d1a94a20082c12a94dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000054259870025a066fcb560b50e577f6dc8c8b2e3019f760da78b4c04021382ba490c572a303a42a0078f5af8ac7e11caba9b7dc7a64f7bdc3b4ce1a6ab0a1246771d7cc3524a7200';
// Decode tx information
deepStrictEqual(decodeTx(tx), {
  name: 'transfer',
  signature: 'transfer(address,uint256)',
  value: {
    to: '0xdac17f958d2ee523a2206206994597c13d831ec7',
    value: 22588000000n,
  },
  hint: 'Transfer 22588 USDT to 0xdac17f958d2ee523a2206206994597c13d831ec7',
});

Or if you have already decoded tx:

import { decodeData } from 'micro-eth-signer/abi';

const to = '0x7a250d5630b4cf539739df2c5dacb4c659f2488d';
const data =
  '7ff36ab5000000000000000000000000000000000000000000000000ab54a98ceb1f0ad30000000000000000000000000000000000000000000000000000000000000080000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045000000000000000000000000000000000000000000000000000000006fd9c6ea0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000106d3c66d22d2dd0446df23d7f5960752994d600';
const value = 100000000000000000n;

deepStrictEqual(decodeData(to, data, value, { customContracts }), {
  name: 'swapExactETHForTokens',
  signature: 'swapExactETHForTokens(uint256,address[],address,uint256)',
  value: {
    amountOutMin: 12345678901234567891n,
    path: [
      '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
      '0x106d3c66d22d2dd0446df23d7f5960752994d600',
    ],
    to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045',
    deadline: 1876543210n,
  },
});

// With custom tokens/contracts
const customContracts = {
  '0x106d3c66d22d2dd0446df23d7f5960752994d600': { abi: 'ERC20', symbol: 'LABRA', decimals: 9 },
};
deepStrictEqual(decodeData(to, data, value, { customContracts }), {
  name: 'swapExactETHForTokens',
  signature: 'swapExactETHForTokens(uint256,address[],address,uint256)',
  value: {
    amountOutMin: 12345678901234567891n,
    path: [
      '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
      '0x106d3c66d22d2dd0446df23d7f5960752994d600',
    ],
    to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045',
    deadline: 1876543210n,
  },
  hint: 'Swap 0.1 ETH for at least 12345678901.234567891 LABRA. Expires at Tue, 19 Jun 2029 06:00:10 GMT',
});

Decoding events

Decoding the event produces the following hint:

Allow 0xe592427a0aece92de3edee1f18e0157c05861564 spending up to 1000 BAT from 0xd8da6bf26964af9d7eed9e03e53415d37aa96045

import { decodeEvent } from 'micro-eth-signer/abi';

const to = '0x0d8775f648430679a709e98d2b0cb6250d2887ef';
const topics = [
  '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925',
  '0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045',
  '0x000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564',
];
const data = '0x00000000000000000000000000000000000000000000003635c9adc5dea00000';
const einfo = decodeEvent(to, topics, data);
console.log(einfo);

Call smart contracts

User explicitly passes built-in function fetch to enable network access to JSON-RPC web3 node.

Fetch Chainlink oracle prices

import FetchProvider from 'micro-eth-signer/net/provider';
import Chainlink from 'micro-eth-signer/net/chainlink';
const provider = new FetchProvider(thisGlobal.fetch, 'https://nodes.mewapi.io/rpc/eth', {
  Origin: 'https://www.myetherwallet.com',
});
const link = new Chainlink(provider);
const btc = await link.coinPrice('BTC');
const bat = await link.tokenPrice('BAT');
console.log({ btc, bat }); // BTC 19188.68870991, BAT 0.39728989 in USD

Swap tokens with Uniswap

Btw cool tool, glad you built it!

Uniswap Founder

Swap 12.12 USDT to BAT with uniswap V3 defaults of 0.5% slippage, 30 min expiration.

import { tokenFromSymbol } from 'micro-eth-signer/abi';
import FetchProvider from 'micro-eth-signer/net/provider';
// import Uniswap2 from 'micro-eth-signer/net/uniswap-v2';
import Uniswap3 from 'micro-eth-signer/net/uniswap-v3';

const provider = new FetchProvider(thisGlobal.fetch, 'https://nodes.mewapi.io/rpc/eth', {
  Origin: 'https://www.myetherwallet.com',
});
const USDT = tokenFromSymbol('USDT');
const BAT = tokenFromSymbol('BAT');
const u3 = new Uniswap3(provider); // or new UniswapV2(provider)
const fromAddress = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045';
const toAddress = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045';
const swap = await u3.swap(USDT, BAT, '12.12', { slippagePercent: 0.5, ttl: 30 * 60 });
const swapData = await swap.tx(fromAddress, toAddress);
console.log(swapData.amount, swapData.expectedAmount, swapData.allowance);

ABI type inference

The ABI is type-safe when as const is specified:

import { createContract } from 'micro-eth-signer/abi';
const PAIR_CONTRACT = [
  {
    type: 'function',
    name: 'getReserves',
    outputs: [
      { name: 'reserve0', type: 'uint112' },
      { name: 'reserve1', type: 'uint112' },
      { name: 'blockTimestampLast', type: 'uint32' },
    ],
  },
] as const;

const contract = createContract(PAIR_CONTRACT);
// Would create following typescript type:
{
  getReserves: {
    encodeInput: () => Uint8Array;
    decodeOutput: (b: Uint8Array) => {
      reserve0: bigint;
      reserve1: bigint;
      blockTimestampLast: bigint;
    };
  }
}

We're parsing values as:

// no inputs
{} -> encodeInput();
// single input
{inputs: [{type: 'uint'}]} -> encodeInput(bigint);
// all inputs named
{inputs: [{type: 'uint', name: 'lol'}, {type: 'address', name: 'wut'}]} -> encodeInput({lol: bigint, wut: string})
// at least one input is unnamed
{inputs: [{type: 'uint', name: 'lol'}, {type: 'address'}]} -> encodeInput([bigint, string])
// Same applies for output!

There are following limitations:

  • Fixed size arrays can have 999 elements at max: string[], string[1], ..., string[999]
  • Fixed size 2d arrays can have 39 elements at max: string[][], string[][1], ..., string[39][39]
  • Which is enough for almost all cases
  • ABI must be described as constant value: [...] as const
  • We're not able to handle contracts with method overload (same function names with different args) โ€” the code will still work, but not types

Check out src/net/ens.ts for type-safe contract execution example.

RLP parsing

We implement RLP in just 100 lines of code, powered by packed:

import { RLP } from 'micro-eth-signer/rlp';
RLP.decode(RLP.encode('dog'));

SSZ parsing

Simple serialize (SSZ) is the serialization method used on the Beacon Chain. We implement RLP in just 900 lines of code, powered by packed:

import * as ssz from 'micro-eth-signer/ssz';

Sign and verify messages

EIP-712 is not supported yet.

import { addr, messenger } from 'micro-eth-signer';
const { privateKey, address } = addr.random();
const msg = 'noble';
const sig = messenger.sign(msg, privateKey);
const isValid = messenger.verify(sig, msg, address);

Security

Main points to consider when auditing the library:

  • ABI correctness
    • All ABI JSON should be compared to some external source
    • There are different databases of ABI: one is hosted by Etherscan, when you open contract page
  • Network access
    • There must be no network calls in the library
    • Some functionality requires network: these need external network interface, conforming to Web3Provider
    • createContract(abi) should create purely offline contract
    • createContract(abi, net) would create contract that calls network using net, using external interface
  • Skipped test vectors
    • There is SKIPPED_ERRORS, which contains list of test vectors from other libs that we skip
    • They are skipped because we consider them invalid, or so
    • If you believe they're skipped for wrong reasons, investigate and report

The library is cross-tested against other libraries (last update on 25 Feb 2024):

  • ethereum-tests v13.1
  • ethers 6.11.1
  • viem v2.7.13

Performance

Transaction signature matches noble-curves sign() speed, which means over 4000 times per second on macs.

The first call of sign will take 20ms+ due to noble-curves secp256k1 utils.precompute.

To run benchmarks, execute npm run bench.

Contributing

Make sure to use recursive cloning for tests:

git clone --recursive https://github.com/paulmillr/micro-eth-signer.git

License

MIT License

Copyright (c) 2021 Paul Miller (https://paulmillr.com)

micro-eth-signer's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

micro-eth-signer's Issues

Using with node + tsc

Using in nodejs with typescript (either tsc or ts-node) I get:

Error [ERR_REQUIRE_ESM]: require() of ES Module /app/node_modules/micro-eth-signer/index.js from /app/build/src/server.js not supported.

But other libraries of yours like @scure/base work fine, so seems like some aspect of the packaging config is different. Would it be possible to package this lib like the rest so it Just Works in node + typescript?

Thanks for all the great libs btw!

speed of signing

When I run the the sign script constantly without any wait I get 1500 ops / sec (.8 ms each), but when I put a pause in the script (like it will be in real life for me, it takes about 3-7 ms to sign (average about 5ms each). 5-6 times as slow. Any ideas on why this may be?

Speed of sign

Hello,

I was expecting this library to make sign progress faster, but somehow its 20x slower than other libraries. (30-40ms)

Any workaround on this ? I was expecting lower than <1 ms, because there are other libraries can handle sign in 2ms.
Am i doing something wrong ?

Error: recovery id invalid

Hi, great library ๐Ÿ‘

I'm trying to use it with Ethereum Classic transactions which have chainId = 61, but I got error shown below.

// https://blockscout.com/etc/mainnet/tx/0x50d0c4d644045e37a459829e17cc463f88591f270dd9bb801da45604496098aa

const raw = '0xf86a14843b9aca00825208947fe9b4581d788e3efffb2b24b16c2bfd86d0c9368612309ce5400080819da0a687e91d2bcf2f9bd83ad2f85d378f730bd1630fba914fd21fe866160df15bb1a00532fc7319cf9325699a306874c602418cc58ce3041bf6f3becb1f1200883004';
const tx = new Transaction(raw);
console.log(tx.sender);

// Error

Error: recovery id invalid
      at Signature.recoverPublicKey (file:///myproject/node_modules/@noble/curves/esm/abstract/weierstrass.js:636:23)
      at Object.sigRecoverPub (file:///myproject/node_modules/micro-eth-signer/index.js:24:20)
      at Transaction.recoverSenderPublicKey (file:///myproject/node_modules/micro-eth-signer/index.js:420:21)
      at get sender [as sender] (file:///myproject/node_modules/micro-eth-signer/index.js:332:29)
      at Object.decodeRawTx (file:///myproject/app/lib/utils.js:37:18)
      at Context.<anonymous> (file:///myproject/test/lib/utils.test.js:86:24)
      at process.processImmediate (node:internal/timers:476:21)

My current fix is to set chainId manually

...
const tx = new Transaction(raw);
tx.raw.chainId = 61;
...

But I hope there could be more appropriate solution.
Please have a look!

Invalid type Error

/node_modules/micro-eth-signer/index.js:63
throw new TypeError('Invalid type');

Node v15.14.0
OS : Big Sur (Mac)

Sign with different chainId

Thank you the great work. I signed a transaction using micro-eth-signer library then recreated the transaction from hex field. It threw an error 'Cannot recover signature: invalid recovery bit' when I tried to get the sender. Here is the code

let hex = "0xf86c0383023e38825208948bac311a9fb1fc90c4ab8cee1c8437087169849a8853444835ec58000080820a95a0cc6c14bf8ca2b7f864f986925e77a92b9ca4ed3ec6dde30b36500bc3a233456ea00142fa729584f570cc2c66daa11e99d6660164f27240a3e4deb6b523937629a3";
let tx = new Transaction(hex);
tx .raw.chainId = numberTo0xHex(1337);
console.log(tx );
console.log(tx .sender);

The weird thing is that I could decode the hex to get the sender correctly with this tool that used ethereumjs-tx. I signed the transaction with the following code:

let pk = "8c6b19592316688fd537f726db8f2c2237af5cd1a0eb634efdb4bc3c3677f693";
let adr = "0x081766f51efed19582636972b461fe32f33dfab6";
const ethTx = new Transaction({
        chainId: 1337,
        to: "0x8bac311a9fb1fc90c4ab8cee1c8437087169849a",
        gasLimit: numberTo0xHex(21000),
        gasPrice: numberTo0xHex(147000),
        value: numberTo0xHex(BigInt(6000000000000000000)),
        nonce: numberTo0xHex(1)
} );
    ethTx.raw.chainId = numberTo0xHex(1337);
    let signedTx = await ethTx.sign(pk, false);

It seems that the transaction signed by chainId 1 not 1337 that caused the above error. The question is how I could set different chaindId?

Signing messages

Is it possible to sign messages similar to web3.eth.accounts.sign(msg, pk)?

Library broken with micro-packed v0.5.2 compared to v0.5.1

Hi. Thank you for a great library.
I found that using micro-packed v0.5.2 broke some features of library related to ABI encoding.
By default micro-eth-signer uses "micro-packed~0.5.1" that includes v0.5.2 too
Standard 'npm upgrade' command upgrading dependency to 0.5.2

import * as abi from 'micro-eth-signer/lib/esm/web3.js'; // here also bug, export should be in package.json
const abiString = abi.mapComponent({type: 'string'});
console.log(abiString.encode('USDT'));

For V0.5.1 all correct

Uint8Array(96) [......

For V0.5.2

error : Error: Writer(): bigint: invalid value: 4
    at Writer.err (file:///var/www/node_modules/micro-packed/lib/esm/index.js:319:16)
    at Object.encodeStream (file:///var/www/node_modules/micro-packed/lib/esm/index.js:570:21)
    at Writer.length (file:///var/www/node_modules/micro-packed/lib/esm/index.js:350:24)
    at Object.encodeStream (file:///var/www/node_modules/micro-packed/lib/esm/index.js:654:15)
    at Object.encodeStream (file:///var/www/node_modules/micro-packed/lib/esm/index.js:677:43)
    at Object.encodeStream (file:///var/www/node_modules/micro-packed/lib/esm/index.js:1132:19)
    at Object.encode (file:///var/www/node_modules/micro-packed/lib/esm/index.js:407:19)
    at Object.encodeStream (file:///var/www/node_modules/micro-packed/lib/esm/index.js:1161:58)
    at Object.encode (file:///var/www/node_modules/micro-packed/lib/esm/index.js:407:19)

Env: node v20.7.0 with "type:module" via docker

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.