Skip to content

Commit

Permalink
feat: use wallet connection component
Browse files Browse the repository at this point in the history
  • Loading branch information
samsiegart committed Feb 14, 2024
1 parent 91e461d commit 4e0ceff
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 209 deletions.
6 changes: 5 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
"@agoric/store": "^0.9.2",
"@agoric/ui-components": "^0.9.0",
"@agoric/web-components": "^0.15.0",
"buffer": "^6.0.3",
"chain-registry": "^1.28.0",
"cosmos-kit": "^2.9.0",
"@testing-library/react": "^14.1.2",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
Expand Down Expand Up @@ -50,6 +53,7 @@
"prettier": {
"trailingComma": "all",
"arrowParens": "avoid",
"singleQuote": true
"singleQuote": true,
"tabWidth": 2
}
}
6 changes: 3 additions & 3 deletions ui/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,11 @@ input {
}

.error {
background-color: #E11D48;
background-color: #e11d48;
color: #fff;
}

/* increment/decrement arrows always visible */
input[type=number]::-webkit-inner-spin-button {
opacity: 1
input[type='number']::-webkit-inner-spin-button {
opacity: 1;
}
176 changes: 29 additions & 147 deletions ui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,156 +1,38 @@
import { useEffect } from 'react';

import './App.css';
import {
makeAgoricChainStorageWatcher,
AgoricChainStoragePathKind as Kind,
} from '@agoric/rpc';
import { create } from 'zustand';
import {
makeAgoricWalletConnection,
suggestChain,
} from '@agoric/web-components';
import { subscribeLatest } from '@agoric/notifier';
import { makeCopyBag } from '@agoric/store';
import { Logos } from './components/Logos';
import { Inventory } from './components/Inventory';
import { Trade } from './components/Trade';
import { ContractProvider } from './providers/Contract';
import { AgoricProvider } from '@agoric/react-components';
import { wallets } from 'cosmos-kit';
import '@agoric/react-components/dist/style.css';

const { entries, fromEntries } = Object;

type Wallet = Awaited<ReturnType<typeof makeAgoricWalletConnection>>;

const ENDPOINTS = {
RPC: 'http://localhost:26657',
API: 'http://localhost:1317',
};

const watcher = makeAgoricChainStorageWatcher(ENDPOINTS.API, 'agoriclocal');

interface AppState {
wallet?: Wallet;
offerUpInstance?: unknown;
brands?: Record<string, unknown>;
purses?: Array<Purse>;
}

const useAppStore = create<AppState>(() => ({}));

const setup = async () => {
watcher.watchLatest<Array<[string, unknown]>>(
[Kind.Data, 'published.agoricNames.instance'],
instances => {
console.log('got instances', instances);
useAppStore.setState({
offerUpInstance: instances.find(([name]) => name === 'offerUp')!.at(1),
});
},
);

watcher.watchLatest<Array<[string, unknown]>>(
[Kind.Data, 'published.agoricNames.brand'],
brands => {
console.log('Got brands', brands);
useAppStore.setState({
brands: fromEntries(brands),
});
},
);
};

const connectWallet = async () => {
await suggestChain('https://local.agoric.net/network-config');
const wallet = await makeAgoricWalletConnection(watcher, ENDPOINTS.RPC);
useAppStore.setState({ wallet });
const { pursesNotifier } = wallet;
for await (const purses of subscribeLatest(pursesNotifier)) {
console.log('got purses', purses);
useAppStore.setState({ purses });
}
};

const makeOffer = (giveValue: bigint, wantChoices: Record<string, bigint>) => {
const { wallet, offerUpInstance, brands } = useAppStore.getState();
if (!offerUpInstance) throw Error('no contract instance');
if (!(brands && brands.IST && brands.Item))
throw Error('brands not available');

const value = makeCopyBag(entries(wantChoices));
const want = { Items: { brand: brands.Item, value } };
const give = { Price: { brand: brands.IST, value: giveValue } };

wallet?.makeOffer(
{
source: 'contract',
instance: offerUpInstance,
publicInvitationMaker: 'makeTradeInvitation',
},
{ give, want },
undefined,
(update: { status: string; data?: unknown }) => {
if (update.status === 'error') {
alert(`Offer error: ${update.data}`);
}
if (update.status === 'accepted') {
alert('Offer accepted');
}
if (update.status === 'refunded') {
alert('Offer rejected');
}
},
);
};

function App() {
useEffect(() => {
setup();
}, []);

const { wallet, purses } = useAppStore(({ wallet, purses }) => ({
wallet,
purses,
}));
const istPurse = purses?.find(p => p.brandPetname === 'IST');
const itemsPurse = purses?.find(p => p.brandPetname === 'Item');

const tryConnectWallet = () => {
connectWallet().catch(err => {
switch (err.message) {
case 'KEPLR_CONNECTION_ERROR_NO_SMART_WALLET':
alert(
'no smart wallet at that address; try: yarn docker:make print-key',
);
break;
default:
alert(err.message);
}
});
};

const App = () => {
return (
<>
<Logos />
<h1>Items Listed on Offer Up</h1>

<div className="card">
<Trade
makeOffer={makeOffer}
istPurse={istPurse as Purse}
walletConnected={!!wallet}
/>
<hr />
{wallet && istPurse ? (
<Inventory
address={wallet.address}
istPurse={istPurse}
itemsPurse={itemsPurse as Purse}
/>
) : (
<button onClick={tryConnectWallet}>Connect Wallet</button>
)}
</div>
</>
<AgoricProvider
wallets={wallets.extension}
defaultNetworkConfig={{
testChain: {
chainId: 'agoriclocal',
chainName: 'agoric-local',
},
apis: {
rest: ['http://localhost:1317'],
rpc: ['http://localhost:26657'],
},
}}
>
<ContractProvider>
<Logos />
<h1>Items Listed on Offer Up</h1>
<div className="card">
<Trade />
<hr />
<Inventory />
</div>
</ContractProvider>
</AgoricProvider>
);
}
};

export default App;
84 changes: 44 additions & 40 deletions ui/src/components/Inventory.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,53 @@
import { ConnectWalletButton, useAgoric } from '@agoric/react-components';
import { stringifyAmountValue } from '@agoric/ui-components';
import { usePurse } from '../hooks/usePurse';
import type { CopyBag } from '../types';

type InventoryProps = {
address: string;
istPurse: Purse;
itemsPurse: Purse;
};
const Inventory = () => {
const istPurse = usePurse('IST');
const itemsPurse = usePurse('Item');
const { walletConnection } = useAgoric();

const Inventory = ({ address, istPurse, itemsPurse }: InventoryProps) => (
<div className="card">
<h3>My Wallet</h3>
<div>
return (
<div className="card">
<h3>My Wallet</h3>
<div>
<small>
<code>{address}</code>
</small>
</div>

<div style={{ textAlign: 'left' }}>
<div>
<b>IST: </b>
{stringifyAmountValue(
istPurse.currentAmount,
istPurse.displayInfo.assetKind,
istPurse.displayInfo.decimalPlaces,
)}
</div>
<div>
<b>Items:</b>
{itemsPurse ? (
<ul style={{ marginTop: 0, textAlign: 'left' }}>
{(itemsPurse.currentAmount.value as CopyBag).payload.map(
([name, number]) => (
<li key={name}>
{String(number)} {name}
</li>
),
<ConnectWalletButton />
{walletConnection && (
<div style={{ textAlign: 'left' }}>
<div>
<b>IST: </b>
{istPurse ? (
stringifyAmountValue(
istPurse.currentAmount,
istPurse.displayInfo.assetKind,
istPurse.displayInfo.decimalPlaces,
)
) : (
<i>Fetching balance...</i>
)}
</ul>
) : (
'None'
)}
</div>
</div>
<div>
<b>Items: </b>
{itemsPurse ? (
<ul style={{ marginTop: 0, textAlign: 'left' }}>
{(itemsPurse.currentAmount.value as CopyBag).payload.map(
([name, number]) => (
<li key={name}>
{String(number)} {name}
</li>
),
)}
</ul>
) : (
'None'
)}
</div>
</div>
)}
</div>
</div>
</div>
);
);
};

export { Inventory };
Loading

0 comments on commit 4e0ceff

Please sign in to comment.