uniswap-v2-loader 4.0.2 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ name: Test Uniswap V2 loader
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+ branches: [ main ]
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ test:
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - name: Checkout code
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Setup Node.js
19
+ uses: actions/setup-node@v4
20
+ with:
21
+ node-version: '22'
22
+ cache: 'npm'
23
+
24
+ - name: Global install package CLI version
25
+ run: npm i -g .
26
+
27
+ - name: Load first 4 pairs from Uniswap V2 using 2 workers
28
+ run: uniswap-v2-loader --to=4 --multicall_size=2 --workers=2
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # <picture><source media="(prefers-color-scheme: dark)" srcset="./calp-dark.svg"><img alt="calp.pro icon" src="./calp-light.svg" height="32" align="absmiddle"></picture>&nbsp;&nbsp;&nbsp;uniswap-v2-loader
1
+ # <picture><source media="(prefers-color-scheme: dark)" srcset="./logo-dark.svg"><img alt="calp.pro icon" src="./logo-light.svg" height="32" align="absmiddle"></picture>&nbsp;&nbsp;&nbsp;uniswap-v2-loader
2
2
 
3
3
  <br>
4
4
 
@@ -42,8 +42,8 @@ High-performance parallel fetcher for liquidity pairs. Efficiently synchronizes
42
42
  | `from` | `number` | Start loading from this pair index. | `0` |
43
43
  | `to` | `number` | End index (exclusive). Required for range loading. | `undefined` |
44
44
  | `filename` | `string` | Local CSV cache path. Supports OS-standard locations. | *Auto-detected* |
45
- | `factory` | `string` | Smart contract factory address. | `Uniswap V2` |
46
- | `key` | `string` | Alchemy/RPC API Key (priority over ENV). | `process.env.KEY` |
45
+ | `factory` | `string` | Smart contract factory address. | `0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f` |
46
+ | `key` | `string` | Alchemy/RPC API Key (priority over ENV). | `FZBvlPrOxtgaKBBkry3SH0W1IqH4Y5tu` |
47
47
  | `multicall_size` | `number` | RPC batch size per multicall request. | `50` |
48
48
  | `workers` | `number` | Number of parallel worker threads. | `CPU - 1` |
49
49
  | `progress` | `function` | Progress callback: `(current, total) => {}`. | `undefined` |
@@ -74,7 +74,7 @@ Cache files are named following the pattern `${package_name}_{factory_address}.c
74
74
 
75
75
  ---
76
76
 
77
- ### `onupdate(callback, params)`
77
+ ### `subscribe(callback, params)`
78
78
  Continuous synchronization engine. Performs initial load and subsequently polls for new pairs.
79
79
 
80
80
  **Parameters**
@@ -102,7 +102,7 @@ Standardized liquidity pool object.
102
102
 
103
103
  ## Usage Example
104
104
  ```javascript
105
- const { load, onupdate } = require('uniswap-v2-loader')
105
+ const { load, subscribe } = require('uniswap-v2-loader')
106
106
  const rl = require('readline')
107
107
 
108
108
 
@@ -1,19 +1,24 @@
1
1
  const path = require('path')
2
- const env = process.env
2
+ const {GITHUB_ACTIONS, XDG_CACHE_HOME, LOCALAPPDATA, APPDATA} = process.env
3
+ const fs = require('fs')
3
4
  const os = require('os')
4
5
  const home = os.homedir()
5
6
  const pkg = require('./package.json')
6
7
 
