-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from peterferguson/feat/coupon-nft
Feat/coupon nft
- Loading branch information
Showing
8 changed files
with
304 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; | ||
import { getNftUri, getBalance } from "@/lib/example-nft"; | ||
import React from "react"; | ||
|
||
export function NftImageCard() { | ||
const tokenId = 1n; | ||
const [uri, setUri] = React.useState<string | null>(null); | ||
const [hasClaimed, setHasClaimed] = React.useState(false); | ||
|
||
React.useEffect(() => { | ||
getNftUri(0).then((r) => setUri(r)); | ||
}, []); | ||
|
||
React.useEffect(() => { | ||
getBalance('0x3896a2938d345d3A351cE152AF1b8Cb17bb006be', 0n).then((r) => { | ||
console.log({ r }); | ||
setHasClaimed(r > 0) | ||
}); | ||
}, []); | ||
|
||
return ( | ||
<Card> | ||
<CardHeader> | ||
<CardTitle>10 Eth Coupon</CardTitle> | ||
<CardDescription>Claim your free 10 eth coupon which can</CardDescription> | ||
</CardHeader> | ||
<CardContent> | ||
<img src={uri} alt="NFT" /> | ||
</CardContent> | ||
<CardFooter> | ||
<p>You have {hasClaimed ? '' : 'not'} claimed</p> | ||
</CardFooter> | ||
</Card> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
import * as React from "react" | ||
|
||
import { cn } from "@/lib/utils" | ||
|
||
const Card = React.forwardRef< | ||
HTMLDivElement, | ||
React.HTMLAttributes<HTMLDivElement> | ||
>(({ className, ...props }, ref) => ( | ||
<div | ||
ref={ref} | ||
className={cn( | ||
"rounded-lg border bg-card text-card-foreground shadow-sm", | ||
className | ||
)} | ||
{...props} | ||
/> | ||
)) | ||
Card.displayName = "Card" | ||
|
||
const CardHeader = React.forwardRef< | ||
HTMLDivElement, | ||
React.HTMLAttributes<HTMLDivElement> | ||
>(({ className, ...props }, ref) => ( | ||
<div | ||
ref={ref} | ||
className={cn("flex flex-col space-y-1.5 p-6", className)} | ||
{...props} | ||
/> | ||
)) | ||
CardHeader.displayName = "CardHeader" | ||
|
||
const CardTitle = React.forwardRef< | ||
HTMLParagraphElement, | ||
React.HTMLAttributes<HTMLHeadingElement> | ||
>(({ className, ...props }, ref) => ( | ||
<h3 | ||
ref={ref} | ||
className={cn( | ||
"text-2xl font-semibold leading-none tracking-tight", | ||
className | ||
)} | ||
{...props} | ||
/> | ||
)) | ||
CardTitle.displayName = "CardTitle" | ||
|
||
const CardDescription = React.forwardRef< | ||
HTMLParagraphElement, | ||
React.HTMLAttributes<HTMLParagraphElement> | ||
>(({ className, ...props }, ref) => ( | ||
<p | ||
ref={ref} | ||
className={cn("text-sm text-muted-foreground", className)} | ||
{...props} | ||
/> | ||
)) | ||
CardDescription.displayName = "CardDescription" | ||
|
||
const CardContent = React.forwardRef< | ||
HTMLDivElement, | ||
React.HTMLAttributes<HTMLDivElement> | ||
>(({ className, ...props }, ref) => ( | ||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} /> | ||
)) | ||
CardContent.displayName = "CardContent" | ||
|
||
const CardFooter = React.forwardRef< | ||
HTMLDivElement, | ||
React.HTMLAttributes<HTMLDivElement> | ||
>(({ className, ...props }, ref) => ( | ||
<div | ||
ref={ref} | ||
className={cn("flex items-center p-6 pt-0", className)} | ||
{...props} | ||
/> | ||
)) | ||
CardFooter.displayName = "CardFooter" | ||
|
||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import { encodeFunctionData, type Address } from "viem" | ||
import { walletClient, publicClient } from './permissionless' | ||
import { formatExecutionCalldata } from "./safe-account" | ||
|
||
/** | ||
* @dev Example NFT contract functions | ||
*/ | ||
|
||
// addresses & abis -------------------------------------------- | ||
const NFT_ADDRESS = '0x4A56fD1D63D99978FDb3aC5C152ea122514b6792' | ||
|
||
const EXAMPLE_NFT_ABI = [ | ||
{ "inputs": [], "name": "exchangeForNft", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, | ||
{ "inputs": [], "name": "mintCoupon", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, | ||
{ "inputs": [{ "internalType": "uint256", "name": "id", "type": "uint256" }], "name": "uri", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" }, | ||
{ "inputs": [{ "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "uint256", "name": "id", "type": "uint256" }], "name": "balanceOf", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" } | ||
] | ||
|
||
// nft functions -------------------------------------------- | ||
export const getNftUri = async (tokenId: bigint) => await publicClient.readContract({ | ||
address: NFT_ADDRESS, | ||
abi: EXAMPLE_NFT_ABI, | ||
functionName: 'uri', | ||
args: [tokenId], | ||
}) | ||
|
||
export const getBalance = async (address: Address, tokenId: bigint) => { | ||
if (!address) return 0 | ||
const results = await publicClient.readContract( | ||
{ | ||
address: NFT_ADDRESS, | ||
abi: EXAMPLE_NFT_ABI, | ||
functionName: 'balanceOf', | ||
args: [address, tokenId], | ||
} | ||
) | ||
return results | ||
} | ||
|
||
// nft function formatting -------------------------------------------- | ||
/// @dev To be called from the Onit 4337 account, we encode our function call into an executeUserOp payload | ||
export const formatMintCouponToModuleExecutionCalldata = () => formatExecutionCalldata( | ||
NFT_ADDRESS, | ||
0n, | ||
encodeFunctionData({ | ||
abi: EXAMPLE_NFT_ABI, | ||
functionName: 'mintCoupon', | ||
args: [], | ||
}), | ||
0 | ||
) | ||
|
||
/// @dev To be called from the Onit 4337 account, we encode our function call into an executeUserOp payload | ||
export const formatExchangeForNftToModuleExecutionCalldata = () => formatExecutionCalldata( | ||
NFT_ADDRESS, | ||
0n, | ||
encodeFunctionData({ | ||
abi: EXAMPLE_NFT_ABI, | ||
functionName: 'exchangeForNft', | ||
args: [], | ||
}), | ||
0 | ||
) | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { encodeFunctionData, type Address, type Hex } from "viem" | ||
import { walletClient, publicClient } from './permissionless' | ||
|
||
/** | ||
* @dev Onit Safe 4337 Module functions | ||
*/ | ||
|
||
// abis -------------------------------------------- | ||
const ONIT_FACTORY_ADDRESS = '0x42ab880ea77fc7a09eb6ba0fe82fbc9901c114b6' | ||
|
||
const ONIT_FACTORY_ABI = [ | ||
{ "type": "function", "name": "createSafe4337", "inputs": [{ "name": "passkeyPublicKey", "type": "uint256[2]", "internalType": "uint256[2]" }, { "name": "nonce", "type": "uint256", "internalType": "uint256" }], "outputs": [{ "name": "onitAccountAddress", "type": "address", "internalType": "address" }], "stateMutability": "nonpayable" } | ||
] | ||
const ONIT_SAFE_4337_ABI = [ | ||
{ "type": "function", "name": "executeUserOp", "inputs": [{ "name": "to", "type": "address", "internalType": "address" }, { "name": "value", "type": "uint256", "internalType": "uint256" }, { "name": "data", "type": "bytes", "internalType": "bytes" }, { "name": "operation", "type": "uint8", "internalType": "uint8" }], "outputs": [], "stateMutability": "nonpayable" } | ||
] | ||
|
||
// safe factory calls -------------------------------------------- | ||
export const createSafe4337Account = async () => { | ||
const { request } = await publicClient.simulateContract({ | ||
account: walletClient.account, | ||
address: ONIT_FACTORY_ADDRESS, | ||
abi: ONIT_FACTORY_ABI, | ||
functionName: 'createSafe4337', | ||
args: [[1n, 2n], 3n], | ||
}) | ||
console.log({ request }) | ||
|
||
const deployResponse = await walletClient.writeContract(request) | ||
console.log({ deployResponse }) | ||
|
||
return deployResponse | ||
} | ||
|
||
// 4337 module calls -------------------------------------------- | ||
/// @dev Build payload which the entryPoint will call on the sender Onit 4337 account | ||
export const formatExecutionCalldata = (to: Address, value: bigint, data: Hex, operation: number) => encodeFunctionData({ | ||
abi: ONIT_SAFE_4337_ABI, | ||
functionName: 'executeUserOp', | ||
args: [to, value, data, operation], | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.13; | ||
|
||
import {Script, console2} from "forge-std/Script.sol"; | ||
|
||
import {ExampleNft} from "../../src/dapp-examples/ExampleNft.sol"; | ||
|
||
contract ExampleNftDeployer is Script { | ||
string public constant EXAMPLE_NFT_URI_1 = | ||
"https://res.cloudinary.com/merkle-manufactory/image/fetch/c_fill,f_jpg,w_168/https%3A%2F%2Fi.imgur.com%2F7Q0QBrm.jpg"; | ||
string public constant EXAMPLE_NFT_URI_2 = | ||
"https://res.cloudinary.com/merkle-manufactory/image/fetch/c_fill,f_jpg,w_168/https%3A%2F%2Fi.imgur.com%2F4t3zVHj.jpg"; | ||
|
||
function setUp() public {} | ||
|
||
function run() public { | ||
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); | ||
vm.startBroadcast(deployerPrivateKey); | ||
ExampleNft exampleNft = new ExampleNft(EXAMPLE_NFT_URI_1, EXAMPLE_NFT_URI_2); | ||
console2.log("implementation", address(exampleNft)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// SPDX-License-Identifier: GPL-3.0-or-later | ||
pragma solidity ^0.8.0; | ||
|
||
import {ERC1155} from "solady/tokens/ERC1155.sol"; | ||
|
||
/** | ||
* @title ExampleNft | ||
* @dev This contract is an example of an ERC1155 NFT contract. | ||
* It is used to demonstrate the usage of the Safe4337Module and it not meant to be used in production! | ||
*/ | ||
contract ExampleNft is ERC1155 { | ||
/// @dev Constants for the token types, 0 for dapp token, 1 for NFT | ||
uint256 public constant DAPP_TOKEN = 0; | ||
uint256 public constant NFT = 1; | ||
|
||
mapping(uint256 => string) public uris; | ||
|
||
constructor(string memory uri1, string memory uri2) ERC1155() { | ||
// Set some URIs for the tokens | ||
uris[DAPP_TOKEN] = uri1; | ||
uris[NFT] = uri2; | ||
} | ||
|
||
function uri(uint256 id) public view override returns (string memory) { | ||
return uris[id]; | ||
} | ||
|
||
/** | ||
* @dev This function is used to demonstrate the usage of the Safe4337Module | ||
* It mints 10 ether of token 0 to the caller | ||
*/ | ||
function mintCoupon() public { | ||
_mint(msg.sender, DAPP_TOKEN, 10 ether, ""); | ||
} | ||
|
||
/** | ||
* @dev This function is used to demonstrate the usage of the Safe4337Module | ||
* It burns 1 ether of token 0 from the caller, in exchange for an NFT | ||
*/ | ||
function exchangeForNft() public { | ||
_burn(msg.sender, DAPP_TOKEN, 1 ether); | ||
_mint(msg.sender, NFT, 1, ""); | ||
} | ||
} |