clanker-sdk 1.4.0 → 1.5.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/README.md CHANGED
@@ -26,6 +26,9 @@ CLANKER_API_KEY=your_api_key_here
26
26
 
27
27
  # Dune Analytics API Key (required for market data)
28
28
  DUNE_API_KEY=your_dune_api_key_here
29
+
30
+ # The Graph API Key (optional, for Uniswap data)
31
+ GRAPH_API_KEY=your_graph_api_key_here
29
32
  ```
30
33
 
31
34
  Copy the `.env.example` file to get started:
@@ -36,10 +39,10 @@ cp .env.example .env
36
39
 
37
40
  Make sure to add your API keys to the `.env` file and never commit it to version control.
38
41
 
39
- ### 3. Getting Your Dune API Key
40
-
41
- To access market data features, you'll need a Dune Analytics API key:
42
+ ### 3. Getting Your API Keys
42
43
 
44
+ #### Dune Analytics API Key
45
+ To access market data features:
43
46
  1. Visit [dune.xyz](https://dune.xyz) and sign up for an account
44
47
  2. Go to your [API Keys page](https://dune.com/settings/api)
45
48
  3. Create a new API key
@@ -47,6 +50,13 @@ To access market data features, you'll need a Dune Analytics API key:
47
50
 
48
51
  Note: Dune API access requires a paid subscription. Check their [pricing page](https://dune.com/pricing) for more details.
49
52
 
53
+ #### The Graph API Key (Optional)
54
+ To access Uniswap data:
55
+ 1. Visit [thegraph.com](https://thegraph.com) and create an account
56
+ 2. Go to your billing settings
57
+ 3. Create an API key
58
+ 4. Add the key to your `.env` file as `GRAPH_API_KEY`
59
+
50
60
  ### 4. Basic Usage
51
61
 
52
62
  ```typescript
@@ -59,8 +69,11 @@ dotenv.config();
59
69
  // Initialize SDK for token operations
60
70
  const clanker = new ClankerSDK(process.env.CLANKER_API_KEY);
61
71
 
62
- // Initialize market data client for analytics
63
- const marketData = new MarketDataClient(process.env.DUNE_API_KEY);
72
+ // Initialize market data client with both Dune and Graph API keys
73
+ const marketData = new MarketDataClient(
74
+ process.env.DUNE_API_KEY,
75
+ process.env.GRAPH_API_KEY
76
+ );
64
77
 
65
78
  // Deploy a token
