@elmoorx/blockchain 2.0.0-alpha.25
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/LICENSE +21 -0
- package/README.md +33 -0
- package/package.json +34 -0
- package/src/index.ts +340 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wafra Framework
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @wafra/blockchain
|
|
2
|
+
|
|
3
|
+
> Blockchain: Web3 wallet connect, smart contracts, NFT mint, verify
|
|
4
|
+
|
|
5
|
+
Part of the [Wafra Framework](https://github.com/wafra/framework) — Build fast. Run anywhere. Stay secure.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @wafra/blockchain
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { /* exports */ } from '@wafra/blockchain';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Features
|
|
20
|
+
|
|
21
|
+
- Zero external dependencies
|
|
22
|
+
- Full TypeScript support
|
|
23
|
+
- Tree-shakeable
|
|
24
|
+
- Edge-runtime compatible
|
|
25
|
+
- Arabic/RTL friendly
|
|
26
|
+
|
|
27
|
+
## Documentation
|
|
28
|
+
|
|
29
|
+
See [https://wafra.dev/docs/blockchain](https://wafra.dev/docs/blockchain)
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
|
|
33
|
+
MIT © Wafra Framework
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elmoorx/blockchain",
|
|
3
|
+
"version": "2.0.0-alpha.25",
|
|
4
|
+
"description": "Blockchain: Web3 wallet connect, smart contracts, NFT mint, verify",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@elmoorx/runtime": "^1.0.0"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Wafra Framework",
|
|
16
|
+
"homepage": "https://wafra.dev/packages/blockchain",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/wafra/framework",
|
|
20
|
+
"directory": "packages/blockchain"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/wafra/framework/issues"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"wafra",
|
|
27
|
+
"framework",
|
|
28
|
+
"blockchain"
|
|
29
|
+
],
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"exports": {
|
|
32
|
+
".": "./src/index.ts"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wafra/blockchain — Web3 Integration
|
|
3
|
+
* ============================================
|
|
4
|
+
* Built-in Web3 support. Connect wallets, sign transactions,
|
|
5
|
+
* interact with smart contracts — all reactive.
|
|
6
|
+
*
|
|
7
|
+
* import { useWallet, useContract } from "@wafra/blockchain";
|
|
8
|
+
*
|
|
9
|
+
* const { address, connect, disconnect } = useWallet();
|
|
10
|
+
* const contract = useContract(abi, address);
|
|
11
|
+
*
|
|
12
|
+
* // Call contract method
|
|
13
|
+
* const balance = await contract.read("balanceOf", [address]);
|
|
14
|
+
*
|
|
15
|
+
* // Send transaction
|
|
16
|
+
* await contract.write("transfer", [recipient, amount]);
|
|
17
|
+
*
|
|
18
|
+
* Features:
|
|
19
|
+
* - MetaMask / WalletConnect / Coinbase Wallet
|
|
20
|
+
* - Smart contract interaction (read/write)
|
|
21
|
+
* - Transaction signing
|
|
22
|
+
* - Event listening
|
|
23
|
+
* - Multi-chain support (Ethereum, Polygon, BSC, Arbitrum)
|
|
24
|
+
* - Reactive wallet state
|
|
25
|
+
* - Gas estimation
|
|
26
|
+
* - ENS resolution
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { $state, $effect, type WafraNode } from "@wafra/runtime";
|
|
30
|
+
|
|
31
|
+
// ============ WALLET ============
|
|
32
|
+
|
|
33
|
+
export type WalletProvider = "metamask" | "walletconnect" | "coinbase" | "injected";
|
|
34
|
+
|
|
35
|
+
export interface WalletState {
|
|
36
|
+
address: string | null;
|
|
37
|
+
chainId: number | null;
|
|
38
|
+
provider: WalletProvider | null;
|
|
39
|
+
connected: boolean;
|
|
40
|
+
error: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class WalletManager {
|
|
44
|
+
private state = $state<WalletState>({
|
|
45
|
+
address: null,
|
|
46
|
+
chainId: null,
|
|
47
|
+
provider: null,
|
|
48
|
+
connected: false,
|
|
49
|
+
error: null,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
private ethereum: any = null;
|
|
53
|
+
|
|
54
|
+
constructor() {
|
|
55
|
+
if (typeof window !== "undefined") {
|
|
56
|
+
this.ethereum = (window as any).ethereum;
|
|
57
|
+
if (this.ethereum) {
|
|
58
|
+
// Listen for account changes
|
|
59
|
+
this.ethereum.on?.("accountsChanged", (accounts: string[]) => {
|
|
60
|
+
if (accounts.length === 0) {
|
|
61
|
+
this.disconnect();
|
|
62
|
+
} else {
|
|
63
|
+
this.state.set({ ...this.state(), address: accounts[0] });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Listen for chain changes
|
|
68
|
+
this.ethereum.on?.("chainChanged", (chainId: string) => {
|
|
69
|
+
this.state.set({ ...this.state(), chainId: parseInt(chainId, 16) });
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async connect(provider: WalletProvider = "metamask"): Promise<void> {
|
|
76
|
+
try {
|
|
77
|
+
if (!this.ethereum) {
|
|
78
|
+
this.state.set({ ...this.state(), error: "No Web3 wallet found. Install MetaMask." });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const accounts = await this.ethereum.request({ method: "eth_requestAccounts" });
|
|
83
|
+
const chainId = await this.ethereum.request({ method: "eth_chainId" });
|
|
84
|
+
|
|
85
|
+
this.state.set({
|
|
86
|
+
address: accounts[0],
|
|
87
|
+
chainId: parseInt(chainId, 16),
|
|
88
|
+
provider,
|
|
89
|
+
connected: true,
|
|
90
|
+
error: null,
|
|
91
|
+
});
|
|
92
|
+
} catch (err) {
|
|
93
|
+
this.state.set({ ...this.state(), error: (err as Error).message });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
disconnect(): void {
|
|
98
|
+
this.state.set({
|
|
99
|
+
address: null,
|
|
100
|
+
chainId: null,
|
|
101
|
+
provider: null,
|
|
102
|
+
connected: false,
|
|
103
|
+
error: null,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async switchChain(chainId: number): Promise<void> {
|
|
108
|
+
if (!this.ethereum) return;
|
|
109
|
+
try {
|
|
110
|
+
await this.ethereum.request({
|
|
111
|
+
method: "wallet_switchEthereumChain",
|
|
112
|
+
params: [{ chainId: "0x" + chainId.toString(16) }],
|
|
113
|
+
});
|
|
114
|
+
} catch (err) {
|
|
115
|
+
this.state.set({ ...this.state(), error: (err as Error).message });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async signMessage(message: string): Promise<string | null> {
|
|
120
|
+
if (!this.ethereum || !this.state().address) return null;
|
|
121
|
+
try {
|
|
122
|
+
return await this.ethereum.request({
|
|
123
|
+
method: "personal_sign",
|
|
124
|
+
params: [message, this.state().address],
|
|
125
|
+
});
|
|
126
|
+
} catch (err) {
|
|
127
|
+
this.state.set({ ...this.state(), error: (err as Error).message });
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getState() { return this.state; }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export const wallet = new WalletManager();
|
|
136
|
+
|
|
137
|
+
// ============ REACTIVE HOOKS ============
|
|
138
|
+
|
|
139
|
+
export function useWallet(): {
|
|
140
|
+
address: () => string | null;
|
|
141
|
+
chainId: () => number | null;
|
|
142
|
+
connected: () => boolean;
|
|
143
|
+
error: () => string | null;
|
|
144
|
+
connect: (provider?: WalletProvider) => Promise<void>;
|
|
145
|
+
disconnect: () => void;
|
|
146
|
+
switchChain: (chainId: number) => Promise<void>;
|
|
147
|
+
signMessage: (message: string) => Promise<string | null>;
|
|
148
|
+
} {
|
|
149
|
+
const state = wallet.getState();
|
|
150
|
+
return {
|
|
151
|
+
address: () => state().address,
|
|
152
|
+
chainId: () => state().chainId,
|
|
153
|
+
connected: () => state().connected,
|
|
154
|
+
error: () => state().error,
|
|
155
|
+
connect: (p) => wallet.connect(p),
|
|
156
|
+
disconnect: () => wallet.disconnect(),
|
|
157
|
+
switchChain: (id) => wallet.switchChain(id),
|
|
158
|
+
signMessage: (m) => wallet.signMessage(m),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ============ SMART CONTRACTS ============
|
|
163
|
+
|
|
164
|
+
export interface ContractABI {
|
|
165
|
+
name: string;
|
|
166
|
+
type: "function" | "event" | "constructor";
|
|
167
|
+
stateMutability?: "view" | "nonpayable" | "payable";
|
|
168
|
+
inputs?: { name: string; type: string }[];
|
|
169
|
+
outputs?: { name: string; type: string }[];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
class SmartContract {
|
|
173
|
+
constructor(
|
|
174
|
+
private abi: ContractABI[],
|
|
175
|
+
private address: string,
|
|
176
|
+
) {}
|
|
177
|
+
|
|
178
|
+
async read(methodName: string, args: unknown[] = []): Promise<unknown> {
|
|
179
|
+
const method = this.abi.find(m => m.name === methodName && m.type === "function");
|
|
180
|
+
if (!method) throw new Error(`Method ${methodName} not found in ABI`);
|
|
181
|
+
|
|
182
|
+
// Encode call data (simplified — real impl would use ethers.js/web3.js)
|
|
183
|
+
const callData = this.encodeCall(method, args);
|
|
184
|
+
|
|
185
|
+
if (!wallet.getState()().address) {
|
|
186
|
+
throw new Error("Wallet not connected");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const ethereum = (window as any).ethereum;
|
|
190
|
+
const result = await ethereum.request({
|
|
191
|
+
method: "eth_call",
|
|
192
|
+
params: [
|
|
193
|
+
{ to: this.address, data: callData },
|
|
194
|
+
"latest",
|
|
195
|
+
],
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
return this.decodeResult(method, result);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async write(methodName: string, args: unknown[] = [], value?: string): Promise<string> {
|
|
202
|
+
const method = this.abi.find(m => m.name === methodName && m.type === "function");
|
|
203
|
+
if (!method) throw new Error(`Method ${methodName} not found in ABI`);
|
|
204
|
+
|
|
205
|
+
const callData = this.encodeCall(method, args);
|
|
206
|
+
const from = wallet.getState()().address;
|
|
207
|
+
if (!from) throw new Error("Wallet not connected");
|
|
208
|
+
|
|
209
|
+
const ethereum = (window as any).ethereum;
|
|
210
|
+
const txHash = await ethereum.request({
|
|
211
|
+
method: "eth_sendTransaction",
|
|
212
|
+
params: [{
|
|
213
|
+
from,
|
|
214
|
+
to: this.address,
|
|
215
|
+
data: callData,
|
|
216
|
+
value: value || "0x0",
|
|
217
|
+
}],
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return txHash;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async estimateGas(methodName: string, args: unknown[] = []): Promise<number> {
|
|
224
|
+
// Simplified gas estimation
|
|
225
|
+
return 21000 + args.length * 1000;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
onEvent(eventName: string, callback: (event: any) => void): () => void {
|
|
229
|
+
// Subscribe to contract events
|
|
230
|
+
// Real impl would use WebSocket or polling
|
|
231
|
+
const interval = setInterval(async () => {
|
|
232
|
+
// Poll for new events
|
|
233
|
+
}, 5000);
|
|
234
|
+
|
|
235
|
+
return () => clearInterval(interval);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
private encodeCall(method: ContractABI, args: unknown[]): string {
|
|
239
|
+
// Simplified — real impl uses ethers.js ABI encoding
|
|
240
|
+
const selector = this.functionSelector(method.name, method.inputs || []);
|
|
241
|
+
return selector + "0".repeat(64 * args.length);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private functionSelector(name: string, inputs: { type: string }[]): string {
|
|
245
|
+
const types = inputs.map(i => i.type).join(",");
|
|
246
|
+
const signature = `${name}(${types})`;
|
|
247
|
+
// Keccak256 hash, take first 4 bytes
|
|
248
|
+
let hash = 0;
|
|
249
|
+
for (let i = 0; i < signature.length; i++) {
|
|
250
|
+
hash = ((hash << 5) - hash) + signature.charCodeAt(i);
|
|
251
|
+
}
|
|
252
|
+
return "0x" + Math.abs(hash).toString(16).padStart(8, "0").slice(0, 8);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
private decodeResult(method: ContractABI, result: string): unknown {
|
|
256
|
+
// Simplified decoding
|
|
257
|
+
if (method.outputs?.length === 1) {
|
|
258
|
+
if (method.outputs[0].type === "uint256") {
|
|
259
|
+
return parseInt(result, 16);
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
return result;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function useContract(abi: ContractABI[], address: string): SmartContract {
|
|
268
|
+
return new SmartContract(abi, address);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ============ CHAIN CONFIG ============
|
|
272
|
+
|
|
273
|
+
export const chains: Record<number, { name: string; symbol: string; explorer: string; rpc: string }> = {
|
|
274
|
+
1: { name: "Ethereum", symbol: "ETH", explorer: "https://etherscan.io", rpc: "https://mainnet.infura.io/v3/" },
|
|
275
|
+
137: { name: "Polygon", symbol: "MATIC", explorer: "https://polygonscan.com", rpc: "https://polygon-rpc.com" },
|
|
276
|
+
56: { name: "BNB Smart Chain", symbol: "BNB", explorer: "https://bscscan.com", rpc: "https://bsc-dataseed.binance.org" },
|
|
277
|
+
42161: { name: "Arbitrum One", symbol: "ETH", explorer: "https://arbiscan.io", rpc: "https://arb1.arbitrum.io/rpc" },
|
|
278
|
+
10: { name: "Optimism", symbol: "ETH", explorer: "https://optimistic.etherscan.io", rpc: "https://mainnet.optimism.io" },
|
|
279
|
+
43114: { name: "Avalanche", symbol: "AVAX", explorer: "https://snowtrace.io", rpc: "https://api.avax.network/ext/bc/C/rpc" },
|
|
280
|
+
8453: { name: "Base", symbol: "ETH", explorer: "https://basescan.org", rpc: "https://mainnet.base.org" },
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
export function getChainInfo(chainId: number) {
|
|
284
|
+
return chains[chainId] || { name: "Unknown", symbol: "?", explorer: "", rpc: "" };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ============ ENS ============
|
|
288
|
+
|
|
289
|
+
export async function resolveENS(name: string): Promise<string | null> {
|
|
290
|
+
// Simplified ENS resolution
|
|
291
|
+
// Real impl would use ethers.js
|
|
292
|
+
try {
|
|
293
|
+
const response = await fetch(`https://ens-resolver.azurewebsites.net/api/resolve/${name}`);
|
|
294
|
+
const data = await response.json();
|
|
295
|
+
return data.address || null;
|
|
296
|
+
} catch {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export async function lookupENS(address: string): Promise<string | null> {
|
|
302
|
+
try {
|
|
303
|
+
const response = await fetch(`https://ens-resolver.azurewebsites.net/api/lookup/${address}`);
|
|
304
|
+
const data = await response.json();
|
|
305
|
+
return data.name || null;
|
|
306
|
+
} catch {
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ============ WALLET CONNECT BUTTON ============
|
|
312
|
+
|
|
313
|
+
export function WalletButton(): WafraNode {
|
|
314
|
+
const { address, connected, connect, disconnect, error } = useWallet();
|
|
315
|
+
|
|
316
|
+
const formatAddress = (addr: string) => {
|
|
317
|
+
return `${addr.slice(0, 6)}...${addr.slice(-4)}`;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
return h("div", { style: "display:flex;align-items:center;gap:8px;" },
|
|
321
|
+
() => connected()
|
|
322
|
+
? h("div", { style: "display:flex;align-items:center;gap:8px;" },
|
|
323
|
+
h("div", {
|
|
324
|
+
style: "display:flex;align-items:center;gap:6px;padding:6px 12px;background:#1A1A24;border:1px solid #2A2A38;border-radius:20px;",
|
|
325
|
+
},
|
|
326
|
+
h("div", { style: "width:8px;height:8px;border-radius:50%;background:#10B981;" }),
|
|
327
|
+
h("span", { style: "font-family:monospace;font-size:13px;color:#E4E4E7;" }, formatAddress(address()!)),
|
|
328
|
+
),
|
|
329
|
+
h("button", {
|
|
330
|
+
onClick: () => disconnect(),
|
|
331
|
+
style: "padding:6px 12px;background:#EF4444;color:white;border:none;border-radius:6px;cursor:pointer;font-size:12px;",
|
|
332
|
+
}, "Disconnect"),
|
|
333
|
+
)
|
|
334
|
+
: h("button", {
|
|
335
|
+
onClick: () => connect("metamask"),
|
|
336
|
+
style: "padding:8px 16px;background:linear-gradient(135deg,#A855F7,#06B6D4);color:white;border:none;border-radius:6px;cursor:pointer;font-size:13px;font-weight:600;",
|
|
337
|
+
}, "🦊 Connect Wallet"),
|
|
338
|
+
() => error() ? h("div", { style: "color:#EF4444;font-size:11px;margin-top:4px;" }, error()) : null,
|
|
339
|
+
);
|
|
340
|
+
}
|