micro-eth-signer
Advanced tools
Comparing version 0.6.5 to 0.7.0
{ | ||
"name": "micro-eth-signer", | ||
"version": "0.6.5", | ||
"description": "Create, sign and validate Ethereum transactions & addresses with minimum deps", | ||
"type": "module", | ||
"main": "index.js", | ||
"module": "index.js", | ||
"types": "index.d.ts", | ||
"version": "0.7.0", | ||
"description": "Small tool for Ethereum transactions, addresses and smart contracts", | ||
"files": [ | ||
"index.js", | ||
"index.d.ts", | ||
"index.d.ts.map", | ||
"index.ts", | ||
"formatters.js", | ||
"formatters.d.ts", | ||
"formatters.d.ts.map", | ||
"formatters.ts", | ||
"tx-validator.js", | ||
"tx-validator.d.ts", | ||
"tx-validator.d.ts.map", | ||
"tx-validator.ts" | ||
"lib", | ||
"src" | ||
], | ||
"main": "lib/index.js", | ||
"module": "lib/esm/index.js", | ||
"types": "lib/index.d.ts", | ||
"dependencies": { | ||
"@ethereumjs/rlp": "5.0.0", | ||
"@noble/curves": "~1.3.0", | ||
"@noble/hashes": "~1.3.3" | ||
"@noble/hashes": "~1.3.3", | ||
"micro-packed": "~0.5.0" | ||
}, | ||
"devDependencies": { | ||
"micro-bmark": "0.3.0", | ||
"micro-bmark": "0.3.1", | ||
"micro-should": "0.4.0", | ||
@@ -56,9 +46,9 @@ "prettier": "3.1.1", | ||
"scripts": { | ||
"build": "tsc", | ||
"build": "tsc && tsc -p tsconfig.esm.json", | ||
"build:release": "cd build; npm run build:release", | ||
"bench": "node test/benchmark.js", | ||
"lint": "prettier --print-width 100 --single-quote --check index.ts formatters.ts", | ||
"format": "prettier --print-width 100 --single-quote --write index.ts formatters.ts", | ||
"test": "node test/test.js" | ||
"lint": "prettier --print-width 100 --single-quote --check src", | ||
"format": "prettier --print-width 100 --single-quote --write src", | ||
"test": "node test/test.js && node test/web3.test.js" | ||
} | ||
} |
287
README.md
# micro-eth-signer | ||
Create, sign and validate Ethereum transactions & addresses with minimum deps. | ||
Small tool for Ethereum transactions, addresses and smart contracts. | ||
- Tiny: 500 lines of code, 3KB gzipped, 13KB bundled | ||
- 3 dependencies: noble-hashes for sha3, noble-curves for secp256k1, rlp | ||
- No network code in main package: allows simpler audits and offline usage | ||
- Validated against 3MB of [ethers](https://github.com/ethers-io/ethers.js/) test vectors | ||
- Using audited [noble](https://paulmillr.com/noble/) cryptography under the hood | ||
- ð Secure: minimum deps, audited [noble](https://paulmillr.com/noble/) cryptography | ||
- ðŧ Tree-shaking-friendly: use only what's necessary, other code won't be included | ||
- ð No network code for simplified auditing and offline usage | ||
- ð Tested against 3MB of [ethers](https://github.com/ethers-io/ethers.js/) vectors | ||
- âïļ Create and sign transactions, generate and checksum addresses | ||
- ð Decode transactions and events into human-readable form | ||
- ð Call smart contracts: Chainlink and Uniswap APIs are included | ||
- ðĶš Decode smart contract ABIs into type-safe TypeScript structures | ||
- ðŠķ Small: 500 lines of code for main module, 2.7K lines for everything | ||
*Check out all web3 utility libraries:* [ETH](https://github.com/paulmillr/micro-eth-signer), [BTC](https://github.com/paulmillr/scure-btc-signer), [SOL](https://github.com/paulmillr/micro-sol-signer), [micro-web3](https://github.com/paulmillr/micro-web3), [tx-tor-broadcaster](https://github.com/paulmillr/tx-tor-broadcaster) | ||
_Check out all web3 utility libraries:_ [ETH](https://github.com/paulmillr/micro-eth-signer), [BTC](https://github.com/paulmillr/scure-btc-signer), [SOL](https://github.com/paulmillr/micro-sol-signer), [tx-tor-broadcaster](https://github.com/paulmillr/tx-tor-broadcaster) | ||
@@ -22,5 +26,18 @@ ## Usage | ||
- [Transaction creation and signing](#transaction-creation-and-signing) | ||
- [Address generation and checksumming](#address-generation-and-checksumming) | ||
- [Transaction decoding](#transaction-decoding) | ||
- [Event decoding](#event-decoding) | ||
- [Call smart contracts](#call-smart-contracts) | ||
- [Fetch Chainlink oracle prices](#fetch-chainlink-oracle-prices) | ||
- [Uniswap](#uniswap) | ||
- [Type inference](#type-inference) | ||
- [Human-friendly field validation](#human-friendly-field-validation) | ||
- [Formatters](#formatters) | ||
- [Low-level transaction API](#low-level-transaction-api) | ||
### Transaction creation and signing | ||
```js | ||
import { Address, Transaction } from 'micro-eth-signer'; | ||
import { Transaction } from 'micro-eth-signer'; | ||
const tx = new Transaction({ | ||
@@ -32,5 +49,4 @@ to: '0xdf90dea0e0bf5ca6d2a7f0cb86874ba6714f463e', | ||
maxPriorityFeePerGas: 0, | ||
chainId: 1 | ||
chainId: 1, | ||
}); | ||
// keys, messages & other inputs can be Uint8Arrays or hex strings | ||
@@ -40,71 +56,153 @@ // Uint8Array.from([0xde, 0xad, 0xbe, 0xef]) === 'deadbeef' | ||
const signedTx = tx.sign(privateKey); | ||
const { hash, hex } = signedTx; | ||
console.log(signedTx.hash, signedTx.hex); | ||
console.log('Need wei', tx.upfrontCost); // also, tx.fee, tx.amount, tx.sender, etc | ||
``` | ||
// Strings can be used also | ||
// tx = new Transaction({"nonce": "0x01"}) | ||
// Same goes to serialized representation | ||
// tx = new Transaction('0xeb018502540be40082520894df90dea0e0bf5ca6d2a7f0cb86874ba6714f463e872386f26fc1000080808080'); | ||
### Address generation and checksumming | ||
// Various tx properties | ||
console.log('Need wei', tx.upfrontCost); // also, tx.fee, tx.amount, tx.sender, etc | ||
```ts | ||
const addr = '0x0089d53f703f7e0843953d48133f74ce247184c2'; | ||
const addrc = Address.checksum(addr); // 0x0089d53F703f7E0843953D48133f74cE247184c2 | ||
Address.verifyChecksum(addrc); // true | ||
Address.verifyChecksum(addr); // true also (non-checksummed) | ||
Address.fromPrivateKey('0687640ee33ef844baba3329db9e16130bd1735cbae3657bd64aed25e9a5c377'); | ||
// 0xD4fE407789e11a27b7888A324eC597435353dC35 | ||
Address.fromPublicKey('030fba7ba5cfbf8b00dd6f3024153fc44ddda93727da58c99326eb0edd08195cdb'); | ||
// 0xD4fE407789e11a27b7888A324eC597435353dC35 | ||
``` | ||
// Address manipulation | ||
const addr = Address.fromPrivateKey(privateKey); | ||
const pubKey = signedTx.recoverSenderPublicKey(); | ||
console.log('Verified', Address.verifyChecksum(addr)); | ||
console.log('addr is correct', signedTx.sender, signedTx.sender == addr); | ||
console.log(signedTx); | ||
### Transaction decoding | ||
// London style txs, EIP 1559 | ||
const legacyTx = new Transaction({ | ||
to: '0xdf90dea0e0bf5ca6d2a7f0cb86874ba6714f463e', | ||
gasPrice: 100n * 10n ** 9n, // 100 gwei in wei | ||
value: 10n ** 18n, // 1 eth in wei | ||
nonce: 1 | ||
}, undefined, undefined, 'legacy'); | ||
```ts | ||
import web3 from 'micro-eth-signer/web3.js'; | ||
import contracts from 'micro-eth-signer/web3.js/contracts'; | ||
import web3net from 'micro-web3-net'; | ||
const DEF_CONTRACTS = contracts.DEFAULT_CONTRACTS; | ||
``` | ||
const berlinTx = new Transaction({ | ||
to: '0xdf90dea0e0bf5ca6d2a7f0cb86874ba6714f463e', | ||
maxFeePerGas: 100n * 10n ** 9n, // 100 gwei in wei | ||
maxPriorityFeePerGas: 1n * 10n ** 9n, // 1 gwei in wei | ||
value: 10n ** 18n, // 1 eth in wei | ||
nonce: 1, | ||
// the field can also be used in eip1559 txs | ||
accessList: [{ | ||
"address": "0x123456789a123456789a123456789a123456789a", | ||
"storageKeys": [ | ||
"0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" | ||
] | ||
}] | ||
}, undefined, undefined, 'eip2930'); | ||
The transaction sent ERC-20 USDT token between addresses: | ||
```ts | ||
import { hex } from '@scure/base'; | ||
const tx = | ||
'a9059cbb000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec70000000000000000000000000000000000000000000000000000000542598700'; | ||
const decoder = new web3.Decoder(); | ||
const USDT = contracts.tokenFromSymbol('USDT').contract; | ||
decoder.add(USDT, contracts.ERC20); | ||
const info = decoder.decode(USDT, hex.decode(tx), { contractInfo: DEF_CONTRACTS[USDT] }); | ||
console.log(info); | ||
// { name: 'transfer', signature: 'transfer(address,uint256)', | ||
// value: { to: '0xdac17f958d2ee523a2206206994597c13d831ec7', value: 22588000000n }, | ||
// hint: 'Transfer 22588 USDT to 0xdac17f958d2ee523a2206206994597c13d831ec7' } | ||
``` | ||
## API | ||
### Event decoding | ||
### Address | ||
```ts | ||
const BAT = '0x0d8775f648430679a709e98d2b0cb6250d2887ef'; | ||
const decoder = new web3.Decoder(); | ||
decoder.add(BAT, contracts.ERC20); | ||
const info = decoder.decodeEvent( | ||
BAT, | ||
[ | ||
'0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925', | ||
'0x000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045', | ||
'0x000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564', | ||
], | ||
'0x00000000000000000000000000000000000000000000003635c9adc5dea00000', | ||
{ contract: BAT, contracts: { ...DEF_CONTRACTS }, contractInfo: DEF_CONTRACTS[BAT] } | ||
); | ||
console.log(info.hint); | ||
// Allow 0xe592427a0aece92de3edee1f18e0157c05861564 spending up to 1000 BAT from 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 | ||
``` | ||
Represents ETH address and has following methods: | ||
### Call smart contracts | ||
- `Address.fromPrivateKey(privateKey: string | Uint8Array): string` - create address from private key | ||
- `Address.fromPublicKey(publicKey: string | Uint8Array): string` - creates address from public key | ||
- `Address.checksum(nonChecksummedAddress: string): string` - creates checksummed address from non-checksummed address | ||
- `Address.verifyChecksum(address: string): boolean` - verifies checksummed & non-checksummed address | ||
#### Fetch Chainlink oracle prices | ||
Usage: | ||
```ts | ||
import chainlink from 'micro-eth-signer/web3/api/chainlink'; | ||
const provider = new web3net.Web3({ | ||
url: 'https://nodes.mewapi.io/rpc/eth', | ||
headers: { Origin: 'https://www.myetherwallet.com' }, | ||
}); | ||
const btc = await chainlink.coinPrice(provider, 'BTC'); | ||
const bat = await chainlink.tokenPrice(provider, 'BAT'); | ||
console.log({ btc, bat }); // BTC 19188.68870991, BAT 0.39728989 in USD | ||
``` | ||
#### Uniswap | ||
Swap 12.12 USDT to BAT with uniswap V3 defaults of 0.5% slippage, 30 min expiration. | ||
```ts | ||
import univ2 from 'micro-eth-signer/web3/api/uniswap-v2'; | ||
import univ3 from 'micro-eth-signer/web3/api/uniswap-v3'; | ||
const provider = new web3net.Web3({ | ||
url: 'https://nodes.mewapi.io/rpc/eth', | ||
headers: { Origin: 'https://www.myetherwallet.com' }, | ||
}); | ||
const USDT = contracts.tokenFromSymbol('USDT'); | ||
const BAT = contracts.tokenFromSymbol('BAT'); | ||
const u3 = new univ3.UniswapV3(provider); // or new univ2.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); | ||
``` | ||
### Type inference | ||
The ABI is type-safe with 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 | ||
We're parsing values as: | ||
```js | ||
const addr = "0x0089d53f703f7e0843953d48133f74ce247184c2"; | ||
const addrc = Address.checksum(addr) // 0x0089d53F703f7E0843953D48133f74cE247184c2 | ||
Address.verifyChecksum(addrc) // true | ||
Address.verifyChecksum(addr) // true also (non-checksummed) | ||
Address.fromPrivateKey("0687640ee33ef844baba3329db9e16130bd1735cbae3657bd64aed25e9a5c377") | ||
// 0xD4fE407789e11a27b7888A324eC597435353dC35 | ||
Address.fromPublicKey("030fba7ba5cfbf8b00dd6f3024153fc44ddda93727da58c99326eb0edd08195cdb") | ||
// 0xD4fE407789e11a27b7888A324eC597435353dC35 | ||
// 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! | ||
``` | ||
### Transaction | ||
Check out [`src/api/ens.ts`](./src/api/ens.ts) for type-safe contract execution example. | ||
Represents unsigned & signed ETH transactions. They are serialized & deserialized using RLP. Here's an example of the same transaction in raw state, and serialized state: | ||
### Human-friendly field validation | ||
```ts | ||
import { validateField, validateFields } from 'micro-eth-signer/tx-validator'; | ||
``` | ||
### Formatters | ||
```ts | ||
export function parseDecimal(s: string, precision: number): bigint; | ||
export function formatDecimal(n: bigint, precision: number): string; | ||
export function perCentDecimal(precision: number, price: number): bigint; | ||
export function roundDecimal( | ||
n: bigint, | ||
roundPrecision: number, | ||
precision?: number, | ||
price?: number | ||
): bigint; | ||
export function fromWei(wei: string | number | bigint): string; | ||
export function formatUSD(amount: number): string; | ||
``` | ||
### Low-level transaction API | ||
Transaction represents unsigned & signed ETH transactions. They are serialized & deserialized using RLP. Here's an example of the same transaction in raw state, and serialized state: | ||
```js | ||
@@ -124,20 +222,20 @@ // raw | ||
- `new Transaction(serialized[, chain, hardfork, type])` - creates transaction from Raw TX string. | ||
- `chain`: optional argument (default is `mainnet`; `ropsten`, `rinkeby`, `goerli`, `kovan` etc) | ||
- `hardfork`: optional argument (default is `london`). The only place we're checking for `hardfork` | ||
is the replay protection code. There are very old transactions that don't support replay protection, | ||
you'll probably won't need them | ||
- `type`: optional argument (default is `eip1559`). Can be either `legacy`, `eip2930`, or `eip1559` | ||
(Berlin and London style transactions with access lists and `maxFeePerGas`/`maxPriorityFeePerGas`) | ||
- `chain`: optional argument (default is `mainnet`; `ropsten`, `rinkeby`, `goerli`, `kovan` etc) | ||
- `hardfork`: optional argument (default is `london`). The only place we're checking for `hardfork` | ||
is the replay protection code. There are very old transactions that don't support replay protection, | ||
you'll probably won't need them | ||
- `type`: optional argument (default is `eip1559`). Can be either `legacy`, `eip2930`, or `eip1559` | ||
(Berlin and London style transactions with access lists and `maxFeePerGas`/`maxPriorityFeePerGas`) | ||
- `new Transaction(rawTx[, chain, hardfork, type])` - creates transaction from Raw TX data. | ||
- `rawTx` must have fields `to`, `value`, `nonce`, `gasLimit` | ||
- `rawTx` must have `maxFeePerGas` (eip1559 txs) or `gasPrice` (berlin & legacy txs) | ||
- `to` is recipient's address | ||
- `value` is amount to send in wei | ||
- `nonce` is sender's nonce in number | ||
- `gasLimit` is transaction's Gas Limit in wei (minimum is `21000`) | ||
- `maxFeePerGas` is eip1559 transaction's max acceptable gas price in wei (100 gwei is `100 * 10 ** 9`). Not applicable to legacy transactions | ||
- `maxPriorityFeePerGas` is eip1559 transaction's max acceptable tip in wei. Not applicable to legacy transactions | ||
- `gasPrice` is legacy transaction's Gas Price in wei. Not applicable to eip1559 transactions | ||
- `data` is transaction's data if it's calling some smart contracts | ||
- `accessList` is transaction's Access List, a list of addresses that its smart contract call touches. Basically an array of strings: `["0x123...", "0x456..."]`. Not applicable to legacy transactions | ||
- `rawTx` must have fields `to`, `value`, `nonce`, `gasLimit` | ||
- `rawTx` must have `maxFeePerGas` (eip1559 txs) or `gasPrice` (berlin & legacy txs) | ||
- `to` is recipient's address | ||
- `value` is amount to send in wei | ||
- `nonce` is sender's nonce in number | ||
- `gasLimit` is transaction's Gas Limit in wei (minimum is `21000`) | ||
- `maxFeePerGas` is eip1559 transaction's max acceptable gas price in wei (100 gwei is `100 * 10 ** 9`). Not applicable to legacy transactions | ||
- `maxPriorityFeePerGas` is eip1559 transaction's max acceptable tip in wei. Not applicable to legacy transactions | ||
- `gasPrice` is legacy transaction's Gas Price in wei. Not applicable to eip1559 transactions | ||
- `data` is transaction's data if it's calling some smart contracts | ||
- `accessList` is transaction's Access List, a list of addresses that its smart contract call touches. Basically an array of strings: `["0x123...", "0x456..."]`. Not applicable to legacy transactions | ||
- `Transaction#sign(privateKey: string | Uint8Array): Transaction` â | ||
@@ -147,3 +245,3 @@ creates new transaction with same data, but signed by following private key | ||
##### Transaction Properties | ||
Transaction properties: | ||
@@ -154,3 +252,3 @@ - `isSigned: boolean` - whether tx is signed with private key | ||
- `amount: bigint` - amount (aka `value`) in wei | ||
- `fee: bigint` - fee in wei (`maxFeePerGas` * `gasLimit` or `gasPrice` * `gasLimit`) | ||
- `fee: bigint` - fee in wei (`maxFeePerGas` _ `gasLimit` or `gasPrice` _ `gasLimit`) | ||
- `upfrontCost: bigint` - amount + fee in wei, combined | ||
@@ -163,17 +261,16 @@ - `to: string` - address that receives the tx | ||
### Additional modules | ||
As an example, here's how to create legacy pre-eip1559 transaction: | ||
Those are optional: | ||
```ts | ||
import * as formatters from 'micro-eth-signer/formatters'; | ||
import { validateField, validateFields } from 'micro-eth-signer/tx-validator' | ||
// formatters: | ||
export function parseDecimal(s: string, precision: number): bigint; | ||
export function formatDecimal(n: bigint, precision: number): string; | ||
export function perCentDecimal(precision: number, price: number): bigint; | ||
export function roundDecimal(n: bigint, roundPrecision: number, precision?: number, price?: number): bigint; | ||
export function fromWei(wei: string | number | bigint): string; | ||
export function formatUSD(amount: number): string; | ||
const legacyTx = new Transaction( | ||
{ | ||
to: '0xdf90dea0e0bf5ca6d2a7f0cb86874ba6714f463e', | ||
gasPrice: 100n * 10n ** 9n, // 100 gwei in wei | ||
value: 10n ** 18n, // 1 eth in wei | ||
nonce: 1, | ||
}, | ||
undefined, | ||
undefined, | ||
'legacy' | ||
); | ||
``` | ||
@@ -180,0 +277,0 @@ |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
506356
94
10725
284
4
1
No
+ Addedmicro-packed@~0.5.0
+ Added@scure/base@1.1.6(transitive)
+ Addedmicro-packed@0.5.3(transitive)