66
79
  const token = await clanker.deployToken({
@@ -85,7 +98,7 @@ const marketStats = await marketData.getClankerDictionary();
85
98
 
86
99
  ## Market Data Features
87
100
 
88
- The SDK provides access to comprehensive market data through Dune Analytics integration:
101
+ The SDK provides access to comprehensive market data through multiple sources:
89
102
 
90
103
  ### Clanker Dictionary
91
104
  Get detailed information about all Clanker tokens:
@@ -107,6 +120,20 @@ const dexStats = await marketData.getDexPairStats(
107
120
  // - Volume/Liquidity ratios
108
121
  ```
109
122
 
123
+ ### Uniswap Data
124
+ Get detailed Uniswap pool data for tokens (requires Graph API key):
125
+ ```typescript
126
+ const uniswapData = await marketData.getUniswapData(
127
+ ['0x1234...', '0x5678...'], // Array of token addresses
128
+ 15_000_000 // Optional: Block number for historical data
129
+ );
130
+ // Returns: Array of tokens with:
131
+ // - WETH price
132
+ // - Transaction count
133
+ // - Volume in USD
134
+ // - Decimals
135
+ ```
136
+
110
137
  Supported chains for DEX stats:
111
138
  - ethereum
112
139
  - arbitrum
package/dist/index.d.mts CHANGED
@@ -75,29 +75,69 @@ interface ClankerMarketData {
75
75
  volume7d?: number;
76
76
  liquidity?: number;
77
77
  }
78
+ interface DexPairStats {
79
+ token_a_address: string;
80
+ token_a_symbol: string;
81
+ token_b_address: string;
82
+ token_b_symbol: string;
83
+ volume_24h: number;
84
+ volume_7d: number;
85
+ volume_30d: number;
86
+ liquidity: number;
87
+ volume_to_liquidity_ratio: number;
88
+ }
89
+ interface GraphToken {
90
+ contractAddress: string;
91
+ decimals: number;
92
+ transactionCount: number;
93
+ volumeUSD: number;
94
+ priceWETH: number;
95
+ }
96
+ interface CoinGeckoTokenData {
97
+ price: number;
98
+ marketCap: number;
99
+ volume24h: number;
100
+ priceChange24h: number;
101
+ lastUpdated: Date;
102
+ }
78
103
  declare class MarketDataClient {
79
- private readonly dune;
80
- private readonly apiKey;
104
+ private readonly dune?;
105
+ private readonly duneApiKey?;
106
+ private readonly graphApiKey?;
107
+ private readonly geckoApiKey?;
81
108
  private readonly DICTIONARY_QUERY_ID;
82
- constructor(duneApiKey: string);
109
+ private readonly GRAPH_API_ENDPOINT;
110
+ private readonly UNISWAP_SUBGRAPH_ID;
111
+ private readonly COINGECKO_API_ENDPOINT;
112
+ constructor(duneApiKey?: string, graphApiKey?: string, geckoApiKey?: string);
113
+ /**
114
+ * Get market data from CoinGecko (requires CoinGecko API key)
115
+ * @param tokenIds Array of CoinGecko token IDs
116
+ */
117
+ getGeckoTokenData(tokenIds: string[]): Promise<Record<string, CoinGeckoTokenData>>;
83
118
  /**
84
- * Get market data from the materialized view
119
+ * Get market data from the materialized view (requires Dune API key)
85
120
  */
86
121
  getClankerDictionary(): Promise<ClankerMarketData[]>;
87
122
  /**
88
- * Get DEX pair stats for a specific chain
123
+ * Get DEX pair stats for a specific chain (requires Dune API key)
89
124
  * @param chain - The blockchain to query (e.g., 'ethereum', 'arbitrum', etc.)
90
125
  * @param tokenAddress - Optional token address to filter by
91
126
  */
92
- getDexPairStats(chain: string, tokenAddress?: string): Promise<any>;
127
+ getDexPairStats(chain: string, tokenAddress?: string): Promise<DexPairStats[]>;
93
128
  /**
94
- * Transform raw dictionary data into a standardized format
129
+ * Fetch Uniswap data for multiple tokens using The Graph (requires Graph API key)
130
+ * @param contractAddresses Array of token contract addresses
131
+ * @param blockNumber Optional block number for historical data
95
132
  */
96
- private transformDictionaryData;
133
+ getUniswapData(contractAddresses: string[], blockNumber?: number): Promise<GraphToken[]>;
97
134
  /**
98
135
  * Filter DEX pairs by token address
99
136
  */
100
137
  private filterPairsByToken;
138
+ private buildUniswapQuery;
139
+ private transformUniswapData;
140
+ private calculatePrice;
101
141
  }
102
142
 
103
- export { ClankerError, type ClankerMarketData, ClankerSDK, type DeployTokenOptions, type DeployTokenWithSplitsOptions, type DeployedToken, type DeployedTokensResponse, type EstimatedRewardsResponse, MarketDataClient, type Token, type UncollectedFeesResponse, ClankerSDK as default };
143
+ export { ClankerError, type ClankerMarketData, ClankerSDK, type CoinGeckoTokenData, type DeployTokenOptions, type DeployTokenWithSplitsOptions, type DeployedToken, type DeployedTokensResponse, type DexPairStats, type EstimatedRewardsResponse, type GraphToken, MarketDataClient, type Token, type UncollectedFeesResponse, ClankerSDK as default };
package/dist/index.d.ts CHANGED
@@ -75,29 +75,69 @@ interface ClankerMarketData {
75
75
  volume7d?: number;
76
76
  liquidity?: number;
77
77
  }
78
+ interface DexPairStats {
79
+ token_a_address: string;
80
+ token_a_symbol: string;
81
+ token_b_address: string;
82
+ token_b_symbol: string;
83
+ volume_24h: number;
84
+ volume_7d: number;
85
+ volume_30d: number;
86
+ liquidity: number;
87
+ volume_to_liquidity_ratio: number;
88
+ }
89
+ interface GraphToken {
90
+ contractAddress: string;
91
+ decimals: number;
92
+ transactionCount: number;
93
+ volumeUSD: number;
94
+ priceWETH: number;
95
+ }
96
+ interface CoinGeckoTokenData {
97
+ price: number;
98
+ marketCap: number;
99
+ volume24h: number;
100
+ priceChange24h: number;
101
+ lastUpdated: Date;
102
+ }
78
103
  declare class MarketDataClient {
79
- private readonly dune;
80
- private readonly apiKey;
104
+ private readonly dune?;
105
+ private readonly duneApiKey?;
106
+ private readonly graphApiKey?;
107
+ private readonly geckoApiKey?;
81
108
  private readonly DICTIONARY_QUERY_ID;
82
- constructor(duneApiKey: string);
109
+ private readonly GRAPH_API_ENDPOINT;
110
+ private readonly UNISWAP_SUBGRAPH_ID;
111
+ private readonly COINGECKO_API_ENDPOINT;
112
+ constructor(duneApiKey?: string, graphApiKey?: string, geckoApiKey?: string);
113
+ /**
114
+ * Get market data from CoinGecko (requires CoinGecko API key)
115
+ * @param tokenIds Array of CoinGecko token IDs
116
+ */
117
+ getGeckoTokenData(tokenIds: string[]): Promise<Record<string, CoinGeckoTokenData>>;
83
118
  /**
84
- * Get market data from the materialized view
119
+ * Get market data from the materialized view (requires Dune API key)
85
120
  */
86
121
  getClankerDictionary(): Promise<ClankerMarketData[]>;
87
122
  /**
88
- * Get DEX pair stats for a specific chain
123
+ * Get DEX pair stats for a specific chain (requires Dune API key)
89
124
  * @param chain - The blockchain to query (e.g., 'ethereum', 'arbitrum', etc.)
90
125
  * @param tokenAddress - Optional token address to filter by
91
126
  */
92
- getDexPairStats(chain: string, tokenAddress?: string): Promise<any>;
127
+ getDexPairStats(chain: string, tokenAddress?: string): Promise<DexPairStats[]>;
93
128
  /**
94
- * Transform raw dictionary data into a standardized format
129
+ * Fetch Uniswap data for multiple tokens using The Graph (requires Graph API key)
130
+ * @param contractAddresses Array of token contract addresses
131
+ * @param blockNumber Optional block number for historical data
95
132
  */
96
- private transformDictionaryData;
133
+ getUniswapData(contractAddresses: string[], blockNumber?: number): Promise<GraphToken[]>;
97
134
  /**
98
135
  * Filter DEX pairs by token address
99
136
  */
100
137
  private filterPairsByToken;
138
+ private buildUniswapQuery;
139
+ private transformUniswapData;
140
+ private calculatePrice;
101
141
  }
102
142
 
103
- export { ClankerError, type ClankerMarketData, ClankerSDK, type DeployTokenOptions, type DeployTokenWithSplitsOptions, type DeployedToken, type DeployedTokensResponse, type EstimatedRewardsResponse, MarketDataClient, type Token, type UncollectedFeesResponse, ClankerSDK as default };
143
+ export { ClankerError, type ClankerMarketData, ClankerSDK, type CoinGeckoTokenData, type DeployTokenOptions, type DeployTokenWithSplitsOptions, type DeployedToken, type DeployedTokensResponse, type DexPairStats, type EstimatedRewardsResponse, type GraphToken, MarketDataClient, type Token, type UncollectedFeesResponse, ClankerSDK as default };
package/dist/index.js CHANGED
@@ -26,6 +26,26 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  mod
27
27
  ));
28
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+ var __async = (__this, __arguments, generator) => {
30
+ return new Promise((resolve, reject) => {
31
+ var fulfilled = (value) => {
32
+ try {
33
+ step(generator.next(value));
34
+ } catch (e) {
35
+ reject(e);
36
+ }
37
+ };
38
+ var rejected = (value) => {
39
+ try {
40
+ step(generator.throw(value));
41
+ } catch (e) {
42
+ reject(e);
43
+ }
44
+ };
45
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
46
+ step((generator = generator.apply(__this, __arguments)).next());
47
+ });
48
+ };
29
49
 
30
50
  // src/index.ts
31
51
  var index_exports = {};
@@ -69,10 +89,11 @@ var ClankerSDK = class {
69
89
  this.api.interceptors.response.use(
70
90
  (response) => response,
71
91
  (error) => {
92
+ var _a, _b;
72
93
  if (error.response) {
73
94
  throw new ClankerError(
74
- error.response.data?.message || "API request failed",
75
- error.response.data?.code,
95
+ ((_a = error.response.data) == null ? void 0 : _a.message) || "API request failed",
96
+ (_b = error.response.data) == null ? void 0 : _b.code,
76
97
  error.response.status,
77
98
  error.response.data
78
99
  );
@@ -82,60 +103,72 @@ var ClankerSDK = class {
82
103
  );
83
104
  }
84
105
  // Get estimated uncollected fees
85
- async getEstimatedUncollectedFees(contractAddress) {
86
- if (!this.isValidAddress(contractAddress)) {
87
- throw new ClankerError("Invalid contract address format");
88
- }
89
- const response = await this.api.get(`/get-estimated-uncollected-fees/${contractAddress}`);
90
- return response.data;
106
+ getEstimatedUncollectedFees(contractAddress) {
107
+ return __async(this, null, function* () {
108
+ if (!this.isValidAddress(contractAddress)) {
109
+ throw new ClankerError("Invalid contract address format");
110
+ }
111
+ const response = yield this.api.get(`/get-estimated-uncollected-fees/${contractAddress}`);
112
+ return response.data;
113
+ });
91
114
  }
92
115
  // Deploy a new token
93
- async deployToken(options) {
94
- this.validateDeployOptions(options);
95
- const response = await this.api.post("/tokens/deploy", options);
96
- return response.data;
116
+ deployToken(options) {
117
+ return __async(this, null, function* () {
118
+ this.validateDeployOptions(options);
119
+ const response = yield this.api.post("/tokens/deploy", options);
120
+ return response.data;
121
+ });
97
122
  }
98
123
  // Deploy token with splits
99
- async deployTokenWithSplits(options) {
100
- this.validateDeployOptions(options);
101
- if (!this.isValidAddress(options.splitAddress)) {
102
- throw new ClankerError("Invalid split address format");
103
- }
104
- const response = await this.api.post("/tokens/deploy/with-splits", options);
105
- return response.data;
124
+ deployTokenWithSplits(options) {
125
+ return __async(this, null, function* () {
126
+ this.validateDeployOptions(options);
127
+ if (!this.isValidAddress(options.splitAddress)) {
128
+ throw new ClankerError("Invalid split address format");
129
+ }
130
+ const response = yield this.api.post("/tokens/deploy/with-splits", options);
131
+ return response.data;
132
+ });
106
133
  }
107
134
  // Fetch clankers deployed by address
108
- async fetchDeployedByAddress(address, page = 1) {
109
- if (!this.isValidAddress(address)) {
110
- throw new ClankerError("Invalid address format");
111
- }
112
- if (page < 1) {
113
- throw new ClankerError("Page number must be greater than 0");
114
- }
115
- const response = await this.api.get(`/tokens/fetch-deployed-by-address`, {
116
- params: { address, page }
135
+ fetchDeployedByAddress(address, page = 1) {
136
+ return __async(this, null, function* () {
137
+ if (!this.isValidAddress(address)) {
138
+ throw new ClankerError("Invalid address format");
139
+ }
140
+ if (page < 1) {
141
+ throw new ClankerError("Page number must be greater than 0");
142
+ }
143
+ const response = yield this.api.get(`/tokens/fetch-deployed-by-address`, {
144
+ params: { address, page }
145
+ });
146
+ return response.data;
117
147
  });
118
- return response.data;
119
148
  }
120
149
  // Get estimated rewards by pool address
121
- async getEstimatedRewardsByPoolAddress(poolAddress) {
122
- if (!this.isValidAddress(poolAddress)) {
123
- throw new ClankerError("Invalid pool address format");
124
- }
125
- const response = await this.api.get(`/tokens/estimate-rewards-by-pool-address`, {
126
- params: { poolAddress }
150
+ getEstimatedRewardsByPoolAddress(poolAddress) {
151
+ return __async(this, null, function* () {
152
+ if (!this.isValidAddress(poolAddress)) {
153
+ throw new ClankerError("Invalid pool address format");
154
+ }
155
+ const response = yield this.api.get(`/tokens/estimate-rewards-by-pool-address`, {
156
+ params: { poolAddress }
157
+ });
158
+ return response.data;
127
159
  });
128
- return response.data;
129
160
  }
130
161
  // Fetch clanker by contract address
131
- async getClankerByAddress(address) {
132
- if (!this.isValidAddress(address)) {
133
- throw new ClankerError("Invalid address format");
134
- }
135
- const response = await this.api.get(`/get-clanker-by-address`, {
136
- params: { address }
162
+ getClankerByAddress(address) {
163
+ return __async(this, null, function* () {
164
+ if (!this.isValidAddress(address)) {
165
+ throw new ClankerError("Invalid address format");
166
+ }
167
+ const response = yield this.api.get(`/get-clanker-by-address`, {
168
+ params: { address }
169
+ });
170
+ return response.data;
137
171
  });
138
- return response.data;
139
172
  }
140
173
  // Utility function to generate request key
141
174
  generateRequestKey() {
@@ -168,85 +201,235 @@ var ClankerSDK = class {
168
201
 
169
202
  // src/MarketData.ts
170
203
  var import_client_sdk = require("@duneanalytics/client-sdk");
204
+ var import_axios2 = __toESM(require("axios"));
171
205
  var MarketDataClient = class {
172
- // Your materialized view query ID
173
- constructor(duneApiKey) {
206
+ constructor(duneApiKey, graphApiKey, geckoApiKey) {
174
207
  this.DICTIONARY_QUERY_ID = 4405741;
175
- if (!duneApiKey) {
176
- throw new ClankerError("Dune API key is required for market data");
208
+ this.GRAPH_API_ENDPOINT = "https://gateway.thegraph.com/api";
209
+ this.UNISWAP_SUBGRAPH_ID = "GqzP4Xaehti8KSfQmv3ZctFSjnSUYZ4En5NRsiTbvZpz";
210
+ this.COINGECKO_API_ENDPOINT = "https://pro-api.coingecko.com/api/v3";
211
+ this.duneApiKey = duneApiKey;
212
+ this.graphApiKey = graphApiKey;
213
+ this.geckoApiKey = geckoApiKey;
214
+ if (duneApiKey) {
215
+ this.dune = new import_client_sdk.DuneClient(duneApiKey);
177
216
  }
178
- this.apiKey = duneApiKey;
179
- this.dune = new import_client_sdk.DuneClient(duneApiKey);
180
217
  }
181
218
  /**
182
- * Get market data from the materialized view
219
+ * Get market data from CoinGecko (requires CoinGecko API key)
220
+ * @param tokenIds Array of CoinGecko token IDs
183
221
  */
184
- async getClankerDictionary() {
185
- try {
186
- const result = await this.dune.getLatestResult({ queryId: this.DICTIONARY_QUERY_ID });
187
- if (!result?.result?.rows) {
188
- throw new ClankerError("No data returned from Dune");
222
+ getGeckoTokenData(tokenIds) {
223
+ return __async(this, null, function* () {
224
+ var _a;
225
+ if (!this.geckoApiKey) {
226
+ throw new ClankerError("CoinGecko API key is required for getGeckoTokenData");
189
227
  }
190
- return this.transformDictionaryData(result.result.rows);
191
- } catch (error) {
192
- if (error instanceof Error) {
193
- throw new ClankerError(`Failed to fetch Clanker dictionary: ${error.message}`);
228
+ try {
229
+ const response = yield import_axios2.default.get(
230
+ `${this.COINGECKO_API_ENDPOINT}/simple/price`,
231
+ {
232
+ params: {
233
+ ids: tokenIds.join(","),
234
+ vs_currencies: "usd",
235
+ include_market_cap: true,
236
+ include_24hr_vol: true,
237
+ include_24hr_change: true,
238
+ include_last_updated_at: true
239
+ },
240
+ headers: {
241
+ "x-cg-pro-api-key": this.geckoApiKey,
242
+ "accept": "application/json"
243
+ }
244
+ }
245
+ );
246
+ const result = {};
247
+ for (const [id, data] of Object.entries(response.data)) {
248
+ result[id] = {
249
+ price: data.usd,
250
+ marketCap: data.usd_market_cap,
251
+ volume24h: data.usd_24h_vol,
252
+ priceChange24h: data.usd_24h_change,
253
+ lastUpdated: new Date(data.last_updated_at * 1e3)
254
+ };
255
+ }
256
+ return result;
257
+ } catch (error) {
258
+ if (import_axios2.default.isAxiosError(error) && ((_a = error.response) == null ? void 0 : _a.data)) {
259
+ if (error.response.status === 429) {
260
+ throw new ClankerError("CoinGecko API rate limit exceeded");
261
+ }
262
+ const errorMessage = typeof error.response.data === "object" && error.response.data !== null ? error.response.data.error || error.message : error.message;
263
+ throw new ClankerError(`Failed to fetch CoinGecko data: ${errorMessage}`);
264
+ }
265
+ throw new ClankerError("Failed to fetch CoinGecko data");
194
266
  }
195
- throw new ClankerError("Failed to fetch Clanker dictionary");
196
- }
267
+ });
197
268
  }
198
269
  /**
199
- * Get DEX pair stats for a specific chain
200
- * @param chain - The blockchain to query (e.g., 'ethereum', 'arbitrum', etc.)
201
- * @param tokenAddress - Optional token address to filter by
270
+ * Get market data from the materialized view (requires Dune API key)
202
271
  */
203
- async getDexPairStats(chain, tokenAddress) {
204
- const url = `https://api.dune.com/api/v1/dex/pairs/${chain}`;
205
- const options = {
206
- method: "GET",
207
- headers: {
208
- "X-Dune-Api-Key": this.apiKey
272
+ getClankerDictionary() {
273
+ return __async(this, null, function* () {
274
+ var _a;
275
+ if (!this.dune || !this.duneApiKey) {
276
+ throw new ClankerError("Dune API key is required for getClankerDictionary");
209
277
  }
210
- };
211
- try {
212
- const response = await fetch(url, options);
213
- if (!response.ok) {
214
- throw new Error(`HTTP error! status: ${response.status}`);
278
+ try {
279
+ const result = yield this.dune.getLatestResult({ queryId: this.DICTIONARY_QUERY_ID });
280
+ if (!((_a = result == null ? void 0 : result.result) == null ? void 0 : _a.rows)) {
281
+ throw new ClankerError("No data returned from Dune");
282
+ }
283
+ const rows = result.result.rows;
284
+ return rows.map((row) => ({
285
+ name: row.name,
286
+ symbol: row.symbol,
287
+ marketCap: row.market_cap,
288
+ volume24h: row.volume_24h,
289
+ volume7d: row.volume_7d,
290
+ liquidity: row.liquidity
291
+ }));
292
+ } catch (error) {
293
+ if (error instanceof Error) {
294
+ throw new ClankerError(`Failed to fetch Clanker dictionary: ${error.message}`);
295
+ }
296
+ throw new ClankerError("Failed to fetch Clanker dictionary");
215
297
  }
216
- const data = await response.json();
217
- if (tokenAddress) {
218
- return this.filterPairsByToken(data, tokenAddress);
298
+ });
299
+ }
300
+ /**
301
+ * Get DEX pair stats for a specific chain (requires Dune API key)
302
+ * @param chain - The blockchain to query (e.g., 'ethereum', 'arbitrum', etc.)
303
+ * @param tokenAddress - Optional token address to filter by
304
+ */
305
+ getDexPairStats(chain, tokenAddress) {
306
+ return __async(this, null, function* () {
307
+ if (!this.duneApiKey) {
308
+ throw new ClankerError("Dune API key is required for getDexPairStats");
219
309
  }
220
- return data;
221
- } catch (error) {
222
- if (error instanceof Error) {
223
- throw new ClankerError(`Failed to fetch DEX pair stats: ${error.message}`);
310
+ const url = `https://api.dune.com/api/v1/dex/pairs/${chain}`;
311
+ const options = {
312
+ method: "GET",
313
+ headers: {
314
+ "X-Dune-Api-Key": this.duneApiKey
315
+ }
316
+ };
317
+ try {
318
+ const response = yield fetch(url, options);
319
+ if (!response.ok) {
320
+ throw new Error(`HTTP error! status: ${response.status}`);
321
+ }
322
+ const data = yield response.json();
323
+ if (tokenAddress) {
324
+ return this.filterPairsByToken(data.result.rows, tokenAddress);
325
+ }
326
+ return data.result.rows;
327
+ } catch (error) {
328
+ if (error instanceof Error) {
329
+ throw new ClankerError(`Failed to fetch DEX pair stats: ${error.message}`);
330
+ }
331
+ throw new ClankerError("Failed to fetch DEX pair stats");
224
332
  }
225
- throw new ClankerError("Failed to fetch DEX pair stats");
226
- }
333
+ });
227
334
  }
228
335
  /**
229
- * Transform raw dictionary data into a standardized format
336
+ * Fetch Uniswap data for multiple tokens using The Graph (requires Graph API key)
337
+ * @param contractAddresses Array of token contract addresses
338
+ * @param blockNumber Optional block number for historical data
230
339
  */
231
- transformDictionaryData(rows) {
232
- return rows.map((row) => ({
233
- name: row.name || "",
234
- symbol: row.symbol || "",
235
- marketCap: row.market_cap || void 0,
236
- volume24h: row.volume_24h || void 0,
237
- volume7d: row.volume_7d || void 0,
238
- liquidity: row.liquidity || void 0
239
- }));
340
+ getUniswapData(contractAddresses, blockNumber) {
341
+ return __async(this, null, function* () {
342
+ if (!this.graphApiKey) {
343
+ throw new ClankerError("Graph API key is required for getUniswapData");
344
+ }
345
+ try {
346
+ const query = this.buildUniswapQuery(contractAddresses, blockNumber);
347
+ const response = yield import_axios2.default.post(
348
+ `${this.GRAPH_API_ENDPOINT}/${this.graphApiKey}/subgraphs/id/${this.UNISWAP_SUBGRAPH_ID}`,
349
+ { query },
350
+ { headers: { "Content-Type": "application/json" } }
351
+ );
352
+ return this.transformUniswapData(response.data.data, contractAddresses);
353
+ } catch (error) {
354
+ if (error instanceof Error) {
355
+ throw new ClankerError(`Failed to fetch Uniswap data: ${error.message}`);
356
+ }
357
+ throw new ClankerError("Failed to fetch Uniswap data");
358
+ }
359
+ });
240
360
  }
241
361
  /**
242
362
  * Filter DEX pairs by token address
243
363
  */
244
- filterPairsByToken(data, tokenAddress) {
245
- const rows = data?.result?.rows || [];
246
- return rows.filter(
247
- (pair) => pair.token_a_address?.toLowerCase() === tokenAddress.toLowerCase() || pair.token_b_address?.toLowerCase() === tokenAddress.toLowerCase()
364
+ filterPairsByToken(pairs, tokenAddress) {
365
+ return pairs.filter(
366
+ (pair) => {
367
+ var _a, _b;
368
+ return ((_a = pair.token_a_address) == null ? void 0 : _a.toLowerCase()) === tokenAddress.toLowerCase() || ((_b = pair.token_b_address) == null ? void 0 : _b.toLowerCase()) === tokenAddress.toLowerCase();
369
+ }
248
370
  );
249
371
  }
372
+ buildUniswapQuery(contractAddresses, blockNumber) {
373
+ const queryParts = contractAddresses.map((address) => {
374
+ const key = `token${address.toLowerCase()}`;
375
+ return `
376
+ ${key}: token(id:"${address.toLowerCase()}"${blockNumber ? `, block: {number: ${blockNumber}}` : ""}) {
377
+ id,
378
+ whitelistPools(orderBy:createdAtBlockNumber, orderDirection:asc, first: 1) {
379
+ id,
380
+ createdAtBlockNumber,
381
+ token0 {
382
+ name,
383
+ symbol,
384
+ decimals
385
+ },
386
+ token1 {
387
+ name,
388
+ symbol,
389
+ decimals
390
+ },
391
+ sqrtPrice
392
+ },
393
+ untrackedVolumeUSD,
394
+ txCount,
395
+ decimals
396
+ }
397
+ `;
398
+ });
399
+ return `{ ${queryParts.join("\n")} }`;
400
+ }
401
+ transformUniswapData(data, addresses) {
402
+ const tokens = [];
403
+ for (const address of addresses) {
404
+ const key = `token${address.toLowerCase()}`;
405
+ const tokenData = data[key];
406
+ if (!tokenData || tokenData.whitelistPools.length === 0) {
407
+ continue;
408
+ }
409
+ const pool = tokenData.whitelistPools[0];
410
+ if (pool.token1.symbol !== "WETH") {
411
+ continue;
412
+ }
413
+ const price = this.calculatePrice(
414
+ Number(pool.sqrtPrice),
415
+ Number(pool.token0.decimals),
416
+ Number(pool.token1.decimals)
417
+ );
418
+ tokens.push({
419
+ contractAddress: tokenData.id,
420
+ decimals: Number(tokenData.decimals),
421
+ transactionCount: Number(tokenData.txCount),
422
+ volumeUSD: Number(tokenData.untrackedVolumeUSD),
423
+ priceWETH: price
424
+ });
425
+ }
426
+ return tokens;
427
+ }
428
+ calculatePrice(sqrtPriceX96, decimalsToken0, decimalsToken1) {
429
+ const price = Math.pow(sqrtPriceX96, 2) / Math.pow(2, 192);
430
+ const decimalAdjustment = Math.pow(10, decimalsToken1 - decimalsToken0);
431
+ return price * decimalAdjustment;
432
+ }
250
433
  };
251
434
  // Annotate the CommonJS export names for ESM import in node:
252
435
  0 && (module.exports = {
package/dist/index.mjs CHANGED
@@ -1,3 +1,24 @@
1
+ var __async = (__this, __arguments, generator) => {
2
+ return new Promise((resolve, reject) => {
3
+ var fulfilled = (value) => {
4
+ try {
5
+ step(generator.next(value));
6
+ } catch (e) {
7
+ reject(e);
8
+ }
9
+ };
10
+ var rejected = (value) => {
11
+ try {
12
+ step(generator.throw(value));
13
+ } catch (e) {
14
+ reject(e);
15
+ }
16
+ };
17
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
18
+ step((generator = generator.apply(__this, __arguments)).next());
19
+ });
20
+ };
21
+
1
22
  // src/ClankerSDK.ts
2
23
  import axios from "axios";
3
24
  import { randomBytes } from "crypto";
@@ -30,10 +51,11 @@ var ClankerSDK = class {
30
51
  this.api.interceptors.response.use(
31
52
  (response) => response,
32
53
  (error) => {
54
+ var _a, _b;
33
55
  if (error.response) {
34
56
  throw new ClankerError(
35
- error.response.data?.message || "API request failed",
36
- error.response.data?.code,
57
+ ((_a = error.response.data) == null ? void 0 : _a.message) || "API request failed",
58
+ (_b = error.response.data) == null ? void 0 : _b.code,
37
59
  error.response.status,
38
60
  error.response.data
39
61
  );
@@ -43,60 +65,72 @@ var ClankerSDK = class {
43
65
  );
44
66
  }
45
67
  // Get estimated uncollected fees
46
- async getEstimatedUncollectedFees(contractAddress) {
47
- if (!this.isValidAddress(contractAddress)) {
48
- throw new ClankerError("Invalid contract address format");
49
- }
50
- const response = await this.api.get(`/get-estimated-uncollected-fees/${contractAddress}`);
51
- return response.data;
68
+ getEstimatedUncollectedFees(contractAddress) {
69
+ return __async(this, null, function* () {
70
+ if (!this.isValidAddress(contractAddress)) {
71
+ throw new ClankerError("Invalid contract address format");
72
+ }
73
+ const response = yield this.api.get(`/get-estimated-uncollected-fees/${contractAddress}`);
74
+ return response.data;
75
+ });
52
76
  }
53
77
  // Deploy a new token
54
- async deployToken(options) {
55
- this.validateDeployOptions(options);
56
- const response = await this.api.post("/tokens/deploy", options);
57
- return response.data;
78
+ deployToken(options) {
79
+ return __async(this, null, function* () {
80
+ this.validateDeployOptions(options);
81
+ const response = yield this.api.post("/tokens/deploy", options);
82
+ return response.data;
83
+ });
58
84
  }
59
85
  // Deploy token with splits
60
- async deployTokenWithSplits(options) {
61
- this.validateDeployOptions(options);
62
- if (!this.isValidAddress(options.splitAddress)) {
63
- throw new ClankerError("Invalid split address format");
64
- }
65
- const response = await this.api.post("/tokens/deploy/with-splits", options);
66
- return response.data;
86
+ deployTokenWithSplits(options) {
87
+ return __async(this, null, function* () {
88
+ this.validateDeployOptions(options);
89
+ if (!this.isValidAddress(options.splitAddress)) {
90
+ throw new ClankerError("Invalid split address format");
91
+ }
92
+ const response = yield this.api.post("/tokens/deploy/with-splits", options);
93
+ return response.data;
94
+ });
67
95
  }
68
96
  // Fetch clankers deployed by address
69
- async fetchDeployedByAddress(address, page = 1) {
70
- if (!this.isValidAddress(address)) {
71
- throw new ClankerError("Invalid address format");
72
- }
73
- if (page < 1) {
74
- throw new ClankerError("Page number must be greater than 0");
75
- }
76
- const response = await this.api.get(`/tokens/fetch-deployed-by-address`, {
77
- params: { address, page }
97
+ fetchDeployedByAddress(address, page = 1) {
98
+ return __async(this, null, function* () {
99
+ if (!this.isValidAddress(address)) {
100
+ throw new ClankerError("Invalid address format");
101
+ }
102
+ if (page < 1) {
103
+ throw new ClankerError("Page number must be greater than 0");
104
+ }
105
+ const response = yield this.api.get(`/tokens/fetch-deployed-by-address`, {
106
+ params: { address, page }
107
+ });
108
+ return response.data;
78
109
  });
79
- return response.data;
80
110
  }
81
111
  // Get estimated rewards by pool address
82
- async getEstimatedRewardsByPoolAddress(poolAddress) {
83
- if (!this.isValidAddress(poolAddress)) {
84
- throw new ClankerError("Invalid pool address format");
85
- }
86
- const response = await this.api.get(`/tokens/estimate-rewards-by-pool-address`, {
87
- params: { poolAddress }
112
+ getEstimatedRewardsByPoolAddress(poolAddress) {
113
+ return __async(this, null, function* () {
114
+ if (!this.isValidAddress(poolAddress)) {
115
+ throw new ClankerError("Invalid pool address format");
116
+ }
117
+ const response = yield this.api.get(`/tokens/estimate-rewards-by-pool-address`, {
118
+ params: { poolAddress }
119
+ });
120
+ return response.data;
88
121
  });
89
- return response.data;
90
122
  }
91
123
  // Fetch clanker by contract address
92
- async getClankerByAddress(address) {
93
- if (!this.isValidAddress(address)) {
94
- throw new ClankerError("Invalid address format");
95
- }
96
- const response = await this.api.get(`/get-clanker-by-address`, {
97
- params: { address }
124
+ getClankerByAddress(address) {
125
+ return __async(this, null, function* () {
126
+ if (!this.isValidAddress(address)) {
127
+ throw new ClankerError("Invalid address format");
128
+ }
129
+ const response = yield this.api.get(`/get-clanker-by-address`, {
130
+ params: { address }
131
+ });
132
+ return response.data;
98
133
  });
99
- return response.data;
100
134
  }
101
135
  // Utility function to generate request key
102
136
  generateRequestKey() {
@@ -129,85 +163,235 @@ var ClankerSDK = class {
129
163
 
130
164
  // src/MarketData.ts
131
165
  import { DuneClient } from "@duneanalytics/client-sdk";
166
+ import axios2 from "axios";
132
167
  var MarketDataClient = class {
133
- // Your materialized view query ID
134
- constructor(duneApiKey) {
168
+ constructor(duneApiKey, graphApiKey, geckoApiKey) {
135
169
  this.DICTIONARY_QUERY_ID = 4405741;
136
- if (!duneApiKey) {
137
- throw new ClankerError("Dune API key is required for market data");
170
+ this.GRAPH_API_ENDPOINT = "https://gateway.thegraph.com/api";
171
+ this.UNISWAP_SUBGRAPH_ID = "GqzP4Xaehti8KSfQmv3ZctFSjnSUYZ4En5NRsiTbvZpz";
172
+ this.COINGECKO_API_ENDPOINT = "https://pro-api.coingecko.com/api/v3";
173
+ this.duneApiKey = duneApiKey;
174
+ this.graphApiKey = graphApiKey;
175
+ this.geckoApiKey = geckoApiKey;
176
+ if (duneApiKey) {
177
+ this.dune = new DuneClient(duneApiKey);
138
178
  }
139
- this.apiKey = duneApiKey;
140
- this.dune = new DuneClient(duneApiKey);
141
179
  }
142
180
  /**
143
- * Get market data from the materialized view
181
+ * Get market data from CoinGecko (requires CoinGecko API key)
182
+ * @param tokenIds Array of CoinGecko token IDs
144
183
  */
145
- async getClankerDictionary() {
146
- try {
147
- const result = await this.dune.getLatestResult({ queryId: this.DICTIONARY_QUERY_ID });
148
- if (!result?.result?.rows) {
149
- throw new ClankerError("No data returned from Dune");
150
- }
151
- return this.transformDictionaryData(result.result.rows);
152
- } catch (error) {
153
- if (error instanceof Error) {
154
- throw new ClankerError(`Failed to fetch Clanker dictionary: ${error.message}`);
155
- }
156
- throw new ClankerError("Failed to fetch Clanker dictionary");
157
- }
184
+ getGeckoTokenData(tokenIds) {
185
+ return __async(this, null, function* () {
186
+ var _a;
187
+ if (!this.geckoApiKey) {
188
+ throw new ClankerError("CoinGecko API key is required for getGeckoTokenData");
189
+ }
190
+ try {
191
+ const response = yield axios2.get(
192
+ `${this.COINGECKO_API_ENDPOINT}/simple/price`,
193
+ {
194
+ params: {
195
+ ids: tokenIds.join(","),
196
+ vs_currencies: "usd",
197
+ include_market_cap: true,
198
+ include_24hr_vol: true,
199
+ include_24hr_change: true,
200
+ include_last_updated_at: true
201
+ },
202
+ headers: {
203
+ "x-cg-pro-api-key": this.geckoApiKey,
204
+ "accept": "application/json"
205
+ }
206
+ }
207
+ );
208
+ const result = {};
209
+ for (const [id, data] of Object.entries(response.data)) {
210
+ result[id] = {
211
+ price: data.usd,
212
+ marketCap: data.usd_market_cap,
213
+ volume24h: data.usd_24h_vol,
214
+ priceChange24h: data.usd_24h_change,
215
+ lastUpdated: new Date(data.last_updated_at * 1e3)
216
+ };
217
+ }
218
+ return result;
219
+ } catch (error) {
220
+ if (axios2.isAxiosError(error) && ((_a = error.response) == null ? void 0 : _a.data)) {
221
+ if (error.response.status === 429) {
222
+ throw new ClankerError("CoinGecko API rate limit exceeded");
223
+ }
224
+ const errorMessage = typeof error.response.data === "object" && error.response.data !== null ? error.response.data.error || error.message : error.message;
225
+ throw new ClankerError(`Failed to fetch CoinGecko data: ${errorMessage}`);
226
+ }
227
+ throw new ClankerError("Failed to fetch CoinGecko data");
228
+ }
229
+ });
158
230
  }
159
231
  /**
160
- * Get DEX pair stats for a specific chain
161
- * @param chain - The blockchain to query (e.g., 'ethereum', 'arbitrum', etc.)
162
- * @param tokenAddress - Optional token address to filter by
232
+ * Get market data from the materialized view (requires Dune API key)
163
233
  */
164
- async getDexPairStats(chain, tokenAddress) {
165
- const url = `https://api.dune.com/api/v1/dex/pairs/${chain}`;
166
- const options = {
167
- method: "GET",
168
- headers: {
169
- "X-Dune-Api-Key": this.apiKey
234
+ getClankerDictionary() {
235
+ return __async(this, null, function* () {
236
+ var _a;
237
+ if (!this.dune || !this.duneApiKey) {
238
+ throw new ClankerError("Dune API key is required for getClankerDictionary");
170
239
  }
171
- };
172
- try {
173
- const response = await fetch(url, options);
174
- if (!response.ok) {
175
- throw new Error(`HTTP error! status: ${response.status}`);
240
+ try {
241
+ const result = yield this.dune.getLatestResult({ queryId: this.DICTIONARY_QUERY_ID });
242
+ if (!((_a = result == null ? void 0 : result.result) == null ? void 0 : _a.rows)) {
243
+ throw new ClankerError("No data returned from Dune");
244
+ }
245
+ const rows = result.result.rows;
246
+ return rows.map((row) => ({
247
+ name: row.name,
248
+ symbol: row.symbol,
249
+ marketCap: row.market_cap,
250
+ volume24h: row.volume_24h,
251
+ volume7d: row.volume_7d,
252
+ liquidity: row.liquidity
253
+ }));
254
+ } catch (error) {
255
+ if (error instanceof Error) {
256
+ throw new ClankerError(`Failed to fetch Clanker dictionary: ${error.message}`);
257
+ }
258
+ throw new ClankerError("Failed to fetch Clanker dictionary");
176
259
  }
177
- const data = await response.json();
178
- if (tokenAddress) {
179
- return this.filterPairsByToken(data, tokenAddress);
260
+ });
261
+ }
262
+ /**
263
+ * Get DEX pair stats for a specific chain (requires Dune API key)
264
+ * @param chain - The blockchain to query (e.g., 'ethereum', 'arbitrum', etc.)
265
+ * @param tokenAddress - Optional token address to filter by
266
+ */
267
+ getDexPairStats(chain, tokenAddress) {
268
+ return __async(this, null, function* () {
269
+ if (!this.duneApiKey) {
270
+ throw new ClankerError("Dune API key is required for getDexPairStats");
180
271
  }
181
- return data;
182
- } catch (error) {
183
- if (error instanceof Error) {
184
- throw new ClankerError(`Failed to fetch DEX pair stats: ${error.message}`);
272
+ const url = `https://api.dune.com/api/v1/dex/pairs/${chain}`;
273
+ const options = {
274
+ method: "GET",
275
+ headers: {
276
+ "X-Dune-Api-Key": this.duneApiKey
277
+ }
278
+ };
279
+ try {
280
+ const response = yield fetch(url, options);
281
+ if (!response.ok) {
282
+ throw new Error(`HTTP error! status: ${response.status}`);
283
+ }
284
+ const data = yield response.json();
285
+ if (tokenAddress) {
286
+ return this.filterPairsByToken(data.result.rows, tokenAddress);
287
+ }
288
+ return data.result.rows;
289
+ } catch (error) {
290
+ if (error instanceof Error) {
291
+ throw new ClankerError(`Failed to fetch DEX pair stats: ${error.message}`);
292
+ }
293
+ throw new ClankerError("Failed to fetch DEX pair stats");
185
294
  }
186
- throw new ClankerError("Failed to fetch DEX pair stats");
187
- }
295
+ });
188
296
  }
189
297
  /**
190
- * Transform raw dictionary data into a standardized format
298
+ * Fetch Uniswap data for multiple tokens using The Graph (requires Graph API key)
299
+ * @param contractAddresses Array of token contract addresses
300
+ * @param blockNumber Optional block number for historical data
191
301
  */
192
- transformDictionaryData(rows) {
193
- return rows.map((row) => ({
194
- name: row.name || "",
195
- symbol: row.symbol || "",
196
- marketCap: row.market_cap || void 0,
197
- volume24h: row.volume_24h || void 0,
198
- volume7d: row.volume_7d || void 0,
199
- liquidity: row.liquidity || void 0
200
- }));
302
+ getUniswapData(contractAddresses, blockNumber) {
303
+ return __async(this, null, function* () {
304
+ if (!this.graphApiKey) {
305
+ throw new ClankerError("Graph API key is required for getUniswapData");
306
+ }
307
+ try {
308
+ const query = this.buildUniswapQuery(contractAddresses, blockNumber);
309
+ const response = yield axios2.post(
310
+ `${this.GRAPH_API_ENDPOINT}/${this.graphApiKey}/subgraphs/id/${this.UNISWAP_SUBGRAPH_ID}`,
311
+ { query },
312
+ { headers: { "Content-Type": "application/json" } }
313
+ );
314
+ return this.transformUniswapData(response.data.data, contractAddresses);
315
+ } catch (error) {
316
+ if (error instanceof Error) {
317
+ throw new ClankerError(`Failed to fetch Uniswap data: ${error.message}`);
318
+ }
319
+ throw new ClankerError("Failed to fetch Uniswap data");
320
+ }
321
+ });
201
322
  }
202
323
  /**
203
324
  * Filter DEX pairs by token address
204
325
  */
205
- filterPairsByToken(data, tokenAddress) {
206
- const rows = data?.result?.rows || [];
207
- return rows.filter(
208
- (pair) => pair.token_a_address?.toLowerCase() === tokenAddress.toLowerCase() || pair.token_b_address?.toLowerCase() === tokenAddress.toLowerCase()
326
+ filterPairsByToken(pairs, tokenAddress) {
327
+ return pairs.filter(
328
+ (pair) => {
329
+ var _a, _b;
330
+ return ((_a = pair.token_a_address) == null ? void 0 : _a.toLowerCase()) === tokenAddress.toLowerCase() || ((_b = pair.token_b_address) == null ? void 0 : _b.toLowerCase()) === tokenAddress.toLowerCase();
331
+ }
209
332
  );
210
333
  }
334
+ buildUniswapQuery(contractAddresses, blockNumber) {
335
+ const queryParts = contractAddresses.map((address) => {
336
+ const key = `token${address.toLowerCase()}`;
337
+ return `
338
+ ${key}: token(id:"${address.toLowerCase()}"${blockNumber ? `, block: {number: ${blockNumber}}` : ""}) {
339
+ id,
340
+ whitelistPools(orderBy:createdAtBlockNumber, orderDirection:asc, first: 1) {
341
+ id,
342
+ createdAtBlockNumber,
343
+ token0 {
344
+ name,
345
+ symbol,
346
+ decimals
347
+ },
348
+ token1 {
349
+ name,
350
+ symbol,
351
+ decimals
352
+ },
353
+ sqrtPrice
354
+ },
355
+ untrackedVolumeUSD,
356
+ txCount,
357
+ decimals
358
+ }
359
+ `;
360
+ });
361
+ return `{ ${queryParts.join("\n")} }`;
362
+ }
363
+ transformUniswapData(data, addresses) {
364
+ const tokens = [];
365
+ for (const address of addresses) {
366
+ const key = `token${address.toLowerCase()}`;
367
+ const tokenData = data[key];
368
+ if (!tokenData || tokenData.whitelistPools.length === 0) {
369
+ continue;
370
+ }
371
+ const pool = tokenData.whitelistPools[0];
372
+ if (pool.token1.symbol !== "WETH") {
373
+ continue;
374
+ }
375
+ const price = this.calculatePrice(
376
+ Number(pool.sqrtPrice),
377
+ Number(pool.token0.decimals),
378
+ Number(pool.token1.decimals)
379
+ );
380
+ tokens.push({
381
+ contractAddress: tokenData.id,
382
+ decimals: Number(tokenData.decimals),
383
+ transactionCount: Number(tokenData.txCount),
384
+ volumeUSD: Number(tokenData.untrackedVolumeUSD),
385
+ priceWETH: price
386
+ });
387
+ }
388
+ return tokens;
389
+ }
390
+ calculatePrice(sqrtPriceX96, decimalsToken0, decimalsToken1) {
391
+ const price = Math.pow(sqrtPriceX96, 2) / Math.pow(2, 192);
392
+ const decimalAdjustment = Math.pow(10, decimalsToken1 - decimalsToken0);
393
+ return price * decimalAdjustment;
394
+ }
211
395
  };
212
396
  export {
213
397
  ClankerError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clanker-sdk",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "A lightweight TypeScript SDK for interacting with the Clanker API",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",