Skip to content

Commit 540a250

Browse files
authored
Merge pull request #18 from windingtree/develop
Client-side contract interactions
2 parents 035cd10 + e6f3429 commit 540a250

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2901
-902
lines changed

.env.example

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
EXAMPLE_ENTITY_SIGNER_MNEMONIC=
2+
EXAMPLE_ENTITY_ID=

.lintstagedrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{
2-
"*.{ts,js}": ["prettier --write", "eslint --cache --fix"],
2+
"*.{ts,js}": ["prettier --write", "eslint --fix"],
33
"*.md": ["prettier --write"]
44
}

examples/client/.env.example

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
VITE_LOCAL_NODE=

examples/client/package.json

+7-6
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@
33
"private": true,
44
"version": "0.0.0",
55
"type": "module",
6-
"scripts": {
7-
"dev": "vite",
8-
"build": "tsc && vite build",
9-
"preview": "vite preview"
10-
},
116
"devDependencies": {
127
"@types/react": "^18.2.6",
138
"@types/react-dom": "^18.2.4",
@@ -17,7 +12,8 @@
1712
"react": "^18.2.0",
1813
"react-dom": "^18.2.0",
1914
"typescript": "^5.0.4",
20-
"vite": "^4.3.4"
15+
"viem": "^0.3.43",
16+
"vite": "^4.3.9"
2117
},
2218
"eslintConfig": {
2319
"extends": [
@@ -41,5 +37,10 @@
4137
"last 1 firefox version",
4238
"last 1 safari version"
4339
]
40+
},
41+
"scripts": {
42+
"dev": "vite --force",
43+
"build": "tsc && vite build",
44+
"preview": "vite preview"
4445
}
4546
}

examples/client/src/App.tsx

+91-170
Original file line numberDiff line numberDiff line change
@@ -1,154 +1,34 @@
11
import { useState, useEffect, useRef } from 'react';
2-
import { RequestQuery, OfferOptions, contractConfig, serverAddress } from '../../shared/index.js';
3-
import {
4-
Client,
5-
ClientOptions,
6-
RequestRecord,
7-
createClient,
8-
storage,
9-
utils,
10-
} from '../../../src/index.js'; //@windingtree/sdk
2+
import { Client, ClientOptions, createClient, storage } from '../../../src/index.js'; // @windingtree/sdk
3+
import { RequestQuery, OfferOptions, chainConfig, serverAddress } from '../../shared/index.js';
4+
import { OfferData } from '../../../src/shared/types.js';
5+
import { useWallet } from './providers/WalletProvider/WalletProviderContext.js';
6+
import { AccountWidget } from './providers/WalletProvider/AccountWidget.js';
7+
import { FormValues, RequestForm } from './components/RequestForm.js';
8+
import { Tabs, TabPanel } from './components/Tabs.js';
9+
import { Requests, RequestsRegistryRecord } from './components/Requests.js';
10+
import { MakeDeal } from './components/MakeDeal.js';
11+
import { Offers } from './components/Offers.js';
12+
import { Deals, DealsRegistryRecord } from './components/Deals.js';
1113

1214
/** Default request expiration time */
1315
const defaultExpire = '30s';
1416

1517
/** Default topic to publish requests the same as for the supplier node */
1618
const defaultTopic = 'hello';
1719

