Skip to content

Commit 301a98e

Browse files
committed
fix: use eventemitter3 instead of node:events for browser compatibility
- Replace node:events with eventemitter3 in packages/core/src/clients/manager.ts - Update pnpm-lock.yaml with eventemitter3 resolution - Update docs-site: playground layout, examples, search (pagefind), new examples - Remove localhost network option from playground
1 parent 5916779 commit 301a98e

File tree

14 files changed

+666
-220
lines changed

14 files changed

+666
-220
lines changed

docker/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# conflux-devkit
1616
# ──────────────────────────────────────────────────────────────────────────────
1717

18-
FROM --platform=linux/amd64 node:22-slim
18+
FROM node:22-slim
1919

2020
LABEL org.opencontainers.image.title="devkit" \
2121
org.opencontainers.image.description="Conflux local development environment" \

docs-site/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
out/
33
node_modules/
44
*.tsbuildinfo
5+
public/_pagefind/

docs-site/components/Playground.tsx

Lines changed: 162 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
'use client'
22

3-
import { useState } from 'react'
43
import {
54
SandpackProvider,
6-
SandpackLayout,
75
SandpackCodeEditor,
86
SandpackConsole,
97
SandpackPreview,
@@ -34,16 +32,8 @@ const NETWORKS = {
3432
rpcUrl: 'https://evmtestnet.confluxrpc.com',
3533
blockExplorer: 'https://evmtestnet.confluxscan.io',
3634
},
37-
local: {
38-
label: 'Local (localhost:8545)',
39-
chainId: 2030,
40-
rpcUrl: 'http://localhost:8545',
41-
blockExplorer: '',
42-
},
4335
} as const
4436

