micro-eth-signer
Advanced tools
Comparing version 0.8.1 to 0.9.0
{ | ||
"name": "micro-eth-signer", | ||
"version": "0.8.1", | ||
"version": "0.9.0", | ||
"description": "Minimal library for Ethereum transactions, addresses and smart contracts", | ||
"files": [ | ||
"lib", | ||
"src" | ||
], | ||
"main": "lib/index.js", | ||
"module": "lib/esm/index.js", | ||
"types": "lib/index.d.ts", | ||
"files": ["abi", "esm", "net", "src", "*.js", "*.d.ts", "*.js.map", "*.d.ts.map"], | ||
"main": "index.js", | ||
"module": "esm/index.js", | ||
"types": "index.d.ts", | ||
"dependencies": { | ||
"@noble/curves": "~1.4.0", | ||
"@noble/hashes": "~1.4.0", | ||
"micro-packed": "~0.5.3" | ||
"micro-packed": "~0.6.2" | ||
}, | ||
@@ -22,3 +19,5 @@ "devDependencies": { | ||
"prettier": "3.1.1", | ||
"typescript": "5.3.2" | ||
"snappyjs": "0.7.0", | ||
"typescript": "5.3.2", | ||
"yaml": "2.4.1" | ||
}, | ||
@@ -33,25 +32,35 @@ "author": "Paul Miller (https://paulmillr.com)", | ||
".": { | ||
"types": "./lib/index.d.ts", | ||
"import": "./lib/esm/index.js", | ||
"default": "./lib/index.js" | ||
"types": "./index.d.ts", | ||
"import": "./esm/index.js", | ||
"default": "./index.js" | ||
}, | ||
"./abi": { | ||
"types": "./lib/abi/index.d.ts", | ||
"import": "./lib/esm/abi/index.js", | ||
"default": "./lib/abi/index.js" | ||
"types": "./abi/index.d.ts", | ||
"import": "./esm/abi/index.js", | ||
"default": "./abi/index.js" | ||
}, | ||
"./net": { | ||
"types": "./lib/net/index.d.ts", | ||
"import": "./lib/esm/net/index.js", | ||
"default": "./lib/net/index.js" | ||
"types": "./net/index.d.ts", | ||
"import": "./esm/net/index.js", | ||
"default": "./net/index.js" | ||
}, | ||
"./rlp": { | ||
"types": "./net/rlp.d.ts", | ||
"import": "./esm/net/rlp.js", | ||
"default": "./net/rlp.js" | ||
}, | ||
"./ssz": { | ||
"types": "./net/ssz.d.ts", | ||
"import": "./esm/net/ssz.js", | ||
"default": "./net/ssz.js" | ||
}, | ||
"./tx": { | ||
"types": "./lib/tx.d.ts", | ||
"import": "./lib/esm/tx.js", | ||
"default": "./lib/tx.js" | ||
"types": "./tx.d.ts", | ||
"import": "./esm/tx.js", | ||
"default": "./tx.js" | ||
}, | ||
"./utils": { | ||
"types": "./lib/utils.d.ts", | ||
"import": "./lib/esm/utils.js", | ||
"default": "./lib/utils.js" | ||
"types": "./utils.d.ts", | ||
"import": "./esm/utils.js", | ||
"default": "./utils.js" | ||
} | ||
@@ -58,0 +67,0 @@ }, |
249
README.md
@@ -5,11 +5,10 @@ # micro-eth-signer | ||
- 🔓 Secure: 3 deps, audited [noble](https://paulmillr.com/noble/) cryptography | ||
- 🔓 Secure: 3 deps, audited [noble](https://paulmillr.com/noble/) cryptography, no network code | ||
- 🔻 Tree-shaking-friendly: use only what's necessary, other code won't be included | ||
- 🌍 No network code: simplified auditing and offline usage | ||
- 🔍 Unique tests: 150MB of test vectors from EIPs, ethers and viem | ||
- ✍️ Create and sign transactions, generate and checksum addresses | ||
- 📖 Human-readable hints for transactions and events | ||
- 🔍 Reliable: 150MB of test vectors from EIPs, ethers and viem | ||
- ✍️ Create, sign and decode transactions using human-readable hints | ||
- 🌍 Fetch balances and history from an archive node | ||
- 🆎 Call smart contracts: Chainlink and Uniswap APIs are included | ||
- 🦺 Typescript-friendly ABI and RLP decoding | ||
- 🪶 3000 lines for everything | ||
- 🦺 Typescript-friendly ABI, RLP and SSZ decoding | ||
- 🪶 1200 lines for core functionality | ||
@@ -29,8 +28,6 @@ Check out article [ZSTs, ABIs, stolen keys and broken legs](https://github.com/paulmillr/micro-eth-signer/discussions/20) about caveats of secure ABI parsing found during development of the library. | ||
- [Create and sign transactions](#create-and-sign-transactions) | ||
- [Create and checksum addresses](#create-and-checksum-addresses) | ||
- [Generate random keys and addresses](#generate-random-keys-and-addresses) | ||
- [Human-readable hints](#human-readable-hints) | ||
- [Decoding transactions](#decoding-transactions) | ||
- [Decoding events](#decoding-events) | ||
- [Transactions: create, sign](#create-and-sign-transactions) | ||
- [Addresses: create, checksum](#create-and-checksum-addresses) | ||
- [Generate random wallet](#generate-random-keys-and-addresses) | ||
- [Fetch balances and history from an archive node](#fetch-balances-and-history-from-an-archive-node) | ||
- [Call smart contracts](#call-smart-contracts) | ||
@@ -40,3 +37,7 @@ - [Fetch Chainlink oracle prices](#fetch-chainlink-oracle-prices) | ||
- [ABI type inference](#abi-type-inference) | ||
- [RLP parsing](#rlp-parsing) | ||
- Parsing | ||
- [Human-readable transaction hints](#human-readable-transaction-hints) | ||
- [Human-readable event hints](#human-readable-event-hints) | ||
- [RLP parsing](#rlp-parsing) | ||
- [SSZ parsing](#ssz-parsing) | ||
- [Sign and verify messages](#sign-and-verify-messages) | ||
@@ -47,3 +48,3 @@ - [Security](#security) | ||
### Create and sign transactions | ||
### Transactions: create, sign | ||
@@ -68,3 +69,3 @@ ```ts | ||
### Create and checksum addresses | ||
### Addresses: create, checksum | ||
@@ -79,4 +80,4 @@ ```ts | ||
checksummedAddress, // 0x0089d53F703f7E0843953D48133f74cE247184c2 | ||
addr.verifyChecksum(checksummedAddress), // true | ||
addr.verifyChecksum(nonChecksummedAddress), // also true | ||
addr.isValid(checksummedAddress), // true | ||
addr.isValid(nonChecksummedAddress), // also true | ||
addr.fromPrivateKey(priv), | ||
@@ -87,3 +88,3 @@ addr.fromPublicKey(pub) | ||
### Generate random keys and addresses | ||
### Generate random wallet | ||
@@ -98,91 +99,39 @@ ```ts | ||
### Human-readable hints | ||
### Fetch balances and history from an archive node | ||
#### Decoding transactions | ||
The transaction sent ERC-20 USDT token between addresses. The library produces a following hint: | ||
> Transfer 22588 USDT to 0xdac17f958d2ee523a2206206994597c13d831ec7 | ||
```ts | ||
import { decodeTx } from 'micro-eth-signer/abi'; | ||
import { ArchiveNodeProvider, FetchProvider } from 'micro-eth-signer/net'; | ||
const RPC_URL = 'http://localhost:8545'; // URL to any RPC | ||
const rpc = new FetchProvider(globalThis.fetch, RPC_URL); // use built-in fetch() | ||
const prov = new ArchiveNodeProvider(rpc); | ||
const addr = '0xd8da6bf26964af9d7eed9e03e53415d37aa96045'; | ||
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', | ||
}); | ||
``` | ||
const block = await prov.blockInfo(await prov.height()); | ||
console.log('current block', block.number, block.timestamp, block.baseFeePerGas); | ||
console.log('info for addr', addr, await prov.unspent(addr)); | ||
Or if you have already decoded tx: | ||
// ENS example: | ||
// import { ENS } from 'micro-eth-signer/net'; const ens = new ENS(rpc), addr = await ens.nameToAddress('vitalik.eth'); | ||
```ts | ||
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', | ||
}); | ||
// Other methods of ArchiveNodeProvider: | ||
// blockInfo(block: number): Promise<BlockInfo>; // {baseFeePerGas, hash, timestamp...} | ||
// height(): Promise<number>; | ||
// internalTransactions(address: string, opts?: TraceOpts): Promise<any[]>; | ||
// ethLogsSingle(topics: Topics, opts: LogOpts): Promise<Log[]>; | ||
// ethLogs(topics: Topics, opts?: LogOpts): Promise<Log[]>; | ||
// tokenTransfers(address: string, opts?: LogOpts): Promise<[Log[], Log[]]>; | ||
// wethTransfers(address: string, opts?: LogOpts): Promise<[Log[]]>; | ||
// txInfo(txHash: string, opts?: TxInfoOpts): Promise<{ | ||
// type: "legacy" | "eip2930" | "eip1559" | "eip4844"; info: any; receipt: any; raw: string | undefined; | ||
// }>; | ||
// tokenInfo(address: string): Promise<TokenInfo | undefined>; | ||
// transfers(address: string, opts?: TraceOpts & LogOpts): Promise<TxTransfers[]>; | ||
// allowances(address: string, opts?: LogOpts): Promise<TxAllowances>; | ||
// tokenBalances(address: string, tokens: string[]): Promise<Record<string, bigint>>; | ||
``` | ||
#### Decoding events | ||
Basic data can be fetched from any node. | ||
Decoding the event produces the following hint: | ||
Historical balances, transactions and others can only be fetched from an archive node, such as Erigon or Reth. | ||
> Allow 0xe592427a0aece92de3edee1f18e0157c05861564 spending up to 1000 BAT from 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 | ||
```ts | ||
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 | ||
@@ -289,11 +238,109 @@ | ||
### RLP parsing | ||
### Parsers | ||
#### Human-readable transaction hints | ||
The transaction sent ERC-20 USDT token between addresses. The library produces a following hint: | ||
> Transfer 22588 USDT to 0xdac17f958d2ee523a2206206994597c13d831ec7 | ||
```ts | ||
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: | ||
```ts | ||
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', | ||
}); | ||
``` | ||
#### Human-readable event hints | ||
Decoding the event produces the following hint: | ||
> Allow 0xe592427a0aece92de3edee1f18e0157c05861564 spending up to 1000 BAT from 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 | ||
```ts | ||
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); | ||
``` | ||
#### RLP parsing | ||
We implement RLP in just 100 lines of code, powered by [packed](https://github.com/paulmillr/micro-packed): | ||
```ts | ||
import { RLP } from 'micro-eth-signer/tx'; | ||
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](https://github.com/paulmillr/micro-packed): | ||
```ts | ||
import * as ssz from 'micro-eth-signer/ssz'; | ||
``` | ||
### Sign and verify messages | ||
@@ -300,0 +347,0 @@ |
import { keccak_256 } from '@noble/hashes/sha3'; | ||
import { concatBytes } from '@noble/hashes/utils'; | ||
import { bytesToHex, concatBytes, hexToBytes } from '@noble/hashes/utils'; | ||
import * as P from 'micro-packed'; | ||
import { Web3CallArgs, Web3Provider, add0x, strip0x, omit, zip } from '../utils.js'; | ||
import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; | ||
@@ -18,15 +17,13 @@ /* | ||
} | ||
// Save pointers values next to array. ETH only stuff. | ||
// By some reason, ptr value inside arrays is placed right after ptr. | ||
// This should work by default without 'wrappedArray'. | ||
// TODO: Debug more later. | ||
function wrappedArray<T>(len: P.Length, inner: P.CoderType<T>): P.CoderType<T[]> { | ||
// Main difference between regular array: length stored outside and offsets calculated without length | ||
function ethArray<T>(inner: P.CoderType<T>): P.CoderType<T[]> { | ||
return P.wrap({ | ||
size: typeof len === 'number' && inner.size ? len * inner.size : undefined, | ||
size: undefined, | ||
encodeStream: (w: P.Writer, value: T[]) => { | ||
w.length(len, value.length); | ||
U256BE_LEN.encodeStream(w, value.length); | ||
w.bytes(P.array(value.length, inner).encode(value)); | ||
}, | ||
decodeStream: (r: P.Reader): T[] => | ||
P.array(r.length(len), inner).decodeStream(r.offsetReader(r.pos)), | ||
P.array(U256BE_LEN.decodeStream(r), inner).decodeStream(r.offsetReader(r.pos)), | ||
}); | ||
@@ -43,15 +40,15 @@ } | ||
const inner = P.bigint(32, false, signed); | ||
return P.wrap({ | ||
size: inner.size, | ||
encodeStream: (w: P.Writer, value: bigint) => { | ||
const _value = BigInt(value); | ||
P.checkBounds(w, _value, _bits, !!signed); | ||
inner.encodeStream(w, BigInt(_value)); | ||
}, | ||
decodeStream: (r: P.Reader): bigint => { | ||
const value = inner.decodeStream(r); | ||
P.checkBounds(r, value, _bits, !!signed); | ||
return P.validate( | ||
P.wrap({ | ||
size: inner.size, | ||
encodeStream: (w: P.Writer, value: bigint) => inner.encodeStream(w, value), | ||
decodeStream: (r: P.Reader): bigint => inner.decodeStream(r), | ||
}), | ||
(value) => { | ||
// TODO: validate useful for narrowing types, need to add support in types? | ||
if (typeof value === 'number') value = BigInt(value); | ||
P.utils.checkBounds(value, _bits, !!signed); | ||
return value; | ||
}, | ||
}); | ||
} | ||
); | ||
}; | ||
@@ -175,3 +172,3 @@ | ||
// Dynamic array | ||
return P.pointer(PTR, wrappedArray(U256BE_LEN, inner)) as any; | ||
return P.pointer(PTR, ethArray(inner)) as any; | ||
} | ||
@@ -205,3 +202,3 @@ } | ||
return P.pointer(PTR, P.padRight(32, P.bytes(U256BE_LEN), P.ZeroPad)) as any; | ||
if (c.type === 'address') return EPad(P.hex(20, false, true)) as any; | ||
if (c.type === 'address') return EPad(P.hex(20, { isLE: false, with0x: true })) as any; | ||
if (c.type === 'bool') return EPad(P.bool) as any; | ||
@@ -208,0 +205,0 @@ if ((m = /^(u?)int([0-9]+)?$/.exec(c.type))) |
@@ -9,5 +9,2 @@ /*! micro-eth-signer - MIT License (c) 2021 Paul Miller (paulmillr.com) */ | ||
RE: /^(0[xX])?([0-9a-fA-F]{40})?$/, | ||
// Not much support, only RSK for now | ||
EIP1991_CHAINS: [30n, 31n], | ||
parse(address: string) { | ||
@@ -25,11 +22,2 @@ astr(address); | ||
isValid(address: string) { | ||
try { | ||
const a = addr.parse(address); | ||
return a && a.hasPrefix; | ||
} catch (error) { | ||
return false; | ||
} | ||
}, | ||
/** | ||
@@ -41,9 +29,5 @@ * Address checksum is calculated by hashing with keccak_256. | ||
*/ | ||
addChecksum(nonChecksummedAddress: string, chainId = 1n): string { | ||
if (typeof chainId !== 'bigint') | ||
throw new Error(`address.addChecksum wrong chainId=${chainId}`); | ||
const hasEIP1191 = addr.EIP1991_CHAINS.includes(chainId); | ||
addChecksum(nonChecksummedAddress: string): string { | ||
const low = addr.parse(nonChecksummedAddress).data.toLowerCase(); | ||
const hashInput = hasEIP1191 ? `${chainId}0x${low}` : low; | ||
const hash = bytesToHex(keccak_256(hashInput)); | ||
const hash = bytesToHex(keccak_256(low)); | ||
let checksummed = ''; | ||
@@ -89,9 +73,16 @@ for (let i = 0; i < low.length; i++) { | ||
*/ | ||
verifyChecksum(checksummedAddress: string, chainId = 1n): boolean { | ||
const { data: address } = addr.parse(checksummedAddress); | ||
isValid(checksummedAddress: string): boolean { | ||
let parsed: { hasPrefix: boolean; data: string }; | ||
try { | ||
parsed = addr.parse(checksummedAddress); | ||
} catch (error) { | ||
return false; | ||
} | ||
const { data: address, hasPrefix } = parsed; | ||
if (!hasPrefix) return false; | ||
const low = address.toLowerCase(); | ||
const upp = address.toUpperCase(); | ||
if (address === low || address === upp) return true; | ||
return addr.addChecksum(low, chainId) === checksummedAddress; | ||
return addr.addChecksum(low) === checksummedAddress; | ||
}, | ||
}; |
import Chainlink from './chainlink.js'; | ||
import ENS from './ens.js'; | ||
import FetchProvider from './provider.js'; | ||
import { ArchiveNodeProvider, calcTransfersDiff } from './archive.js'; | ||
import UniswapV2 from './uniswap-v2.js'; | ||
import UniswapV3 from './uniswap-v3.js'; | ||
import { Web3Provider, Web3CallArgs, hexToNumber } from '../utils.js'; | ||
// There are many low level APIs inside which are not exported yet. | ||
export { Chainlink, ENS, UniswapV2, UniswapV3 }; | ||
export const FetchProvider = ( | ||
fetch: (url: string, opt?: Record<string, any>) => Promise<{ json: () => Promise<any> }>, | ||
url: string, | ||
headers: Record<string, string> = {} | ||
): Web3Provider => { | ||
const jsonrpc = async (method: string, ...params: any[]) => { | ||
const res = await fetch(url, { | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/json', ...headers }, | ||
body: JSON.stringify({ jsonrpc: '2.0', id: 0, method, params }), | ||
}); | ||
const json = await res.json(); | ||
if (json && json.error) | ||
throw new Error(`FetchProvider(${json.error.code}): ${json.error.message}`); | ||
return json.result; | ||
}; | ||
return { | ||
ethCall: (args: Web3CallArgs, tag = 'latest') => | ||
jsonrpc('eth_call', args, tag) as Promise<string>, | ||
estimateGas: async (args: Web3CallArgs, tag = 'latest') => | ||
hexToNumber(await jsonrpc('eth_estimateGas', args, tag)), | ||
}; | ||
export { | ||
ArchiveNodeProvider, | ||
calcTransfersDiff, | ||
Chainlink, | ||
ENS, | ||
FetchProvider, | ||
UniswapV2, | ||
UniswapV3, | ||
}; |
@@ -1,2 +0,2 @@ | ||
import { Web3Provider, ethHex, ethDecimal, createDecimal } from '../utils.js'; | ||
import { Web3Provider, ethHex, ethDecimal, isBytes, createDecimal } from '../utils.js'; | ||
import { addr } from '../index.js'; | ||
@@ -49,3 +49,3 @@ import { tokenFromSymbol } from '../abi/index.js'; | ||
if (Array.isArray(o)) return o.map((i) => traverse(i)); | ||
if (o instanceof Uint8Array) return o; // TODO: replace with isBytes | ||
if (isBytes(o)) return o; | ||
if (isPromise(o)) return { awaitDeep: promises.push(o) }; | ||
@@ -69,3 +69,3 @@ if (typeof o === 'object') { | ||
if (Array.isArray(o)) return o.map((i) => trBack(i)); | ||
if (o instanceof Uint8Array) return o; // TODO: replace with isBytes | ||
if (isBytes(o)) return o; | ||
if (typeof o === 'object') { | ||
@@ -72,0 +72,0 @@ if (typeof o === 'object' && o.awaitDeep) return values[o.awaitDeep - 1]; |
@@ -74,3 +74,2 @@ import { concatBytes } from '@noble/hashes/utils'; | ||
for (let fee2 in Fee) { | ||
// TODO: replace with ethHex | ||
let path = [wA, packFee(fee1), c.contract, packFee(fee2), wB].map((i) => ethHex.decode(i)); | ||
@@ -77,0 +76,0 @@ if (exactOutput) path = path.reverse(); |
123
src/tx.ts
@@ -1,108 +0,8 @@ | ||
import { numberToVarBytesBE } from '@noble/curves/abstract/utils'; | ||
import * as P from 'micro-packed'; | ||
import { addr } from './address.js'; | ||
import { amounts, ethHex } from './utils.js'; | ||
import { RLP } from './rlp.js'; | ||
import { isBytes, amounts, ethHex } from './utils.js'; | ||
// Transaction parsers | ||
// Spec-compliant RLP in 100 lines of code. | ||
export type RLPInput = string | number | Uint8Array | bigint | RLPInput[] | null; | ||
// length: first 3 bit !== 111 ? 6 bit length : 3bit lenlen | ||
const RLPLength = P.wrap({ | ||
encodeStream(w: P.Writer, value: number) { | ||
if (value < 56) return w.bits(value, 6); | ||
w.bits(0b111, 3); | ||
const length = P.U32BE.encode(value); | ||
let pos = 0; | ||
for (; pos < length.length; pos++) if (length[pos] !== 0) break; | ||
w.bits(4 - pos - 1, 3); | ||
w.bytes(length.slice(pos)); | ||
}, | ||
decodeStream(r: P.Reader): number { | ||
const start = r.bits(3); | ||
if (start !== 0b111) return (start << 3) | r.bits(3); | ||
const len = r.bytes(r.bits(3) + 1); | ||
for (let i = 0; i < len.length; i++) { | ||
if (len[i]) break; | ||
throw new Error('Wrong length encoding with leading zeros'); | ||
} | ||
const res = P.int(len.length).decode(len); | ||
if (res <= 55) throw new Error('RLPLength: less than 55, but used multi-byte flag'); | ||
return res; | ||
}, | ||
}); | ||
// Recursive struct definition | ||
export type InternalRLP = | ||
| { TAG: 'byte'; data: number } | ||
| { | ||
TAG: 'complex'; | ||
data: { TAG: 'string'; data: Uint8Array } | { TAG: 'list'; data: InternalRLP[] }; | ||
}; | ||
const rlpInner = P.tag(P.map(P.bits(1), { byte: 0, complex: 1 }), { | ||
byte: P.bits(7), | ||
complex: P.tag(P.map(P.bits(1), { string: 0, list: 1 }), { | ||
string: P.bytes(RLPLength), | ||
list: P.prefix( | ||
RLPLength, | ||
P.array( | ||
null, | ||
P.lazy((): P.CoderType<InternalRLP> => rlpInner) | ||
) | ||
), | ||
}), | ||
}); | ||
const phex = P.hex(null); | ||
const pstr = P.string(null); | ||
const empty = Uint8Array.from([]); | ||
/** | ||
* RLP parser. | ||
* Real type of rlp is `Item = Uint8Array | Item[]`. | ||
* Strings/number encoded to Uint8Array, but not decoded back: type information is lost. | ||
*/ | ||
export const RLP = P.apply(rlpInner, { | ||
encode(from: InternalRLP): RLPInput { | ||
if (from.TAG === 'byte') return new Uint8Array([from.data]); | ||
if (from.TAG !== 'complex') throw new Error('RLP.encode: unexpected type'); | ||
const complex = from.data; | ||
if (complex.TAG === 'string') { | ||
if (complex.data.length === 1 && complex.data[0] < 128) | ||
throw new Error('RLP.encode: wrong string length encoding, should use single byte mode'); | ||
return complex.data; | ||
} | ||
if (complex.TAG === 'list') return complex.data.map((i) => this.encode(i)); | ||
throw new Error('RLP.encode: unknown TAG'); | ||
}, | ||
decode(data: RLPInput): InternalRLP { | ||
if (data == null) return this.decode(empty); | ||
switch (typeof data) { | ||
case 'object': | ||
if (P.isBytes(data)) { | ||
if (data.length === 1) { | ||
const head = data[0]; | ||
if (head < 128) return { TAG: 'byte', data: head }; | ||
} | ||
return { TAG: 'complex', data: { TAG: 'string', data: data } }; | ||
} | ||
if (Array.isArray(data)) | ||
return { TAG: 'complex', data: { TAG: 'list', data: data.map((i) => this.decode(i)) } }; | ||
throw new Error('RLP.encode: unknown type'); | ||
case 'number': | ||
if (data < 0) throw new Error('RLP.encode: invalid integer as argument, must be unsigned'); | ||
if (data === 0) return this.decode(empty); | ||
return this.decode(numberToVarBytesBE(data)); | ||
case 'bigint': | ||
if (data < 0n) throw new Error('RLP.encode: invalid integer as argument, must be unsigned'); | ||
return this.decode(numberToVarBytesBE(data)); | ||
case 'string': | ||
return this.decode(data.startsWith('0x') ? phex.encode(data) : pstr.encode(data)); | ||
default: | ||
throw new Error('RLP.encode: unknown type'); | ||
} | ||
}, | ||
}); | ||
export type AnyCoder = Record<string, P.Coder<any, any>>; | ||
@@ -200,3 +100,3 @@ export type AnyCoderStream = Record<string, P.CoderType<any>>; | ||
function ensureBlob(hash: Uint8Array): Uint8Array { | ||
if (!P.isBytes(hash) || hash.length !== 32) | ||
if (!isBytes(hash) || hash.length !== 32) | ||
throw new Error('blobVersionedHashes must contain 32-byte Uint8Array-s'); | ||
@@ -216,3 +116,3 @@ return hash; | ||
// - allows to keep legacy logic here, instead of copying to Transaction | ||
const legacySig = { | ||
export const legacySig = { | ||
encode: (data: VRS) => { | ||
@@ -433,6 +333,6 @@ const { v, r, s } = data; | ||
encode: (data) => { | ||
data.data.to = addr.addChecksum(data.data.to, data.data.chainId); | ||
data.data.to = addr.addChecksum(data.data.to); | ||
if (data.type !== 'legacy' && data.data.accessList) { | ||
for (const item of data.data.accessList) { | ||
item[0] = addr.addChecksum(item[0], data.data.chainId); | ||
item[0] = addr.addChecksum(item[0]); | ||
} | ||
@@ -508,5 +408,4 @@ } | ||
}, | ||
to(address: string, { data }: ValidationOpts) { | ||
const chainId = typeof data.chainId === 'bigint' ? data.chainId : undefined; | ||
if (!addr.verifyChecksum(address, chainId)) throw new Error('address checksum does not match'); | ||
to(address: string) { | ||
if (!addr.isValid(address)) throw new Error('address checksum does not match'); | ||
}, | ||
@@ -529,8 +428,6 @@ value(num: bigint) { | ||
}, | ||
accessList(list: [string, string[]][], { data }: ValidationOpts) { | ||
accessList(list: [string, string[]][]) { | ||
// NOTE: we cannot handle this validation in coder, since it requires chainId to calculate correct checksum | ||
const chainId = typeof data.chainId === 'bigint' ? data.chainId : undefined; | ||
for (const [address, _] of list) { | ||
if (!addr.verifyChecksum(address, chainId)) | ||
throw new Error('address checksum does not match'); | ||
if (!addr.isValid(address)) throw new Error('address checksum does not match'); | ||
} | ||
@@ -537,0 +434,0 @@ }, |
@@ -1,4 +0,6 @@ | ||
import { hexToBytes as _hexToBytes, bytesToHex } from '@noble/hashes/utils'; | ||
import { isBytes as _isBytes, hexToBytes as _hexToBytes, bytesToHex } from '@noble/hashes/utils'; | ||
import { Coder, coders } from 'micro-packed'; | ||
export const isBytes = _isBytes; | ||
// There is no network code in the library. | ||
@@ -20,2 +22,3 @@ // The types are used to check external network provider interfaces. | ||
estimateGas: (args: Web3CallArgs) => Promise<bigint>; | ||
call: (method: string, ...args: any[]) => Promise<any>; | ||
}; | ||
@@ -22,0 +25,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
1197398
173
18054
392
1
7
+ Addedmicro-packed@0.6.3(transitive)
- Removedmicro-packed@0.5.3(transitive)
Updatedmicro-packed@~0.6.2