Skip to content

Commit 2de295d

Browse files
Refactor Marketplace contract to implement reentrancy protection, update listing count logic, and enhance action handling. Improve README for clarity on action selectors and update metadata handling in Next.js app for mini app integration.
1 parent e977928 commit 2de295d

File tree

5 files changed

+34
-15
lines changed

5 files changed

+34
-15
lines changed

README.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Farcaster-ready marketplace mini app built on Scaffold-ETH 2. It lets creators p
2121
- **TestERC20.sol**: Simple mintable tokens for local development (2 and 6 decimals). Only deployed on `localhost`/`hardhat`.
2222

2323
### Marketplace data model
24-
- `listingCount`: sequential marketplace-level IDs starting at 1 (also used as the listing-type ID).
24+
- `listingCount`: sequential marketplace-level IDs starting at 0 (also used as the listing-type ID).
2525
- `listings[id]`: `ListingPointer { creator, listingType, contenthash, active }` where:
2626
- `listingType` is the address of the listing-type contract (e.g., `SimpleListings`).
2727
- `contenthash` is an arbitrary string (e.g., IPFS CID) set at creation time.
@@ -34,7 +34,7 @@ Farcaster-ready marketplace mini app built on Scaffold-ETH 2. It lets creators p
3434

3535
2) **Actions**: `Marketplace.callAction(id, action, data)`
3636
- Delegates to `IListingType.handleAction(listingId=id, creator, active, caller=msg.sender, action, data)`.
37-
- `action` is the 4-byte function selector (left-padded to 32 bytes) of a function exposed by the listing-type for dynamic dispatch.
37+
- `action` is a 32-byte word whose MOST SIGNIFICANT 4 bytes are the function selector. In other words, pass the 4-byte selector RIGHT-PADDED to 32 bytes.
3838
- Emits `ListingAction(id, caller, action)`.
3939

4040
3) **Activation toggle**
@@ -59,11 +59,14 @@ Farcaster-ready marketplace mini app built on Scaffold-ETH 2. It lets creators p
5959
- SimpleListings: `SimpleListingCreated`, `SimpleListingSold`, `SimpleListingClosed`.
6060

6161
### Calling actions from the frontend
62-
- With SE-2 hooks, write to `Marketplace.callAction` and pass a selector for the action you want. For example, to buy a listing with ETH using `SimpleListings`:
62+
- With SE-2 hooks, write to `Marketplace.callAction` and pass the selector in the MOST SIGNIFICANT 4 bytes of a 32-byte word (right-padded). For example, to buy a listing with ETH using `SimpleListings`:
6363