7
- module.exports = (factory) => path.join(
8
- ...(process.platform === 'win32'
9
- ? (env.LOCALAPPDATA || env.APPDATA)
10
- ? [env.LOCALAPPDATA || env.APPDATA]
11
- : [home, 'AppData', 'Local']
12
- : process.platform === 'darwin'
13
- ? [home, 'Library', 'Caches']
14
- : (env.XDG_CACHE_HOME && path.isAbsolute(env.XDG_CACHE_HOME))
15
- ? [env.XDG_CACHE_HOME]
16
- : [home, '.cache']
17
- ),
18
- `${pkg.name}_${factory.toLowerCase()}.csv`
8
+ module.exports = factory => path.join(
9
+ ...(GITHUB_ACTIONS
10
+ ? ['.']
11
+ : process.platform == 'win32'
12
+ ? LOCALAPPDATA || APPDATA
13
+ ? [LOCALAPPDATA || APPDATA]
14
+ : [home, 'AppData', 'Local']
15
+ : process.platform == 'darwin'
16
+ ? [home, 'Library', 'Caches']
17
+ : XDG_CACHE_HOME && path.isAbsolute(XDG_CACHE_HOME) && fs.existsSync(XDG_CACHE_HOME)
18
+ ? [XDG_CACHE_HOME]
19
+ : fs.existsSync(path.join(home, '.cache'))
20
+ ? [home, '.cache']
21
+ : [os.tmpdir()]
22
+ ),
23
+ `${pkg.name}_${factory.toLowerCase()}.csv`
19
24
  )
package/index.d.ts CHANGED
@@ -47,11 +47,11 @@ export function load(params?: load_params): Promise<pair[]>
47
47
 
48
48
  /**
49
49
  * Subscribes to new pairs being added to the factory.
50
- * @param callback Called whenever new pairs are loaded.
50
+ * @param callback Called with total available data first and then whenever new pairs added to factory.
51
51
  * @param params Loading configuration.
52
52
  * @returns An unsubscribe function.
53
53
  */
54
- export function onupdate(
54
+ export function subscribe(
55
55
  callback: (pairs: pair[]) => void,
56
56
  params?: load_params
57
57
  ): () => void
package/index.js CHANGED
@@ -2,12 +2,10 @@ const cluster = require('cluster')
2
2
  const fs = require('fs')
3
3
  const os = require('os')
4
4
  const path = require('path')
5
- const { parseAbiItem, createPublicClient, http } = require('viem')
6
- const { mainnet } = require('viem/chains')
7
5
  const default_cache_filename = require('./default_cache_filename')
8
6
  const max_workers = os.cpus().length - 1
9
7
  const debug_key = process.env.KEY || 'FZBvlPrOxtgaKBBkry3SH0W1IqH4Y5tu'
10
- const uniswap_v2_factory = '0x5c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f'
8
+ const uniswap_v2_factory = '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f'
11
9
 
12
10
  const load = (params = {}) => {
13
11
  var {
@@ -22,10 +20,6 @@ const load = (params = {}) => {
22
20
  pairs,
23
21
  } = params
24
22
  filename ??= default_cache_filename(factory)
25
- const client = createPublicClient({
26
- chain: mainnet,
27
- transport: http('https://eth-mainnet.g.alchemy.com/v2/' + key)
28
- })
29
23
 
30
24
  pairs ??= fs.existsSync(filename)
31
25
  ? fs.readFileSync(filename).toString().trim().split('\n')
@@ -47,11 +41,16 @@ const load = (params = {}) => {
47
41
 
48
42
  return (to
49
43
  ? Promise.resolve(to)
50
- : client.readContract({
51
- address: factory,
52
- abi: [parseAbiItem('function allPairsLength() view returns (uint256)')],
53
- functionName: 'allPairsLength'
54
- }).then(_ => Number(_))
44
+ : fetch('https://eth-mainnet.g.alchemy.com/v2/' + key, {
45
+ method: 'POST',
46
+ headers: { 'Content-Type': 'application/json' },
47
+ body: JSON.stringify({
48
+ jsonrpc: '2.0',
49
+ id: 1,
50
+ method: 'eth_call',
51
+ params: [{ to: factory, data: '0x574f2ba3' }, 'latest']
52
+ })
53
+ }).then(_ => _.json()).then(_ => Number(_.result))
55
54
  ).then(all_pairs_length => {
56
55
  const start_loading_from = pairs.length
57
56
  ? Math.max(from || 0, pairs[pairs.length - 1].id + 1)
@@ -110,9 +109,9 @@ const load = (params = {}) => {
110
109
  module.exports.load = (params = {}) =>
111
110
  load(params)
112
111
 
113
- module.exports.onupdate = function onupdate(callback, params = {}) {
112
+ module.exports.subscribe = (callback, params = {}) => {
114
113
  params.update_timeout ??= 5000
115
- var subscribe = true, timeout
114
+ var subscribed = true, timeout
116
115
  load(params)
117
116
  .then(pairs => {
118
117
  callback(pairs)
@@ -122,22 +121,22 @@ module.exports.onupdate = function onupdate(callback, params = {}) {
122
121
  () =>
123
122
  load({...params, pairs, from: pairs.length})
124
123
  .then(pairs => {
125
- if (!subscribe) return
124
+ if (!subscribed) return
126
125
  callback(pairs)
127
- if (!subscribe) return
126
+ if (!subscribed) return
128
127
  if (params.to && pairs[pairs.length - 1].id >= params.to) return
129
128
  update(pairs)
130
129
  }),
131
130
  params.update_timeout
132
131
  )
133
132
 
134
- if (!subscribe) return
133
+ if (!subscribed) return
135
134
  if (params.to && pairs[pairs.length - 1].id >= params.to) return
136
135
  update(pairs)
137
136
  })
138
137
 
139
138
  return () => {
140
- subscribe = false
139
+ subscribed = false
141
140
  if (timeout) clearTimeout(timeout)
142
141
  }
143
142
  }
package/loader.js CHANGED
@@ -1,44 +1,49 @@
1
- const { parseAbiItem, createPublicClient, http } = require('viem')
2
- const { mainnet } = require('viem/chains')
3
-
4
- const get_pairs_addresses = (client, factory, ids) => ids.length == 0
1
+ const get_pairs_addresses = (key, factory, ids) => ids.length == 0
5
2
  ? Promise.resolve([])
6
- : client.multicall({
7
- contracts: ids.map(id => ({
8
- address: factory,
9
- abi: [parseAbiItem('function allPairs(uint256) view returns (address)')],
10
- functionName: 'allPairs',
11
- args: [BigInt(id)]
12
- }))
13
- }).then(responds => {
3
+ : fetch('https://eth-mainnet.g.alchemy.com/v2/' + key, {
4
+ method: 'POST',
5
+ headers: { 'Content-Type': 'application/json' },
6
+ body: JSON.stringify(ids.map((id, i) => ({
7
+ jsonrpc: '2.0',
8
+ id: i,
9
+ method: 'eth_call',
10
+ params: [{ to: factory, data: '0x1e3dd18b' + id.toString(16).padStart(64, '0') }, 'latest']
11
+ })))
12
+ }).then(_ => _.json()).then(responds => {
13
+ responds.sort((a, b) => a.id - b.id)
14
14
  const addresses = []
15
15
  const failed_ids = []
16
16
  for (var i = 0; i < responds.length; i++)
17
- responds[i].status == 'success'
18
- ? addresses.push(responds[i].result)
19
- : failed_ids.push(i)
17
+ responds[i].result
18
+ ? addresses.push('0x' + responds[i].result.slice(-40))
19
+ : failed_ids.push(ids[i])
20
20
 
21
- return get_pairs_addresses(client, factory, failed_ids).then(retried_addresses =>
22
- [...addresses, ...retried_addresses]
23
- )
21
+ return failed_ids.length == 0
22
+ ? addresses
23
+ : get_pairs_addresses(key, factory, failed_ids).then(retried => [...addresses, ...retried])
24
24
  })
25
25
 
26
- const get_tokens = (client, addresses) => addresses.length == 0
26
+ const get_tokens = (key, addresses) => addresses.length == 0
27
27
  ? Promise.resolve({})
28
- : client.multicall({
29
- contracts: addresses.flatMap(address => [
28
+ : fetch('https://eth-mainnet.g.alchemy.com/v2/' + key, {
29
+ method: 'POST',
30
+ headers: { 'Content-Type': 'application/json' },
31
+ body: JSON.stringify(addresses.flatMap((address, i) => [
30
32
  {
31
- address,
32
- abi: [parseAbiItem('function token0() view returns (address)')],
33
- functionName: 'token0'
33
+ jsonrpc: '2.0',
34
+ id: i * 2,
35
+ method: 'eth_call',
36
+ params: [{ to: address, data: '0x0dfe1681' }, 'latest']
34
37
  },
35
38
  {
36
- address,
37
- abi: [parseAbiItem('function token1() view returns (address)')],
38
- functionName: 'token1'
39
+ jsonrpc: '2.0',
40
+ id: i * 2 + 1,
41
+ method: 'eth_call',
42
+ params: [{ to: address, data: '0xd21220a7' }, 'latest']
39
43
  }
40
- ])
41
- }).then(responds => {
44
+ ]))
45
+ }).then(_ => _.json()).then(responds => {
46
+ responds.sort((a, b) => a.id - b.id)
42
47
  const tokens = {}
43
48
  const failed_addresses = []
44
49
 
@@ -46,35 +51,29 @@ const get_tokens = (client, addresses) => addresses.length == 0
46
51
  const token0_respond = responds[i * 2]
47
52
  const token1_respond = responds[i * 2 + 1]
48
53
 
49
- if (
50
- token0_respond.status == 'success' &&
51
- token1_respond.status == 'success'
52
- )
53
- tokens[addresses[i]] = [token0_respond.result, token1_respond.result]
54
+ if (token0_respond.result && token1_respond.result)
55
+ tokens[addresses[i]] = [
56
+ '0x' + token0_respond.result.slice(-40),
57
+ '0x' + token1_respond.result.slice(-40)
58
+ ]
54
59
  else
55
60
  failed_addresses.push(addresses[i])
56
61
  }
57
-
58
- return get_tokens(client, failed_addresses).then(retried_tokens => ({
59
- ...tokens,
60
- ...retried_tokens
61
- }))
62
- })
63
62
 
64
- const main = ({ids, factory, key, multicall_size}, onpair) => {
65
- const client = createPublicClient({
66
- chain: mainnet,
67
- transport: http(`https://eth-mainnet.g.alchemy.com/v2/${key}`)
63
+ return failed_addresses.length == 0
64
+ ? tokens
65
+ : get_tokens(key, failed_addresses).then(retried => ({ ...tokens, ...retried }))
68
66
  })
69
67
 
68
+ const main = ({ids, factory, key, multicall_size}, onpair) => {
70
69
  const chunks = []
71
70
  for (let i = 0; i < ids.length; i += multicall_size)
72
71
  chunks.push(ids.slice(i, i + multicall_size))
73
72
 
74
73
  return chunks.reduce((p, ids, ic) =>
75
74
  p.then(() =>
76
- get_pairs_addresses(client, factory, ids).then(pairs_addresses =>
77
- get_tokens(client, pairs_addresses).then(tokens =>
75
+ get_pairs_addresses(key, factory, ids).then(pairs_addresses =>
76
+ get_tokens(key, pairs_addresses).then(tokens =>
78
77
  ids.forEach((id, i) =>
79
78
  onpair({
80
79
  id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniswap-v2-loader",
3
- "version": "4.0.2",
3
+ "version": "5.0.1",
4
4
  "description": "Uniswap v2 protocol loader",
5
5
  "keywords": [
6
6
  "uniswap-v2",
@@ -10,7 +10,10 @@
10
10
  "ethereum",
11
11
  "defi",
12
12
  "sushiswap",
13
- "pancakeswap"
13
+ "pancakeswap",
14
+ "shibaswap",
15
+ "dex",
16
+ "exchange"
14
17
  ],
15
18
  "homepage": "https://github.com/calp-pro/uniswap-v2-loader#readme",
16
19
  "bugs": {
@@ -30,8 +33,5 @@
30
33
  },
31
34
  "bin": {
32
35
  "uniswap-v2-loader": "./bin/uniswap-v2-loader"
33
- },
34
- "dependencies": {
35
- "viem": "^2.46.2"
36
36
  }
37
37
  }
File without changes
File without changes