45-
type Network = keyof typeof NETWORKS
46-
4737
// ── Run button — must be inside SandpackProvider ─────────────────────────────
4838
function RunButton() {
4939
const { sandpack } = useSandpack()
@@ -52,6 +42,7 @@ function RunButton() {
5242

5343
return (
5444
<button
45+
type="button"
5546
onClick={refresh}
5647
disabled={busy}
5748
style={{
@@ -76,14 +67,9 @@ function RunButton() {
7667
)
7768
}
7869

79-
// ── Network toggle ────────────────────────────────────────────────────────────
80-
function NetworkToggle({
81-
network,
82-
onChange,
83-
}: {
84-
network: Network
85-
onChange: (n: Network) => void
86-
}) {
70+
// ── Network label ─────────────────────────────────────────────────────────────
71+
function NetworkLabel() {
72+
const cfg = NETWORKS.testnet
8773
return (
8874
<div
8975
style={{
@@ -93,38 +79,23 @@ function NetworkToggle({
9379
fontSize: '12px',
9480
fontFamily: 'ui-monospace, monospace',
9581
color: T.muted,
96-
minWidth: 0,
97-
overflow: 'hidden',
9882
}}
9983
>
10084
<span style={{ whiteSpace: 'nowrap' }}>network:</span>
101-
{(Object.keys(NETWORKS) as Network[]).map((key) => (
102-
<button
103-
key={key}
104-
onClick={() => onChange(key)}
105-
style={{
106-
padding: '2px 9px',
107-
borderRadius: '4px',
108-
border: 'none',
109-
cursor: 'pointer',
110-
fontFamily: 'inherit',
111-
fontSize: '11px',
112-
whiteSpace: 'nowrap',
113-
background: network === key ? T.activeBg : T.pillBg,
114-
color: network === key ? T.activeText : T.text,
115-
}}
116-
>
117-
{NETWORKS[key].label}
118-
</button>
119-
))}
12085
<span
12186
style={{
122-
color: T.muted,
123-
fontSize: '10px',
87+
padding: '2px 9px',
88+
borderRadius: '4px',
89+
background: T.activeBg,
90+
color: T.activeText,
91+
fontSize: '11px',
12492
whiteSpace: 'nowrap',
12593
}}
12694
>
127-
· chain {NETWORKS[network].chainId}
95+
{cfg.label}
96+
</span>
97+
<span style={{ fontSize: '10px', whiteSpace: 'nowrap' }}>
98+
· chain {cfg.chainId}
12899
</span>
129100
</div>
130101
)
@@ -141,9 +112,130 @@ interface PlaygroundProps {
141112

142113
const DEFAULT_DEPS: Record<string, string> = {
143114
viem: '^2.0.0',
144-
// @cfxdevkit/* packages omitted — not yet on npm; examples use viem directly
145115
}
146116

117+
// ── Browser-compatible shim ────────────────────────────────────────────────
118+
// @cfxdevkit/core uses node:events (via ClientManager) which Sandpack's bundler
119+
// cannot polyfill. This shim re-implements the same public API surface using
120+
// viem directly. In a real project you import from '@cfxdevkit/core'.
121+
const CFXDEVKIT_SHIM = `// cfxdevkit.ts — browser shim for @cfxdevkit/core
122+
// Real project: replace './cfxdevkit' with '@cfxdevkit/core'
123+
import {
124+
createPublicClient, createWalletClient, http,
125+
formatEther, formatUnits, parseEther, parseUnits, defineChain,
126+
} from 'viem'
127+
import { privateKeyToAccount } from 'viem/accounts'
128+
129+
export { formatEther, formatUnits, parseEther, parseUnits } from 'viem'
130+
131+
export const ERC20_ABI = [
132+
{ name: 'name', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'string' }] },
133+
{ name: 'symbol', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'string' }] },
134+
{ name: 'decimals', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint8' }] },
135+
{ name: 'totalSupply', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint256' }] },
136+
{ name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ type: 'uint256' }] },
137+
{ name: 'allowance', type: 'function', stateMutability: 'view', inputs: [{ name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }], outputs: [{ type: 'uint256' }] },
138+
{ name: 'transfer', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] },
139+
{ name: 'approve', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] },
140+
{ name: 'transferFrom',type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ type: 'bool' }] },
141+
] as const
142+
143+
function makeChain(chainId, rpcUrl) {
144+
const names = { 71: 'Conflux eSpace Testnet', 1030: 'Conflux eSpace' }
145+
return defineChain({
146+
id: chainId,
147+
name: names[chainId] || 'Conflux eSpace (' + chainId + ')',
148+
nativeCurrency: { name: 'Conflux', symbol: 'CFX', decimals: 18 },
149+
rpcUrls: { default: { http: [rpcUrl] } },
150+
})
151+
}
152+
153+
export class EspaceClient {
154+
public publicClient
155+
public chainId
156+
public rpcUrl
157+
public address = ''
158+
159+
constructor({ chainId, rpcUrl }) {
160+
this.chainId = chainId
161+
this.rpcUrl = rpcUrl
162+
this.publicClient = createPublicClient({
163+
chain: makeChain(chainId, rpcUrl),
164+
transport: http(rpcUrl),
165+
})
166+
}
167+
168+
async getBlockNumber() { return this.publicClient.getBlockNumber() }
169+
async getChainId() { return this.publicClient.getChainId() }
170+
async getGasPrice() { return this.publicClient.getGasPrice() }
171+
async isConnected() { try { await this.publicClient.getBlockNumber(); return true } catch { return false } }
172+
173+
async getBalance(address) {
174+
const wei = await this.publicClient.getBalance({ address })
175+
return formatEther(wei)
176+
}
177+
178+
async getTokenBalance(address, tokenAddress) {
179+
const bal = await this.publicClient.readContract({
180+
address: tokenAddress, abi: ERC20_ABI, functionName: 'balanceOf', args: [address],
181+
})
182+
return String(bal)
183+
}
184+
185+
async readContract({ address, abi, functionName, args = [] }) {
186+
return this.publicClient.readContract({ address, abi, functionName, args })
187+
}
188+
189+
async estimateGas({ to, value, data }) {
190+
return this.publicClient.estimateGas({ to, value, data })
191+
}
192+
193+
async waitForTransaction(hash) {
194+
const r = await this.publicClient.waitForTransactionReceipt({ hash })
195+
return {
196+
hash: r.transactionHash, blockNumber: r.blockNumber,
197+
gasUsed: r.gasUsed, status: r.status, contractAddress: r.contractAddress ?? undefined,
198+
}
199+
}
200+
}
201+
202+
export class EspaceWalletClient extends EspaceClient {
203+
_account
204+
_walletClient
205+
206+
constructor({ chainId, rpcUrl, privateKey }) {
207+
super({ chainId, rpcUrl })
208+
this._account = privateKeyToAccount(privateKey)
209+
this.address = this._account.address
210+
this._walletClient = createWalletClient({
211+
account: this._account, chain: makeChain(chainId, rpcUrl), transport: http(rpcUrl),
212+
})
213+
}
214+
215+
getAddress() { return this.address }
216+
217+
async sendTransaction({ to, value, data }) {
218+
return this._walletClient.sendTransaction({
219+
account: this._account, chain: makeChain(this.chainId, this.rpcUrl),
220+
to, value, data,
221+
})
222+
}
223+
224+
async signMessage(message) {
225+
return this._walletClient.signMessage({ account: this._account, message })
226+
}
227+
228+
async deployContract(abi, bytecode, args = []) {
229+
const hash = await this._walletClient.deployContract({
230+
account: this._account, chain: makeChain(this.chainId, this.rpcUrl),
231+
abi, bytecode, args,
232+
})
233+
const r = await this.waitForTransaction(hash)
234+
return r.contractAddress
235+
}
236+
}
237+
`
238+
147239
// ── Main component ────────────────────────────────────────────────────────────
148240
export function Playground({
149241
files = {},
@@ -152,13 +244,12 @@ export function Playground({
152244
extraDeps = {},
153245
file = 'index.ts',
154246
}: PlaygroundProps) {
155-
const [network, setNetwork] = useState<Network>('testnet')
247+
const networkConfig = NETWORKS.testnet
156248

157-
const networkConfig = NETWORKS[network]
158-
159-
const networkFile = `// Auto-generated — changes when you toggle network\nexport const NETWORK = {\n chainId: ${networkConfig.chainId},\n rpcUrl: '${networkConfig.rpcUrl}',\n blockExplorer: '${networkConfig.blockExplorer}',\n} as const\n`
249+
const networkFile = `// Auto-generated network config\nexport const NETWORK = {\n chainId: ${networkConfig.chainId},\n rpcUrl: '${networkConfig.rpcUrl}',\n blockExplorer: '${networkConfig.blockExplorer}',\n} as const\n`
160250

161251
const mergedFiles = {
252+
'/cfxdevkit.ts': { code: CFXDEVKIT_SHIM, hidden: true, readOnly: true },
162253
'/network-config.ts': { code: networkFile, readOnly: true },
163254
...Object.fromEntries(
164255
Object.entries(files).map(([name, code]) => [
@@ -180,13 +271,14 @@ export function Playground({
180271
}}
181272
>
182273
<SandpackProvider
183-
key={network}
184274
template={template}
185275
theme="dark"
186276
files={mergedFiles}
187277
options={{
188278
activeFile,
189-
visibleFiles: Object.keys(mergedFiles),
279+
visibleFiles: Object.entries(mergedFiles)
280+
.filter(([, v]) => !(v as { hidden?: boolean }).hidden)
281+
.map(([k]) => k),
190282
recompileMode: 'delayed',
191283
recompileDelay: 600,
192284
autorun: true,
@@ -209,23 +301,38 @@ export function Playground({
209301
gap: '8px',
210302
}}
211303
>
212-
<NetworkToggle network={network} onChange={setNetwork} />
304+
<NetworkLabel />
213305
<RunButton />
214306
</div>
215307

216-
<SandpackLayout>
308+
<div
309+
style={{
310+
display: 'flex',
311+
flexDirection: 'column',
312+
background: T.bg,
313+
}}
314+
>
217315
<SandpackCodeEditor
218316
showLineNumbers
219317
showInlineErrors
220318
wrapContent
221-
style={{ height: 380 }}
319+
style={{ height: 340, minWidth: 0 }}
222320
/>
321+
<div style={{ height: 1, background: T.border }} />
223322
{showConsole ? (
224-
<SandpackConsole showHeader style={{ height: 380 }} />
323+
<SandpackConsole showHeader style={{ height: 260, minWidth: 0 }} />
225324
) : (
226-
<SandpackPreview style={{ height: 380 }} showNavigator={false} />
325+
<SandpackPreview style={{ height: 260, minWidth: 0 }} showNavigator={false} />
227326
)}
228-
</SandpackLayout>
327+
</div>
328+
329+
{/* SandpackConsole needs a running iframe (SandpackPreview) to execute code.
330+
When only the console is shown, render a hidden preview to provide that client. */}
331+
{showConsole && (
332+
<div aria-hidden="true" style={{ height: 0, overflow: 'hidden' }}>
333+
<SandpackPreview />
334+
</div>
335+
)}
229336
</SandpackProvider>
230337
</div>
231338
)
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
export default {
22
index: 'Overview',
3+
'espace-block-number': 'Block Number',
34
'read-balance': 'Read Balance',
4-
'deploy-token': 'Deploy ERC-20',
5+
'erc20-info': 'ERC-20 Token Info',
6+
'swap-quote': 'Swap Quote',
57
'send-cfx': 'Send CFX',
8+
'deploy-token': 'Deploy ERC-20',
69
}

0 commit comments

Comments
 (0)