6464
```ts
6565
// selector for: buy(uint256,address,bool,address,bytes)
66-
const action = '0x9570e8f9' as const; // bytes4; pass left-padded to 32 bytes
66+
const selector = '0x9570e8f9' as const; // bytes4
67+
// Right-pad to 32 bytes so the selector sits in the most significant 4 bytes
68+
// Practical approach: pack into a 32-byte hex by appending 28 bytes of zeros after the 4-byte selector
69+
const action = (selector + '0'.repeat(64 - 8)) as `0x${string}`; // 8 hex chars for 4 bytes, total 32 bytes
6770
await writeMarketplaceAsync({
6871
functionName: 'callAction',
6972
args: [listingId, action, '0x'],

packages/hardhat/contracts/Marketplace.sol

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
pragma solidity >=0.8.0 <0.9.0;
33

44
import { IListingType } from "./IListingType.sol";
5+
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
56

6-
contract Marketplace {
7+
contract Marketplace is ReentrancyGuard {
78
error ListingCreationFailed();
89
error OnlyListingTypeCanModify();
910
error ListingNotFound();
@@ -26,8 +27,8 @@ contract Marketplace {
2627
address listingType,
2728
string calldata contenthash,
2829
bytes calldata data
29-
) external returns (uint256 id) {
30-
id = ++listingCount;
30+
) external nonReentrant returns (uint256 id) {
31+
id = listingCount++;
3132
bool success = IListingType(listingType).create(msg.sender, id, data);
3233
if (!success) revert ListingCreationFailed();
3334
listings[id] = ListingPointer(msg.sender, listingType, contenthash, true);
@@ -38,14 +39,14 @@ contract Marketplace {
3839
uint256 id,
3940
bytes32 action,
4041
bytes calldata data
41-
) external payable {
42-
if (id > listingCount) revert ListingNotFound();
42+
) external payable nonReentrant {
43+
if (id >= listingCount) revert ListingNotFound();
4344
ListingPointer memory ptr = listings[id];
4445
IListingType(ptr.listingType).handleAction{value: msg.value}(id, ptr.creator, ptr.active, msg.sender, action, data);
4546
emit ListingAction(id, msg.sender, action);
4647
}
4748

48-
function setActive(uint256 listingId, bool active) external {
49+
function setActive(uint256 listingId, bool active) external nonReentrant {
4950
ListingPointer storage record = listings[listingId];
5051
if (msg.sender != record.listingType) revert OnlyListingTypeCanModify();
5152
record.active = active;

packages/hardhat/contracts/SimpleListings.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ contract SimpleListings is IListingType {
8282
bytes calldata /*data*/
8383
) external payable onlySelf isActive(active) {
8484
SimpleListing storage l = listings[listingId];
85+
Marketplace(marketplace).setActive(listingId, false);
8586
if (l.paymentToken == address(0)) {
8687
if (msg.value != l.price) revert IncorrectEth();
8788
(bool sent, ) = creator.call{ value: msg.value }("");
@@ -91,7 +92,6 @@ contract SimpleListings is IListingType {
9192
bool ok = IERC20(l.paymentToken).transferFrom(buyer, creator, l.price);
9293
if (!ok) revert Erc20TransferFailed();
9394
}
94-
Marketplace(marketplace).setActive(listingId, false);
9595
emit SimpleListingSold(listingId, buyer, l.price, l.paymentToken);
9696
}
9797

packages/nextjs/app/.well-known/farcaster.json/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export async function GET() {
1818
payload: process.env.FARCASTER_PAYLOAD,
1919
signature: process.env.FARCASTER_SIGNATURE,
2020
},
21-
frame: withValidProperties({
21+
miniapp: withValidProperties({
2222
version: "1",
2323
name: "Ethereum Bazaar",
2424
subtitle: "A peer to peer marketplace",

packages/nextjs/utils/scaffold-eth/getMetadata.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import type { Metadata } from "next";
22

3-
const baseUrl = process.env.VERCEL_PROJECT_PRODUCTION_URL
4-
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
5-
: `http://localhost:${process.env.PORT || 3000}`;
3+
const baseUrl = process.env.NEXT_PUBLIC_URL ?? `http://localhost:${process.env.NEXT_PUBLIC_PORT || 3000}`;
64
const titleTemplate = "%s | Ethereum Bazaar";
75

86
export const getMetadata = ({
@@ -16,13 +14,30 @@ export const getMetadata = ({
1614
}): Metadata => {
1715
const imageUrl = `${baseUrl}${imageRelativePath}`;
1816

17+
const miniAppContent = JSON.stringify({
18+
version: "1",
19+
imageUrl: process.env.NEXT_PUBLIC_IMAGE_URL ?? imageUrl,
20+
button: {
21+
title: `${process.env.NEXT_PUBLIC_APP_NAME ?? title}`,
22+
action: {
23+
url: `${baseUrl}/`,
24+
type: "launch_miniapp",
25+
},
26+
},
27+
});
28+
1929
return {
2030
metadataBase: new URL(baseUrl),
2131
title: {
2232
default: title,
2333
template: titleTemplate,
2434
},
2535
description: description,
36+
manifest: "/manifest.json",
37+
other: {
38+
"fc:miniapp": miniAppContent,
39+
"fc:frame": miniAppContent,
40+
},
2641
openGraph: {
2742
title: {
2843
default: title,

0 commit comments

Comments
 (0)