@miradorlabs/parallax-web 1.0.4 â 1.0.7
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 +300 -134
- package/dist/index.d.ts +206 -77
- package/dist/index.esm.js +770 -2828
- package/dist/index.esm.js.map +1 -1
- package/dist/index.umd.js +772 -2827
- package/dist/index.umd.js.map +1 -1
- package/example/README.md +257 -0
- package/example/app.js +411 -0
- package/example/index.html +469 -0
- package/example/package-lock.json +1877 -0
- package/example/package.json +33 -0
- package/example/proxy-server.js +86 -0
- package/example/rollup.config.js +16 -0
- package/index.ts +3 -10
- package/package.json +3 -2
- package/src/parallax/ParallaxService.ts +296 -0
- package/src/parallax/index.ts +168 -155
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Parallax Web Client Demo
|
|
2
|
+
|
|
3
|
+
An interactive web application demonstrating how to use the Parallax SDK to create and manage transaction traces.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Builder Pattern Interface**: Use the fluent API to construct traces step-by-step
|
|
8
|
+
- **Real-time Trace Building**: Add attributes, tags, and events dynamically
|
|
9
|
+
- **Transaction Hash Support**: Optionally associate traces with blockchain transactions
|
|
10
|
+
- **Activity Logging**: See all SDK operations in real-time
|
|
11
|
+
- **Client Metadata**: Automatically includes browser, OS, and screen information
|
|
12
|
+
|
|
13
|
+
## Getting Started
|
|
14
|
+
|
|
15
|
+
### Quick Start (Recommended)
|
|
16
|
+
|
|
17
|
+
The easiest way to run the demo:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cd ./example
|
|
21
|
+
npm install
|
|
22
|
+
npm run dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This will:
|
|
26
|
+
1. Install dependencies (Rollup)
|
|
27
|
+
2. Bundle from TypeScript source
|
|
28
|
+
3. Compile TypeScript and bundle everything
|
|
29
|
+
4. Start a local web server
|
|
30
|
+
5. Open your browser automatically
|
|
31
|
+
|
|
32
|
+
That's it! The demo will be running at http://localhost:8000
|
|
33
|
+
|
|
34
|
+
### Manual Setup
|
|
35
|
+
|
|
36
|
+
If you prefer to run it manually:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 1. Install dependencies
|
|
40
|
+
npm install
|
|
41
|
+
|
|
42
|
+
# 2. Build the bundle
|
|
43
|
+
npm run build
|
|
44
|
+
|
|
45
|
+
# 3. Start a web server
|
|
46
|
+
npm start # Using Python
|
|
47
|
+
# OR
|
|
48
|
+
npm run serve # Using http-server (auto-opens browser)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Alternative: Using VS Code Live Server
|
|
52
|
+
1. Run `npm run build` to create the bundle
|
|
53
|
+
2. Install the "Live Server" extension in VS Code
|
|
54
|
+
3. Right-click on `index.html`
|
|
55
|
+
4. Select "Open with Live Server"
|
|
56
|
+
|
|
57
|
+
## How to Use
|
|
58
|
+
|
|
59
|
+
### Step 1: Create a Trace
|
|
60
|
+
1. Fill in the trace name (e.g., "swap_execution", "payment_flow")
|
|
61
|
+
2. Enter transaction details:
|
|
62
|
+
- From address
|
|
63
|
+
- To address
|
|
64
|
+
- Value (in ETH)
|
|
65
|
+
- Network (Ethereum, Polygon, etc.)
|
|
66
|
+
3. Click "Create Trace"
|
|
67
|
+
|
|
68
|
+
This initializes a trace builder with:
|
|
69
|
+
- Default transaction attributes
|
|
70
|
+
- Network-specific tags
|
|
71
|
+
- Automatic client metadata (browser, OS, screen size, etc.)
|
|
72
|
+
|
|
73
|
+
### Step 2: Add Custom Attributes (Optional)
|
|
74
|
+
Add any custom key-value pairs to enrich your trace:
|
|
75
|
+
- `slippage_bps`: "50"
|
|
76
|
+
- `gas_limit`: "21000"
|
|
77
|
+
- `dex`: "uniswap"
|
|
78
|
+
- Any other relevant metadata
|
|
79
|
+
|
|
80
|
+
### Step 3: Add Tags (Optional)
|
|
81
|
+
Add tags to categorize your trace:
|
|
82
|
+
- "dex"
|
|
83
|
+
- "swap"
|
|
84
|
+
- "critical"
|
|
85
|
+
- "production"
|
|
86
|
+
|
|
87
|
+
### Step 4: Add Events (Optional)
|
|
88
|
+
Track important moments in the transaction lifecycle:
|
|
89
|
+
- Event name: "wallet_connected"
|
|
90
|
+
- Details: `{"wallet": "MetaMask", "version": "10.0.0"}`
|
|
91
|
+
|
|
92
|
+
Or:
|
|
93
|
+
- Event name: "quote_received"
|
|
94
|
+
- Details: (leave empty for timestamp-only events)
|
|
95
|
+
|
|
96
|
+
**JSON Support**: If your details start with `{` or `[`, they'll be parsed as JSON automatically.
|
|
97
|
+
|
|
98
|
+
### Step 5: Submit the Trace
|
|
99
|
+
Submit your trace to the Parallax Gateway:
|
|
100
|
+
|
|
101
|
+
**Without Transaction Hash:**
|
|
102
|
+
- Just click "Submit Trace"
|
|
103
|
+
- Use this when you don't have a blockchain tx hash yet
|
|
104
|
+
|
|
105
|
+
**With Transaction Hash:**
|
|
106
|
+
- Enter the transaction hash
|
|
107
|
+
- Enter the chain ID (1 for Ethereum, 137 for Polygon, etc.)
|
|
108
|
+
- Click "Submit Trace"
|
|
109
|
+
|
|
110
|
+
## Example Usage Flow
|
|
111
|
+
|
|
112
|
+
```javascript
|
|
113
|
+
// The demo app uses the builder pattern like this:
|
|
114
|
+
|
|
115
|
+
// 1. Create trace builder
|
|
116
|
+
const trace = client.trace("swap_execution", true); // includeClientMeta
|
|
117
|
+
|
|
118
|
+
// 2. Add attributes
|
|
119
|
+
trace
|
|
120
|
+
.addAttribute("from", "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb")
|
|
121
|
+
.addAttribute("to", "0x1234567890123456789012345678901234567890")
|
|
122
|
+
.addAttribute("value", "0.5")
|
|
123
|
+
.addAttribute("network", "ethereum")
|
|
124
|
+
.addAttribute("slippage_bps", "50");
|
|
125
|
+
|
|
126
|
+
// 3. Add tags
|
|
127
|
+
trace
|
|
128
|
+
.addTag("dex")
|
|
129
|
+
.addTag("swap")
|
|
130
|
+
.addTag("uniswap");
|
|
131
|
+
|
|
132
|
+
// 4. Add events
|
|
133
|
+
trace
|
|
134
|
+
.addEvent("wallet_connected", { wallet: "MetaMask" })
|
|
135
|
+
.addEvent("quote_received", { amount: "0.5 ETH", price: "$2000" })
|
|
136
|
+
.addEvent("tx_signed");
|
|
137
|
+
|
|
138
|
+
// 5. Submit with transaction hash
|
|
139
|
+
const response = await trace.submit(
|
|
140
|
+
"0x1234567890abcdef...", // txHash
|
|
141
|
+
"1", // chainId (Ethereum)
|
|
142
|
+
"Swap transaction" // details (optional)
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
console.log("Trace ID:", response.getTraceId());
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Understanding the Builder Pattern
|
|
149
|
+
|
|
150
|
+
The Parallax SDK uses a **builder pattern** for creating traces:
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
client.trace(name, includeClientMeta)
|
|
154
|
+
.addAttribute(key, value) // Add single attribute
|
|
155
|
+
.addAttributes({...}) // Add multiple attributes
|
|
156
|
+
.addTag(tag) // Add single tag
|
|
157
|
+
.addTags([...]) // Add multiple tags
|
|
158
|
+
.addEvent(name, details, ts) // Add event
|
|
159
|
+
.setTxHash(hash, chainId) // Set transaction hash
|
|
160
|
+
.submit() // Submit without tx hash
|
|
161
|
+
.submit(hash, chainId) // Submit with tx hash
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Benefits:**
|
|
165
|
+
- â
Method chaining for clean, readable code
|
|
166
|
+
- â
All data collected before submission
|
|
167
|
+
- â
Type-safe with TypeScript
|
|
168
|
+
- â
Automatic JSON stringification for objects
|
|
169
|
+
- â
Optional client metadata collection
|
|
170
|
+
|
|
171
|
+
## Activity Log
|
|
172
|
+
|
|
173
|
+
The demo includes a real-time activity log showing:
|
|
174
|
+
- â
Successful operations (green)
|
|
175
|
+
- â Errors (red)
|
|
176
|
+
- âšī¸ Information messages (blue)
|
|
177
|
+
- â ī¸ Warnings (yellow)
|
|
178
|
+
|
|
179
|
+
All SDK operations are logged with timestamps for debugging.
|
|
180
|
+
|
|
181
|
+
## API Configuration
|
|
182
|
+
|
|
183
|
+
By default, the demo connects to:
|
|
184
|
+
```javascript
|
|
185
|
+
const client = new ParallaxClient(
|
|
186
|
+
'demo-api-key',
|
|
187
|
+
'https://parallax-gateway.dev.mirador.org:443'
|
|
188
|
+
);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
To use a different endpoint, modify `app.js`:
|
|
192
|
+
```javascript
|
|
193
|
+
const client = new ParallaxClient(
|
|
194
|
+
'your-api-key',
|
|
195
|
+
'https://your-gateway-url:port'
|
|
196
|
+
);
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Client Metadata
|
|
200
|
+
|
|
201
|
+
When `includeClientMeta` is `true`, the SDK automatically includes:
|
|
202
|
+
- Browser name and version
|
|
203
|
+
- Operating system
|
|
204
|
+
- Screen resolution
|
|
205
|
+
- Viewport size
|
|
206
|
+
- User agent
|
|
207
|
+
- Language
|
|
208
|
+
- Timezone
|
|
209
|
+
- Current URL
|
|
210
|
+
- Referrer
|
|
211
|
+
|
|
212
|
+
All metadata is prefixed with `client.*` (e.g., `client.browser`, `client.os`)
|
|
213
|
+
|
|
214
|
+
## Troubleshooting
|
|
215
|
+
|
|
216
|
+
### CORS Errors
|
|
217
|
+
If you see CORS errors in the console:
|
|
218
|
+
- Make sure you're serving the files via HTTP (not opening `file://` directly)
|
|
219
|
+
- Check that the Parallax Gateway allows requests from your origin
|
|
220
|
+
|
|
221
|
+
### Import Errors
|
|
222
|
+
If you see module import errors:
|
|
223
|
+
- Ensure the SDK is built: `npm run build`
|
|
224
|
+
- Check that `dist/index.esm.js` exists
|
|
225
|
+
- Verify you're using a browser that supports ES modules
|
|
226
|
+
|
|
227
|
+
### Connection Errors
|
|
228
|
+
If trace submission fails:
|
|
229
|
+
- Check that the gateway URL is correct
|
|
230
|
+
- Verify your API key is valid
|
|
231
|
+
- Check the browser console for detailed error messages
|
|
232
|
+
- Look at the Activity Log for specific error details
|
|
233
|
+
|
|
234
|
+
## Code Structure
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
example/
|
|
238
|
+
âââ index.html # Main HTML page with styles
|
|
239
|
+
âââ app.js # Application logic using Parallax SDK
|
|
240
|
+
âââ README.md # This file
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
The demo is intentionally simple (vanilla JavaScript + HTML/CSS) to focus on SDK usage without framework complexity.
|
|
244
|
+
|
|
245
|
+
## Next Steps
|
|
246
|
+
|
|
247
|
+
After exploring the demo:
|
|
248
|
+
1. Check the Activity Log to see all SDK operations
|
|
249
|
+
2. Open the browser DevTools to see network requests
|
|
250
|
+
3. Modify `app.js` to experiment with different trace patterns
|
|
251
|
+
4. Integrate the builder pattern into your own application
|
|
252
|
+
|
|
253
|
+
## Learn More
|
|
254
|
+
|
|
255
|
+
- [Parallax SDK Documentation](../README.md)
|
|
256
|
+
- [API Reference](../src/parallax/index.ts)
|
|
257
|
+
- [ParallaxService](../src/parallax/ParallaxService.ts) - Higher-level service wrapper
|
package/example/app.js
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
// Import the Parallax SDK
|
|
2
|
+
import { ParallaxClient } from '../dist/index.esm.js';
|
|
3
|
+
|
|
4
|
+
// Initialize the client
|
|
5
|
+
// Use proxy to avoid CORS issues (default)
|
|
6
|
+
// To use direct connection, change to: 'https://parallax-gateway.dev.mirador.org:443'
|
|
7
|
+
const USE_PROXY = true;
|
|
8
|
+
const GATEWAY_URL = USE_PROXY
|
|
9
|
+
? 'http://localhost:3001' // Proxy server (no CORS issues)
|
|
10
|
+
: 'https://parallax-gateway.dev.mirador.org:443'; // Direct (may have CORS issues)
|
|
11
|
+
|
|
12
|
+
const client = new ParallaxClient('demo-api-key', GATEWAY_URL);
|
|
13
|
+
|
|
14
|
+
// State management
|
|
15
|
+
let currentTrace = null;
|
|
16
|
+
let attributes = {};
|
|
17
|
+
let tags = [];
|
|
18
|
+
let events = [];
|
|
19
|
+
|
|
20
|
+
// DOM elements
|
|
21
|
+
const elements = {
|
|
22
|
+
// Inputs
|
|
23
|
+
traceName: document.getElementById('traceName'),
|
|
24
|
+
fromAddress: document.getElementById('fromAddress'),
|
|
25
|
+
toAddress: document.getElementById('toAddress'),
|
|
26
|
+
value: document.getElementById('value'),
|
|
27
|
+
network: document.getElementById('network'),
|
|
28
|
+
attrKey: document.getElementById('attrKey'),
|
|
29
|
+
attrValue: document.getElementById('attrValue'),
|
|
30
|
+
tagInput: document.getElementById('tagInput'),
|
|
31
|
+
eventName: document.getElementById('eventName'),
|
|
32
|
+
eventDetails: document.getElementById('eventDetails'),
|
|
33
|
+
txHash: document.getElementById('txHash'),
|
|
34
|
+
chainId: document.getElementById('chainId'),
|
|
35
|
+
|
|
36
|
+
// Buttons
|
|
37
|
+
createTraceBtn: document.getElementById('createTraceBtn'),
|
|
38
|
+
addAttributeBtn: document.getElementById('addAttributeBtn'),
|
|
39
|
+
addTagBtn: document.getElementById('addTagBtn'),
|
|
40
|
+
addEventBtn: document.getElementById('addEventBtn'),
|
|
41
|
+
submitBtn: document.getElementById('submitBtn'),
|
|
42
|
+
resetBtn: document.getElementById('resetBtn'),
|
|
43
|
+
|
|
44
|
+
// Sections
|
|
45
|
+
attributesSection: document.getElementById('attributesSection'),
|
|
46
|
+
tagsSection: document.getElementById('tagsSection'),
|
|
47
|
+
eventsSection: document.getElementById('eventsSection'),
|
|
48
|
+
submitSection: document.getElementById('submitSection'),
|
|
49
|
+
|
|
50
|
+
// Display
|
|
51
|
+
traceInfo: document.getElementById('traceInfo'),
|
|
52
|
+
traceId: document.getElementById('traceId'),
|
|
53
|
+
txId: document.getElementById('txId'),
|
|
54
|
+
attributesContainer: document.getElementById('attributesContainer'),
|
|
55
|
+
tagsContainer: document.getElementById('tagsContainer'),
|
|
56
|
+
log: document.getElementById('log'),
|
|
57
|
+
status: document.getElementById('status'),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Logging functions
|
|
61
|
+
function log(message, type = 'info') {
|
|
62
|
+
const time = new Date().toLocaleTimeString();
|
|
63
|
+
const entry = document.createElement('div');
|
|
64
|
+
entry.className = 'log-entry';
|
|
65
|
+
entry.innerHTML = `<span class="log-time">[${time}]</span> <span class="log-${type}">${message}</span>`;
|
|
66
|
+
elements.log.appendChild(entry);
|
|
67
|
+
elements.log.scrollTop = elements.log.scrollHeight;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function showStatus(message, type = 'info') {
|
|
71
|
+
elements.status.textContent = message;
|
|
72
|
+
elements.status.className = `status ${type} show`;
|
|
73
|
+
setTimeout(() => {
|
|
74
|
+
elements.status.classList.remove('show');
|
|
75
|
+
}, 5000);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Step 1: Create Trace
|
|
79
|
+
elements.createTraceBtn.addEventListener('click', () => {
|
|
80
|
+
const traceName = elements.traceName.value.trim();
|
|
81
|
+
const from = elements.fromAddress.value.trim();
|
|
82
|
+
const to = elements.toAddress.value.trim();
|
|
83
|
+
const value = elements.value.value.trim();
|
|
84
|
+
const network = elements.network.value;
|
|
85
|
+
|
|
86
|
+
if (!traceName || !from || !to || !value) {
|
|
87
|
+
showStatus('Please fill in all transaction fields', 'error');
|
|
88
|
+
log('â Validation failed: Missing required fields', 'error');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
// Initialize a new trace builder
|
|
94
|
+
currentTrace = client.trace(traceName, true); // includeClientMeta = true
|
|
95
|
+
|
|
96
|
+
// Add default transaction attributes
|
|
97
|
+
currentTrace
|
|
98
|
+
.addAttribute('from', from)
|
|
99
|
+
.addAttribute('to', to)
|
|
100
|
+
.addAttribute('value', value)
|
|
101
|
+
.addAttribute('network', network)
|
|
102
|
+
.addAttribute('timestamp', new Date().toISOString())
|
|
103
|
+
.addTags(['demo', 'web-app', network]);
|
|
104
|
+
|
|
105
|
+
// Generate a mock transaction ID for demo purposes
|
|
106
|
+
const mockTxId = `demo_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
107
|
+
|
|
108
|
+
// Show trace info
|
|
109
|
+
elements.traceInfo.classList.add('show');
|
|
110
|
+
elements.traceId.textContent = 'Pending (will be generated on submit)';
|
|
111
|
+
elements.txId.textContent = mockTxId;
|
|
112
|
+
|
|
113
|
+
// Show next sections
|
|
114
|
+
elements.attributesSection.classList.remove('hidden');
|
|
115
|
+
elements.tagsSection.classList.remove('hidden');
|
|
116
|
+
elements.eventsSection.classList.remove('hidden');
|
|
117
|
+
elements.submitSection.classList.remove('hidden');
|
|
118
|
+
|
|
119
|
+
log(`â
Trace builder created: "${traceName}"`, 'success');
|
|
120
|
+
log(`đ Added default attributes: from, to, value, network, timestamp`, 'info');
|
|
121
|
+
log(`đˇī¸ Added default tags: demo, web-app, ${network}`, 'info');
|
|
122
|
+
showStatus('Trace builder initialized! Add attributes, tags, and events below.', 'success');
|
|
123
|
+
|
|
124
|
+
// Disable create button
|
|
125
|
+
elements.createTraceBtn.disabled = true;
|
|
126
|
+
elements.createTraceBtn.textContent = 'Trace Created â';
|
|
127
|
+
} catch (error) {
|
|
128
|
+
log(`â Error creating trace: ${error.message}`, 'error');
|
|
129
|
+
showStatus(`Error: ${error.message}`, 'error');
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Step 2: Add Attributes
|
|
134
|
+
elements.addAttributeBtn.addEventListener('click', () => {
|
|
135
|
+
const key = elements.attrKey.value.trim();
|
|
136
|
+
const value = elements.attrValue.value.trim();
|
|
137
|
+
|
|
138
|
+
if (!key || !value) {
|
|
139
|
+
showStatus('Please enter both key and value', 'error');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!currentTrace) {
|
|
144
|
+
showStatus('Please create a trace first', 'error');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Add to trace builder
|
|
149
|
+
currentTrace.addAttribute(key, value);
|
|
150
|
+
attributes[key] = value;
|
|
151
|
+
|
|
152
|
+
// Update UI
|
|
153
|
+
updateAttributesDisplay();
|
|
154
|
+
|
|
155
|
+
// Clear inputs
|
|
156
|
+
elements.attrKey.value = '';
|
|
157
|
+
elements.attrValue.value = '';
|
|
158
|
+
|
|
159
|
+
log(`â Added attribute: ${key} = "${value}"`, 'info');
|
|
160
|
+
showStatus('Attribute added successfully', 'success');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
function updateAttributesDisplay() {
|
|
164
|
+
if (Object.keys(attributes).length === 0) {
|
|
165
|
+
elements.attributesContainer.innerHTML = '<em style="color: #6c757d; font-size: 13px;">No custom attributes added yet</em>';
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
elements.attributesContainer.innerHTML = '';
|
|
170
|
+
for (const [key, value] of Object.entries(attributes)) {
|
|
171
|
+
const item = document.createElement('div');
|
|
172
|
+
item.className = 'attribute-item';
|
|
173
|
+
item.innerHTML = `
|
|
174
|
+
<span class="attribute-key">${key}:</span>
|
|
175
|
+
<span class="attribute-value">${value}</span>
|
|
176
|
+
`;
|
|
177
|
+
elements.attributesContainer.appendChild(item);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Step 3: Add Tags
|
|
182
|
+
elements.addTagBtn.addEventListener('click', () => {
|
|
183
|
+
const tag = elements.tagInput.value.trim();
|
|
184
|
+
|
|
185
|
+
if (!tag) {
|
|
186
|
+
showStatus('Please enter a tag', 'error');
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!currentTrace) {
|
|
191
|
+
showStatus('Please create a trace first', 'error');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (tags.includes(tag)) {
|
|
196
|
+
showStatus('Tag already added', 'error');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Add to trace builder
|
|
201
|
+
currentTrace.addTag(tag);
|
|
202
|
+
tags.push(tag);
|
|
203
|
+
|
|
204
|
+
// Update UI
|
|
205
|
+
updateTagsDisplay();
|
|
206
|
+
|
|
207
|
+
// Clear input
|
|
208
|
+
elements.tagInput.value = '';
|
|
209
|
+
|
|
210
|
+
log(`đˇī¸ Added tag: "${tag}"`, 'info');
|
|
211
|
+
showStatus('Tag added successfully', 'success');
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
function updateTagsDisplay() {
|
|
215
|
+
if (tags.length === 0) {
|
|
216
|
+
elements.tagsContainer.innerHTML = '<em style="color: #6c757d; font-size: 13px;">No tags added yet</em>';
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
elements.tagsContainer.innerHTML = '';
|
|
221
|
+
tags.forEach(tag => {
|
|
222
|
+
const chip = document.createElement('span');
|
|
223
|
+
chip.className = 'chip';
|
|
224
|
+
chip.textContent = tag;
|
|
225
|
+
elements.tagsContainer.appendChild(chip);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Step 4: Add Events
|
|
230
|
+
elements.addEventBtn.addEventListener('click', () => {
|
|
231
|
+
const eventName = elements.eventName.value.trim();
|
|
232
|
+
const eventDetails = elements.eventDetails.value.trim();
|
|
233
|
+
|
|
234
|
+
if (!eventName) {
|
|
235
|
+
showStatus('Please enter an event name', 'error');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (!currentTrace) {
|
|
240
|
+
showStatus('Please create a trace first', 'error');
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
let details = eventDetails;
|
|
246
|
+
|
|
247
|
+
// Try to parse as JSON if it looks like JSON
|
|
248
|
+
if (eventDetails && (eventDetails.startsWith('{') || eventDetails.startsWith('['))) {
|
|
249
|
+
try {
|
|
250
|
+
details = JSON.parse(eventDetails);
|
|
251
|
+
} catch (e) {
|
|
252
|
+
// If parsing fails, use as string
|
|
253
|
+
details = eventDetails;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Add to trace builder
|
|
258
|
+
currentTrace.addEvent(eventName, details || undefined);
|
|
259
|
+
events.push({ name: eventName, details });
|
|
260
|
+
|
|
261
|
+
// Clear inputs
|
|
262
|
+
elements.eventName.value = '';
|
|
263
|
+
elements.eventDetails.value = '';
|
|
264
|
+
|
|
265
|
+
log(`đ Added event: "${eventName}"${details ? ` with details` : ''}`, 'info');
|
|
266
|
+
showStatus('Event added successfully', 'success');
|
|
267
|
+
} catch (error) {
|
|
268
|
+
log(`â Error adding event: ${error.message}`, 'error');
|
|
269
|
+
showStatus(`Error: ${error.message}`, 'error');
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
// Step 5: Submit Trace
|
|
274
|
+
elements.submitBtn.addEventListener('click', async () => {
|
|
275
|
+
if (!currentTrace) {
|
|
276
|
+
showStatus('Please create a trace first', 'error');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const txHash = elements.txHash.value.trim();
|
|
281
|
+
const chainId = elements.chainId.value.trim();
|
|
282
|
+
|
|
283
|
+
try {
|
|
284
|
+
elements.submitBtn.disabled = true;
|
|
285
|
+
elements.submitBtn.textContent = 'Submitting...';
|
|
286
|
+
|
|
287
|
+
log('đ Submitting trace to Parallax Gateway...', 'info');
|
|
288
|
+
|
|
289
|
+
let response;
|
|
290
|
+
if (txHash && chainId) {
|
|
291
|
+
// Submit with transaction hash
|
|
292
|
+
response = await currentTrace.submit(txHash, chainId, 'Demo transaction');
|
|
293
|
+
log(`â
Trace submitted with tx hash: ${txHash}`, 'success');
|
|
294
|
+
} else {
|
|
295
|
+
// Submit without transaction hash
|
|
296
|
+
response = await currentTrace.submit();
|
|
297
|
+
log('â
Trace submitted without tx hash', 'success');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const traceId = response.getTraceId();
|
|
301
|
+
elements.traceId.textContent = traceId;
|
|
302
|
+
|
|
303
|
+
log(`đ Trace ID: ${traceId}`, 'success');
|
|
304
|
+
showStatus('Trace submitted successfully! Check the Activity Log for details.', 'success');
|
|
305
|
+
|
|
306
|
+
// Update button
|
|
307
|
+
elements.submitBtn.textContent = 'Submitted â';
|
|
308
|
+
elements.submitBtn.classList.remove('btn-success');
|
|
309
|
+
elements.submitBtn.classList.add('btn-secondary');
|
|
310
|
+
|
|
311
|
+
// Log summary
|
|
312
|
+
log('đ Trace Summary:', 'info');
|
|
313
|
+
log(` - Name: ${elements.traceName.value}`, 'info');
|
|
314
|
+
log(` - Attributes: ${Object.keys(attributes).length + 5} total`, 'info');
|
|
315
|
+
log(` - Tags: ${tags.length + 3} total`, 'info');
|
|
316
|
+
log(` - Events: ${events.length} total`, 'info');
|
|
317
|
+
if (txHash) {
|
|
318
|
+
log(` - Transaction Hash: ${txHash}`, 'info');
|
|
319
|
+
log(` - Chain ID: ${chainId}`, 'info');
|
|
320
|
+
}
|
|
321
|
+
} catch (error) {
|
|
322
|
+
log(`â Error submitting trace: ${error.message}`, 'error');
|
|
323
|
+
showStatus(`Error: ${error.message}`, 'error');
|
|
324
|
+
elements.submitBtn.disabled = false;
|
|
325
|
+
elements.submitBtn.textContent = 'Submit Trace';
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Reset functionality
|
|
330
|
+
elements.resetBtn.addEventListener('click', () => {
|
|
331
|
+
if (confirm('Are you sure you want to reset and start over?')) {
|
|
332
|
+
// Reset state
|
|
333
|
+
currentTrace = null;
|
|
334
|
+
attributes = {};
|
|
335
|
+
tags = [];
|
|
336
|
+
events = [];
|
|
337
|
+
|
|
338
|
+
// Reset UI
|
|
339
|
+
elements.traceName.value = 'demo_transaction';
|
|
340
|
+
elements.fromAddress.value = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
|
|
341
|
+
elements.toAddress.value = '0x1234567890123456789012345678901234567890';
|
|
342
|
+
elements.value.value = '0.5';
|
|
343
|
+
elements.network.value = 'ethereum';
|
|
344
|
+
elements.txHash.value = '';
|
|
345
|
+
elements.chainId.value = '1';
|
|
346
|
+
|
|
347
|
+
// Hide sections
|
|
348
|
+
elements.attributesSection.classList.add('hidden');
|
|
349
|
+
elements.tagsSection.classList.add('hidden');
|
|
350
|
+
elements.eventsSection.classList.add('hidden');
|
|
351
|
+
elements.submitSection.classList.add('hidden');
|
|
352
|
+
elements.traceInfo.classList.remove('show');
|
|
353
|
+
|
|
354
|
+
// Reset buttons
|
|
355
|
+
elements.createTraceBtn.disabled = false;
|
|
356
|
+
elements.createTraceBtn.textContent = 'Create Trace';
|
|
357
|
+
elements.submitBtn.disabled = false;
|
|
358
|
+
elements.submitBtn.textContent = 'Submit Trace';
|
|
359
|
+
elements.submitBtn.classList.remove('btn-secondary');
|
|
360
|
+
elements.submitBtn.classList.add('btn-success');
|
|
361
|
+
|
|
362
|
+
// Clear displays
|
|
363
|
+
updateAttributesDisplay();
|
|
364
|
+
updateTagsDisplay();
|
|
365
|
+
|
|
366
|
+
log('đ Reset complete. Ready to create a new trace.', 'warn');
|
|
367
|
+
showStatus('Reset complete! You can create a new trace.', 'info');
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// Keyboard shortcuts
|
|
372
|
+
document.addEventListener('keydown', (e) => {
|
|
373
|
+
// Ctrl/Cmd + Enter to submit in text areas
|
|
374
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
|
|
375
|
+
if (document.activeElement === elements.eventDetails) {
|
|
376
|
+
elements.addEventBtn.click();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
log('đ¨ Demo application loaded', 'success');
|
|
382
|
+
log('âšī¸ Fill in the transaction details and click "Create Trace" to begin', 'info');
|
|
383
|
+
|
|
384
|
+
// Check if proxy is running
|
|
385
|
+
async function checkProxy() {
|
|
386
|
+
const proxyStatus = document.getElementById('proxyStatus');
|
|
387
|
+
if (!proxyStatus) return;
|
|
388
|
+
|
|
389
|
+
if (!USE_PROXY) {
|
|
390
|
+
proxyStatus.innerHTML = '<span style="color: #856404;">Direct connection (may have CORS issues)</span>';
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
try {
|
|
395
|
+
const response = await fetch('http://localhost:3001/health');
|
|
396
|
+
const data = await response.json();
|
|
397
|
+
if (data.status === 'ok') {
|
|
398
|
+
proxyStatus.innerHTML = '<span style="color: #155724;">â Proxy server running on port 3001</span>';
|
|
399
|
+
log('â Proxy server is running', 'success');
|
|
400
|
+
} else {
|
|
401
|
+
proxyStatus.innerHTML = '<span style="color: #721c24;">â Proxy server error</span>';
|
|
402
|
+
log('â Proxy server returned unexpected response', 'error');
|
|
403
|
+
}
|
|
404
|
+
} catch (error) {
|
|
405
|
+
proxyStatus.innerHTML = '<span style="color: #721c24;">â Proxy server not running - run "npm run proxy" in a separate terminal</span>';
|
|
406
|
+
log('â Proxy server not running. Start it with: npm run proxy', 'error');
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Check proxy status on load
|
|
411
|
+
checkProxy();
|