coinscan-sdk 0.1.0__tar.gz

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,8 @@
1
+ .venv/
2
+ node_modules/
3
+ dist/
4
+ __pycache__/
5
+ *.pyc
6
+ .pytest_cache/
7
+ *.egg-info/
8
+ .DS_Store
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PIRB
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,211 @@
1
+ Metadata-Version: 2.5
2
+ Name: coinscan-sdk
3
+ Version: 0.1.0
4
+ Summary: Official Python client for the CoinScan API: token scans, holders, bubble maps, wallets, charts and market data for Solana, Ethereum, BSC and Base.
5
+ Project-URL: Homepage, https://coinscan.tech
6
+ Project-URL: Documentation, https://coinscan.tech/docs/sdks
7
+ Project-URL: API reference, https://coinscan.tech/docs/reference
8
+ Project-URL: Pricing, https://coinscan.tech/pricing
9
+ Author-email: PIRB <contact@pirb.tech>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: api,base,bsc,coinscan,crypto,ethereum,honeypot,pump.fun,rug check,sdk,solana,token scanner
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Topic :: Office/Business :: Financial :: Investment
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.9
21
+ Requires-Dist: httpx<1,>=0.24
22
+ Requires-Dist: typing-extensions>=4.5; python_version < '3.11'
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
25
+ Requires-Dist: pytest>=8; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # coinscan-sdk
29
+
30
+ Official Python client for the [CoinScan API](https://coinscan.tech): scan any token on Solana, Ethereum, BSC, Base and more for price, liquidity, holders, snipers, bundles, LP lock, honeypot and tax checks, dev history and an AI rug estimate; map holders and bubble clusters; read wallet PnL; render charts; pull market and launchpad data.
31
+
32
+ ```bash
33
+ pip install coinscan-sdk
34
+ ```
35
+
36
+ Imported as `coinscan`. Python 3.9+. One dependency (`httpx`). Sync and async clients, typed responses, automatic retries on rate limits, and your remaining credits after every call.
37
+
38
+ ## Quickstart
39
+
40
+ Get a free key (25 scans a day) at [coinscan.tech/signup](https://coinscan.tech/signup), or from Python:
41
+
42
+ ```python
43
+ from coinscan import CoinScan
44
+
45
+ print(CoinScan.signup(email="you@example.com")["api_key"]) # shown once
46
+ ```
47
+
48
+ ```python
49
+ from coinscan import CoinScan
50
+
51
+ cs = CoinScan("cs_live_...") # or set COINSCAN_API_KEY
52
+ scan = cs.scan("6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN")
53
+
54
+ print(scan["name"], scan["market"]["mcap"], scan["market"]["liquidity"])
55
+ print("bundles", scan["holders"]["bundle_pct"], "snipers", scan["holders"]["sniper_pct"])
56
+ print("rug probability", scan["ai_predictions"]["p_rug"])
57
+ print(cs.last_usage.remaining, "credits left today")
58
+ ```
59
+
60
+ ### Async
61
+
62
+ ```python
63
+ import asyncio
64
+ from coinscan import AsyncCoinScan
65
+
66
+ async def main():
67
+ async with AsyncCoinScan() as cs:
68
+ scans = await asyncio.gather(*(cs.scan(a) for a in ["6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN",
69
+ "0x6982508145454Ce325dDbE47a25d4ec3d2311933"]))
70
+ for s in scans:
71
+ print(s["symbol"], s["flags"])
72
+
73
+ asyncio.run(main())
74
+ ```
75
+
76
+ ## What you can call
77
+
78
+ | method | what it returns | credits |
79
+ |-|-|-|
80
+ | `cs.scan(address, chain=None)` | full token scan (dict) | 10 |
81
+ | `cs.scan_lp_lock(address)` | finishes a scan made with `lp_defer=True` | 6 |
82
+ | `cs.detect(address)` | `{"address", "chain"}` from the address shape | 0 |
83
+ | `cs.resolve_ticker("PEPE")` | candidate contracts ranked by liquidity | 2 |
84
+ | `cs.resolve_pair(chain, pair)` | the token behind a pool | 0 |
85
+ | `cs.major("BTC")` | price, cap, supply, ATH of a major coin | 0 |
86
+ | `cs.mcap_batch([addr, ...])` | `{address: mcap}` for up to 500 tokens | 1 |
87
+ | `cs.max_mcap_batch(items)` | highest cap since a timestamp | 3 |
88
+ | `cs.lore(address)`, `cs.community(address)` | backstory; X community stats | 4 |
89
+ | `cs.search("cat")` | DexScreener pairs | 0 |
90
+ | `cs.chart(network, pool, plot_type="ta")` | PNG bytes | 3 |
91
+ | `cs.subscan.holders(token, chain=)` | top holders report | 1 |
92
+ | `cs.subscan.holders_map(token, chain=)` | holders by behaviour | 1 |
93
+ | `cs.subscan.whale_map(token, chain=)` | holders by size | 2 |
94
+ | `cs.subscan.bubble_map(token, chain=)` | `{"messages", "image": PNG bytes}` | 5 |
95
+ | `cs.subscan.early_buyers(token, chain=)` | first 50 buyers and what they did | 2 |
96
+ | `cs.subscan.wallet_map(token, chain=)` | holders labelled bot, insider, smart money | 1 |
97
+ | `cs.subscan.kol_holders(token)` | influencer wallets holding (Solana) | 1 |
98
+ | `cs.subscan.top_holder_bags(token)` | top holders' other pump.fun bags | 1 |
99
+ | `cs.subscan.overlap(token1, token2)` | wallets holding both (Solana) | 2 |
100
+ | `cs.subscan.dev(address, chain=)` | deployer launch history | 4 |
101
+ | `cs.subscan.top_traders(token, chain=)` | traders by realized PnL (list of dicts) | 1 |
102
+ | `cs.subscan.wallet_stats(wallet)` | 30-day stats across chains | 3 |
103
+ | `cs.subscan.pumpfun_meta()` | trending pump.fun words | 0 |
104
+ | `cs.wallet.pnl(wallet, chain=)` | PnL report | 5 |
105
+ | `cs.wallet.floor(token, chain=)` | holders' entry caps, supply in loss | 1 |
106
+ | `cs.wallet.token_balance(wallet, token, chain=)` | float | 1 |
107
+ | `cs.wallet.token_balances(token, wallets, chain=)` | `{wallet: balance}` | 1 |
108
+ | `cs.wallet.tx(hash, chain=)` | `{"from", "to", "value_wei", "block_number"}` | 1 |
109
+ | `cs.market.macro()`, `.news()`, `.calendar()`, `.gas()` | market data | 0 |
110
+ | `cs.market.dex_paid()`, `.dex_trending()`, `.dex_paid_status(token)` | DexScreener signals | 0 |
111
+ | `cs.market.pumpfun_graduates()`, `.pumpfun_search(q)` | pump.fun | 0 |
112
+ | `cs.market.treemap()`, `.top_coins_card()`, `.fear_greed_image()`, `.pnl_card(...)` | PNG bytes | 0 |
113
+ | `cs.launchpads.summary()`, `.stats()`, `.chart()`, `.heat()` | launchpad volume and heat | 0 |
114
+ | `cs.account.me()`, `.usage()`, `.rotate_key()` | your plan, usage, keys | 0 |
115
+ | `cs.billing.plans()`, `.create_invoice(...)`, `.wait_for_payment(id)` | plans and USDT/USDC invoices | 0 |
116
+
117
+ Field-by-field documentation: [coinscan.tech/docs](https://coinscan.tech/docs).
118
+
119
+ ## Text reports
120
+
121
+ Holder, wallet and deployer routes return formatted reports (Telegram-style HTML), ready to post in a bot. `to_text` turns them into plain text:
122
+
123
+ ```python
124
+ from coinscan import CoinScan, to_text
125
+
126
+ cs = CoinScan()
127
+ print(to_text(cs.subscan.dev("6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN")))
128
+ ```
129
+
130
+ ## Images
131
+
132
+ ```python
133
+ from pathlib import Path
134
+
135
+ Path("chart.png").write_bytes(cs.chart("solana", "PAIR_ADDRESS", timeframe="hour", plot_type="ta"))
136
+
137
+ bubble = cs.subscan.bubble_map("6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN")
138
+ if bubble["image"]:
139
+ Path("bubble.png").write_bytes(bubble["image"])
140
+ ```
141
+
142
+ ## Credits, limits and errors
143
+
144
+ `cs.last_usage` holds the usage headers of the last metered call: `cost`, `limit`, `remaining`, `reset` (next 00:00 UTC), `rate_limit`, `rate_remaining`.
145
+
146
+ ```python
147
+ from coinscan import CoinScan, QuotaExceededError, NotFoundError, UpstreamTimeoutError
148
+
149
+ cs = CoinScan()
150
+ try:
151
+ scan = cs.scan("So11111111111111111111111111111111111111112")
152
+ except NotFoundError:
153
+ print("no such token")
154
+ except QuotaExceededError as e:
155
+ print("out of credits until", e.reset_at, "upgrade:", e.upgrade_url)
156
+ except UpstreamTimeoutError:
157
+ print("a data provider was slow; try again")
158
+ ```
159
+
160
+ | exception | when |
161
+ |-|-|
162
+ | `AuthenticationError` | 401: missing or unknown key |
163
+ | `NotFoundError` | 404: token, pair or data not found |
164
+ | `ValidationError` | 422: bad input |
165
+ | `RateLimitError` | 429 per-minute limit (retried automatically, then raised; has `retry_after`) |
166
+ | `QuotaExceededError` | 429 daily credits spent (`reset_at`, `upgrade_url`) |
167
+ | `UpstreamTimeoutError` | a 45 s upstream deadline passed |
168
+ | `InternalServerError` | 5xx (502/503/504 retried; credits are refunded) |
169
+ | `APIConnectionError`, `APITimeoutError` | network problems |
170
+
171
+ All inherit from `CoinScanError`. Retries: `CoinScan(max_retries=2)` by default; `0` turns them off. The default timeout is 90 s because a scan can take up to 60.
172
+
173
+ ## Paying from code
174
+
175
+ ```python
176
+ cs = CoinScan()
177
+ inv = cs.billing.create_invoice("starter", chain="solana", token="USDC")
178
+ print(f"send exactly {inv['amount']} {inv['token']} to {inv['pay_to']}")
179
+ print(inv["payment_uri"]) # Solana Pay / EIP-681 link
180
+ paid = cs.billing.wait_for_payment(inv["id"])
181
+ print(paid["status"], paid.get("plan_expires_at"))
182
+ ```
183
+
184
+ ## Command line
185
+
186
+ ```bash
187
+ export COINSCAN_API_KEY=cs_live_...
188
+ coinscan scan 6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN
189
+ coinscan holders 6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN
190
+ coinscan chart solana PAIR_ADDRESS --plot-type ta > chart.png
191
+ coinscan me
192
+ ```
193
+
194
+ ## Configuration
195
+
196
+ | setting | argument | environment variable | default |
197
+ |-|-|-|-|
198
+ | API key | `CoinScan(api_key)` | `COINSCAN_API_KEY` | none |
199
+ | base URL | `base_url=` | `COINSCAN_BASE_URL` | `https://api.coinscan.tech` |
200
+ | timeout | `timeout=` | | 90 s |
201
+ | retries | `max_retries=` | | 2 |
202
+ | HTTP client | `http_client=httpx.Client(...)` | | one per client |
203
+
204
+ ## Links
205
+
206
+ - Docs: https://coinscan.tech/docs
207
+ - Pricing: https://coinscan.tech/pricing (free 25 scans a day; from $50 a month, paid in USDT or USDC)
208
+ - MCP server for Claude and Cursor: https://coinscan.tech/docs/mcp
209
+ - Support: contact@pirb.tech
210
+
211
+ MIT licensed.
@@ -0,0 +1,184 @@
1
+ # coinscan-sdk
2
+
3
+ Official Python client for the [CoinScan API](https://coinscan.tech): scan any token on Solana, Ethereum, BSC, Base and more for price, liquidity, holders, snipers, bundles, LP lock, honeypot and tax checks, dev history and an AI rug estimate; map holders and bubble clusters; read wallet PnL; render charts; pull market and launchpad data.
4
+
5
+ ```bash
6
+ pip install coinscan-sdk
7
+ ```
8
+
9
+ Imported as `coinscan`. Python 3.9+. One dependency (`httpx`). Sync and async clients, typed responses, automatic retries on rate limits, and your remaining credits after every call.
10
+
11
+ ## Quickstart
12
+
13
+ Get a free key (25 scans a day) at [coinscan.tech/signup](https://coinscan.tech/signup), or from Python:
14
+
15
+ ```python
16
+ from coinscan import CoinScan
17
+
18
+ print(CoinScan.signup(email="you@example.com")["api_key"]) # shown once
19
+ ```
20
+
21
+ ```python
22
+ from coinscan import CoinScan
23
+
24
+ cs = CoinScan("cs_live_...") # or set COINSCAN_API_KEY
25
+ scan = cs.scan("6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN")
26
+
27
+ print(scan["name"], scan["market"]["mcap"], scan["market"]["liquidity"])
28
+ print("bundles", scan["holders"]["bundle_pct"], "snipers", scan["holders"]["sniper_pct"])
29
+ print("rug probability", scan["ai_predictions"]["p_rug"])
30
+ print(cs.last_usage.remaining, "credits left today")
31
+ ```
32
+
33
+ ### Async
34
+
35
+ ```python
36
+ import asyncio
37
+ from coinscan import AsyncCoinScan
38
+
39
+ async def main():
40
+ async with AsyncCoinScan() as cs:
41
+ scans = await asyncio.gather(*(cs.scan(a) for a in ["6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN",
42
+ "0x6982508145454Ce325dDbE47a25d4ec3d2311933"]))
43
+ for s in scans:
44
+ print(s["symbol"], s["flags"])
45
+
46
+ asyncio.run(main())
47
+ ```
48
+
49
+ ## What you can call
50
+
51
+ | method | what it returns | credits |
52
+ |-|-|-|
53
+ | `cs.scan(address, chain=None)` | full token scan (dict) | 10 |
54
+ | `cs.scan_lp_lock(address)` | finishes a scan made with `lp_defer=True` | 6 |
55
+ | `cs.detect(address)` | `{"address", "chain"}` from the address shape | 0 |
56
+ | `cs.resolve_ticker("PEPE")` | candidate contracts ranked by liquidity | 2 |
57
+ | `cs.resolve_pair(chain, pair)` | the token behind a pool | 0 |
58
+ | `cs.major("BTC")` | price, cap, supply, ATH of a major coin | 0 |
59
+ | `cs.mcap_batch([addr, ...])` | `{address: mcap}` for up to 500 tokens | 1 |
60
+ | `cs.max_mcap_batch(items)` | highest cap since a timestamp | 3 |
61
+ | `cs.lore(address)`, `cs.community(address)` | backstory; X community stats | 4 |
62
+ | `cs.search("cat")` | DexScreener pairs | 0 |
63
+ | `cs.chart(network, pool, plot_type="ta")` | PNG bytes | 3 |
64
+ | `cs.subscan.holders(token, chain=)` | top holders report | 1 |
65
+ | `cs.subscan.holders_map(token, chain=)` | holders by behaviour | 1 |
66
+ | `cs.subscan.whale_map(token, chain=)` | holders by size | 2 |
67
+ | `cs.subscan.bubble_map(token, chain=)` | `{"messages", "image": PNG bytes}` | 5 |
68
+ | `cs.subscan.early_buyers(token, chain=)` | first 50 buyers and what they did | 2 |
69
+ | `cs.subscan.wallet_map(token, chain=)` | holders labelled bot, insider, smart money | 1 |
70
+ | `cs.subscan.kol_holders(token)` | influencer wallets holding (Solana) | 1 |
71
+ | `cs.subscan.top_holder_bags(token)` | top holders' other pump.fun bags | 1 |
72
+ | `cs.subscan.overlap(token1, token2)` | wallets holding both (Solana) | 2 |
73
+ | `cs.subscan.dev(address, chain=)` | deployer launch history | 4 |
74
+ | `cs.subscan.top_traders(token, chain=)` | traders by realized PnL (list of dicts) | 1 |
75
+ | `cs.subscan.wallet_stats(wallet)` | 30-day stats across chains | 3 |
76
+ | `cs.subscan.pumpfun_meta()` | trending pump.fun words | 0 |
77
+ | `cs.wallet.pnl(wallet, chain=)` | PnL report | 5 |
78
+ | `cs.wallet.floor(token, chain=)` | holders' entry caps, supply in loss | 1 |
79
+ | `cs.wallet.token_balance(wallet, token, chain=)` | float | 1 |
80
+ | `cs.wallet.token_balances(token, wallets, chain=)` | `{wallet: balance}` | 1 |
81
+ | `cs.wallet.tx(hash, chain=)` | `{"from", "to", "value_wei", "block_number"}` | 1 |
82
+ | `cs.market.macro()`, `.news()`, `.calendar()`, `.gas()` | market data | 0 |
83
+ | `cs.market.dex_paid()`, `.dex_trending()`, `.dex_paid_status(token)` | DexScreener signals | 0 |
84
+ | `cs.market.pumpfun_graduates()`, `.pumpfun_search(q)` | pump.fun | 0 |
85
+ | `cs.market.treemap()`, `.top_coins_card()`, `.fear_greed_image()`, `.pnl_card(...)` | PNG bytes | 0 |
86
+ | `cs.launchpads.summary()`, `.stats()`, `.chart()`, `.heat()` | launchpad volume and heat | 0 |
87
+ | `cs.account.me()`, `.usage()`, `.rotate_key()` | your plan, usage, keys | 0 |
88
+ | `cs.billing.plans()`, `.create_invoice(...)`, `.wait_for_payment(id)` | plans and USDT/USDC invoices | 0 |
89
+
90
+ Field-by-field documentation: [coinscan.tech/docs](https://coinscan.tech/docs).
91
+
92
+ ## Text reports
93
+
94
+ Holder, wallet and deployer routes return formatted reports (Telegram-style HTML), ready to post in a bot. `to_text` turns them into plain text:
95
+
96
+ ```python
97
+ from coinscan import CoinScan, to_text
98
+
99
+ cs = CoinScan()
100
+ print(to_text(cs.subscan.dev("6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN")))
101
+ ```
102
+
103
+ ## Images
104
+
105
+ ```python
106
+ from pathlib import Path
107
+
108
+ Path("chart.png").write_bytes(cs.chart("solana", "PAIR_ADDRESS", timeframe="hour", plot_type="ta"))
109
+
110
+ bubble = cs.subscan.bubble_map("6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN")
111
+ if bubble["image"]:
112
+ Path("bubble.png").write_bytes(bubble["image"])
113
+ ```
114
+
115
+ ## Credits, limits and errors
116
+
117
+ `cs.last_usage` holds the usage headers of the last metered call: `cost`, `limit`, `remaining`, `reset` (next 00:00 UTC), `rate_limit`, `rate_remaining`.
118
+
119
+ ```python
120
+ from coinscan import CoinScan, QuotaExceededError, NotFoundError, UpstreamTimeoutError
121
+
122
+ cs = CoinScan()
123
+ try:
124
+ scan = cs.scan("So11111111111111111111111111111111111111112")
125
+ except NotFoundError:
126
+ print("no such token")
127
+ except QuotaExceededError as e:
128
+ print("out of credits until", e.reset_at, "upgrade:", e.upgrade_url)
129
+ except UpstreamTimeoutError:
130
+ print("a data provider was slow; try again")
131
+ ```
132
+
133
+ | exception | when |
134
+ |-|-|
135
+ | `AuthenticationError` | 401: missing or unknown key |
136
+ | `NotFoundError` | 404: token, pair or data not found |
137
+ | `ValidationError` | 422: bad input |
138
+ | `RateLimitError` | 429 per-minute limit (retried automatically, then raised; has `retry_after`) |
139
+ | `QuotaExceededError` | 429 daily credits spent (`reset_at`, `upgrade_url`) |
140
+ | `UpstreamTimeoutError` | a 45 s upstream deadline passed |
141
+ | `InternalServerError` | 5xx (502/503/504 retried; credits are refunded) |
142
+ | `APIConnectionError`, `APITimeoutError` | network problems |
143
+
144
+ All inherit from `CoinScanError`. Retries: `CoinScan(max_retries=2)` by default; `0` turns them off. The default timeout is 90 s because a scan can take up to 60.
145
+
146
+ ## Paying from code
147
+
148
+ ```python
149
+ cs = CoinScan()
150
+ inv = cs.billing.create_invoice("starter", chain="solana", token="USDC")
151
+ print(f"send exactly {inv['amount']} {inv['token']} to {inv['pay_to']}")
152
+ print(inv["payment_uri"]) # Solana Pay / EIP-681 link
153
+ paid = cs.billing.wait_for_payment(inv["id"])
154
+ print(paid["status"], paid.get("plan_expires_at"))
155
+ ```
156
+
157
+ ## Command line
158
+
159
+ ```bash
160
+ export COINSCAN_API_KEY=cs_live_...
161
+ coinscan scan 6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN
162
+ coinscan holders 6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN
163
+ coinscan chart solana PAIR_ADDRESS --plot-type ta > chart.png
164
+ coinscan me
165
+ ```
166
+
167
+ ## Configuration
168
+
169
+ | setting | argument | environment variable | default |
170
+ |-|-|-|-|
171
+ | API key | `CoinScan(api_key)` | `COINSCAN_API_KEY` | none |
172
+ | base URL | `base_url=` | `COINSCAN_BASE_URL` | `https://api.coinscan.tech` |
173
+ | timeout | `timeout=` | | 90 s |
174
+ | retries | `max_retries=` | | 2 |
175
+ | HTTP client | `http_client=httpx.Client(...)` | | one per client |
176
+
177
+ ## Links
178
+
179
+ - Docs: https://coinscan.tech/docs
180
+ - Pricing: https://coinscan.tech/pricing (free 25 scans a day; from $50 a month, paid in USDT or USDC)
181
+ - MCP server for Claude and Cursor: https://coinscan.tech/docs/mcp
182
+ - Support: contact@pirb.tech
183
+
184
+ MIT licensed.
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "coinscan-sdk"
7
+ dynamic = ["version"]
8
+ description = "Official Python client for the CoinScan API: token scans, holders, bubble maps, wallets, charts and market data for Solana, Ethereum, BSC and Base."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = "MIT"
12
+ authors = [{ name = "PIRB", email = "contact@pirb.tech" }]
13
+ keywords = ["crypto", "token scanner", "solana", "ethereum", "bsc", "base", "pump.fun", "rug check", "honeypot", "api", "sdk", "coinscan"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3 :: Only",
20
+ "Topic :: Office/Business :: Financial :: Investment",
21
+ "Typing :: Typed",
22
+ ]
23
+ dependencies = [
24
+ "httpx>=0.24,<1",
25
+ "typing_extensions>=4.5; python_version < '3.11'",
26
+ ]
27
+
28
+ [project.urls]
29
+ Homepage = "https://coinscan.tech"
30
+ Documentation = "https://coinscan.tech/docs/sdks"
31
+ "API reference" = "https://coinscan.tech/docs/reference"
32
+ Pricing = "https://coinscan.tech/pricing"
33
+
34
+ [project.scripts]
35
+ coinscan = "coinscan.__main__:main"
36
+
37
+ [project.optional-dependencies]
38
+ dev = ["pytest>=8", "pytest-asyncio>=0.23"]
39
+
40
+ [tool.hatch.version]
41
+ path = "src/coinscan/_version.py"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/coinscan"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
48
+ asyncio_mode = "auto"
49
+ asyncio_default_fixture_loop_scope = "function"
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env python3
2
+ """Generate src/coinscan/_async.py from src/coinscan/_sync.py.
3
+
4
+ python scripts/gen_async.py # write
5
+ python scripts/gen_async.py --check # exit 1 if _async.py is stale (CI)
6
+ """
7
+ import re
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ PKG = Path(__file__).resolve().parent.parent / "src" / "coinscan"
12
+ SRC = PKG / "_sync.py"
13
+ DST = PKG / "_async.py"
14
+
15
+ CLASSES = ["CoinScan", "Subscan", "Wallet", "Market", "Launchpads", "AccountAPI", "Billing"]
16
+ NON_IO = {"__init__", "__repr__", "_headers", "__enter__", "__exit__"}
17
+
18
+
19
+ def convert(src: str) -> str:
20
+ out = src
21
+ out = out.replace('"""Synchronous client.', '"""Asynchronous client. GENERATED from _sync.py by scripts/gen_async.py; do not edit.', 1)
22
+ out = out.replace("import time\n", "import asyncio\nimport time\n", 1)
23
+ out = out.replace("httpx.Client", "httpx.AsyncClient")
24
+ out = out.replace("Synchronous CoinScan API client.", "Asynchronous CoinScan API client.")
25
+ out = out.replace(" from coinscan import CoinScan\n cs = CoinScan()",
26
+ " from coinscan import AsyncCoinScan\n cs = AsyncCoinScan()")
27
+ out = out.replace("scan = cs.scan(", "scan = await cs.scan(")
28
+
29
+ for name in CLASSES:
30
+ out = re.sub(rf"\b{name}\b", f"Async{name}", out)
31
+ # Keep the product name in docs and messages.
32
+ out = out.replace("AsyncCoinScan API", "CoinScan API")
33
+
34
+ def _def(m):
35
+ indent, name = m.group(1), m.group(2)
36
+ if name in NON_IO:
37
+ return m.group(0)
38
+ return f"{indent}async def {name}("
39
+ out = re.sub(r"^(\s*)def (\w+)\(", _def, out, flags=re.M)
40
+
41
+ out = out.replace("def __enter__(self):", "async def __aenter__(self):")
42
+ out = out.replace("def __exit__(self, *exc):\n self.close()",
43
+ "async def __aexit__(self, *exc):\n await self.close()")
44
+ out = out.replace("self._http.close()", "await self._http.aclose()")
45
+ out = out.replace("self._http.request(", "await self._http.request(")
46
+ out = out.replace("time.sleep(", "await asyncio.sleep(")
47
+ out = re.sub(r"(?<!await )self\._c\._request\(", "await self._c._request(", out)
48
+ out = re.sub(r"(?<![\w.])(?<!await )self\._request\(", "await self._request(", out)
49
+ out = out.replace("invoice = self.get_invoice(", "invoice = await self.get_invoice(")
50
+ out = out.replace("with cls(api_key=\"\", base_url=base_url) as c:\n return c.account.signup(",
51
+ "async with cls(api_key=\"\", base_url=base_url) as c:\n return await c.account.signup(")
52
+ # A function that only defines the request (helpers like _evm_body, _gas) stays sync.
53
+ out = out.replace("async def _evm_body(", "def _evm_body(").replace("async def _gas(", "def _gas(")
54
+ return out
55
+
56
+
57
+ def main() -> int:
58
+ generated = convert(SRC.read_text())
59
+ if "--check" in sys.argv:
60
+ if not DST.exists() or DST.read_text() != generated:
61
+ print("_async.py is stale: run python scripts/gen_async.py", file=sys.stderr)
62
+ return 1
63
+ return 0
64
+ DST.write_text(generated)
65
+ print(f"wrote {DST.relative_to(PKG.parent.parent)}")
66
+ return 0
67
+
68
+
69
+ if __name__ == "__main__":
70
+ sys.exit(main())
@@ -0,0 +1,28 @@
1
+ """Official Python client for the CoinScan API (https://coinscan.tech).
2
+
3
+ from coinscan import CoinScan
4
+
5
+ cs = CoinScan() # reads COINSCAN_API_KEY
6
+ scan = cs.scan("6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN")
7
+ print(scan["holders"]["bundle_pct"], scan["ai_predictions"]["p_rug"])
8
+ """
9
+ from coinscan._async import (
10
+ AsyncAccountAPI, AsyncBilling, AsyncCoinScan, AsyncLaunchpads, AsyncMarket, AsyncSubscan, AsyncWallet,
11
+ )
12
+ from coinscan._base import DEFAULT_BASE_URL, Usage, to_text
13
+ from coinscan._errors import (
14
+ APIConnectionError, APIStatusError, APITimeoutError, AuthenticationError, BadRequestError, CoinScanError,
15
+ ConflictError, InternalServerError, NotFoundError, PermissionDeniedError, QuotaExceededError, RateLimitError,
16
+ UpstreamTimeoutError, ValidationError,
17
+ )
18
+ from coinscan._sync import AccountAPI, Billing, CoinScan, Launchpads, Market, Subscan, Wallet
19
+ from coinscan._version import __version__
20
+
21
+ __all__ = [
22
+ "CoinScan", "AsyncCoinScan", "Usage", "to_text", "DEFAULT_BASE_URL", "__version__",
23
+ "Subscan", "Wallet", "Market", "Launchpads", "AccountAPI", "Billing",
24
+ "AsyncSubscan", "AsyncWallet", "AsyncMarket", "AsyncLaunchpads", "AsyncAccountAPI", "AsyncBilling",
25
+ "CoinScanError", "APIConnectionError", "APITimeoutError", "APIStatusError", "BadRequestError",
26
+ "AuthenticationError", "PermissionDeniedError", "NotFoundError", "ConflictError", "ValidationError",
27
+ "RateLimitError", "QuotaExceededError", "InternalServerError", "UpstreamTimeoutError",
28
+ ]
@@ -0,0 +1,92 @@
1
+ """Command line: `coinscan scan <address>`, `coinscan me`, ... prints JSON.
2
+
3
+ The key comes from --api-key or COINSCAN_API_KEY.
4
+ """
5
+ import argparse
6
+ import json
7
+ import sys
8
+
9
+ from coinscan._base import to_text
10
+ from coinscan._errors import CoinScanError
11
+ from coinscan._sync import CoinScan
12
+
13
+
14
+ def _print(value, text: bool = False) -> None:
15
+ if isinstance(value, (bytes, bytearray)):
16
+ sys.stdout.buffer.write(value)
17
+ return
18
+ if text and isinstance(value, (str, list)):
19
+ print(to_text(value))
20
+ return
21
+ print(json.dumps(value, indent=2, ensure_ascii=False, default=str))
22
+
23
+
24
+ def main(argv=None) -> int:
25
+ p = argparse.ArgumentParser(prog="coinscan", description="CoinScan API from the command line.")
26
+ p.add_argument("--api-key", help="defaults to $COINSCAN_API_KEY")
27
+ p.add_argument("--base-url", help="defaults to $COINSCAN_BASE_URL or https://api.coinscan.tech")
28
+ sub = p.add_subparsers(dest="cmd", required=True)
29
+
30
+ s = sub.add_parser("scan", help="full token scan (10 credits)")
31
+ s.add_argument("address")
32
+ s.add_argument("--chain")
33
+ d = sub.add_parser("detect", help="chain of an address (free)")
34
+ d.add_argument("address")
35
+ t = sub.add_parser("ticker", help="addresses behind a ticker (2 credits)")
36
+ t.add_argument("query")
37
+ h = sub.add_parser("holders", help="top holders (1 credit)")
38
+ h.add_argument("address")
39
+ h.add_argument("--chain", default="solana")
40
+ dv = sub.add_parser("dev", help="deployer history (4 credits)")
41
+ dv.add_argument("address")
42
+ dv.add_argument("--chain")
43
+ w = sub.add_parser("pnl", help="wallet PnL report (5 credits)")
44
+ w.add_argument("wallet")
45
+ w.add_argument("--chain", default="solana")
46
+ c = sub.add_parser("chart", help="PNG chart to stdout (3 credits): coinscan chart solana <pair> > chart.png")
47
+ c.add_argument("network")
48
+ c.add_argument("pool_address")
49
+ c.add_argument("--timeframe", default="hour")
50
+ c.add_argument("--plot-type", default="ohlcv")
51
+ sub.add_parser("macro", help="market snapshot (free)")
52
+ sub.add_parser("me", help="your plan and today's usage")
53
+ sub.add_parser("plans", help="plans and route costs")
54
+ su = sub.add_parser("signup", help="create a free account and print its key")
55
+ su.add_argument("--email")
56
+
57
+ a = p.parse_args(argv)
58
+ try:
59
+ if a.cmd == "signup":
60
+ _print(CoinScan.signup(email=a.email, base_url=a.base_url))
61
+ return 0
62
+ with CoinScan(a.api_key, base_url=a.base_url) as cs:
63
+ if a.cmd == "scan":
64
+ _print(cs.scan(a.address, chain=a.chain))
65
+ elif a.cmd == "detect":
66
+ _print(cs.detect(a.address))
67
+ elif a.cmd == "ticker":
68
+ _print(cs.resolve_ticker(a.query))
69
+ elif a.cmd == "holders":
70
+ _print(cs.subscan.holders(a.address, chain=a.chain), text=True)
71
+ elif a.cmd == "dev":
72
+ _print(cs.subscan.dev(a.address, chain=a.chain), text=True)
73
+ elif a.cmd == "pnl":
74
+ _print(cs.wallet.pnl(a.wallet, chain=a.chain), text=True)
75
+ elif a.cmd == "chart":
76
+ _print(cs.chart(a.network, a.pool_address, timeframe=a.timeframe, plot_type=a.plot_type))
77
+ elif a.cmd == "macro":
78
+ _print(cs.market.macro())
79
+ elif a.cmd == "me":
80
+ _print(cs.account.me())
81
+ elif a.cmd == "plans":
82
+ _print(cs.billing.plans())
83
+ if cs.last_usage and cs.last_usage.remaining is not None:
84
+ print(f"{cs.last_usage.remaining} credits left today", file=sys.stderr)
85
+ return 0
86
+ except CoinScanError as e:
87
+ print(f"error: {e.message}", file=sys.stderr)
88
+ return 1
89
+
90
+
91
+ if __name__ == "__main__":
92
+ sys.exit(main())