@chainforge/sdk 1.0.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 +494 -0
- package/chainforge-sdk.js +731 -0
- package/package.json +70 -0
package/README.md
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
# ChainForge SDK
|
|
2
|
+
|
|
3
|
+
> **The Firebase of Web3** — Ship multi-chain dApps in minutes, not months.
|
|
4
|
+
|
|
5
|
+
ChainForge SDK eliminates the complexity of Web3 development. No blockchain knowledge required. Works just like Firebase, Auth0, or Stripe.
|
|
6
|
+
|
|
7
|
+
## 🚀 Why ChainForge?
|
|
8
|
+
|
|
9
|
+
| Problem | Traditional Web3 | ChainForge SDK |
|
|
10
|
+
|---------|-----------------|----------------|
|
|
11
|
+
| Wallet Integration | 100+ lines, multiple libraries | `cf.auth.connectWallet('metamask')` |
|
|
12
|
+
| User Management | Complex SIWE, nonces, signatures | Automatic, built-in |
|
|
13
|
+
| Reading Blockchain | Raw hex data, ABI decoding | Human-readable JSON |
|
|
14
|
+
| Transactions | Gas estimation, nonce management | `cf.transactions.send({to, amount})` |
|
|
15
|
+
| Multi-chain | Different APIs for each chain | One API, 7 chains |
|
|
16
|
+
| Auth State | Manual localStorage handling | Automatic persistence |
|
|
17
|
+
|
|
18
|
+
## 📦 Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @chainforge/sdk
|
|
22
|
+
# or
|
|
23
|
+
yarn add @chainforge/sdk
|
|
24
|
+
# or
|
|
25
|
+
<script src="https://unpkg.com/@chainforge/sdk"></script>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 🔑 Quick Start
|
|
29
|
+
|
|
30
|
+
### 1. Initialize SDK
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
import { ChainForge } from '@chainforge/sdk';
|
|
34
|
+
|
|
35
|
+
const cf = new ChainForge({
|
|
36
|
+
apiKey: 'your-api-key', // Get from chainforge.io/dashboard
|
|
37
|
+
baseURL: 'https://api.chainforge.io' // Optional: for self-hosted
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 2. Connect Wallet (One Line)
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
// As simple as Firebase Auth
|
|
45
|
+
const { user, token } = await cf.auth.connectWallet('metamask');
|
|
46
|
+
|
|
47
|
+
console.log(user.address); // 0x742d35...
|
|
48
|
+
console.log(user.chain); // ethereum
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Read Blockchain Data (No Web3 Knowledge)
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
// Get balance - returns human-readable format
|
|
55
|
+
const balance = await cf.data.getBalance();
|
|
56
|
+
console.log(balance.formatted); // "1.5"
|
|
57
|
+
console.log(balance.symbol); // "ETH"
|
|
58
|
+
|
|
59
|
+
// Get transaction history - auto-humanized
|
|
60
|
+
const history = await cf.data.getHistory({ limit: 10 });
|
|
61
|
+
history.forEach(tx => {
|
|
62
|
+
console.log(tx.summary); // "Sent 0.1 ETH to 0x1234..."
|
|
63
|
+
console.log(tx.display.timeAgo); // "2h ago"
|
|
64
|
+
});
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 4. Send Transactions (Abstracted Complexity)
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
// No gas estimation, no nonce management, no hex encoding
|
|
71
|
+
const tx = await cf.transactions.send({
|
|
72
|
+
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f8dEe',
|
|
73
|
+
amount: '0.1 ETH'
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
console.log(tx.hash); // 0xabc123...
|
|
77
|
+
console.log(tx.explorer); // https://etherscan.io/tx/0xabc123...
|
|
78
|
+
|
|
79
|
+
// Wait for confirmation
|
|
80
|
+
await tx.wait();
|
|
81
|
+
console.log('Confirmed!');
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 📖 Full API Reference
|
|
85
|
+
|
|
86
|
+
### Authentication
|
|
87
|
+
|
|
88
|
+
#### `cf.auth.connectWallet(walletType, options)`
|
|
89
|
+
|
|
90
|
+
Connect a wallet and authenticate the user.
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
// Connect MetaMask
|
|
94
|
+
const { user, token } = await cf.auth.connectWallet('metamask');
|
|
95
|
+
|
|
96
|
+
// Connect Phantom on Solana
|
|
97
|
+
const { user, token } = await cf.auth.connectWallet('phantom', { chain: 'solana' });
|
|
98
|
+
|
|
99
|
+
// Supported wallets: 'metamask', 'phantom', 'brave', 'coinbase', 'trust'
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
#### `cf.auth.loginWithEmail(email, password)`
|
|
103
|
+
|
|
104
|
+
Traditional email/password auth.
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
const { user, token } = await cf.auth.loginWithEmail('user@example.com', 'password');
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
#### `cf.auth.signupWithEmail(email, password, name)`
|
|
111
|
+
|
|
112
|
+
Create account with email/password.
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
const { user, token } = await cf.auth.signupWithEmail(
|
|
116
|
+
'user@example.com',
|
|
117
|
+
'password',
|
|
118
|
+
'John Doe'
|
|
119
|
+
);
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### `cf.auth.signOut()`
|
|
123
|
+
|
|
124
|
+
Sign out and clear session.
|
|
125
|
+
|
|
126
|
+
```javascript
|
|
127
|
+
await cf.auth.signOut();
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### `cf.auth.getCurrentUser()`
|
|
131
|
+
|
|
132
|
+
Get currently logged-in user.
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
const user = cf.auth.getCurrentUser();
|
|
136
|
+
console.log(user.wallets); // Array of linked wallets
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Data (Reading Blockchain)
|
|
140
|
+
|
|
141
|
+
#### `cf.data.getBalance(address?, chain?)`
|
|
142
|
+
|
|
143
|
+
Get wallet balance in human-readable format.
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
// Get current user's balance
|
|
147
|
+
const balance = await cf.data.getBalance();
|
|
148
|
+
// { formatted: "1.5", symbol: "ETH", raw: "1500000000000000000" }
|
|
149
|
+
|
|
150
|
+
// Get any address balance
|
|
151
|
+
const balance = await cf.data.getBalance('0x1234...', 'polygon');
|
|
152
|
+
// { formatted: "100.5", symbol: "MATIC", raw: "100500000000000000000" }
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### `cf.data.getHistory(options)`
|
|
156
|
+
|
|
157
|
+
Get human-readable transaction history.
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
const history = await cf.data.getHistory({
|
|
161
|
+
address: '0x1234...', // Optional: defaults to current user
|
|
162
|
+
chain: 'ethereum', // Optional: defaults to user's chain
|
|
163
|
+
limit: 20 // Optional: default 20
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
history.forEach(tx => {
|
|
167
|
+
console.log(tx.summary); // "Sent 0.5 ETH to 0x5678..."
|
|
168
|
+
console.log(tx.display.timeAgo); // "2h ago"
|
|
169
|
+
console.log(tx.display.statusConfig.label); // "Confirmed"
|
|
170
|
+
console.log(tx.display.explorerUrl); // Link to explorer
|
|
171
|
+
|
|
172
|
+
// Raw data also available
|
|
173
|
+
console.log(tx.raw.hash);
|
|
174
|
+
console.log(tx.raw.from);
|
|
175
|
+
console.log(tx.raw.to);
|
|
176
|
+
});
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
#### `cf.data.getTransaction(hash, chain?)`
|
|
180
|
+
|
|
181
|
+
Get details for a specific transaction.
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
const tx = await cf.data.getTransaction('0xabc123...');
|
|
185
|
+
console.log(tx.summary); // Human-readable description
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
#### `cf.data.sync()`
|
|
189
|
+
|
|
190
|
+
Sync blockchain data to ChainForge (for caching/indexing).
|
|
191
|
+
|
|
192
|
+
```javascript
|
|
193
|
+
const result = await cf.data.sync();
|
|
194
|
+
console.log(`Synced ${result.synced} transactions`);
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Transactions
|
|
198
|
+
|
|
199
|
+
#### `cf.transactions.send({ to, amount, data?, options? })`
|
|
200
|
+
|
|
201
|
+
Send a transaction with automatic gas estimation.
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
const tx = await cf.transactions.send({
|
|
205
|
+
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f8dEe',
|
|
206
|
+
amount: '0.1 ETH', // Human-readable: "amount unit"
|
|
207
|
+
data: '0x...', // Optional: contract interaction data
|
|
208
|
+
options: {
|
|
209
|
+
gasLimit: '30000', // Optional: auto-estimated if not provided
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
console.log(tx.hash); // Transaction hash
|
|
214
|
+
console.log(tx.explorer); // Explorer URL
|
|
215
|
+
await tx.wait(); // Wait for confirmation
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
#### `cf.transactions.estimateGas(to, amount)`
|
|
219
|
+
|
|
220
|
+
Estimate gas for a transaction.
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
const gas = await cf.transactions.estimateGas(
|
|
224
|
+
'0x742d35Cc6634C0532925a3b844Bc9e7595f8dEe',
|
|
225
|
+
{ value: '0.1', symbol: 'ETH' }
|
|
226
|
+
);
|
|
227
|
+
console.log(gas); // "21000"
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Wallet Management
|
|
231
|
+
|
|
232
|
+
#### `cf.wallets.getAll()`
|
|
233
|
+
|
|
234
|
+
Get all linked wallets.
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
const wallets = await cf.wallets.getAll();
|
|
238
|
+
wallets.forEach(wallet => {
|
|
239
|
+
console.log(wallet.address);
|
|
240
|
+
console.log(wallet.chain);
|
|
241
|
+
console.log(wallet.isPrimary);
|
|
242
|
+
});
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### `cf.wallets.link(wallet)`
|
|
246
|
+
|
|
247
|
+
Link a new wallet to the account.
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
await cf.wallets.link({
|
|
251
|
+
address: '0x1234...',
|
|
252
|
+
chain: 'solana',
|
|
253
|
+
type: 'solana',
|
|
254
|
+
label: 'My Solana Wallet'
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
#### `cf.wallets.unlink(walletId)`
|
|
259
|
+
|
|
260
|
+
Unlink a wallet.
|
|
261
|
+
|
|
262
|
+
```javascript
|
|
263
|
+
await cf.wallets.unlink('wallet-id');
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
#### `cf.wallets.setPrimary(walletId)`
|
|
267
|
+
|
|
268
|
+
Set a wallet as primary.
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
await cf.wallets.setPrimary('wallet-id');
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Webhooks (Event Subscriptions)
|
|
275
|
+
|
|
276
|
+
#### `cf.webhooks.on(event, callback)`
|
|
277
|
+
|
|
278
|
+
Subscribe to on-chain events.
|
|
279
|
+
|
|
280
|
+
```javascript
|
|
281
|
+
// Subscribe to new transactions
|
|
282
|
+
const unsubscribe = cf.webhooks.on('transaction', (tx) => {
|
|
283
|
+
console.log('New transaction:', tx.hash);
|
|
284
|
+
console.log(tx.summary);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
// Later: unsubscribe
|
|
288
|
+
unsubscribe();
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## 🎯 Real-World Examples
|
|
292
|
+
|
|
293
|
+
### DeFi Dashboard
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
import { ChainForge } from '@chainforge/sdk';
|
|
297
|
+
|
|
298
|
+
const cf = new ChainForge({ apiKey: '...' });
|
|
299
|
+
|
|
300
|
+
// Auto-restore session
|
|
301
|
+
await cf.restoreSession();
|
|
302
|
+
|
|
303
|
+
// Get all user data in parallel
|
|
304
|
+
const [balance, history, wallets] = await Promise.all([
|
|
305
|
+
cf.data.getBalance(),
|
|
306
|
+
cf.data.getHistory({ limit: 10 }),
|
|
307
|
+
cf.wallets.getAll()
|
|
308
|
+
]);
|
|
309
|
+
|
|
310
|
+
// Render dashboard
|
|
311
|
+
renderDashboard({
|
|
312
|
+
balance: `${balance.formatted} ${balance.symbol}`,
|
|
313
|
+
transactions: history.map(tx => ({
|
|
314
|
+
description: tx.summary,
|
|
315
|
+
time: tx.display.timeAgo,
|
|
316
|
+
status: tx.display.statusConfig.label,
|
|
317
|
+
link: tx.display.explorerUrl
|
|
318
|
+
})),
|
|
319
|
+
wallets: wallets.map(w => ({
|
|
320
|
+
address: w.shortAddress,
|
|
321
|
+
chain: w.chain,
|
|
322
|
+
isPrimary: w.isPrimary
|
|
323
|
+
}))
|
|
324
|
+
});
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### NFT Marketplace Integration
|
|
328
|
+
|
|
329
|
+
```javascript
|
|
330
|
+
// Connect wallet
|
|
331
|
+
const { user } = await cf.auth.connectWallet('metamask', { chain: 'polygon' });
|
|
332
|
+
|
|
333
|
+
// Buy NFT - one line transaction
|
|
334
|
+
const tx = await cf.transactions.send({
|
|
335
|
+
to: nftContractAddress,
|
|
336
|
+
amount: '0.05 MATIC',
|
|
337
|
+
data: encodeBuyNFT(tokenId) // Your encoding function
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
await tx.wait();
|
|
341
|
+
alert('NFT purchased successfully!');
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Multi-Chain Wallet Viewer
|
|
345
|
+
|
|
346
|
+
```javascript
|
|
347
|
+
const cf = new ChainForge({ apiKey: '...' });
|
|
348
|
+
await cf.auth.connectWallet('metamask');
|
|
349
|
+
|
|
350
|
+
// Get balances across all chains
|
|
351
|
+
const chains = ['ethereum', 'polygon', 'bnb', 'avalanche', 'arbitrum', 'optimism'];
|
|
352
|
+
const balances = await Promise.all(
|
|
353
|
+
chains.map(chain => cf.data.getBalance(null, chain).catch(() => null))
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
// Show portfolio
|
|
357
|
+
const portfolio = chains.reduce((acc, chain, i) => {
|
|
358
|
+
if (balances[i]) acc[chain] = balances[i];
|
|
359
|
+
return acc;
|
|
360
|
+
}, {});
|
|
361
|
+
|
|
362
|
+
console.log(portfolio);
|
|
363
|
+
// {
|
|
364
|
+
// ethereum: { formatted: "1.5", symbol: "ETH" },
|
|
365
|
+
// polygon: { formatted: "100", symbol: "MATIC" },
|
|
366
|
+
// ...
|
|
367
|
+
// }
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
## 🔧 Configuration
|
|
371
|
+
|
|
372
|
+
### Constructor Options
|
|
373
|
+
|
|
374
|
+
```javascript
|
|
375
|
+
const cf = new ChainForge({
|
|
376
|
+
apiKey: 'required-api-key',
|
|
377
|
+
baseURL: 'https://api.chainforge.io', // Optional
|
|
378
|
+
timeout: 30000 // Request timeout in ms
|
|
379
|
+
});
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Error Handling
|
|
383
|
+
|
|
384
|
+
```javascript
|
|
385
|
+
import { ChainForge, ChainForgeError } from '@chainforge/sdk';
|
|
386
|
+
|
|
387
|
+
try {
|
|
388
|
+
await cf.auth.connectWallet('metamask');
|
|
389
|
+
} catch (error) {
|
|
390
|
+
if (error instanceof ChainForgeError) {
|
|
391
|
+
console.log(error.code); // 'WALLET_NOT_INSTALLED'
|
|
392
|
+
console.log(error.message); // 'MetaMask is not installed'
|
|
393
|
+
console.log(error.status); // HTTP status code
|
|
394
|
+
|
|
395
|
+
// Show user-friendly message
|
|
396
|
+
if (error.code === 'WALLET_NOT_INSTALLED') {
|
|
397
|
+
showInstallPrompt(error.installUrl);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Error Codes
|
|
404
|
+
|
|
405
|
+
- `WALLET_NOT_INSTALLED` - User needs to install wallet extension
|
|
406
|
+
- `USER_REJECTED` - User rejected the connection/transaction
|
|
407
|
+
- `NOT_AUTHENTICATED` - User needs to login first
|
|
408
|
+
- `MISSING_ADDRESS` - No wallet address provided
|
|
409
|
+
- `INVALID_AMOUNT` - Wrong format for amount
|
|
410
|
+
- `NETWORK_ERROR` - Connection issue
|
|
411
|
+
- `CHAIN_NOT_SUPPORTED` - Selected chain not available
|
|
412
|
+
|
|
413
|
+
## 🌐 Supported Chains
|
|
414
|
+
|
|
415
|
+
| Chain | ID | Type | Wallets |
|
|
416
|
+
|-------|-----|------|---------|
|
|
417
|
+
| Ethereum | `ethereum` | EVM | MetaMask, Phantom (EVM), Brave, Coinbase, Trust |
|
|
418
|
+
| Polygon | `polygon` | EVM | MetaMask, Phantom (EVM), Brave, Coinbase, Trust |
|
|
419
|
+
| BNB Chain | `bnb` | EVM | MetaMask, Brave, Coinbase, Trust |
|
|
420
|
+
| Avalanche | `avalanche` | EVM | MetaMask, Brave, Coinbase, Trust |
|
|
421
|
+
| Arbitrum | `arbitrum` | EVM | MetaMask, Brave, Coinbase, Trust |
|
|
422
|
+
| Optimism | `optimism` | EVM | MetaMask, Brave, Coinbase, Trust |
|
|
423
|
+
| Solana | `solana` | Solana | Phantom, Brave, Coinbase, Trust |
|
|
424
|
+
|
|
425
|
+
## 📱 React Integration
|
|
426
|
+
|
|
427
|
+
```jsx
|
|
428
|
+
import { useEffect, useState } from 'react';
|
|
429
|
+
import { ChainForge } from '@chainforge/sdk';
|
|
430
|
+
|
|
431
|
+
const cf = new ChainForge({ apiKey: '...' });
|
|
432
|
+
|
|
433
|
+
function App() {
|
|
434
|
+
const [user, setUser] = useState(null);
|
|
435
|
+
const [balance, setBalance] = useState(null);
|
|
436
|
+
|
|
437
|
+
useEffect(() => {
|
|
438
|
+
// Restore session on load
|
|
439
|
+
cf.restoreSession().then(() => {
|
|
440
|
+
setUser(cf.currentUser);
|
|
441
|
+
});
|
|
442
|
+
}, []);
|
|
443
|
+
|
|
444
|
+
const connect = async () => {
|
|
445
|
+
const { user } = await cf.auth.connectWallet('metamask');
|
|
446
|
+
setUser(user);
|
|
447
|
+
|
|
448
|
+
// Load balance
|
|
449
|
+
const bal = await cf.data.getBalance();
|
|
450
|
+
setBalance(bal);
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
return (
|
|
454
|
+
<div>
|
|
455
|
+
{user ? (
|
|
456
|
+
<div>
|
|
457
|
+
<p>Connected: {user.address}</p>
|
|
458
|
+
<p>Balance: {balance?.formatted} {balance?.symbol}</p>
|
|
459
|
+
<button onClick={() => cf.auth.signOut()}>Disconnect</button>
|
|
460
|
+
</div>
|
|
461
|
+
) : (
|
|
462
|
+
<button onClick={connect}>Connect Wallet</button>
|
|
463
|
+
)}
|
|
464
|
+
</div>
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
## 🔒 Security
|
|
470
|
+
|
|
471
|
+
- API keys are scoped per project
|
|
472
|
+
- All requests use HTTPS
|
|
473
|
+
- Wallet private keys never leave the user's device
|
|
474
|
+
- Authentication via JWT with automatic refresh
|
|
475
|
+
- Rate limiting on all endpoints
|
|
476
|
+
|
|
477
|
+
## 📚 Resources
|
|
478
|
+
|
|
479
|
+
- [Documentation](https://docs.chainforge.io)
|
|
480
|
+
- [Dashboard](https://chainforge.io/dashboard)
|
|
481
|
+
- [GitHub](https://github.com/chainforge/sdk)
|
|
482
|
+
- [Discord](https://discord.gg/chainforge)
|
|
483
|
+
|
|
484
|
+
## 💡 Need Help?
|
|
485
|
+
|
|
486
|
+
- **Dashboard**: Manage API keys, view analytics, configure webhooks
|
|
487
|
+
- **Support**: support@chainforge.io
|
|
488
|
+
- **Discord**: Join our community for real-time help
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
**Built with ❤️ by the ChainForge team**
|
|
493
|
+
|
|
494
|
+
*Making Web3 as easy as Web2*
|
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChainForge SDK
|
|
3
|
+
* The Firebase of Web3 - Simple, powerful Web3 integration
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* import { ChainForge } from '@chainforge/sdk';
|
|
7
|
+
*
|
|
8
|
+
* const cf = new ChainForge({ apiKey: 'your-api-key' });
|
|
9
|
+
*
|
|
10
|
+
* // Auth with wallet - as simple as Firebase Auth
|
|
11
|
+
* const user = await cf.auth.connectWallet('metamask');
|
|
12
|
+
*
|
|
13
|
+
* // Read blockchain data - no Web3 knowledge needed
|
|
14
|
+
* const balance = await cf.data.getBalance(user.address);
|
|
15
|
+
*
|
|
16
|
+
* // Send transactions - abstracted complexity
|
|
17
|
+
* await cf.transactions.send({
|
|
18
|
+
* to: '0x...',
|
|
19
|
+
* amount: '0.1 ETH'
|
|
20
|
+
* });
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
class ChainForgeSDK {
|
|
24
|
+
constructor(config = {}) {
|
|
25
|
+
this.apiKey = config.apiKey;
|
|
26
|
+
this.baseURL = config.baseURL || "https://api.chainforge.io";
|
|
27
|
+
this.timeout = config.timeout || 30000;
|
|
28
|
+
|
|
29
|
+
// Initialize sub-modules
|
|
30
|
+
this.auth = new AuthModule(this);
|
|
31
|
+
this.data = new DataModule(this);
|
|
32
|
+
this.transactions = new TransactionModule(this);
|
|
33
|
+
this.wallets = new WalletModule(this);
|
|
34
|
+
this.webhooks = new WebhookModule(this);
|
|
35
|
+
|
|
36
|
+
// State
|
|
37
|
+
this._user = null;
|
|
38
|
+
this._token = null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Make authenticated API request
|
|
43
|
+
*/
|
|
44
|
+
async _request(method, endpoint, data = null, options = {}) {
|
|
45
|
+
const url = `${this.baseURL}${endpoint}`;
|
|
46
|
+
const headers = {
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
"X-API-Key": this.apiKey,
|
|
49
|
+
...options.headers,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if (this._token) {
|
|
53
|
+
headers["Authorization"] = `Bearer ${this._token}`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const config = {
|
|
57
|
+
method,
|
|
58
|
+
headers,
|
|
59
|
+
...options,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
if (data && method !== "GET") {
|
|
63
|
+
config.body = JSON.stringify(data);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const response = await fetch(url, config);
|
|
68
|
+
const result = await response.json();
|
|
69
|
+
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new ChainForgeError(
|
|
72
|
+
result.error?.message || `HTTP ${response.status}`,
|
|
73
|
+
result.error?.code || "UNKNOWN_ERROR",
|
|
74
|
+
response.status,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return result;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
if (error instanceof ChainForgeError) throw error;
|
|
81
|
+
throw new ChainForgeError(error.message, "NETWORK_ERROR");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Set auth token (called after successful auth)
|
|
87
|
+
*/
|
|
88
|
+
_setAuth(token, user) {
|
|
89
|
+
this._token = token;
|
|
90
|
+
this._user = user;
|
|
91
|
+
|
|
92
|
+
// Store in localStorage for persistence
|
|
93
|
+
if (typeof localStorage !== "undefined") {
|
|
94
|
+
localStorage.setItem("chainforge_token", token);
|
|
95
|
+
localStorage.setItem("chainforge_user", JSON.stringify(user));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get current user
|
|
101
|
+
*/
|
|
102
|
+
get currentUser() {
|
|
103
|
+
return this._user;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check if user is authenticated
|
|
108
|
+
*/
|
|
109
|
+
get isAuthenticated() {
|
|
110
|
+
return !!this._token;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Restore session from storage
|
|
115
|
+
*/
|
|
116
|
+
async restoreSession() {
|
|
117
|
+
if (typeof localStorage === "undefined") return false;
|
|
118
|
+
|
|
119
|
+
const token = localStorage.getItem("chainforge_token");
|
|
120
|
+
const userStr = localStorage.getItem("chainforge_user");
|
|
121
|
+
|
|
122
|
+
if (token && userStr) {
|
|
123
|
+
this._token = token;
|
|
124
|
+
this._user = JSON.parse(userStr);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Sign out
|
|
133
|
+
*/
|
|
134
|
+
async signOut() {
|
|
135
|
+
this._token = null;
|
|
136
|
+
this._user = null;
|
|
137
|
+
|
|
138
|
+
if (typeof localStorage !== "undefined") {
|
|
139
|
+
localStorage.removeItem("chainforge_token");
|
|
140
|
+
localStorage.removeItem("chainforge_user");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Custom error class
|
|
147
|
+
*/
|
|
148
|
+
class ChainForgeError extends Error {
|
|
149
|
+
constructor(message, code, status = null) {
|
|
150
|
+
super(message);
|
|
151
|
+
this.name = "ChainForgeError";
|
|
152
|
+
this.code = code;
|
|
153
|
+
this.status = status;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Auth Module - Wallet Authentication (like Firebase Auth)
|
|
159
|
+
*/
|
|
160
|
+
class AuthModule {
|
|
161
|
+
constructor(sdk) {
|
|
162
|
+
this.sdk = sdk;
|
|
163
|
+
this._walletSDK = null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Connect wallet and authenticate
|
|
168
|
+
* @param {string} walletType - 'metamask', 'phantom', 'brave', 'coinbase', 'trust'
|
|
169
|
+
* @param {Object} options - { chain: 'ethereum', silent: false }
|
|
170
|
+
* @returns {Promise<{user: Object, token: string}>}
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* const { user, token } = await cf.auth.connectWallet('metamask');
|
|
174
|
+
* console.log(user.address); // 0x...
|
|
175
|
+
* console.log(user.chain); // ethereum
|
|
176
|
+
*/
|
|
177
|
+
async connectWallet(walletType, options = {}) {
|
|
178
|
+
const chain = options.chain || "ethereum";
|
|
179
|
+
|
|
180
|
+
// Check if wallet is installed
|
|
181
|
+
const wallet = this._detectWallet(walletType);
|
|
182
|
+
if (!wallet.installed) {
|
|
183
|
+
throw new ChainForgeError(
|
|
184
|
+
`${wallet.name} is not installed`,
|
|
185
|
+
"WALLET_NOT_INSTALLED",
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Connect to wallet
|
|
190
|
+
const connection = await this._connectToWallet(walletType, chain);
|
|
191
|
+
|
|
192
|
+
// Request nonce + canonical message from backend
|
|
193
|
+
const nonceResult = await this.sdk._request(
|
|
194
|
+
"GET",
|
|
195
|
+
`/api/auth/wallet/nonce?address=${encodeURIComponent(connection.address)}&chain=${encodeURIComponent(chain)}`,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
// Ask wallet to sign the exact server-provided message
|
|
199
|
+
const signature = await this._signWalletMessage(
|
|
200
|
+
walletType,
|
|
201
|
+
nonceResult.message,
|
|
202
|
+
connection.address,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Complete auth using compatibility endpoint
|
|
206
|
+
const authResult = await this.sdk._request(
|
|
207
|
+
"POST",
|
|
208
|
+
"/api/client/wallet-auth",
|
|
209
|
+
{
|
|
210
|
+
walletAddress: connection.address,
|
|
211
|
+
chain,
|
|
212
|
+
signature,
|
|
213
|
+
message: nonceResult.message,
|
|
214
|
+
},
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// Store auth
|
|
218
|
+
this.sdk._setAuth(authResult.token, authResult.user);
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
user: authResult.user,
|
|
222
|
+
token: authResult.token,
|
|
223
|
+
wallet: connection,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Login with email/password
|
|
229
|
+
*/
|
|
230
|
+
async loginWithEmail(email, password) {
|
|
231
|
+
const result = await this.sdk._request("POST", "/api/client/login", {
|
|
232
|
+
email,
|
|
233
|
+
password,
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
this.sdk._setAuth(result.token, result.user);
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
user: result.user,
|
|
240
|
+
token: result.token,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Sign up with email/password
|
|
246
|
+
*/
|
|
247
|
+
async signupWithEmail(email, password, name = "") {
|
|
248
|
+
const result = await this.sdk._request("POST", "/api/client/signup", {
|
|
249
|
+
email,
|
|
250
|
+
password,
|
|
251
|
+
name,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
this.sdk._setAuth(result.token, result.user);
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
user: result.user,
|
|
258
|
+
token: result.token,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Sign out
|
|
264
|
+
*/
|
|
265
|
+
async signOut() {
|
|
266
|
+
await this.sdk.signOut();
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Get current user
|
|
271
|
+
*/
|
|
272
|
+
getCurrentUser() {
|
|
273
|
+
return this.sdk.currentUser;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Check if user is logged in
|
|
278
|
+
*/
|
|
279
|
+
isLoggedIn() {
|
|
280
|
+
return this.sdk.isAuthenticated;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Auto-connect to previously used wallet
|
|
285
|
+
*/
|
|
286
|
+
async autoConnect() {
|
|
287
|
+
if (typeof localStorage === "undefined") return null;
|
|
288
|
+
|
|
289
|
+
const saved = localStorage.getItem("chainforge_wallet");
|
|
290
|
+
if (saved) {
|
|
291
|
+
const { walletId, chainId } = JSON.parse(saved);
|
|
292
|
+
return this.connectWallet(walletId, { chain: chainId });
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
_detectWallet(type) {
|
|
298
|
+
const wallets = {
|
|
299
|
+
metamask: {
|
|
300
|
+
name: "MetaMask",
|
|
301
|
+
installed: typeof window !== "undefined" && window.ethereum?.isMetaMask,
|
|
302
|
+
installUrl: "https://metamask.io/download/",
|
|
303
|
+
},
|
|
304
|
+
phantom: {
|
|
305
|
+
name: "Phantom",
|
|
306
|
+
installed: typeof window !== "undefined" && !!window.solana?.isPhantom,
|
|
307
|
+
installUrl: "https://phantom.app/download",
|
|
308
|
+
},
|
|
309
|
+
brave: {
|
|
310
|
+
name: "Brave Wallet",
|
|
311
|
+
installed:
|
|
312
|
+
typeof window !== "undefined" && window.ethereum?.isBraveWallet,
|
|
313
|
+
installUrl: "https://brave.com/wallet/",
|
|
314
|
+
},
|
|
315
|
+
coinbase: {
|
|
316
|
+
name: "Coinbase Wallet",
|
|
317
|
+
installed:
|
|
318
|
+
typeof window !== "undefined" && window.ethereum?.isCoinbaseWallet,
|
|
319
|
+
installUrl: "https://www.coinbase.com/wallet",
|
|
320
|
+
},
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
return wallets[type] || { name: type, installed: false };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async _connectToWallet(type, chain) {
|
|
327
|
+
// Simplified - would use ethers.js or @solana/web3.js
|
|
328
|
+
// This is a placeholder for the actual implementation
|
|
329
|
+
if (
|
|
330
|
+
type === "metamask" &&
|
|
331
|
+
typeof window !== "undefined" &&
|
|
332
|
+
window.ethereum
|
|
333
|
+
) {
|
|
334
|
+
const accounts = await window.ethereum.request({
|
|
335
|
+
method: "eth_requestAccounts",
|
|
336
|
+
});
|
|
337
|
+
return {
|
|
338
|
+
address: accounts[0],
|
|
339
|
+
chain: chain,
|
|
340
|
+
type: "evm",
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (type === "phantom" && typeof window !== "undefined" && window.solana) {
|
|
345
|
+
await window.solana.connect();
|
|
346
|
+
return {
|
|
347
|
+
address: window.solana.publicKey.toString(),
|
|
348
|
+
chain: "solana",
|
|
349
|
+
type: "solana",
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
throw new ChainForgeError("Wallet connection failed", "CONNECTION_FAILED");
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
_bytesToBase58(bytes) {
|
|
357
|
+
const alphabet =
|
|
358
|
+
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
359
|
+
if (!bytes || bytes.length === 0) return "";
|
|
360
|
+
|
|
361
|
+
let digits = [0];
|
|
362
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
363
|
+
let carry = bytes[i];
|
|
364
|
+
for (let j = 0; j < digits.length; j += 1) {
|
|
365
|
+
const value = digits[j] * 256 + carry;
|
|
366
|
+
digits[j] = value % 58;
|
|
367
|
+
carry = Math.floor(value / 58);
|
|
368
|
+
}
|
|
369
|
+
while (carry > 0) {
|
|
370
|
+
digits.push(carry % 58);
|
|
371
|
+
carry = Math.floor(carry / 58);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
let result = "";
|
|
376
|
+
for (let i = 0; i < bytes.length && bytes[i] === 0; i += 1) {
|
|
377
|
+
result += "1";
|
|
378
|
+
}
|
|
379
|
+
for (let i = digits.length - 1; i >= 0; i -= 1) {
|
|
380
|
+
result += alphabet[digits[i]];
|
|
381
|
+
}
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async _signWalletMessage(walletType, message, address) {
|
|
386
|
+
if (!message || !address) {
|
|
387
|
+
throw new ChainForgeError(
|
|
388
|
+
"Missing message or address for wallet signing",
|
|
389
|
+
"SIGNATURE_INPUT_MISSING",
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (
|
|
394
|
+
(walletType === "metamask" ||
|
|
395
|
+
walletType === "brave" ||
|
|
396
|
+
walletType === "coinbase") &&
|
|
397
|
+
typeof window !== "undefined" &&
|
|
398
|
+
window.ethereum
|
|
399
|
+
) {
|
|
400
|
+
return window.ethereum.request({
|
|
401
|
+
method: "personal_sign",
|
|
402
|
+
params: [message, address],
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (
|
|
407
|
+
walletType === "phantom" &&
|
|
408
|
+
typeof window !== "undefined" &&
|
|
409
|
+
window.solana?.signMessage
|
|
410
|
+
) {
|
|
411
|
+
const encodedMessage = new TextEncoder().encode(message);
|
|
412
|
+
const signed = await window.solana.signMessage(encodedMessage, "utf8");
|
|
413
|
+
const signatureBytes = signed?.signature || signed;
|
|
414
|
+
return this._bytesToBase58(signatureBytes);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
throw new ChainForgeError(
|
|
418
|
+
"Wallet does not support message signing",
|
|
419
|
+
"SIGNATURE_NOT_SUPPORTED",
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Data Module - Read blockchain data (no Web3 knowledge needed)
|
|
426
|
+
*/
|
|
427
|
+
class DataModule {
|
|
428
|
+
constructor(sdk) {
|
|
429
|
+
this.sdk = sdk;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get wallet balance
|
|
434
|
+
* @param {string} address - Wallet address (defaults to current user)
|
|
435
|
+
* @param {string} chain - Chain ID (defaults to user's chain)
|
|
436
|
+
* @returns {Promise<{formatted: string, symbol: string, raw: string}>}
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* const balance = await cf.data.getBalance();
|
|
440
|
+
* console.log(balance.formatted); // "1.5"
|
|
441
|
+
* console.log(balance.symbol); // "ETH"
|
|
442
|
+
*/
|
|
443
|
+
async getBalance(address = null, chain = null) {
|
|
444
|
+
const user = this.sdk.currentUser;
|
|
445
|
+
const targetAddress = address || user?.walletAddress;
|
|
446
|
+
const targetChain = chain || user?.chain || "ethereum";
|
|
447
|
+
|
|
448
|
+
if (!targetAddress) {
|
|
449
|
+
throw new ChainForgeError("No address provided", "MISSING_ADDRESS");
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const result = await this.sdk._request(
|
|
453
|
+
"GET",
|
|
454
|
+
`/api/onchain/balance/${targetAddress}?chain=${targetChain}`,
|
|
455
|
+
);
|
|
456
|
+
|
|
457
|
+
return result.data.nativeBalance;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Get transaction history
|
|
462
|
+
* @param {Object} options
|
|
463
|
+
* @returns {Promise<Array>} Human-readable transactions
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* const history = await cf.data.getHistory({ limit: 10 });
|
|
467
|
+
* history.forEach(tx => {
|
|
468
|
+
* console.log(tx.summary); // "Sent 0.1 ETH to 0x1234..."
|
|
469
|
+
* });
|
|
470
|
+
*/
|
|
471
|
+
async getHistory(options = {}) {
|
|
472
|
+
const user = this.sdk.currentUser;
|
|
473
|
+
const address = options.address || user?.walletAddress;
|
|
474
|
+
const chain = options.chain || user?.chain || "ethereum";
|
|
475
|
+
const limit = options.limit || 20;
|
|
476
|
+
|
|
477
|
+
if (!address) {
|
|
478
|
+
throw new ChainForgeError("No address provided", "MISSING_ADDRESS");
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const result = await this.sdk._request(
|
|
482
|
+
"GET",
|
|
483
|
+
`/api/onchain/history/${address}?chain=${chain}&limit=${limit}`,
|
|
484
|
+
);
|
|
485
|
+
|
|
486
|
+
return result.data.transactions;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Get human-readable transaction details
|
|
491
|
+
* @param {string} hash - Transaction hash
|
|
492
|
+
* @param {string} chain - Chain ID
|
|
493
|
+
*/
|
|
494
|
+
async getTransaction(hash, chain = null) {
|
|
495
|
+
const targetChain = chain || this.sdk.currentUser?.chain || "ethereum";
|
|
496
|
+
|
|
497
|
+
const result = await this.sdk._request(
|
|
498
|
+
"GET",
|
|
499
|
+
`/api/onchain/humanize/${hash}?chain=${targetChain}`,
|
|
500
|
+
);
|
|
501
|
+
|
|
502
|
+
return result.data;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Sync blockchain data to ChainForge
|
|
507
|
+
* @returns {Promise<{synced: number}>}
|
|
508
|
+
*/
|
|
509
|
+
async sync() {
|
|
510
|
+
const result = await this.sdk._request("POST", "/api/onchain/multisync");
|
|
511
|
+
return result.data;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Transaction Module - Send transactions (abstracted complexity)
|
|
517
|
+
*/
|
|
518
|
+
class TransactionModule {
|
|
519
|
+
constructor(sdk) {
|
|
520
|
+
this.sdk = sdk;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Send a transaction
|
|
525
|
+
* @param {Object} params
|
|
526
|
+
* @param {string} params.to - Recipient address
|
|
527
|
+
* @param {string} params.amount - Amount with unit (e.g., "0.1 ETH")
|
|
528
|
+
* @param {Object} params.options - Additional options
|
|
529
|
+
* @returns {Promise<{hash: string, explorer: string, wait: Function}>}
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* const tx = await cf.transactions.send({
|
|
533
|
+
* to: '0x1234...',
|
|
534
|
+
* amount: '0.1 ETH'
|
|
535
|
+
* });
|
|
536
|
+
* console.log(tx.hash);
|
|
537
|
+
* await tx.wait(); // Wait for confirmation
|
|
538
|
+
*/
|
|
539
|
+
async send({ to, amount, data = null, options = {} }) {
|
|
540
|
+
const user = this.sdk.currentUser;
|
|
541
|
+
|
|
542
|
+
if (!user) {
|
|
543
|
+
throw new ChainForgeError("User not authenticated", "NOT_AUTHENTICATED");
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Parse amount (e.g., "0.1 ETH" -> { value: "0.1", symbol: "ETH" })
|
|
547
|
+
const parsedAmount = this._parseAmount(amount);
|
|
548
|
+
|
|
549
|
+
// Auto-estimate gas
|
|
550
|
+
const gasEstimate =
|
|
551
|
+
options.gasLimit || (await this._estimateGas(to, parsedAmount));
|
|
552
|
+
|
|
553
|
+
// Send via wallet
|
|
554
|
+
const tx = await this._sendViaWallet({
|
|
555
|
+
to,
|
|
556
|
+
value: parsedAmount,
|
|
557
|
+
data,
|
|
558
|
+
gasLimit: gasEstimate,
|
|
559
|
+
...options,
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
return {
|
|
563
|
+
hash: tx.hash,
|
|
564
|
+
explorer: `${this._getExplorerUrl()}/tx/${tx.hash}`,
|
|
565
|
+
wait: () => this._waitForConfirmation(tx.hash),
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Estimate gas for a transaction
|
|
571
|
+
*/
|
|
572
|
+
async estimateGas(to, amount) {
|
|
573
|
+
// Would use actual gas estimation
|
|
574
|
+
return "21000"; // Default for simple transfer
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
_parseAmount(amount) {
|
|
578
|
+
const match = amount.match(/^([\d.]+)\s*(\w+)$/);
|
|
579
|
+
if (!match) {
|
|
580
|
+
throw new ChainForgeError(
|
|
581
|
+
'Invalid amount format. Use "0.1 ETH"',
|
|
582
|
+
"INVALID_AMOUNT",
|
|
583
|
+
);
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
value: match[1],
|
|
587
|
+
symbol: match[2],
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async _sendViaWallet(params) {
|
|
592
|
+
// Would use ethers.js or @solana/web3.js
|
|
593
|
+
// Placeholder implementation
|
|
594
|
+
if (typeof window !== "undefined" && window.ethereum) {
|
|
595
|
+
const provider = window.ethereum.providers
|
|
596
|
+
? new window.ethereum.providers.Web3Provider(window.ethereum)
|
|
597
|
+
: null;
|
|
598
|
+
const signer = provider ? provider.getSigner() : null;
|
|
599
|
+
|
|
600
|
+
if (signer) {
|
|
601
|
+
const tx = await signer.sendTransaction({
|
|
602
|
+
to: params.to,
|
|
603
|
+
value: window.ethers?.utils.parseEther(params.value.value),
|
|
604
|
+
});
|
|
605
|
+
return tx;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
throw new ChainForgeError("Wallet not connected", "WALLET_NOT_CONNECTED");
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
_getExplorerUrl() {
|
|
613
|
+
const chain = this.sdk.currentUser?.chain || "ethereum";
|
|
614
|
+
const explorers = {
|
|
615
|
+
ethereum: "https://etherscan.io",
|
|
616
|
+
polygon: "https://polygonscan.com",
|
|
617
|
+
bnb: "https://bscscan.com",
|
|
618
|
+
solana: "https://solscan.io",
|
|
619
|
+
};
|
|
620
|
+
return explorers[chain] || explorers.ethereum;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
async _waitForConfirmation(hash) {
|
|
624
|
+
// Would poll for confirmation
|
|
625
|
+
return new Promise((resolve) => {
|
|
626
|
+
setTimeout(
|
|
627
|
+
() => resolve({ confirmed: true, blockNumber: 12345678 }),
|
|
628
|
+
3000,
|
|
629
|
+
);
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Wallet Module - Manage multiple wallets
|
|
636
|
+
*/
|
|
637
|
+
class WalletModule {
|
|
638
|
+
constructor(sdk) {
|
|
639
|
+
this.sdk = sdk;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Get all linked wallets
|
|
644
|
+
* @returns {Promise<Array>}
|
|
645
|
+
*/
|
|
646
|
+
async getAll() {
|
|
647
|
+
const result = await this.sdk._request("GET", "/api/wallets");
|
|
648
|
+
return result.data.wallets;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Link a new wallet
|
|
653
|
+
* @param {Object} wallet
|
|
654
|
+
*/
|
|
655
|
+
async link(wallet) {
|
|
656
|
+
const result = await this.sdk._request("POST", "/api/wallets/link", wallet);
|
|
657
|
+
return result.data;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Unlink a wallet
|
|
662
|
+
* @param {string} walletId
|
|
663
|
+
*/
|
|
664
|
+
async unlink(walletId) {
|
|
665
|
+
await this.sdk._request("DELETE", `/api/wallets/${walletId}`);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Set primary wallet
|
|
670
|
+
* @param {string} walletId
|
|
671
|
+
*/
|
|
672
|
+
async setPrimary(walletId) {
|
|
673
|
+
await this.sdk._request("PATCH", `/api/wallets/${walletId}/primary`);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Webhook Module - Subscribe to on-chain events
|
|
679
|
+
*/
|
|
680
|
+
class WebhookModule {
|
|
681
|
+
constructor(sdk) {
|
|
682
|
+
this.sdk = sdk;
|
|
683
|
+
this._subscriptions = new Map();
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Subscribe to wallet events
|
|
688
|
+
* @param {string} event - 'transaction', 'balance_change'
|
|
689
|
+
* @param {Function} callback
|
|
690
|
+
* @returns {Function} Unsubscribe function
|
|
691
|
+
*
|
|
692
|
+
* @example
|
|
693
|
+
* const unsubscribe = cf.webhooks.on('transaction', (tx) => {
|
|
694
|
+
* console.log('New transaction:', tx.hash);
|
|
695
|
+
* });
|
|
696
|
+
*/
|
|
697
|
+
on(event, callback) {
|
|
698
|
+
if (!this._subscriptions.has(event)) {
|
|
699
|
+
this._subscriptions.set(event, new Set());
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
this._subscriptions.get(event).add(callback);
|
|
703
|
+
|
|
704
|
+
// Return unsubscribe function
|
|
705
|
+
return () => {
|
|
706
|
+
this._subscriptions.get(event)?.delete(callback);
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Trigger event (internal use)
|
|
712
|
+
*/
|
|
713
|
+
_trigger(event, data) {
|
|
714
|
+
this._subscriptions.get(event)?.forEach((callback) => {
|
|
715
|
+
try {
|
|
716
|
+
callback(data);
|
|
717
|
+
} catch (e) {
|
|
718
|
+
console.error("Webhook callback error:", e);
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Export
|
|
725
|
+
export { ChainForgeSDK, ChainForgeError };
|
|
726
|
+
export default ChainForgeSDK;
|
|
727
|
+
|
|
728
|
+
// UMD build compatibility
|
|
729
|
+
if (typeof window !== "undefined") {
|
|
730
|
+
window.ChainForge = ChainForgeSDK;
|
|
731
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chainforge/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The Firebase of Web3 - Simple, powerful Web3 integration without blockchain knowledge",
|
|
5
|
+
"main": "chainforge-sdk.js",
|
|
6
|
+
"module": "chainforge-sdk.js",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"types": "chainforge-sdk.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"chainforge-sdk.js",
|
|
11
|
+
"chainforge-sdk.d.ts",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "node build.js",
|
|
17
|
+
"test": "node test/test.js",
|
|
18
|
+
"test:watch": "node test/test.js --watch",
|
|
19
|
+
"lint": "eslint chainforge-sdk.js",
|
|
20
|
+
"format": "prettier --write chainforge-sdk.js",
|
|
21
|
+
"prepublishOnly": "echo skipping",
|
|
22
|
+
"prepack": "npm run build",
|
|
23
|
+
"version:patch": "npm version patch",
|
|
24
|
+
"version:minor": "npm version minor",
|
|
25
|
+
"version:major": "npm version major",
|
|
26
|
+
"version:pre": "npm version prerelease --preid=beta"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"web3",
|
|
30
|
+
"blockchain",
|
|
31
|
+
"ethereum",
|
|
32
|
+
"solana",
|
|
33
|
+
"wallet",
|
|
34
|
+
"authentication",
|
|
35
|
+
"firebase",
|
|
36
|
+
"sdk",
|
|
37
|
+
"dapp",
|
|
38
|
+
"crypto"
|
|
39
|
+
],
|
|
40
|
+
"author": "ChainForge",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/chainforge/sdk"
|
|
45
|
+
},
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/chainforge/sdk/issues"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://chainforge.io",
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"ethers": "^6.0.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"ethers": {
|
|
55
|
+
"optional": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=14.0.0"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"eslint": "^8.0.0",
|
|
63
|
+
"prettier": "^3.0.0",
|
|
64
|
+
"jest": "^29.0.0"
|
|
65
|
+
},
|
|
66
|
+
"publishConfig": {
|
|
67
|
+
"access": "public",
|
|
68
|
+
"registry": "https://registry.npmjs.org/"
|
|
69
|
+
}
|
|
70
|
+
}
|