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.
- package/.github/workflows/test.yml +28 -0
- package/README.md +5 -5
- package/default_cache_filename.js +18 -13
- package/index.d.ts +2 -2
- package/index.js +17 -18
- package/loader.js +45 -46
- package/package.json +5 -5
- /package/{calp-dark.svg → logo-dark.svg} +0 -0
- /package/{calp-light.svg → logo-light.svg} +0 -0
|
@@ -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="./
|
|
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> 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. | `
|
|
46
|
-
| `key` | `string` | Alchemy/RPC API Key (priority over ENV). | `
|
|
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
|
-
### `
|
|
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,
|
|
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
|
|
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 =
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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
|
|
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 = '
|
|
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
|
-
:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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.
|
|
112
|
+
module.exports.subscribe = (callback, params = {}) => {
|
|
114
113
|
params.update_timeout ??= 5000
|
|
115
|
-
var
|
|
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 (!
|
|
124
|
+
if (!subscribed) return
|
|
126
125
|
callback(pairs)
|
|
127
|
-
if (!
|
|
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 (!
|
|
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
|
-
|
|
139
|
+
subscribed = false
|
|
141
140
|
if (timeout) clearTimeout(timeout)
|
|
142
141
|
}
|
|
143
142
|
}
|
package/loader.js
CHANGED
|
@@ -1,44 +1,49 @@
|
|
|
1
|
-
const
|
|
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
|
-
:
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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].
|
|
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
|
|
22
|
-
|
|
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 = (
|
|
26
|
+
const get_tokens = (key, addresses) => addresses.length == 0
|
|
27
27
|
? Promise.resolve({})
|
|
28
|
-
:
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
jsonrpc: '2.0',
|
|
34
|
+
id: i * 2,
|
|
35
|
+
method: 'eth_call',
|
|
36
|
+
params: [{ to: address, data: '0x0dfe1681' }, 'latest']
|
|
34
37
|
},
|
|
35
38
|
{
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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(
|
|
77
|
-
get_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": "
|
|
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
|