Skip to content

Commit f646461

Browse files
authored
Add metadata cross-chain sync (#165)
* add content * add metadata
1 parent 3ce9f0f commit f646461

File tree

6 files changed

+551
-36
lines changed

6 files changed

+551
-36
lines changed

README.md

Lines changed: 57 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -48,42 +48,6 @@ pnpm deploy:sepolia
4848

4949
Then you can add your DAO in [Tally](https://www.tally.xyz/) and/or spin up your own interface using [Gov UI](https://github.com/w3hc/gov-ui).
5050

51-
## Variants
52-
53-
### Crosschain
54-
55-
Make sure the main account has sufficient balance on OP Sepolia and Arbitrum Sepolia:
56-
57-
```
58-
pnpm bal
59-
```
60-
61-
Deploy:
62-
63-
```
64-
pnpm deploy:all
65-
```
66-
67-
Add a member (mint):
68-
69-
```
70-
./scripts/mint.sh
71-
```
72-
73-
Ban a member (burn):
74-
75-
```
76-
./scripts/burn.sh
77-
```
78-
79-
It will:
80-
81-
- Deploy to OP Sepolia
82-
- Deploy to Arbitrum Sepolia
83-
- Submit a proposal and add a member
84-
- Generate a membership proof on OP Sepolia
85-
- Claim that proof on Arbitrum Sepolia
86-
8751
## Security
8852

8953
Here are the differences between the Governor/ERC-721 implementations suggested by Open Zeppelin and ours:
@@ -126,6 +90,63 @@ The following functions are `onlyOwner`, and since the NFT contract ownership is
12690
| Base Sepolia | https://sepolia.basescan.org | https://api-sepolia.basescan.org/api | BASE_ETHERSCAN_API_KEY |
12791
| Arbitrum Sepolia | https://sepolia.arbiscan.io | https://api-sepolia.arbiscan.io/api | ARBITRUM_ETHERSCAN_API_KEY |
12892

93+
## Variants
94+
95+
### Crosschain
96+
97+
Make sure the main account has sufficient balance on OP Sepolia and Arbitrum Sepolia:
98+
99+
```
100+
pnpm bal
101+
```
102+
103+
Deploy:
104+
105+
```
106+
pnpm deploy:all
107+
```
108+
109+
It will:
110+
111+
- Deploy to OP Sepolia
112+
- Deploy to Arbitrum Sepolia
113+
114+
Add a member (mint):
115+
116+
```
117+
./scripts/mint.sh
118+
```
119+
120+
It will:
121+
122+
- Submit a proposal and add a member on OP Sepolia
123+
- Generate a membership proof on OP Sepolia
124+
- Claim that proof on Arbitrum Sepolia
125+
126+
Ban a member (burn):
127+
128+
```
129+
./scripts/burn.sh
130+
```
131+
132+
It will:
133+
134+
- Submit a proposal and ban a member on OP Sepolia
135+
- Generate a burn proof on OP Sepolia
136+
- Claim that proof on Arbitrum Sepolia
137+
138+
Edit membership NFT metadata:
139+
140+
```
141+
./scripts/metadata.sh
142+
```
143+
144+
It will:
145+
146+
- Submit a proposal edit the NFT metadata of tokenId 1 on OP Sepolia
147+
- Generate a metadata proof on OP Sepolia
148+
- Claim that proof on Arbitrum Sepolia
149+
129150
## Core Dependencies
130151

131152
- Node [v20.9.0](https://nodejs.org/uk/blog/release/v20.9.0/)

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"scripts": {
77
"compile": "hardhat compile",
88
"test": "hardhat test",
9+
"test:all": "./scripts/deploy.sh && ./scripts/mint.sh && ./scripts/burn.sh && ./scripts/metadata.sh",
910
"test:crosschain": "hardhat test test/Gov-crosschain.ts",
1011
"deploy:optimism": "hardhat deploy --network optimism --reset",
1112
"deploy:base": "hardhat deploy --network base --reset",

scripts/claim-metadata.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import hre, { ethers } from "hardhat"
2+
import { NFT__factory } from "../typechain-types/factories/contracts/variants/crosschain/NFT__factory"
3+
import * as fs from "fs"
4+
import * as path from "path"
5+
import color from "cli-color"
6+
var msg = color.xterm(39).bgXterm(128)
7+
8+
function getDeployedAddress(network: string, contractName: string): string {
9+
try {
10+
const deploymentPath = path.join(
11+
__dirname,
12+
"..",
13+
"deployments",
14+
network,
15+
`${contractName}.json`
16+
)
17+
const deployment = JSON.parse(fs.readFileSync(deploymentPath, "utf8"))
18+
return deployment.address
19+
} catch (error) {
20+
throw new Error(
21+
`Failed to read deployment for ${contractName} on ${network}: ${error}`
22+
)
23+
}
24+
}
25+
26+
function getProofFromData(): string {
27+
try {
28+
const dataPath = path.join(__dirname, "..", "data.json")
29+
const data = JSON.parse(fs.readFileSync(dataPath, "utf8"))
30+
return data.proof
31+
} catch (error) {
32+
throw new Error(`Failed to read proof from data.json: ${error}`)
33+
}
34+
}
35+
36+
async function main() {
37+
const SIGNER_PRIVATE_KEY = process.env.SIGNER_PRIVATE_KEY
38+
if (!SIGNER_PRIVATE_KEY) {
39+
throw new Error("Please set SIGNER_PRIVATE_KEY in your .env file")
40+
}
41+
42+
const networkName = hre.network.name
43+
const NFT_ADDRESS = getDeployedAddress(networkName, "CrosschainNFT")
44+
console.log("Using NFT contract address:", NFT_ADDRESS)
45+
46+
const provider = new ethers.JsonRpcProvider(
47+
networkName === "op-sepolia"
48+
? process.env.OP_SEPOLIA_RPC_ENDPOINT_URL
49+
: process.env.ARBITRUM_SEPOLIA_RPC_ENDPOINT_URL
50+
)
51+
const signerZero = new ethers.Wallet(SIGNER_PRIVATE_KEY, provider)
52+
53+
console.log("Using address:", signerZero.address)
54+
55+
const nft = NFT__factory.connect(NFT_ADDRESS, signerZero)
56+
57+
const proof = getProofFromData()
58+
console.log("\nUsing metadata proof:", proof)
59+
60+
try {
61+
console.log("Simulating metadata update claim...")
62+
await nft.claimMetadataUpdate.staticCall(proof)
63+
console.log("✅ Simulation successful")
64+
65+
console.log("Submitting metadata update claim...")
66+
const tx = await nft.claimMetadataUpdate(proof, {
67+
gasLimit: 500000
68+
})
69+
70+
console.log("Transaction submitted:", msg(tx.hash))
71+
console.log("Waiting for confirmation...")
72+
73+
const receipt = await tx.wait()
74+
console.log("Metadata update claimed successfully!")
75+
76+
const updateEvent = receipt?.logs.find(log => {
77+
try {
78+
return nft.interface.parseLog(log)?.name === "MetadataUpdated"
79+
} catch {
80+
return false
81+
}
82+
})
83+
84+
if (updateEvent) {
85+
const parsedEvent = nft.interface.parseLog(updateEvent)
86+
const tokenId = parsedEvent?.args?.tokenId
87+
const newUri = parsedEvent?.args?.newUri
88+
console.log("Updated token ID:", tokenId)
89+
console.log("New metadata URI:", newUri)
90+
}
91+
} catch (error: any) {
92+
console.error("\nError details:", error)
93+
throw error
94+
}
95+
}
96+
97+
main().catch(error => {
98+
console.error(error)
99+
process.exitCode = 1
100+
})

scripts/metadata.sh

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/bin/bash
2+
3+
# Color codes
4+
GREEN='\033[0;32m'
5+
RED='\033[0;31m'
6+
BLUE='\033[0;34m'
7+
NC='\033[0m' # No Color
8+
9+
echo -e "${BLUE}Starting cross-chain metadata update process...${NC}\n"
10+
11+
# Create proposal on OP Sepolia
12+
echo -e "\n${BLUE}Creating metadata update proposal on OP Sepolia...${NC}"
13+
if npx hardhat run scripts/propose-metadata.ts --network op-sepolia; then
14+
echo -e "${GREEN}✓ Metadata update proposal creation successful${NC}"
15+
else
16+
echo -e "${RED}✗ Metadata update proposal creation failed${NC}"
17+
exit 1
18+
fi
19+
20+
# Generate metadata proof from OP Sepolia
21+
echo -e "\n${BLUE}Generating metadata proof from OP Sepolia...${NC}"
22+
if npx hardhat run scripts/verify-metadata-proof.ts --network op-sepolia; then
23+
echo -e "${GREEN}✓ Metadata proof generation successful${NC}"
24+
else
25+
echo -e "${RED}✗ Metadata proof generation failed${NC}"
26+
exit 1
27+
fi
28+
29+
# Claim metadata update on Arbitrum Sepolia
30+
echo -e "\n${BLUE}Claiming metadata update on Arbitrum Sepolia...${NC}"
31+
if npx hardhat run scripts/claim-metadata.ts --network arbitrum-sepolia; then
32+
echo -e "${GREEN}✓ Metadata update claim successful${NC}"
33+
echo -e "\n${GREEN}✓ All metadata update steps completed successfully!${NC}"
34+
exit 0
35+
else
36+
echo -e "${RED}✗ Metadata update claim failed${NC}"
37+
exit 1
38+
fi

0 commit comments

Comments
 (0)