@tari-project/tarijs 0.10.1 → 0.12.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 +127 -67
- package/TODO.md +91 -3
- package/docusaurus/tari-docs/README.md +200 -17
- package/docusaurus/tari-docs/docs/api-reference.md +665 -0
- package/docusaurus/tari-docs/docs/contributing.md +619 -0
- package/docusaurus/tari-docs/docs/guides/getting-started-tutorial.md +965 -0
- package/docusaurus/tari-docs/docs/guides/production-deployment.md +977 -0
- package/docusaurus/tari-docs/docs/index.md +114 -11
- package/docusaurus/tari-docs/docs/installation.md +142 -1
- package/docusaurus/tari-docs/docs/signers/metamask.md +529 -0
- package/docusaurus/tari-docs/docs/troubleshooting.md +661 -0
- package/docusaurus/tari-docs/package.json +4 -4
- package/eslint.config.mjs +9 -0
- package/examples/vite-typescript-react/README.md +9 -0
- package/examples/vite-typescript-react/eslint.config.js +23 -0
- package/examples/vite-typescript-react/index.html +13 -0
- package/examples/vite-typescript-react/package.json +35 -0
- package/examples/vite-typescript-react/public/vite.svg +1 -0
- package/examples/vite-typescript-react/src/App.css +42 -0
- package/examples/vite-typescript-react/src/App.tsx +50 -0
- package/examples/vite-typescript-react/src/assets/react.svg +1 -0
- package/examples/vite-typescript-react/src/index.css +68 -0
- package/examples/vite-typescript-react/src/main.tsx +10 -0
- package/examples/vite-typescript-react/src/vite-env.d.ts +1 -0
- package/examples/vite-typescript-react/tsconfig.app.json +27 -0
- package/examples/vite-typescript-react/tsconfig.json +7 -0
- package/examples/vite-typescript-react/tsconfig.node.json +25 -0
- package/examples/vite-typescript-react/vite.config.ts +7 -0
- package/package.json +16 -3
- package/packages/builders/package.json +2 -2
- package/packages/builders/src/helpers/submitTransaction.ts +10 -35
- package/packages/builders/src/transaction/TransactionBuilder.ts +227 -29
- package/packages/indexer_provider/package.json +2 -2
- package/packages/indexer_provider/src/provider.ts +5 -5
- package/packages/indexer_provider/tsconfig.json +4 -2
- package/packages/metamask_signer/package.json +2 -2
- package/packages/metamask_signer/src/index.ts +3 -15
- package/packages/{tari_permissions → permissions}/package.json +2 -2
- package/packages/{tari_permissions → permissions}/src/helpers.ts +1 -1
- package/packages/permissions/src/index.ts +2 -0
- package/packages/{tari_permissions/src/tari_permissions.ts → permissions/src/permissions.ts} +56 -6
- package/packages/react-mui-connect-button/moon.yml +71 -0
- package/packages/react-mui-connect-button/package.json +40 -0
- package/packages/react-mui-connect-button/src/Logos.tsx +60 -0
- package/packages/react-mui-connect-button/src/TariConnectButton.tsx +51 -0
- package/packages/react-mui-connect-button/src/TariWalletSelectionDialog.tsx +116 -0
- package/packages/react-mui-connect-button/src/content/tari-logo-white.svg +18 -0
- package/packages/react-mui-connect-button/src/content/tari-logo.svg +18 -0
- package/packages/react-mui-connect-button/src/content/walletconnect-logo.svg +13 -0
- package/packages/react-mui-connect-button/src/defaultPermissions.ts +0 -0
- package/packages/react-mui-connect-button/src/index.ts +24 -0
- package/packages/react-mui-connect-button/tsconfig.json +31 -0
- package/packages/tari_provider/package.json +2 -2
- package/packages/tari_provider/src/TariProvider.ts +6 -1
- package/packages/tari_signer/package.json +2 -2
- package/packages/tari_universe/package.json +2 -2
- package/packages/tari_universe/tsconfig.json +4 -2
- package/packages/tarijs/package.json +2 -2
- package/packages/tarijs/src/index.ts +27 -49
- package/packages/tarijs/src/templates/Account.ts +7 -4
- package/packages/tarijs/test/integration-tests/.env +1 -1
- package/packages/tarijs/test/integration-tests/wallet_daemon/json_rpc_provider.spec.ts +112 -73
- package/packages/tarijs/tsconfig.json +6 -4
- package/packages/tarijs/vitest.config.ts +2 -1
- package/packages/tarijs_types/package.json +4 -3
- package/packages/tarijs_types/src/Account.ts +68 -0
- package/packages/tarijs_types/src/Amount.ts +5 -1
- package/packages/tarijs_types/src/TransactionResult.ts +1 -8
- package/packages/tarijs_types/src/consts.ts +3 -0
- package/packages/tarijs_types/src/helpers/index.ts +4 -0
- package/packages/tarijs_types/src/helpers/simpleResult.ts +345 -0
- package/packages/tarijs_types/src/helpers/txResult.ts +1 -2
- package/packages/tarijs_types/src/index.ts +8 -0
- package/packages/tarijs_types/src/signer.ts +1 -1
- package/packages/wallet_daemon/package.json +2 -2
- package/packages/wallet_daemon/src/provider.ts +8 -6
- package/packages/wallet_daemon/src/signer.ts +18 -8
- package/packages/wallet_daemon/tsconfig.json +1 -1
- package/packages/walletconnect/package.json +3 -2
- package/packages/walletconnect/src/index.ts +54 -28
- package/packages/walletconnect/tsconfig.json +3 -0
- package/pnpm-workspace.yaml +15 -7
- package/scripts/check_versions.sh +4 -0
- package/scripts/clean_everything.sh +38 -0
- package/tsconfig.json +6 -0
- package/typedoc.json +10 -0
- package/packages/tari_permissions/src/index.ts +0 -2
- /package/packages/{tari_permissions → permissions}/moon.yml +0 -0
- /package/packages/{tari_permissions → permissions}/tsconfig.json +0 -0
|
@@ -0,0 +1,965 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_position: 2
|
|
3
|
+
title: Getting Started Tutorial
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Getting Started Tutorial
|
|
7
|
+
|
|
8
|
+
This comprehensive tutorial will guide you through building your first tari.js application step by step. By the end, you'll have a working web app that connects to Tari wallets and performs transactions.
|
|
9
|
+
|
|
10
|
+
## What You'll Build
|
|
11
|
+
|
|
12
|
+
A simple wallet interface that can:
|
|
13
|
+
- 🔌 Connect to different Tari wallet types
|
|
14
|
+
- 💰 Display wallet balance
|
|
15
|
+
- 📊 Query blockchain data
|
|
16
|
+
- 💸 Send transactions
|
|
17
|
+
- 📱 Handle wallet events
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
Before starting, ensure you have:
|
|
22
|
+
|
|
23
|
+
- **Node.js 18+** and **npm/pnpm** installed
|
|
24
|
+
- Basic knowledge of **TypeScript/JavaScript**
|
|
25
|
+
- **React** familiarity (optional - adaptable to any framework)
|
|
26
|
+
- A **Tari wallet** for testing (Wallet Daemon or MetaMask with tari-snap)
|
|
27
|
+
|
|
28
|
+
## Step 1: Project Setup
|
|
29
|
+
|
|
30
|
+
### Create a New React Project
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm create vite@latest tari-wallet-app -- --template react-ts
|
|
34
|
+
cd tari-wallet-app
|
|
35
|
+
npm install
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Install tari.js Dependencies
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Install core tari.js packages
|
|
42
|
+
npm install @tari-project/tarijs
|
|
43
|
+
|
|
44
|
+
# Install specific wallet providers
|
|
45
|
+
npm install @tari-project/wallet-daemon @tari-project/indexer-provider
|
|
46
|
+
|
|
47
|
+
# Optional: Add MetaMask support
|
|
48
|
+
npm install @tari-project/metamask-signer
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Configure Build Tools
|
|
52
|
+
|
|
53
|
+
Update `vite.config.ts` for proper bundling:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { defineConfig } from 'vite'
|
|
57
|
+
import react from '@vitejs/plugin-react'
|
|
58
|
+
|
|
59
|
+
export default defineConfig({
|
|
60
|
+
plugins: [react()],
|
|
61
|
+
define: {
|
|
62
|
+
global: 'globalThis',
|
|
63
|
+
},
|
|
64
|
+
optimizeDeps: {
|
|
65
|
+
include: ['@tari-project/tarijs']
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Step 2: Basic Wallet Connection
|
|
71
|
+
|
|
72
|
+
### Create Wallet Service
|
|
73
|
+
|
|
74
|
+
Create `src/services/walletService.ts`:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import {
|
|
78
|
+
WalletDaemonTariSigner,
|
|
79
|
+
IndexerProvider,
|
|
80
|
+
TariSigner,
|
|
81
|
+
TariProvider,
|
|
82
|
+
TariPermissions
|
|
83
|
+
} from '@tari-project/tarijs';
|
|
84
|
+
|
|
85
|
+
export interface WalletConnection {
|
|
86
|
+
signer: TariSigner;
|
|
87
|
+
provider: TariProvider;
|
|
88
|
+
isConnected: boolean;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class WalletService {
|
|
92
|
+
private connection: WalletConnection | null = null;
|
|
93
|
+
|
|
94
|
+
async connectWalletDaemon(endpoint: string = 'http://localhost:18103'): Promise<WalletConnection> {
|
|
95
|
+
try {
|
|
96
|
+
const signer = await WalletDaemonTariSigner.buildFetchSigner({
|
|
97
|
+
serverUrl: endpoint,
|
|
98
|
+
permissions: new TariPermissions()
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const provider = new IndexerProvider({
|
|
102
|
+
endpoint: 'http://localhost:18300'
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Test connection
|
|
106
|
+
await signer.getAccount();
|
|
107
|
+
|
|
108
|
+
this.connection = {
|
|
109
|
+
signer,
|
|
110
|
+
provider,
|
|
111
|
+
isConnected: true
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
return this.connection;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error('Failed to connect to wallet daemon:', error);
|
|
117
|
+
throw new Error('Could not connect to Tari Wallet Daemon. Is it running?');
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async connectMetaMask(): Promise<WalletConnection> {
|
|
122
|
+
if (typeof window.ethereum === 'undefined') {
|
|
123
|
+
throw new Error('MetaMask not installed. Please install MetaMask Flask.');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
// Request access to MetaMask
|
|
128
|
+
await window.ethereum.request({ method: 'eth_requestAccounts' });
|
|
129
|
+
|
|
130
|
+
// Install Tari snap if needed
|
|
131
|
+
await window.ethereum.request({
|
|
132
|
+
method: 'wallet_requestSnaps',
|
|
133
|
+
params: {
|
|
134
|
+
'npm:@tari-project/wallet-snap': {}
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
const { MetaMaskSigner } = await import('@tari-project/metamask-signer');
|
|
139
|
+
const signer = new MetaMaskSigner();
|
|
140
|
+
|
|
141
|
+
const provider = new IndexerProvider({
|
|
142
|
+
endpoint: 'http://localhost:18300'
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
this.connection = {
|
|
146
|
+
signer,
|
|
147
|
+
provider,
|
|
148
|
+
isConnected: true
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return this.connection;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error('Failed to connect to MetaMask:', error);
|
|
154
|
+
throw new Error('Could not connect to MetaMask. Check installation and permissions.');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
getConnection(): WalletConnection | null {
|
|
159
|
+
return this.connection;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async disconnect(): Promise<void> {
|
|
163
|
+
if (this.connection) {
|
|
164
|
+
await this.connection.signer.disconnect?.();
|
|
165
|
+
this.connection = null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Create Wallet Context
|
|
172
|
+
|
|
173
|
+
Create `src/contexts/WalletContext.tsx`:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import React, { createContext, useContext, useState, useCallback } from 'react';
|
|
177
|
+
import { WalletService, WalletConnection } from '../services/walletService';
|
|
178
|
+
|
|
179
|
+
interface WalletContextType {
|
|
180
|
+
connection: WalletConnection | null;
|
|
181
|
+
isConnecting: boolean;
|
|
182
|
+
error: string | null;
|
|
183
|
+
connectWalletDaemon: () => Promise<void>;
|
|
184
|
+
connectMetaMask: () => Promise<void>;
|
|
185
|
+
disconnect: () => Promise<void>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const WalletContext = createContext<WalletContextType | undefined>(undefined);
|
|
189
|
+
|
|
190
|
+
export const useWallet = () => {
|
|
191
|
+
const context = useContext(WalletContext);
|
|
192
|
+
if (!context) {
|
|
193
|
+
throw new Error('useWallet must be used within a WalletProvider');
|
|
194
|
+
}
|
|
195
|
+
return context;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
export const WalletProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
199
|
+
const [connection, setConnection] = useState<WalletConnection | null>(null);
|
|
200
|
+
const [isConnecting, setIsConnecting] = useState(false);
|
|
201
|
+
const [error, setError] = useState<string | null>(null);
|
|
202
|
+
const [walletService] = useState(new WalletService());
|
|
203
|
+
|
|
204
|
+
const handleConnection = useCallback(async (connectFn: () => Promise<WalletConnection>) => {
|
|
205
|
+
setIsConnecting(true);
|
|
206
|
+
setError(null);
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const conn = await connectFn();
|
|
210
|
+
setConnection(conn);
|
|
211
|
+
} catch (err) {
|
|
212
|
+
setError(err instanceof Error ? err.message : 'Connection failed');
|
|
213
|
+
} finally {
|
|
214
|
+
setIsConnecting(false);
|
|
215
|
+
}
|
|
216
|
+
}, []);
|
|
217
|
+
|
|
218
|
+
const connectWalletDaemon = useCallback(() =>
|
|
219
|
+
handleConnection(() => walletService.connectWalletDaemon()),
|
|
220
|
+
[handleConnection, walletService]
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const connectMetaMask = useCallback(() =>
|
|
224
|
+
handleConnection(() => walletService.connectMetaMask()),
|
|
225
|
+
[handleConnection, walletService]
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const disconnect = useCallback(async () => {
|
|
229
|
+
await walletService.disconnect();
|
|
230
|
+
setConnection(null);
|
|
231
|
+
setError(null);
|
|
232
|
+
}, [walletService]);
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<WalletContext.Provider value={{
|
|
236
|
+
connection,
|
|
237
|
+
isConnecting,
|
|
238
|
+
error,
|
|
239
|
+
connectWalletDaemon,
|
|
240
|
+
connectMetaMask,
|
|
241
|
+
disconnect
|
|
242
|
+
}}>
|
|
243
|
+
{children}
|
|
244
|
+
</WalletContext.Provider>
|
|
245
|
+
);
|
|
246
|
+
};
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Step 3: Wallet Connection UI
|
|
250
|
+
|
|
251
|
+
### Create Wallet Connect Component
|
|
252
|
+
|
|
253
|
+
Create `src/components/WalletConnect.tsx`:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import React from 'react';
|
|
257
|
+
import { useWallet } from '../contexts/WalletContext';
|
|
258
|
+
|
|
259
|
+
export const WalletConnect: React.FC = () => {
|
|
260
|
+
const {
|
|
261
|
+
connection,
|
|
262
|
+
isConnecting,
|
|
263
|
+
error,
|
|
264
|
+
connectWalletDaemon,
|
|
265
|
+
connectMetaMask,
|
|
266
|
+
disconnect
|
|
267
|
+
} = useWallet();
|
|
268
|
+
|
|
269
|
+
if (connection?.isConnected) {
|
|
270
|
+
return (
|
|
271
|
+
<div className="wallet-connected">
|
|
272
|
+
<div className="success-message">
|
|
273
|
+
✅ Wallet Connected Successfully!
|
|
274
|
+
</div>
|
|
275
|
+
<button onClick={disconnect} className="disconnect-btn">
|
|
276
|
+
Disconnect Wallet
|
|
277
|
+
</button>
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return (
|
|
283
|
+
<div className="wallet-connect">
|
|
284
|
+
<h2>Connect Your Tari Wallet</h2>
|
|
285
|
+
|
|
286
|
+
{error && (
|
|
287
|
+
<div className="error-message">
|
|
288
|
+
❌ {error}
|
|
289
|
+
</div>
|
|
290
|
+
)}
|
|
291
|
+
|
|
292
|
+
<div className="wallet-options">
|
|
293
|
+
<button
|
|
294
|
+
onClick={connectWalletDaemon}
|
|
295
|
+
disabled={isConnecting}
|
|
296
|
+
className="wallet-btn wallet-daemon-btn"
|
|
297
|
+
>
|
|
298
|
+
{isConnecting ? '🔄 Connecting...' : '🖥️ Connect Wallet Daemon'}
|
|
299
|
+
</button>
|
|
300
|
+
|
|
301
|
+
<button
|
|
302
|
+
onClick={connectMetaMask}
|
|
303
|
+
disabled={isConnecting}
|
|
304
|
+
className="wallet-btn metamask-btn"
|
|
305
|
+
>
|
|
306
|
+
{isConnecting ? '🔄 Connecting...' : '🦊 Connect MetaMask'}
|
|
307
|
+
</button>
|
|
308
|
+
</div>
|
|
309
|
+
|
|
310
|
+
<div className="wallet-help">
|
|
311
|
+
<h3>Need Help?</h3>
|
|
312
|
+
<ul>
|
|
313
|
+
<li>
|
|
314
|
+
<strong>Wallet Daemon:</strong> Make sure your Tari Wallet Daemon is running on localhost:18103
|
|
315
|
+
</li>
|
|
316
|
+
<li>
|
|
317
|
+
<strong>MetaMask:</strong> Install MetaMask Flask and the Tari snap from our example site
|
|
318
|
+
</li>
|
|
319
|
+
</ul>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
);
|
|
323
|
+
};
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Step 4: Display Wallet Information
|
|
327
|
+
|
|
328
|
+
### Create Wallet Info Component
|
|
329
|
+
|
|
330
|
+
Create `src/components/WalletInfo.tsx`:
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
import React, { useState, useEffect } from 'react';
|
|
334
|
+
import { useWallet } from '../contexts/WalletContext';
|
|
335
|
+
|
|
336
|
+
interface AccountInfo {
|
|
337
|
+
address: string;
|
|
338
|
+
balance: number;
|
|
339
|
+
name?: string;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
export const WalletInfo: React.FC = () => {
|
|
343
|
+
const { connection } = useWallet();
|
|
344
|
+
const [accounts, setAccounts] = useState<AccountInfo[]>([]);
|
|
345
|
+
const [loading, setLoading] = useState(false);
|
|
346
|
+
const [error, setError] = useState<string | null>(null);
|
|
347
|
+
|
|
348
|
+
useEffect(() => {
|
|
349
|
+
if (connection?.isConnected) {
|
|
350
|
+
loadAccountInfo();
|
|
351
|
+
}
|
|
352
|
+
}, [connection]);
|
|
353
|
+
|
|
354
|
+
const loadAccountInfo = async () => {
|
|
355
|
+
if (!connection) return;
|
|
356
|
+
|
|
357
|
+
setLoading(true);
|
|
358
|
+
setError(null);
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
// Get default account
|
|
362
|
+
const account = await connection.signer.getAccount();
|
|
363
|
+
|
|
364
|
+
setAccounts([{
|
|
365
|
+
address: account.address,
|
|
366
|
+
balance: account.resources.reduce((total, resource) => total + resource.balance, 0),
|
|
367
|
+
name: 'Default Account'
|
|
368
|
+
}]);
|
|
369
|
+
} catch (err) {
|
|
370
|
+
setError(err instanceof Error ? err.message : 'Failed to load account information');
|
|
371
|
+
} finally {
|
|
372
|
+
setLoading(false);
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
if (!connection?.isConnected) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (loading) {
|
|
381
|
+
return (
|
|
382
|
+
<div className="wallet-info loading">
|
|
383
|
+
<h3>Loading wallet information...</h3>
|
|
384
|
+
</div>
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (error) {
|
|
389
|
+
return (
|
|
390
|
+
<div className="wallet-info error">
|
|
391
|
+
<h3>Error loading wallet info</h3>
|
|
392
|
+
<p>{error}</p>
|
|
393
|
+
<button onClick={loadAccountInfo}>Retry</button>
|
|
394
|
+
</div>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
return (
|
|
399
|
+
<div className="wallet-info">
|
|
400
|
+
<div className="info-header">
|
|
401
|
+
<h3>💰 Wallet Information</h3>
|
|
402
|
+
<button onClick={loadAccountInfo} className="refresh-btn">
|
|
403
|
+
🔄 Refresh
|
|
404
|
+
</button>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
407
|
+
<div className="accounts-list">
|
|
408
|
+
{accounts.length === 0 ? (
|
|
409
|
+
<p>No accounts found</p>
|
|
410
|
+
) : (
|
|
411
|
+
accounts.map((account, index) => (
|
|
412
|
+
<div key={index} className="account-card">
|
|
413
|
+
<div className="account-name">
|
|
414
|
+
<strong>{account.name}</strong>
|
|
415
|
+
</div>
|
|
416
|
+
<div className="account-address">
|
|
417
|
+
Address: <code>{account.address}</code>
|
|
418
|
+
</div>
|
|
419
|
+
<div className="account-balance">
|
|
420
|
+
Balance: <strong>{account.balance.toLocaleString()} Tari</strong>
|
|
421
|
+
</div>
|
|
422
|
+
</div>
|
|
423
|
+
))
|
|
424
|
+
)}
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
);
|
|
428
|
+
};
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
## Step 5: Simple Transaction Interface
|
|
432
|
+
|
|
433
|
+
### Create Transaction Component
|
|
434
|
+
|
|
435
|
+
Create `src/components/TransactionForm.tsx`:
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
import React, { useState } from 'react';
|
|
439
|
+
import { useWallet } from '../contexts/WalletContext';
|
|
440
|
+
import { TransactionBuilder } from '@tari-project/tarijs';
|
|
441
|
+
|
|
442
|
+
export const TransactionForm: React.FC = () => {
|
|
443
|
+
const { connection } = useWallet();
|
|
444
|
+
const [recipient, setRecipient] = useState('');
|
|
445
|
+
const [amount, setAmount] = useState('');
|
|
446
|
+
const [fee, setFee] = useState('100');
|
|
447
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
448
|
+
const [result, setResult] = useState<string | null>(null);
|
|
449
|
+
const [error, setError] = useState<string | null>(null);
|
|
450
|
+
|
|
451
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
452
|
+
e.preventDefault();
|
|
453
|
+
|
|
454
|
+
if (!connection?.isConnected) {
|
|
455
|
+
setError('Wallet not connected');
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (!recipient || !amount) {
|
|
460
|
+
setError('Please fill in all fields');
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
setIsSubmitting(true);
|
|
465
|
+
setError(null);
|
|
466
|
+
setResult(null);
|
|
467
|
+
|
|
468
|
+
try {
|
|
469
|
+
// Get default account
|
|
470
|
+
const account = await connection.signer.getAccount();
|
|
471
|
+
|
|
472
|
+
// Build transaction
|
|
473
|
+
const transaction = new TransactionBuilder()
|
|
474
|
+
.feeTransactionPayFromComponent(account.address, fee)
|
|
475
|
+
.callMethod({
|
|
476
|
+
componentAddress: account.address,
|
|
477
|
+
methodName: 'withdraw',
|
|
478
|
+
}, [{ type: 'Amount', value: amount }])
|
|
479
|
+
.build();
|
|
480
|
+
|
|
481
|
+
// Submit transaction
|
|
482
|
+
const txResult = await connection.signer.submitTransaction({ transaction });
|
|
483
|
+
|
|
484
|
+
setResult(`Transaction submitted successfully! ID: ${txResult.transaction_id}`);
|
|
485
|
+
|
|
486
|
+
// Clear form
|
|
487
|
+
setRecipient('');
|
|
488
|
+
setAmount('');
|
|
489
|
+
} catch (err) {
|
|
490
|
+
setError(err instanceof Error ? err.message : 'Transaction failed');
|
|
491
|
+
} finally {
|
|
492
|
+
setIsSubmitting(false);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
if (!connection?.isConnected) {
|
|
497
|
+
return (
|
|
498
|
+
<div className="transaction-form disabled">
|
|
499
|
+
<h3>💸 Send Transaction</h3>
|
|
500
|
+
<p>Connect a wallet to send transactions</p>
|
|
501
|
+
</div>
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return (
|
|
506
|
+
<div className="transaction-form">
|
|
507
|
+
<h3>💸 Send Transaction</h3>
|
|
508
|
+
|
|
509
|
+
<form onSubmit={handleSubmit}>
|
|
510
|
+
<div className="form-group">
|
|
511
|
+
<label htmlFor="recipient">Recipient Address:</label>
|
|
512
|
+
<input
|
|
513
|
+
id="recipient"
|
|
514
|
+
type="text"
|
|
515
|
+
value={recipient}
|
|
516
|
+
onChange={(e) => setRecipient(e.target.value)}
|
|
517
|
+
placeholder="Enter recipient address..."
|
|
518
|
+
disabled={isSubmitting}
|
|
519
|
+
/>
|
|
520
|
+
</div>
|
|
521
|
+
|
|
522
|
+
<div className="form-group">
|
|
523
|
+
<label htmlFor="amount">Amount (Tari):</label>
|
|
524
|
+
<input
|
|
525
|
+
id="amount"
|
|
526
|
+
type="number"
|
|
527
|
+
value={amount}
|
|
528
|
+
onChange={(e) => setAmount(e.target.value)}
|
|
529
|
+
placeholder="Enter amount..."
|
|
530
|
+
min="1"
|
|
531
|
+
disabled={isSubmitting}
|
|
532
|
+
/>
|
|
533
|
+
</div>
|
|
534
|
+
|
|
535
|
+
<div className="form-group">
|
|
536
|
+
<label htmlFor="fee">Fee (Tari):</label>
|
|
537
|
+
<input
|
|
538
|
+
id="fee"
|
|
539
|
+
type="number"
|
|
540
|
+
value={fee}
|
|
541
|
+
onChange={(e) => setFee(e.target.value)}
|
|
542
|
+
placeholder="Enter fee..."
|
|
543
|
+
min="1"
|
|
544
|
+
disabled={isSubmitting}
|
|
545
|
+
/>
|
|
546
|
+
</div>
|
|
547
|
+
|
|
548
|
+
<button
|
|
549
|
+
type="submit"
|
|
550
|
+
disabled={isSubmitting || !recipient || !amount}
|
|
551
|
+
className="submit-btn"
|
|
552
|
+
>
|
|
553
|
+
{isSubmitting ? '🔄 Sending...' : '📤 Send Transaction'}
|
|
554
|
+
</button>
|
|
555
|
+
</form>
|
|
556
|
+
|
|
557
|
+
{error && (
|
|
558
|
+
<div className="error-message">
|
|
559
|
+
❌ {error}
|
|
560
|
+
</div>
|
|
561
|
+
)}
|
|
562
|
+
|
|
563
|
+
{result && (
|
|
564
|
+
<div className="success-message">
|
|
565
|
+
✅ {result}
|
|
566
|
+
</div>
|
|
567
|
+
)}
|
|
568
|
+
</div>
|
|
569
|
+
);
|
|
570
|
+
};
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
## Step 6: Bring It All Together
|
|
574
|
+
|
|
575
|
+
### Update Main App
|
|
576
|
+
|
|
577
|
+
Update `src/App.tsx`:
|
|
578
|
+
|
|
579
|
+
```typescript
|
|
580
|
+
import React from 'react';
|
|
581
|
+
import { WalletProvider } from './contexts/WalletContext';
|
|
582
|
+
import { WalletConnect } from './components/WalletConnect';
|
|
583
|
+
import { WalletInfo } from './components/WalletInfo';
|
|
584
|
+
import { TransactionForm } from './components/TransactionForm';
|
|
585
|
+
import './App.css';
|
|
586
|
+
|
|
587
|
+
function App() {
|
|
588
|
+
return (
|
|
589
|
+
<WalletProvider>
|
|
590
|
+
<div className="app">
|
|
591
|
+
<header className="app-header">
|
|
592
|
+
<h1>🏗️ Tari Wallet Demo</h1>
|
|
593
|
+
<p>Connect to your Tari wallet and perform transactions</p>
|
|
594
|
+
</header>
|
|
595
|
+
|
|
596
|
+
<main className="app-main">
|
|
597
|
+
<section className="connect-section">
|
|
598
|
+
<WalletConnect />
|
|
599
|
+
</section>
|
|
600
|
+
|
|
601
|
+
<section className="info-section">
|
|
602
|
+
<WalletInfo />
|
|
603
|
+
</section>
|
|
604
|
+
|
|
605
|
+
<section className="transaction-section">
|
|
606
|
+
<TransactionForm />
|
|
607
|
+
</section>
|
|
608
|
+
</main>
|
|
609
|
+
|
|
610
|
+
<footer className="app-footer">
|
|
611
|
+
<p>
|
|
612
|
+
Built with <a href="https://tari-project.github.io/tari.js/">tari.js</a> |
|
|
613
|
+
<a href="https://github.com/tari-project/tari.js">GitHub</a>
|
|
614
|
+
</p>
|
|
615
|
+
</footer>
|
|
616
|
+
</div>
|
|
617
|
+
</WalletProvider>
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export default App;
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
### Add Basic Styling
|
|
625
|
+
|
|
626
|
+
Create/update `src/App.css`:
|
|
627
|
+
|
|
628
|
+
```css
|
|
629
|
+
.app {
|
|
630
|
+
max-width: 800px;
|
|
631
|
+
margin: 0 auto;
|
|
632
|
+
padding: 20px;
|
|
633
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
.app-header {
|
|
637
|
+
text-align: center;
|
|
638
|
+
margin-bottom: 40px;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
.app-header h1 {
|
|
642
|
+
color: #333;
|
|
643
|
+
margin-bottom: 10px;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
.app-main {
|
|
647
|
+
display: flex;
|
|
648
|
+
flex-direction: column;
|
|
649
|
+
gap: 30px;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/* Wallet Connect Styles */
|
|
653
|
+
.wallet-connect {
|
|
654
|
+
background: #f8f9fa;
|
|
655
|
+
padding: 30px;
|
|
656
|
+
border-radius: 12px;
|
|
657
|
+
text-align: center;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
.wallet-options {
|
|
661
|
+
display: flex;
|
|
662
|
+
gap: 15px;
|
|
663
|
+
justify-content: center;
|
|
664
|
+
margin: 20px 0;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.wallet-btn {
|
|
668
|
+
padding: 12px 24px;
|
|
669
|
+
border: none;
|
|
670
|
+
border-radius: 8px;
|
|
671
|
+
font-size: 16px;
|
|
672
|
+
cursor: pointer;
|
|
673
|
+
transition: all 0.2s;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.wallet-daemon-btn {
|
|
677
|
+
background: #007bff;
|
|
678
|
+
color: white;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
.metamask-btn {
|
|
682
|
+
background: #f6851b;
|
|
683
|
+
color: white;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
.wallet-btn:hover:not(:disabled) {
|
|
687
|
+
transform: translateY(-2px);
|
|
688
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
.wallet-btn:disabled {
|
|
692
|
+
opacity: 0.6;
|
|
693
|
+
cursor: not-allowed;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
.wallet-connected {
|
|
697
|
+
background: #d4edda;
|
|
698
|
+
color: #155724;
|
|
699
|
+
padding: 20px;
|
|
700
|
+
border-radius: 8px;
|
|
701
|
+
text-align: center;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
.disconnect-btn {
|
|
705
|
+
background: #dc3545;
|
|
706
|
+
color: white;
|
|
707
|
+
border: none;
|
|
708
|
+
padding: 8px 16px;
|
|
709
|
+
border-radius: 4px;
|
|
710
|
+
cursor: pointer;
|
|
711
|
+
margin-top: 10px;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/* Wallet Info Styles */
|
|
715
|
+
.wallet-info {
|
|
716
|
+
background: white;
|
|
717
|
+
border: 1px solid #e9ecef;
|
|
718
|
+
border-radius: 12px;
|
|
719
|
+
padding: 24px;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
.info-header {
|
|
723
|
+
display: flex;
|
|
724
|
+
justify-content: space-between;
|
|
725
|
+
align-items: center;
|
|
726
|
+
margin-bottom: 20px;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
.refresh-btn {
|
|
730
|
+
background: #28a745;
|
|
731
|
+
color: white;
|
|
732
|
+
border: none;
|
|
733
|
+
padding: 6px 12px;
|
|
734
|
+
border-radius: 4px;
|
|
735
|
+
cursor: pointer;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
.account-card {
|
|
739
|
+
background: #f8f9fa;
|
|
740
|
+
padding: 16px;
|
|
741
|
+
border-radius: 8px;
|
|
742
|
+
margin-bottom: 12px;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
.account-address code {
|
|
746
|
+
background: #e9ecef;
|
|
747
|
+
padding: 2px 6px;
|
|
748
|
+
border-radius: 4px;
|
|
749
|
+
font-size: 12px;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/* Transaction Form Styles */
|
|
753
|
+
.transaction-form {
|
|
754
|
+
background: white;
|
|
755
|
+
border: 1px solid #e9ecef;
|
|
756
|
+
border-radius: 12px;
|
|
757
|
+
padding: 24px;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.form-group {
|
|
761
|
+
margin-bottom: 16px;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.form-group label {
|
|
765
|
+
display: block;
|
|
766
|
+
margin-bottom: 4px;
|
|
767
|
+
font-weight: 500;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.form-group input {
|
|
771
|
+
width: 100%;
|
|
772
|
+
padding: 10px;
|
|
773
|
+
border: 1px solid #ced4da;
|
|
774
|
+
border-radius: 4px;
|
|
775
|
+
font-size: 14px;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
.submit-btn {
|
|
779
|
+
background: #007bff;
|
|
780
|
+
color: white;
|
|
781
|
+
border: none;
|
|
782
|
+
padding: 12px 24px;
|
|
783
|
+
border-radius: 4px;
|
|
784
|
+
cursor: pointer;
|
|
785
|
+
font-size: 16px;
|
|
786
|
+
width: 100%;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
.submit-btn:disabled {
|
|
790
|
+
opacity: 0.6;
|
|
791
|
+
cursor: not-allowed;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/* Message Styles */
|
|
795
|
+
.error-message {
|
|
796
|
+
background: #f8d7da;
|
|
797
|
+
color: #721c24;
|
|
798
|
+
padding: 12px;
|
|
799
|
+
border-radius: 4px;
|
|
800
|
+
margin-top: 16px;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.success-message {
|
|
804
|
+
background: #d4edda;
|
|
805
|
+
color: #155724;
|
|
806
|
+
padding: 12px;
|
|
807
|
+
border-radius: 4px;
|
|
808
|
+
margin-top: 16px;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/* Responsive Design */
|
|
812
|
+
@media (max-width: 768px) {
|
|
813
|
+
.wallet-options {
|
|
814
|
+
flex-direction: column;
|
|
815
|
+
align-items: center;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
.wallet-btn {
|
|
819
|
+
width: 100%;
|
|
820
|
+
max-width: 300px;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
## Step 7: Testing Your Application
|
|
826
|
+
|
|
827
|
+
### Start the Development Server
|
|
828
|
+
|
|
829
|
+
```bash
|
|
830
|
+
npm run dev
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
Your app should now be running at `http://localhost:5173`
|
|
834
|
+
|
|
835
|
+
### Test Wallet Connections
|
|
836
|
+
|
|
837
|
+
#### Testing with Wallet Daemon:
|
|
838
|
+
|
|
839
|
+
1. Ensure your Tari Wallet Daemon is running:
|
|
840
|
+
```bash
|
|
841
|
+
./target/release/minotari_wallet_daemon --config-path ./config.toml
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
2. Click "Connect Wallet Daemon" in your app
|
|
845
|
+
3. If successful, you should see wallet information displayed
|
|
846
|
+
|
|
847
|
+
#### Testing with MetaMask:
|
|
848
|
+
|
|
849
|
+
1. Install MetaMask Flask (developer version)
|
|
850
|
+
2. Visit the tari.js example site to install the Tari snap
|
|
851
|
+
3. Click "Connect MetaMask" in your app
|
|
852
|
+
4. Follow the MetaMask prompts to connect
|
|
853
|
+
|
|
854
|
+
## Step 8: Advanced Features
|
|
855
|
+
|
|
856
|
+
### Add Real-time Updates
|
|
857
|
+
|
|
858
|
+
```typescript
|
|
859
|
+
// In WalletInfo component, add polling for balance updates
|
|
860
|
+
useEffect(() => {
|
|
861
|
+
if (!connection?.isConnected) return;
|
|
862
|
+
|
|
863
|
+
const interval = setInterval(() => {
|
|
864
|
+
loadAccountInfo();
|
|
865
|
+
}, 30000); // Update every 30 seconds
|
|
866
|
+
|
|
867
|
+
return () => clearInterval(interval);
|
|
868
|
+
}, [connection]);
|
|
869
|
+
```
|
|
870
|
+
|
|
871
|
+
### Add Transaction History
|
|
872
|
+
|
|
873
|
+
```typescript
|
|
874
|
+
// Add to WalletInfo component
|
|
875
|
+
const [transactions, setTransactions] = useState([]);
|
|
876
|
+
|
|
877
|
+
const loadTransactionHistory = async () => {
|
|
878
|
+
if (!connection) return;
|
|
879
|
+
|
|
880
|
+
try {
|
|
881
|
+
const accounts = await connection.signer.getAccounts();
|
|
882
|
+
const txHistory = await connection.provider.getTransactionHistory(accounts[0]);
|
|
883
|
+
setTransactions(txHistory);
|
|
884
|
+
} catch (error) {
|
|
885
|
+
console.error('Failed to load transaction history:', error);
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
```
|
|
889
|
+
|
|
890
|
+
### Error Boundaries
|
|
891
|
+
|
|
892
|
+
```typescript
|
|
893
|
+
// Create src/components/ErrorBoundary.tsx
|
|
894
|
+
import React from 'react';
|
|
895
|
+
|
|
896
|
+
interface Props {
|
|
897
|
+
children: React.ReactNode;
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
interface State {
|
|
901
|
+
hasError: boolean;
|
|
902
|
+
error?: Error;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
export class ErrorBoundary extends React.Component<Props, State> {
|
|
906
|
+
constructor(props: Props) {
|
|
907
|
+
super(props);
|
|
908
|
+
this.state = { hasError: false };
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
static getDerivedStateFromError(error: Error): State {
|
|
912
|
+
return { hasError: true, error };
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
916
|
+
console.error('App Error:', error, errorInfo);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
render() {
|
|
920
|
+
if (this.state.hasError) {
|
|
921
|
+
return (
|
|
922
|
+
<div className="error-boundary">
|
|
923
|
+
<h2>Something went wrong!</h2>
|
|
924
|
+
<p>{this.state.error?.message}</p>
|
|
925
|
+
<button onClick={() => window.location.reload()}>
|
|
926
|
+
Reload Page
|
|
927
|
+
</button>
|
|
928
|
+
</div>
|
|
929
|
+
);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
return this.props.children;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
## Next Steps
|
|
938
|
+
|
|
939
|
+
Congratulations! 🎉 You've built a functional Tari wallet application. Here's what you can explore next:
|
|
940
|
+
|
|
941
|
+
### 📚 Learn More:
|
|
942
|
+
- **[Advanced Transaction Building](../wallet/submit-transaction/transaction-builder/)** - Complex smart contract interactions
|
|
943
|
+
- **[Template System](../wallet/template-definition.md)** - Working with smart contract templates
|
|
944
|
+
- **[Provider Types](../providers/indexer-provider.md)** - Different data access patterns
|
|
945
|
+
|
|
946
|
+
### 🚀 Production Ready:
|
|
947
|
+
- **[Production Deployment Guide](./production-deployment.md)** - Security and performance best practices
|
|
948
|
+
- **[Error Handling](../troubleshooting.md)** - Comprehensive error management
|
|
949
|
+
- **[GitHub Testing Examples](https://github.com/tari-project/tari.js/tree/main/packages/tarijs/test)** - Unit and integration testing examples
|
|
950
|
+
|
|
951
|
+
### 🛠️ Extend Your App:
|
|
952
|
+
- Add support for multiple wallet types
|
|
953
|
+
- Implement transaction history visualization
|
|
954
|
+
- Create reusable wallet components
|
|
955
|
+
- Add offline transaction queuing
|
|
956
|
+
- Implement advanced authentication
|
|
957
|
+
|
|
958
|
+
### 💬 Get Help:
|
|
959
|
+
- **[Discord Community](https://discord.gg/tari)** - Join the conversation
|
|
960
|
+
- **[GitHub Discussions](https://github.com/tari-project/tari.js/discussions)** - Ask questions
|
|
961
|
+
- **[API Reference](../api-reference.md)** - Complete method documentation
|
|
962
|
+
|
|
963
|
+
---
|
|
964
|
+
|
|
965
|
+
*Happy building with tari.js! 🏗️*
|