uniswap-v2-loader 1.1.0 → 1.2.0
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.
- package/CHANGELOG.md +2 -0
- package/README.md +31 -10
- package/index.js +88 -21
- package/loader.js +8 -5
- package/package.json +1 -1
- package/test.js +18 -1
package/CHANGELOG.md
ADDED
package/README.md
CHANGED
|
@@ -7,12 +7,26 @@ The package uses Alchemy. Set your key as an environment variable (a default key
|
|
|
7
7
|
`export KEY=your_alchemy_key`
|
|
8
8
|
|
|
9
9
|
## API Reference
|
|
10
|
-
### `all()`
|
|
11
|
-
- **Description**: Fetches
|
|
12
|
-
- **
|
|
10
|
+
### `all(params)`
|
|
11
|
+
- **Description**: Fetches token pairs from the Uniswap v2 factory. It utilizes multicall from `viem` and splits the loading process between multiple CPUs for high-speed execution.
|
|
12
|
+
- **Arguments**:
|
|
13
|
+
- `params`: (Object)
|
|
14
|
+
- `from`: (number) Start loading from this index (default 0).
|
|
15
|
+
- `to`: (number) Load up to this index.
|
|
16
|
+
- `filename`: (string) Path to cache CSV file.
|
|
17
|
+
- `chunk_size`: (number) Items per multicall (default 50).
|
|
18
|
+
- **Returns**: `Promise<Array<Object>>` (Array of Pair Objects)
|
|
13
19
|
|
|
14
|
-
###
|
|
15
|
-
|
|
20
|
+
### `onupdate(callback, params)`
|
|
21
|
+
- **Description**: Subscribes to new pairs appearing at the factory contract. It initially calls the callback with cached pairs and then polls for updates.
|
|
22
|
+
- **Arguments**:
|
|
23
|
+
- `callback`: (function) Called with an array of Pair Objects.
|
|
24
|
+
- `params`: (Object) Same as `all()` plus:
|
|
25
|
+
- `update_timeout`: (number) Polling interval in ms (default 5000).
|
|
26
|
+
- **Returns**: `Function` An unsubscribe function to stop polling.
|
|
27
|
+
|
|
28
|
+
### Pair Object
|
|
29
|
+
The pair object contains the following fields:
|
|
16
30
|
- `id`: (number) The pair index.
|
|
17
31
|
- `pair`: (string) The pair contract address.
|
|
18
32
|
- `token0`: (string) The address of the first token in the pair.
|
|
@@ -20,11 +34,18 @@ The function returns a `Promise` resolving to an array of objects with the follo
|
|
|
20
34
|
|
|
21
35
|
## Usage
|
|
22
36
|
```javascript
|
|
23
|
-
const { all } = require('uniswap-v2-loader')
|
|
37
|
+
const { all, onupdate } = require('uniswap-v2-loader')
|
|
24
38
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
39
|
+
// Load initial set
|
|
40
|
+
all({to: 10}).then(pairs =>
|
|
41
|
+
pairs.forEach(({id, pair}) => console.log(`ID: ${id} | Pair: ${pair}`))
|
|
29
42
|
)
|
|
43
|
+
|
|
44
|
+
// Subscribe to new pairs (24/7 monitoring)
|
|
45
|
+
const unsubscribe = onupdate(pairs => {
|
|
46
|
+
console.log(`Pools count: ${pairs.length}`)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// Stop monitoring if needed
|
|
50
|
+
// unsubscribe()
|
|
30
51
|
```
|
package/index.js
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
const { spawn } = require('child_process')
|
|
2
|
-
const os = require('os')
|
|
3
2
|
const fs = require('fs')
|
|
3
|
+
const os = require('os')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const env = process.env
|
|
6
|
+
const home = os.homedir()
|
|
7
|
+
const pkg = require('./package.json')
|
|
8
|
+
const default_filename = path.join(
|
|
9
|
+
...(process.platform === 'win32'
|
|
10
|
+
? (env.LOCALAPPDATA || env.APPDATA)
|
|
11
|
+
? [env.LOCALAPPDATA || env.APPDATA]
|
|
12
|
+
: [home, 'AppData', 'Local']
|
|
13
|
+
: process.platform === 'darwin'
|
|
14
|
+
? [home, 'Library', 'Caches']
|
|
15
|
+
: (env.XDG_CACHE_HOME && path.isAbsolute(env.XDG_CACHE_HOME))
|
|
16
|
+
? [env.XDG_CACHE_HOME]
|
|
17
|
+
: [home, '.cache']
|
|
18
|
+
),
|
|
19
|
+
pkg.name + '_pairs.csv'
|
|
20
|
+
)
|
|
4
21
|
const { parseAbiItem, createPublicClient, http } = require('viem')
|
|
5
22
|
const { mainnet } = require('viem/chains')
|
|
23
|
+
|
|
6
24
|
const workers = os.cpus().length - 1
|
|
7
25
|
const missed = Array(workers).fill(null).map(() => [])
|
|
8
26
|
const key = process.env.KEY || 'FZBvlPrOxtgaKBBkry3SH0W1IqH4Y5tu'
|
|
@@ -13,12 +31,24 @@ const client = createPublicClient({
|
|
|
13
31
|
})
|
|
14
32
|
|
|
15
33
|
const load = params => {
|
|
16
|
-
const {filename, to} = params
|
|
17
|
-
const
|
|
34
|
+
const {filename = default_filename, to, from = 0, chunk_size = 50} = params
|
|
35
|
+
const pairs = params.pairs || fs.existsSync(filename)
|
|
18
36
|
? fs.readFileSync(filename).toString().trim().split('\n')
|
|
19
|
-
.
|
|
37
|
+
.reduce((pairs, line) => {
|
|
38
|
+
line = line.split(',')
|
|
39
|
+
const id = +line[0]
|
|
40
|
+
if (id >= from && (to == undefined || id <= to)) pairs.push({
|
|
41
|
+
id,
|
|
42
|
+
pair: line[1],
|
|
43
|
+
token0: line[2],
|
|
44
|
+
token1: line[3]
|
|
45
|
+
})
|
|
46
|
+
return pairs
|
|
47
|
+
}, [])
|
|
20
48
|
: []
|
|
21
49
|
|
|
50
|
+
if (pairs.length > to) return Promise.resolve(pairs.slice(0, to))
|
|
51
|
+
|
|
22
52
|
return (to
|
|
23
53
|
? Promise.resolve(to)
|
|
24
54
|
: client.readContract({
|
|
@@ -27,23 +57,24 @@ const load = params => {
|
|
|
27
57
|
functionName: 'allPairsLength'
|
|
28
58
|
}).then(_ => Number(_))
|
|
29
59
|
).then(allPairsLength => {
|
|
30
|
-
const pairs = Array(allPairsLength)
|
|
31
60
|
var next_pair_order = 0
|
|
32
|
-
|
|
33
|
-
?
|
|
61
|
+
const start_loading_from = pairs.length
|
|
62
|
+
? Math.max(from || 0, pairs[pairs.length - 1].id + 1)
|
|
34
63
|
: 0
|
|
35
64
|
|
|
36
65
|
missed.forEach(_ => _.length = 0)
|
|
37
66
|
|
|
38
|
-
for (var i =
|
|
67
|
+
for (var i = start_loading_from, rr = 0; i < allPairsLength; i++) {
|
|
39
68
|
missed[rr].push(i)
|
|
40
|
-
|
|
69
|
+
if (missed[rr].length % chunk_size == 0)
|
|
70
|
+
rr = (rr + 1) % workers
|
|
41
71
|
}
|
|
42
|
-
|
|
72
|
+
|
|
43
73
|
const jobs_data_filename = `jobs_data_${Date.now()}.json`
|
|
44
74
|
fs.writeFileSync(jobs_data_filename, JSON.stringify({
|
|
45
75
|
missed,
|
|
46
76
|
factory,
|
|
77
|
+
chunk_size,
|
|
47
78
|
key
|
|
48
79
|
}), 'utf8')
|
|
49
80
|
|
|
@@ -52,16 +83,21 @@ const load = params => {
|
|
|
52
83
|
.filter(_ => _.length)
|
|
53
84
|
.map((_, i) => new Promise(y => {
|
|
54
85
|
const loader = spawn('node', ['loader.js', jobs_data_filename, i.toString()])
|
|
55
|
-
loader.stdout.on('data',
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
86
|
+
loader.stdout.on('data', data => {
|
|
87
|
+
data += data.toString()
|
|
88
|
+
if (!data.includes('\n')) return
|
|
89
|
+
const lines = data.split('\n')
|
|
90
|
+
data = lines.shift()
|
|
91
|
+
lines.forEach(line => {
|
|
92
|
+
const a = line.split(',')
|
|
93
|
+
const id = +a[0]
|
|
94
|
+
pairs[id] = {
|
|
95
|
+
id,
|
|
96
|
+
pair: a[1],
|
|
97
|
+
token0: a[2],
|
|
98
|
+
token1: a[3]
|
|
99
|
+
}
|
|
100
|
+
})
|
|
65
101
|
if (filename) {
|
|
66
102
|
var pair
|
|
67
103
|
while (pair = pairs[next_pair_order]) {
|
|
@@ -81,5 +117,36 @@ const load = params => {
|
|
|
81
117
|
}
|
|
82
118
|
|
|
83
119
|
|
|
84
|
-
module.exports.all = params =>
|
|
120
|
+
module.exports.all = (params = {}) =>
|
|
121
|
+
load(params)
|
|
122
|
+
|
|
123
|
+
module.exports.onupdate = function onupdate(callback, params = {}) {
|
|
124
|
+
var subscribe = true, timeout
|
|
85
125
|
load(params)
|
|
126
|
+
.then(pairs => {
|
|
127
|
+
callback(pairs)
|
|
128
|
+
|
|
129
|
+
const update = pairs =>
|
|
130
|
+
timeout = setTimeout(
|
|
131
|
+
() =>
|
|
132
|
+
load({...params, pairs, from: pairs.length})
|
|
133
|
+
.then(pairs => {
|
|
134
|
+
if (!subscribe) return
|
|
135
|
+
callback(pairs)
|
|
136
|
+
if (!subscribe) return
|
|
137
|
+
if (params.to && pairs[pairs.length - 1].id >= params.to) return
|
|
138
|
+
update(pairs)
|
|
139
|
+
}),
|
|
140
|
+
params.update_timeout || 5000
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if (!subscribe) return
|
|
144
|
+
if (params.to && pairs[pairs.length - 1].id >= params.to) return
|
|
145
|
+
update(pairs)
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
return () => {
|
|
149
|
+
subscribe = false
|
|
150
|
+
if (timeout) clearTimeout(timeout)
|
|
151
|
+
}
|
|
152
|
+
}
|
package/loader.js
CHANGED
|
@@ -68,14 +68,17 @@ async function load(params) {
|
|
|
68
68
|
pool[POOL.ADDRESS] = pools_ok[j].address.toLowerCase()
|
|
69
69
|
pool[POOL.TOKEN0] = token0_result.result.toLowerCase()
|
|
70
70
|
pool[POOL.TOKEN1] = token1_result.result.toLowerCase()
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
console.log(pool.join(','))
|
|
73
73
|
} else {
|
|
74
74
|
retry_missed.push(pools_ok[j].id)
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
} catch (
|
|
78
|
-
|
|
77
|
+
} catch (_) {
|
|
78
|
+
chunk.forEach(id => {
|
|
79
|
+
if (retry_missed.includes(id)) return
|
|
80
|
+
retry_missed.push(id)
|
|
81
|
+
})
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
|
|
@@ -88,7 +91,7 @@ async function load(params) {
|
|
|
88
91
|
const jobs_data_filename = process.argv[2]
|
|
89
92
|
const job_index = +process.argv[3]
|
|
90
93
|
|
|
91
|
-
if (isNaN(job_index))
|
|
94
|
+
if (isNaN(job_index)) process.exit(1)
|
|
92
95
|
|
|
93
96
|
const jobs_data = JSON.parse(fs.readFileSync(jobs_data_filename, 'utf8'))
|
|
94
97
|
jobs_data.missed = jobs_data.missed[job_index]
|
|
@@ -98,4 +101,4 @@ const client = createPublicClient({
|
|
|
98
101
|
transport: http(`https://eth-mainnet.g.alchemy.com/v2/${jobs_data.key}`)
|
|
99
102
|
})
|
|
100
103
|
|
|
101
|
-
load({client, ...jobs_data
|
|
104
|
+
load({client, ...jobs_data})
|
package/package.json
CHANGED
package/test.js
CHANGED
|
@@ -28,4 +28,21 @@ test('Load first two pairs to file', () => {
|
|
|
28
28
|
.finally(() =>
|
|
29
29
|
fs.unlinkSync(filename)
|
|
30
30
|
)
|
|
31
|
-
})
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('onupdate should call provided callback with 2 pairs for a current moment (from cache)', () => {
|
|
34
|
+
return new Promise(y => {
|
|
35
|
+
const unsubscribe = uniswap_v2_loader.onupdate(pairs => {
|
|
36
|
+
assert.equal(pairs.length, 2)
|
|
37
|
+
unsubscribe()
|
|
38
|
+
y()
|
|
39
|
+
}, {to: 2})
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test('Heavy test load first 3000 pairs', () =>
|
|
44
|
+
uniswap_v2_loader.all({to: 3000})
|
|
45
|
+
.then(pairs => {
|
|
46
|
+
assert.equal(pairs.length, 3000)
|
|
47
|
+
})
|
|
48
|
+
)
|