18-
type RequestsRegistryRecord = Required<RequestRecord<RequestQuery, OfferOptions>>;
19-
20-
interface FormValues {
21-
topic: string;
22-
message: string;
23-
}
24-
25-
interface RequestFormProps {
26-
connected: boolean;
27-
onSubmit(query: FormValues): void;
28-
}
29-
30-
interface RequestsProps {
31-
requests: RequestsRegistryRecord[];
32-
subscribed?: (id: string) => boolean;
33-
onClear(): void;
34-
onCancel(id: string): void;
35-
}
36-
37-
/**
38-
* Accepts user input
39-
*/
40-
export const RequestForm = ({ connected, onSubmit }: RequestFormProps) => {
41-
const [topic, setTopic] = useState<string>(defaultTopic);
42-
const [message, setMessage] = useState<string>('');
43-
44-
if (!connected) {
45-
return null;
46-
}
47-
48-
return (
49-
<div style={{ marginTop: 20 }}>
50-
<form
51-
onSubmit={(e) => {
52-
e.preventDefault();
53-
if (message === '') {
54-
return;
55-
}
56-
onSubmit({
57-
topic,
58-
message,
59-
});
60-
}}
61-
>
62-
<div style={{ marginBottom: 20 }}>
63-
<div>
64-
<strong>Topic:</strong>
65-
</div>
66-
<div style={{ marginBottom: 5 }}>
67-
<input value={topic} onChange={(e) => setTopic(e.target.value)} />
68-
</div>
69-
<div>
70-
<strong>Request:</strong>
71-
</div>
72-
<div>
73-
<input value={message} onChange={(e) => setMessage(e.target.value)} />
74-
</div>
75-
<div style={{ display: 'flex', alignItems: 'center', marginTop: 10 }}>
76-
<div>
77-
<button type="submit">Send</button>
78-
</div>
79-
</div>
80-
</div>
81-
</form>
82-
</div>
83-
);
84-
};
85-
8620
/**
87-
* Published requests table
21+
* Main application component
8822
*/
89-
export const Requests = ({ requests, subscribed, onClear, onCancel }: RequestsProps) => {
90-
if (requests.length === 0) {
91-
return null;
92-
}
93-
94-
return (
95-
<div style={{ marginTop: 20 }}>
96-
<table border={1} cellPadding={5}>
97-
<thead>
98-
<tr>
99-
<td>Topic</td>
100-
<td>Id</td>
101-
<td>Query</td>
102-
<td>Subscribed</td>
103-
<td>Expired</td>
104-
<td>Offers</td>
105-
<td>Cancel</td>
106-
</tr>
107-
</thead>
108-
<tbody>
109-
{requests.map((r, index) => (
110-
<tr key={index}>
111-
<td>{r.data.topic}</td>
112-
<td>{r.data.id}</td>
113-
<td>{JSON.stringify(r.data.query)}</td>
114-
<td>{subscribed && subscribed(r.data.id) ? '✅' : 'no'}</td>
115-
<td>{utils.isExpired(r.data.expire) || r.cancelled ? '✅' : 'no'}</td>
116-
<td>{r.offers.length}</td>
117-
<td>
118-
{!r.cancelled && !utils.isExpired(r.data.expire) ? (
119-
<button
120-
onClick={() => {
121-
onCancel(r.data.id);
122-
}}
123-
>
124-
Cancel
125-
</button>
126-
) : (
127-
'cancelled'
128-
)}
129-
</td>
130-
</tr>
131-
))}
132-
</tbody>
133-
</table>
134-
<div style={{ marginTop: 10 }}>
135-
<button
136-
onClick={(e) => {
137-
e.preventDefault();
138-
onClear();
139-
}}
140-
>
141-
Clear
142-
</button>
143-
</div>
144-
</div>
145-
);
146-
};
147-
14823
export const App = () => {
14924
const client = useRef<Client<RequestQuery, OfferOptions> | undefined>();
25+
const { publicClient } = useWallet();
15026
const [connected, setConnected] = useState<boolean>(false);
27+
const [selectedTab, setSelectedTab] = useState<number>(0);
15128
const [requests, setRequests] = useState<RequestsRegistryRecord[]>([]);
29+
const [deals, setDeals] = useState<DealsRegistryRecord[]>([]);
30+
const [offers, setOffers] = useState<OfferData<RequestQuery, OfferOptions>[] | undefined>();
31+
const [offer, setOffer] = useState<OfferData<RequestQuery, OfferOptions> | undefined>();
15232
const [error, setError] = useState<string | undefined>();
15333

15434
/** This hook starts the client that will be available via `client.current` */
@@ -158,21 +38,35 @@ export const App = () => {
15838
setError(undefined);
15939

16040
const options: ClientOptions = {
161-
contractConfig,
41+
chain: chainConfig,
16242
serverAddress,
16343
storageInitializer: storage.localStorage.createInitializer({
164-
session: true,
44+
session: false, // session or local storage
16545
}),
166-
requestRegistryPrefix: 'requestsRegistry',
46+
dbKeysPrefix: 'wt_',
47+
publicClient,
48+
};
49+
50+
const updateRequests = () => {
51+
if (client.current) {
52+
setRequests(client.current.requests.getAll());
53+
}
54+
};
55+
56+
const updateDeals = () => {
57+
if (client.current) {
58+
client.current.deals.getAll().then((newDeals) => {
59+
setDeals(newDeals);
60+
}).catch(console.error);
61+
}
16762
};
16863

16964
client.current = createClient<RequestQuery, OfferOptions>(options);
17065

17166
client.current.addEventListener('start', () => {
17267
console.log('🚀 Client started at:', new Date().toISOString());
173-
if (client.current) {
174-
setRequests(client.current.requests.getAll());
175-
}
68+
updateRequests();
69+
updateDeals();
17670
});
17771

17872
client.current.addEventListener('stop', () => {
@@ -189,14 +83,7 @@ export const App = () => {
18983
console.log('🔌 Client disconnected from server at:', new Date().toISOString());
19084
});
19185

192-
const updateRequests = () => {
193-
if (!client.current) {
194-
return;
195-
}
196-
setRequests(client.current.requests.getAll());
197-
};
198-
199-
/** Listening for requests events and update the table */
86+
/** Listening for requests events and update tables */
20087
client.current.addEventListener('request:create', updateRequests);
20188
client.current.addEventListener('request:subscribe', updateRequests);
20289
client.current.addEventListener('request:publish', updateRequests);
@@ -206,6 +93,7 @@ export const App = () => {
20693
client.current.addEventListener('request:delete', updateRequests);
20794
client.current.addEventListener('request:offer', updateRequests);
20895
client.current.addEventListener('request:clear', updateRequests);
96+
client.current.addEventListener('deal:changed', updateDeals);
20997

21098
await client.current.start();
21199
} catch (error) {
@@ -215,7 +103,7 @@ export const App = () => {
215103
};
216104

217105
startClient();
218-
}, []);
106+
}, [publicClient]);
219107

