@explorins/pers-sdk 1.0.0-alpha.1 → 1.1.2
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/config/domains.js +22 -0
- package/explorins-pers-sdk-1.0.0-alpha.1.tgz +0 -0
- package/package.json +116 -23
- package/rollup.config.js +50 -54
- package/scripts/copy-declarations.js +147 -0
- package/src/analytics/api/analytics-api.ts +24 -0
- package/src/analytics/index.ts +52 -0
- package/src/analytics/models/index.ts +74 -0
- package/src/analytics/services/analytics-service.ts +28 -0
- package/src/auth-admin/api/auth-admin-api.ts +42 -0
- package/src/auth-admin/index.ts +47 -0
- package/src/auth-admin/services/auth-admin-service.ts +36 -0
- package/src/business/api/business-api.ts +181 -19
- package/src/business/index.ts +4 -3
- package/src/business/models/index.ts +4 -4
- package/src/business/services/business-service.ts +1 -1
- package/src/campaign/api/campaign-api.ts +376 -0
- package/src/campaign/index.ts +67 -0
- package/src/campaign/services/campaign-service.ts +164 -0
- package/src/core/abstractions/http-client.ts +1 -0
- package/src/core/auth/auth-provider.interface.ts +2 -2
- package/src/core/auth/create-auth-provider.ts +6 -6
- package/src/core/index.ts +33 -0
- package/src/core/pers-api-client.ts +211 -19
- package/src/core/pers-config.ts +34 -7
- package/src/core/utils/jwt.function.ts +24 -0
- package/src/donation/api/donation-api.ts +24 -0
- package/src/donation/index.ts +47 -0
- package/src/donation/models/index.ts +11 -0
- package/src/donation/services/donation-service.ts +25 -0
- package/src/index.ts +40 -1
- package/src/payment/api/payment-api.ts +185 -0
- package/src/payment/index.ts +64 -0
- package/src/payment/models/index.ts +29 -0
- package/src/payment/services/payment-service.ts +70 -0
- package/src/redemption/api/redemption-api.ts +241 -0
- package/src/redemption/index.ts +60 -0
- package/src/redemption/models/index.ts +17 -0
- package/src/redemption/services/redemption-service.ts +103 -0
- package/src/shared/interfaces/pers-shared-lib.interfaces.ts +99 -0
- package/src/tenant/api/tenant-api.ts +92 -0
- package/src/tenant/index.ts +61 -0
- package/src/tenant/models/index.ts +20 -0
- package/src/tenant/services/tenant-service.ts +78 -0
- package/src/token/api/token-api.ts +129 -0
- package/src/token/base/base-token-service.ts +167 -0
- package/src/token/index.ts +38 -0
- package/src/token/models/index.ts +30 -0
- package/src/token/services/token-service.ts +125 -0
- package/src/token/token-sdk.ts +231 -0
- package/src/transaction/api/transaction-api.ts +296 -0
- package/src/transaction/index.ts +65 -0
- package/src/transaction/models/index.ts +60 -0
- package/src/transaction/services/transaction-service.ts +104 -0
- package/src/user/api/user-api.ts +98 -0
- package/src/user/index.ts +62 -0
- package/src/user/models/index.ts +10 -0
- package/src/user/services/user-service.ts +75 -0
- package/src/user-status/api/user-status-api.ts +78 -0
- package/src/user-status/index.ts +55 -0
- package/src/user-status/models/index.ts +11 -0
- package/src/user-status/services/user-status-service.ts +51 -0
- package/src/web3/api/web3-api.ts +68 -0
- package/src/web3/index.ts +38 -0
- package/src/web3/models/index.ts +150 -0
- package/src/web3/services/web3-service.ts +338 -0
- package/src/web3-chain/api/web3-chain-api.ts +42 -0
- package/src/web3-chain/index.ts +27 -0
- package/src/web3-chain/models/index.ts +45 -0
- package/src/web3-chain/services/getWeb3FCD.service.ts +47 -0
- package/src/web3-chain/services/provider.service.ts +123 -0
- package/src/web3-chain/services/public-http-provider.service.ts +26 -0
- package/src/web3-chain/services/web3-chain-service.ts +131 -0
- package/src/business/business/tsconfig.json +0 -18
- package/src/core/abstractions/core-interfaces.ts +0 -56
- package/src/core/core.ts +0 -30
- package/src/core.ts +0 -30
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { Web3ChainService } from '../../web3-chain';
|
|
2
|
+
import { Web3Api } from '../api/web3-api';
|
|
3
|
+
import {
|
|
4
|
+
ERC20BalanceRequest,
|
|
5
|
+
ERC1155CollectionRequest,
|
|
6
|
+
ERC721CollectionRequest,
|
|
7
|
+
ERC721CollectionResponse,
|
|
8
|
+
NFTItem,
|
|
9
|
+
Web3BalanceResponse,
|
|
10
|
+
Web3TokenListResponse,
|
|
11
|
+
Web3TokenResult,
|
|
12
|
+
SimpleCache
|
|
13
|
+
} from '../models';
|
|
14
|
+
|
|
15
|
+
export class Web3Service {
|
|
16
|
+
//temporary fix, remove when the backend supports custom gateways
|
|
17
|
+
private readonly defaultIpfsGatewayDomain = 'pers.mypinata.cloud';
|
|
18
|
+
|
|
19
|
+
// ✅ CACHE: Simple 10-second cache instance
|
|
20
|
+
private cache = new SimpleCache();
|
|
21
|
+
private cleanupInterval: NodeJS.Timeout | null = null;
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
private web3Api: Web3Api,
|
|
25
|
+
private web3ChainService: Web3ChainService
|
|
26
|
+
) {
|
|
27
|
+
this.cleanupInterval = setInterval(() => {
|
|
28
|
+
this.cache.cleanup();
|
|
29
|
+
}, 30 * 1000);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
destroy(): void {
|
|
33
|
+
if (this.cleanupInterval) {
|
|
34
|
+
clearInterval(this.cleanupInterval);
|
|
35
|
+
this.cleanupInterval = null;
|
|
36
|
+
}
|
|
37
|
+
this.cache.clear();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async getERC20Balance(request: ERC20BalanceRequest): Promise<Web3BalanceResponse> {
|
|
41
|
+
|
|
42
|
+
const cacheKey = `erc20_balance_${request.accountAddress}_${request.token.contractAddress}_${request.token.chainId}`;
|
|
43
|
+
|
|
44
|
+
// ✅ CACHE CHECK: Try to get from cache first
|
|
45
|
+
const cached = this.cache.get<Web3BalanceResponse>(cacheKey);
|
|
46
|
+
if (cached) {
|
|
47
|
+
console.debug(`💾 [Web3Service] Using cached ERC20 balance for ${request.token.symbol}`);
|
|
48
|
+
return cached;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
console.debug(`🔄 [Web3Service] Fetching fresh ERC20 balance for ${request.token.symbol}`);
|
|
52
|
+
|
|
53
|
+
const rawBalance = await this.web3Api.getTokenBalance({
|
|
54
|
+
accountAddress: request.accountAddress,
|
|
55
|
+
contractAddress: request.token.contractAddress,
|
|
56
|
+
abi: request.token.abi,
|
|
57
|
+
tokenId: null, // Always null for ERC20
|
|
58
|
+
chainId: request.token.chainId
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const decimals = request.token.decimals ?? 18;
|
|
62
|
+
const symbol = request.token.symbol ?? 'UNKNOWN';
|
|
63
|
+
|
|
64
|
+
const response: Web3BalanceResponse = {
|
|
65
|
+
rawBalance,
|
|
66
|
+
formattedBalance: this.formatBalance(rawBalance, decimals),
|
|
67
|
+
decimals,
|
|
68
|
+
symbol,
|
|
69
|
+
hasBalance: rawBalance > 0
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// ✅ CACHE SET: Store result in cache
|
|
73
|
+
this.cache.set(cacheKey, response);
|
|
74
|
+
|
|
75
|
+
return response;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async getERC1155Collection(request: ERC1155CollectionRequest): Promise<Web3TokenListResponse> {
|
|
79
|
+
|
|
80
|
+
// ✅ CACHE KEY: Create unique cache key for collection request
|
|
81
|
+
const contractAddresses = request.tokens.map(t => t.contractAddress).sort().join(',');
|
|
82
|
+
const cacheKey = `erc1155_collection_${request.accountAddress}_${contractAddresses}`;
|
|
83
|
+
|
|
84
|
+
// ✅ CACHE CHECK: Try to get from cache first
|
|
85
|
+
const cached = this.cache.get<Web3TokenListResponse>(cacheKey);
|
|
86
|
+
if (cached) {
|
|
87
|
+
console.debug(`💾 [Web3Service] Using cached ERC1155 collection for ${request.accountAddress}`);
|
|
88
|
+
return cached;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.debug(`🔄 [Web3Service] Fetching fresh ERC1155 collection for ${request.accountAddress}`);
|
|
92
|
+
|
|
93
|
+
const tokenResults = await Promise.all(
|
|
94
|
+
request.tokens.map(async (token) => {
|
|
95
|
+
// ✅ FIXED: Handle null metadata properly
|
|
96
|
+
const tokenIds: string[] = token.metadata?.map(m =>
|
|
97
|
+
m.tokenMetadataIncrementalId?.toString()
|
|
98
|
+
).filter((id): id is string => id !== undefined) ?? [];
|
|
99
|
+
|
|
100
|
+
// Check balance for each known tokenId
|
|
101
|
+
const balanceResults = await Promise.allSettled(
|
|
102
|
+
tokenIds.map(async (tokenId) => {
|
|
103
|
+
try {
|
|
104
|
+
const rawBalance = await this.web3Api.getTokenBalance({
|
|
105
|
+
accountAddress: request.accountAddress,
|
|
106
|
+
contractAddress: token.contractAddress,
|
|
107
|
+
abi: token.abi,
|
|
108
|
+
tokenId,
|
|
109
|
+
chainId: token.chainId
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const decimals = token.decimals ?? 0; // ERC1155 usually no decimals
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
tokenId,
|
|
116
|
+
balance: rawBalance,
|
|
117
|
+
formattedBalance: this.formatBalance(rawBalance, decimals),
|
|
118
|
+
hasBalance: rawBalance > 0,
|
|
119
|
+
// ✅ FIXED: Convert null to undefined for findMetadata
|
|
120
|
+
metadata: this.findMetadata(token.metadata ?? undefined, tokenId)
|
|
121
|
+
};
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.warn(`Failed to get balance for token ${token.contractAddress}:${tokenId}`, error);
|
|
124
|
+
return null; // Skip failed tokens
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Filter successful results with balance > 0
|
|
130
|
+
const successfulResults: Web3TokenResult[] = [];
|
|
131
|
+
for (const result of balanceResults) {
|
|
132
|
+
if (result.status === 'fulfilled' && result.value !== null && result.value.hasBalance) {
|
|
133
|
+
successfulResults.push(result.value);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
token,
|
|
139
|
+
results: successfulResults
|
|
140
|
+
};
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const response: Web3TokenListResponse = {
|
|
145
|
+
accountAddress: request.accountAddress,
|
|
146
|
+
tokens: tokenResults.filter(t => t.results.length > 0)
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// ✅ CACHE SET: Store complete collection result
|
|
150
|
+
this.cache.set(cacheKey, response);
|
|
151
|
+
|
|
152
|
+
return response;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async getERC721Collection(request: ERC721CollectionRequest): Promise<ERC721CollectionResponse> {
|
|
156
|
+
|
|
157
|
+
// ✅ CACHE KEY: Create unique cache key for NFT collection
|
|
158
|
+
const contractAddresses = request.nftContracts.map(t => t.contractAddress).sort().join(',');
|
|
159
|
+
const maxNFTs = request.maxNFTsPerContract || 50;
|
|
160
|
+
const cacheKey = `erc721_collection_${request.accountAddress}_${contractAddresses}_${maxNFTs}`;
|
|
161
|
+
|
|
162
|
+
// ✅ CACHE CHECK: Try to get from cache first
|
|
163
|
+
const cached = this.cache.get<ERC721CollectionResponse>(cacheKey);
|
|
164
|
+
if (cached) {
|
|
165
|
+
console.debug(`💾 [Web3Service] Using cached ERC721 collection for ${request.accountAddress}`);
|
|
166
|
+
return cached;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.debug(`🔄 [Web3Service] Fetching fresh ERC721 collection for ${request.accountAddress}`);
|
|
170
|
+
|
|
171
|
+
const startTime = Date.now();
|
|
172
|
+
|
|
173
|
+
const contractResults = await Promise.all(
|
|
174
|
+
request.nftContracts.map(async (token) => {
|
|
175
|
+
try {
|
|
176
|
+
const totalBalance = await this.web3Api.getTokenBalance({
|
|
177
|
+
accountAddress: request.accountAddress,
|
|
178
|
+
contractAddress: token.contractAddress,
|
|
179
|
+
abi: token.abi,
|
|
180
|
+
tokenId: null,
|
|
181
|
+
chainId: token.chainId
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
if (totalBalance === 0) {
|
|
185
|
+
return {
|
|
186
|
+
token,
|
|
187
|
+
totalNFTs: 0,
|
|
188
|
+
nfts: [],
|
|
189
|
+
hasMore: false
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const nftsToLoad = Math.min(totalBalance, maxNFTs);
|
|
194
|
+
|
|
195
|
+
const nftResults = await Promise.allSettled(
|
|
196
|
+
Array.from({ length: nftsToLoad }, async (_, index): Promise<NFTItem | null> => {
|
|
197
|
+
try {
|
|
198
|
+
const tokenId = await this.web3Api.getTokenOfOwnerByIndex({
|
|
199
|
+
contractAddress: token.contractAddress,
|
|
200
|
+
abi: token.abi,
|
|
201
|
+
accountAddress: request.accountAddress,
|
|
202
|
+
tokenIndex: index,
|
|
203
|
+
chainId: token.chainId
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const tokenUri = await this.web3Api.getTokenUri({
|
|
207
|
+
contractAddress: token.contractAddress,
|
|
208
|
+
abi: token.abi,
|
|
209
|
+
tokenId,
|
|
210
|
+
chainId: token.chainId
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const metadata = await this.fetchMetadata(tokenUri, token.chainId);
|
|
214
|
+
|
|
215
|
+
const nftItem: NFTItem = {
|
|
216
|
+
tokenId,
|
|
217
|
+
name: metadata?.name || `Token #${tokenId}`,
|
|
218
|
+
description: metadata?.description || '',
|
|
219
|
+
imageUrl: await this.resolveIPFSUrl(metadata?.image || '', token.chainId),
|
|
220
|
+
rawBalance: 1,
|
|
221
|
+
formattedBalance: '1',
|
|
222
|
+
hasBalance: true,
|
|
223
|
+
metadata,
|
|
224
|
+
tokenIndex: index
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
return nftItem;
|
|
228
|
+
|
|
229
|
+
} catch (error) {
|
|
230
|
+
console.warn(`Failed to load NFT at index ${index} for ${token.symbol}:`, error);
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
// ✅ FIXED: Usar tipo específico NFTItem
|
|
237
|
+
const successfulNFTs = nftResults
|
|
238
|
+
.filter((result): result is PromiseFulfilledResult<NFTItem | null> =>
|
|
239
|
+
result.status === 'fulfilled' && result.value !== null
|
|
240
|
+
)
|
|
241
|
+
.map(result => result.value as NFTItem);
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
token,
|
|
245
|
+
totalNFTs: totalBalance,
|
|
246
|
+
nfts: successfulNFTs,
|
|
247
|
+
hasMore: totalBalance > maxNFTs
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
} catch (error) {
|
|
251
|
+
console.error(`Failed to load NFT collection for ${token.symbol}:`, error);
|
|
252
|
+
return {
|
|
253
|
+
token,
|
|
254
|
+
totalNFTs: 0,
|
|
255
|
+
nfts: [],
|
|
256
|
+
hasMore: false
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
})
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const totalNFTs = contractResults.reduce((sum, contract) => sum + contract.nfts.length, 0);
|
|
263
|
+
const loadingTime = Date.now() - startTime;
|
|
264
|
+
|
|
265
|
+
const response: ERC721CollectionResponse = {
|
|
266
|
+
accountAddress: request.accountAddress,
|
|
267
|
+
contracts: contractResults,
|
|
268
|
+
summary: {
|
|
269
|
+
totalContracts: request.nftContracts.length,
|
|
270
|
+
totalNFTs,
|
|
271
|
+
loadingTime
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// ✅ CACHE SET: Store complete collection response
|
|
276
|
+
this.cache.set(cacheKey, response);
|
|
277
|
+
|
|
278
|
+
return response;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ==========================================
|
|
282
|
+
// HELPER METHODS
|
|
283
|
+
// ==========================================
|
|
284
|
+
|
|
285
|
+
private formatBalance(rawBalance: number, decimals: number): string {
|
|
286
|
+
const balance = rawBalance / Math.pow(10, decimals);
|
|
287
|
+
|
|
288
|
+
return balance.toLocaleString('en-US', {
|
|
289
|
+
minimumFractionDigits: 0,
|
|
290
|
+
maximumFractionDigits: decimals > 0 ? 2 : 0
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ✅ FIXED: Update method signature to handle null properly
|
|
295
|
+
private findMetadata(metadata: any[] | undefined, tokenId: string | null): any | null {
|
|
296
|
+
if (!metadata || tokenId === null) return null;
|
|
297
|
+
return metadata.find(m =>
|
|
298
|
+
m.tokenMetadataIncrementalId?.toString() === tokenId
|
|
299
|
+
) || null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
private async fetchMetadata(uri: string, chainId: number): Promise<any> {
|
|
303
|
+
try {
|
|
304
|
+
const httpUrl = await this.resolveIPFSUrl(uri, chainId);
|
|
305
|
+
const response = await fetch(httpUrl);
|
|
306
|
+
|
|
307
|
+
if (!response.ok) {
|
|
308
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return await response.json();
|
|
312
|
+
} catch (error) {
|
|
313
|
+
console.warn('Failed to fetch NFT metadata:', error);
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private async getIpfsGatewayDomain(chainId: number): Promise<string> {
|
|
319
|
+
try {
|
|
320
|
+
const chainData = await this.web3ChainService.getChainDataWithCache(chainId);
|
|
321
|
+
return chainData.ipfsGatewayDomain || this.defaultIpfsGatewayDomain;
|
|
322
|
+
} catch (error) {
|
|
323
|
+
console.warn(`Failed to get chain data for chainId ${chainId}, using default IPFS gateway:`, error);
|
|
324
|
+
return this.defaultIpfsGatewayDomain;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private async resolveIPFSUrl(url: string, chainId: number): Promise<string> {
|
|
329
|
+
if (!url) return '';
|
|
330
|
+
|
|
331
|
+
if (url.startsWith('ipfs://')) {
|
|
332
|
+
const gatewayDomain = await this.getIpfsGatewayDomain(chainId);
|
|
333
|
+
return `https://${gatewayDomain}/ipfs/${url.slice(7)}`;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return url;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { PersApiClient } from '../../core/pers-api-client';
|
|
2
|
+
import { ChainData } from '../models';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Platform-Agnostic Web3 Chain API Client
|
|
6
|
+
*
|
|
7
|
+
* Handles blockchain chain operations using the PERS backend.
|
|
8
|
+
* Uses @explorins/web3-ts types for perfect framework compatibility.
|
|
9
|
+
*/
|
|
10
|
+
export class Web3ChainApi {
|
|
11
|
+
constructor(private apiClient: PersApiClient) {}
|
|
12
|
+
|
|
13
|
+
private basePath = '/chains';
|
|
14
|
+
|
|
15
|
+
// ==========================================
|
|
16
|
+
// PUBLIC OPERATIONS
|
|
17
|
+
// ==========================================
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* PUBLIC: Get chain data by chain ID
|
|
21
|
+
* ✅ Returns ChainData exactly as framework expects from @explorins/web3-ts
|
|
22
|
+
*/
|
|
23
|
+
async getChainData(chainId: number): Promise<ChainData> {
|
|
24
|
+
try {
|
|
25
|
+
|
|
26
|
+
console.log('🔍 [Web3ChainApi] Fetching chain data for chainId:', chainId);
|
|
27
|
+
const response = await this.apiClient.get<ChainData>(`${this.basePath}/${chainId}`);
|
|
28
|
+
|
|
29
|
+
if (!response) {
|
|
30
|
+
throw new Error(`No chain data received for chainId: ${chainId}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return response;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('❌ [Web3ChainApi] Failed to get chain data:', {
|
|
36
|
+
chainId,
|
|
37
|
+
error: error instanceof Error ? error.message : String(error)
|
|
38
|
+
});
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PersApiClient } from '../core/pers-api-client';
|
|
2
|
+
import { Web3ChainApi } from './api/web3-chain-api';
|
|
3
|
+
import { Web3ProviderService } from './services/provider.service';
|
|
4
|
+
|
|
5
|
+
import { Web3ChainService } from './services/web3-chain-service';
|
|
6
|
+
|
|
7
|
+
export function createWeb3ChainSDK(apiClient: PersApiClient, providerService: Web3ProviderService) {
|
|
8
|
+
const web3ChainApi = new Web3ChainApi(apiClient);
|
|
9
|
+
const web3ChainService = new Web3ChainService(web3ChainApi, providerService); // ✅ DIRECT INJECTION
|
|
10
|
+
|
|
11
|
+
return {
|
|
12
|
+
// ✅ REPLICA: Same methods as framework
|
|
13
|
+
getChainDataById: (chainId: number) => web3ChainService.getChainDataById(chainId),
|
|
14
|
+
getWeb3ByChainId: (chainId: number) => web3ChainService.getWeb3ByChainId(chainId),
|
|
15
|
+
|
|
16
|
+
api: web3ChainApi,
|
|
17
|
+
service: web3ChainService
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ✅ EXPORT: All models and service
|
|
22
|
+
export { Web3ChainApi } from './api/web3-chain-api';
|
|
23
|
+
export { Web3ChainService } from './services/web3-chain-service';
|
|
24
|
+
export { Web3ProviderService } from './services/provider.service';
|
|
25
|
+
export * from './models';
|
|
26
|
+
export type { ChainData } from './models';
|
|
27
|
+
export * from '../shared/interfaces/pers-shared-lib.interfaces';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web3 Chain Domain Models
|
|
3
|
+
*
|
|
4
|
+
* Minimal interface definitions matching framework usage exactly.
|
|
5
|
+
* Local ChainData interface to avoid external dependency issues.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Web3 } from "web3";
|
|
9
|
+
|
|
10
|
+
// ✅ FIXED: Local ChainData interface instead of problematic external import
|
|
11
|
+
export interface ChainData {
|
|
12
|
+
name: string;
|
|
13
|
+
shortName?: string;
|
|
14
|
+
chain?: string;
|
|
15
|
+
network?: string;
|
|
16
|
+
chainId?: number;
|
|
17
|
+
networkId?: number | null;
|
|
18
|
+
rpcUrl: string;
|
|
19
|
+
explorerUrl?: string;
|
|
20
|
+
authHeader?: string;
|
|
21
|
+
nativeCurrency?: any;
|
|
22
|
+
chainType: ChainType;
|
|
23
|
+
ipfsGatewayDomain?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type ChainType = typeof ChainTypes[keyof typeof ChainTypes];
|
|
27
|
+
|
|
28
|
+
export const ChainTypes = {
|
|
29
|
+
PUBLIC: 'PUBLIC',
|
|
30
|
+
PRIVATE: 'PRIVATE'
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
export interface CachedWeb3Instance {
|
|
34
|
+
instance: Web3;
|
|
35
|
+
createdAt: number;
|
|
36
|
+
chainId: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface CachedChainInstance {
|
|
40
|
+
web3Instance: Web3;
|
|
41
|
+
chainData: ChainData;
|
|
42
|
+
createdAt: number;
|
|
43
|
+
chainId: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
|
|
2
|
+
//IMPORTANT//
|
|
3
|
+
//This function is temporary so we install ethers just to make it work, once we delete this function we must uninstall Ethers
|
|
4
|
+
|
|
5
|
+
import { ChainData } from "@explorins/web3-ts";
|
|
6
|
+
import { FetchRequest, JsonRpcProvider } from "ethers";
|
|
7
|
+
|
|
8
|
+
export const getWeb3ProviderFromChainData = (
|
|
9
|
+
chainData: ChainData,
|
|
10
|
+
timeout = 15000,
|
|
11
|
+
customUserAgentName = '',
|
|
12
|
+
tokenRefresher?: () => Promise<string>
|
|
13
|
+
) => {
|
|
14
|
+
|
|
15
|
+
// Fixed ethers provider setup for authenticated requests
|
|
16
|
+
let ethersProvider: JsonRpcProvider;
|
|
17
|
+
|
|
18
|
+
if (chainData.authHeader) {
|
|
19
|
+
// For authenticated requests, create a custom FetchRequest
|
|
20
|
+
const fetchRequest = new FetchRequest(chainData.rpcUrl);
|
|
21
|
+
fetchRequest.timeout = timeout;
|
|
22
|
+
fetchRequest.setHeader('Authorization', chainData.authHeader);
|
|
23
|
+
fetchRequest.setHeader('Content-Type', 'application/json');
|
|
24
|
+
|
|
25
|
+
if (customUserAgentName) {
|
|
26
|
+
fetchRequest.setHeader('User-Agent', customUserAgentName);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create provider with the configured FetchRequest
|
|
30
|
+
ethersProvider = new JsonRpcProvider(fetchRequest, undefined, {
|
|
31
|
+
staticNetwork: false,
|
|
32
|
+
polling: false, // Disable polling for better Lambda performance
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
// For public chains, use simple URL-based provider
|
|
36
|
+
ethersProvider = new JsonRpcProvider(chainData.rpcUrl, undefined, {
|
|
37
|
+
staticNetwork: false,
|
|
38
|
+
polling: false,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
web3Provider: null,
|
|
44
|
+
ethersProvider: ethersProvider,
|
|
45
|
+
isAuthenticated: !!chainData.authHeader,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import Web3 from "web3";
|
|
2
|
+
import { ChainData, ChainType, ChainTypes } from "@explorins/web3-ts";
|
|
3
|
+
import { PublicHttpProviderService } from "./public-http-provider.service";
|
|
4
|
+
import { getWeb3ProviderFromChainData } from "./getWeb3FCD.service";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export class Web3ProviderService {
|
|
8
|
+
|
|
9
|
+
private _web3: Web3 | null = null;
|
|
10
|
+
private _currentChainId: number | null = null;
|
|
11
|
+
|
|
12
|
+
constructor(
|
|
13
|
+
private readonly publicHttpProviderService: PublicHttpProviderService,
|
|
14
|
+
) {
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public async getWeb3(chainId: number, chainType: ChainType, privateChainData: ChainData | null = null) {
|
|
18
|
+
if (!this._web3 || this._currentChainId !== chainId) {
|
|
19
|
+
|
|
20
|
+
if(!chainId) throw new Error('ChainId not found')
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
this._currentChainId = chainId;
|
|
24
|
+
const provider = await this.getWeb3ByChainId(chainId, chainType, privateChainData);
|
|
25
|
+
this._web3 = this.convertToWeb3(provider);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Error getting web3 connection from chain id ' + chainId , error)
|
|
28
|
+
throw new Error('Error getting web3 connection from chain id ' + chainId)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return this._web3 as Web3;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Keep return type as 'any' to avoid TypeScript errors while still being adapted later
|
|
35
|
+
private getWeb3ByChainId(chainId: number, chainType: ChainType, privateChainData: ChainData | null = null): any {
|
|
36
|
+
// Rest of the method remains the same
|
|
37
|
+
if(chainType === ChainTypes.PRIVATE && privateChainData) {
|
|
38
|
+
//const privateProvider = this.privateChainProviderService.getProviderFromChainData(privateChainData)
|
|
39
|
+
const privateProvider = getWeb3ProviderFromChainData(privateChainData);
|
|
40
|
+
|
|
41
|
+
if(!privateProvider || privateProvider instanceof Error) throw new Error('Error getting web3 provider');
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
return privateProvider;
|
|
45
|
+
|
|
46
|
+
} else {
|
|
47
|
+
|
|
48
|
+
const publicProvider = this.publicHttpProviderService.getProvider(chainId)
|
|
49
|
+
if(!publicProvider || publicProvider instanceof Error) throw new Error('Error getting web3 provider');
|
|
50
|
+
|
|
51
|
+
return publicProvider;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private convertToWeb3(provider: unknown): Web3 {
|
|
56
|
+
if (provider instanceof Web3) {
|
|
57
|
+
return provider as Web3;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (provider && typeof provider === 'object' && 'web3Provider' in provider) {
|
|
61
|
+
const providerObj = provider as {
|
|
62
|
+
web3Provider?: unknown;
|
|
63
|
+
ethersProvider?: any;
|
|
64
|
+
isAuthenticated?: boolean;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// If we want to user the web3Provider directly:
|
|
68
|
+
/*if (providerObj.web3Provider) {
|
|
69
|
+
return new Web3(providerObj.web3Provider as never);
|
|
70
|
+
}*/
|
|
71
|
+
|
|
72
|
+
if (providerObj.ethersProvider) {
|
|
73
|
+
|
|
74
|
+
const url = this.extractUrlFromEthersProvider(providerObj.ethersProvider);
|
|
75
|
+
const headers = this.extractHeadersFromEthersProvider(providerObj.ethersProvider);
|
|
76
|
+
|
|
77
|
+
const web3 = new Web3(url);
|
|
78
|
+
const currentProvider = web3.currentProvider as unknown as Record<string, unknown>;
|
|
79
|
+
|
|
80
|
+
if (currentProvider) {
|
|
81
|
+
currentProvider['url'] = url;
|
|
82
|
+
|
|
83
|
+
if (headers && Object.keys(headers).length > 0) {
|
|
84
|
+
currentProvider['request'] = async (payload: Record<string, unknown>): Promise<Record<string, unknown>> => {
|
|
85
|
+
const response = await fetch(url, {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
headers: {
|
|
88
|
+
'Content-Type': 'application/json',
|
|
89
|
+
...headers
|
|
90
|
+
},
|
|
91
|
+
body: JSON.stringify(payload)
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return await response.json() as Record<string, unknown>;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return web3;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return new Web3(provider as never);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
private extractUrlFromEthersProvider(ethersProvider: any): string {
|
|
112
|
+
return ethersProvider.connection?.url ||
|
|
113
|
+
ethersProvider._getConnection?.()?.url ||
|
|
114
|
+
ethersProvider.url ||
|
|
115
|
+
'';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private extractHeadersFromEthersProvider(ethersProvider: any): Record<string, string> {
|
|
119
|
+
return ethersProvider.connection?.headers ||
|
|
120
|
+
ethersProvider._getConnection?.()?.headers ||
|
|
121
|
+
{};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PublicWeb3ProviderData } from "@explorins/web3/types";
|
|
2
|
+
import type Web3 from "web3";
|
|
3
|
+
|
|
4
|
+
import { adaptToWeb3, getPublicWeb3ConnectionFromChainId } from "@explorins/web3-ts";
|
|
5
|
+
|
|
6
|
+
export class PublicHttpProviderService {
|
|
7
|
+
private $providerData: PublicWeb3ProviderData | null;
|
|
8
|
+
|
|
9
|
+
constructor(providerData: PublicWeb3ProviderData | null = null) {
|
|
10
|
+
this.$providerData = providerData;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
public getProvider(chainId: number): Web3 {
|
|
14
|
+
const providerApiKey = this.$providerData ? this.$providerData?.apiKey || null : null;
|
|
15
|
+
if (!providerApiKey) throw new Error('Provider API key not found');
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
// Use the adapter function instead of unsafe type casting
|
|
19
|
+
const provider = getPublicWeb3ConnectionFromChainId(chainId, 'infuraHttp', providerApiKey);
|
|
20
|
+
return adaptToWeb3(provider);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Error getting web3 connection from chain id ' + chainId, error);
|
|
23
|
+
throw new Error('Error getting web3 connection from chain id ' + chainId);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|