220108
/** Publishing of request */
221109
const sendRequest = async ({ topic, message }: FormValues) => {
@@ -229,7 +117,7 @@ export const App = () => {
229117
const request = await client.current.requests.create({
230118
topic,
231119
expire: defaultExpire,
232-
nonce: 1,
120+
nonce: BigInt(1),
233121
query: {
234122
greeting: message,
235123
},
@@ -244,26 +132,59 @@ export const App = () => {
244132

245133
return (
246134
<>
135+
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
136+
<div style={{ flex: 1 }}>
137+
<h1>Client</h1>
138+
</div>
139+
<AccountWidget />
140+
</div>
247141
{client.current && <div>✅ Client started</div>}
248142
{connected && <div>✅ Connected to the coordination server</div>}
249-
<RequestForm connected={connected} onSubmit={sendRequest} />
250-
<Requests
251-
requests={requests}
252-
subscribed={(id) => {
253-
console.log('###', id);
254-
return client.current?.requests.subscribed(id) || false;
255-
}}
256-
onClear={() => {
257-
if (client.current) {
258-
client.current?.requests.clear();
259-
}
260-
}}
261-
onCancel={(id) => {
262-
if (client.current) {
263-
client.current.requests.cancel(id);
264-
}
265-
}}
143+
<RequestForm connected={connected} onSubmit={sendRequest} defaultTopic={defaultTopic} />
144+
<Tabs
145+
tabs={[
146+
{
147+
id: 0,
148+
title: 'Requests',
149+
active: true,
150+
},
151+
{
152+
id: 1,
153+
title: 'Deals',
154+
},
155+
]}
156+
onChange={setSelectedTab}
266157
/>
158+
<TabPanel id={0} activeTab={selectedTab}>
159+
<Requests
160+
requests={requests}
161+
subscribed={(id) => client.current?.requests.subscribed(id) || false}
162+
onClear={() => {
163+
if (client.current) {
164+
client.current?.requests.clear();
165+
}
166+
}}
167+
onCancel={(id) => {
168+
if (client.current) {
169+
client.current.requests.cancel(id);
170+
}
171+
}}
172+
onOffers={setOffers}
173+
/>
174+
<Offers
175+
offers={offers}
176+
onAccept={setOffer}
177+
onClose={() => {
178+
setOffer(undefined);
179+
setOffers(undefined);
180+
}}
181+
/>
182+
<MakeDeal offer={offer} client={client.current} />
183+
</TabPanel>
184+
<TabPanel id={1} activeTab={selectedTab}>
185+
<Deals deals={deals} client={client.current} />
186+
</TabPanel>
187+
267188
{error && <div style={{ marginTop: 20 }}>🚨 {error}</div>}
268189
</>
269190
);

0 commit comments

Comments
 (0)