Skip to content

Commit ed67eb6

Browse files
authored
Initial commit
0 parents  commit ed67eb6

13 files changed

+778
-0
lines changed

.github/workflows/test.yml

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: test
2+
3+
on: workflow_dispatch
4+
5+
env:
6+
FOUNDRY_PROFILE: ci
7+
8+
jobs:
9+
check:
10+
strategy:
11+
fail-fast: true
12+
13+
name: Foundry project
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v3
17+
with:
18+
submodules: recursive
19+
20+
- name: Install Foundry
21+
uses: foundry-rs/foundry-toolchain@v1
22+
with:
23+
version: nightly
24+
25+
- name: Run Forge build
26+
run: |
27+
forge --version
28+
forge build --sizes
29+
id: build
30+
31+
- name: Run Forge tests
32+
run: |
33+
forge test -vvv
34+
id: test

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
cache/
2+
out/
3+
/node_modules
4+
yarn.lock
5+
.DS_Store

.gitmodules

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[submodule "lib/forge-std"]
2+
path = lib/forge-std
3+
url = https://github.com/foundry-rs/forge-std
4+
[submodule "lib/contracts"]
5+
path = lib/contracts
6+
url = https://github.com/thirdweb-dev/contracts

README.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
## Custom Smart Accounts
2+
3+
Use the [Solidity SDK](https://portal.thirdweb.com/solidity) to create custom ERC-4337 [Smart Wallets](https://portal.thirdweb.com/wallet/smart-wallet) in which the owner of the wallet is tied to the ownership of a specified erc-721 token.
4+
5+
## Getting Started
6+
7+
To create a custom smart wallet, clone this repo using the [thirdweb CLI](https://portal.thirdweb.com/cli):
8+
9+
```bash
10+
npx thirdweb create --contract --template token-gated-account
11+
```
12+
13+
## Building the project & running tests
14+
15+
_Note: This repository is a [Foundry](https://book.getfoundry.sh/) project, so make sure that [Foundry is installed](https://book.getfoundry.sh/getting-started/installation) locally._
16+
17+
To make sure that Foundry is up to date and install dependencies, run the following command:
18+
19+
```bash
20+
foundryup && forge install
21+
```
22+
23+
Once the dependencies are installed, tests can be run:
24+
25+
```bash
26+
forge test
27+
```
28+
29+
## Deploying Contracts
30+
31+
To deploy *ANY* contract, with no requirements, use thirdweb Deploy:
32+
33+
```bash
34+
npx thirdweb deploy
35+
```
36+
37+
1. Deploy the implementation contract, `TokenGatedAccount` as this will be needed as a constructor parameter for the factory.
38+
2. Deploy the factory contract `TokenGatedAccountFactory`
39+
40+
In both cases, set the `EntryPoint` contract address as `0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789`.
41+
This address is the same on all chains it is deployed to.
42+
43+
## Join our Discord!
44+
45+
For any questions or suggestions, join our discord at [https://discord.gg/thirdweb](https://discord.gg/thirdweb).

deployArgs.json

+53
Large diffs are not rendered by default.

foundry.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[profile.default]
2+
src = 'src'
3+
out = 'out'
4+
libs = ['lib']
5+
ffi = true
6+
remappings = [
7+
'@thirdweb-dev/=lib/contracts/',
8+
'@ds-test=lib/forge-std/lib/ds-test/src/',
9+
'@std=lib/forge-std/src/',
10+
]
11+
12+
13+
# See more config options https://github.com/foundry-rs/foundry/tree/master/config

lib/contracts

Submodule contracts added at 7825602

lib/forge-std

Submodule forge-std added at 20872c5

package.json

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "forge-starter",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"repository": "https://github.com/thirdweb-example/forge-starter.git",
6+
"author": "thirdweb <[email protected]>",
7+
"license": "MIT",
8+
"dependencies": {
9+
"@thirdweb-dev/contracts": "^3"
10+
},
11+
"scripts": {
12+
"build": "npx thirdweb@latest detect",
13+
"deploy": "npx thirdweb@latest deploy",
14+
"release": "npx thirdweb@latest release"
15+
}
16+
}

remappings.txt

Whitespace-only changes.

src/TokenGatedAccount.sol

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity ^0.8.13;
3+
4+
import "@thirdweb-dev/contracts/smart-wallet/non-upgradeable/Account.sol";
5+
import "@thirdweb-dev/contracts/eip/interface/IERC721.sol";
6+
7+
contract TokenGatedAccount is Account {
8+
uint256 public chainId;
9+
address public tokenContract;
10+
uint256 public tokenId;
11+
12+
event TokenGatedAccountCreated(address indexed account, bytes indexed data);
13+
14+
/**
15+
* @notice Executes once when a contract is created to initialize state variables
16+
*
17+
* @param _entrypoint - 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789
18+
* @param _factory - The factory contract address to issue token Gated accounts
19+
*
20+
*/
21+
constructor(
22+
IEntryPoint _entrypoint,
23+
address _factory
24+
) Account(_entrypoint, _factory) {
25+
_disableInitializers();
26+
}
27+
28+
function isValidSigner(
29+
address _signer
30+
) public view override returns (bool) {
31+
return (owner() == _signer);
32+
}
33+
34+
function owner() public view returns (address) {
35+
if (chainId != block.chainid) {
36+
revert("Invalid chainId");
37+
}
38+
return IERC721(tokenContract).ownerOf(tokenId);
39+
}
40+
41+
function initialize(
42+
address _admin,
43+
bytes calldata _data
44+
) public override initializer {
45+
(chainId, tokenContract, tokenId) = abi.decode(
46+
_data,
47+
(uint256, address, uint256)
48+
);
49+
require(owner() == _admin, "Account: not token owner.");
50+
emit TokenGatedAccountCreated(_admin, _data);
51+
}
52+
53+
/// @notice Executes a transaction (called directly from the token owner, or by entryPoint)
54+
function execute(
55+
address _target,
56+
uint256 _value,
57+
bytes calldata _calldata
58+
) external virtual override onlyOwnerOrEntrypoint {
59+
_call(_target, _value, _calldata);
60+
}
61+
62+
/// @notice Executes a sequence transaction (called directly from the token owner, or by entryPoint)
63+
function executeBatch(
64+
address[] calldata _target,
65+
uint256[] calldata _value,
66+
bytes[] calldata _calldata
67+
) external virtual override onlyOwnerOrEntrypoint {
68+
require(
69+
_target.length == _calldata.length &&
70+
_target.length == _value.length,
71+
"Account: wrong array lengths."
72+
);
73+
for (uint256 i = 0; i < _target.length; i++) {
74+
_call(_target[i], _value[i], _calldata[i]);
75+
}
76+
}
77+
78+
/// @notice Withdraw funds for this account from Entrypoint.
79+
function withdrawDepositTo(
80+
address payable withdrawAddress,
81+
uint256 amount
82+
) public virtual override {
83+
require(owner() == msg.sender, "Account: not NFT owner");
84+
entryPoint().withdrawTo(withdrawAddress, amount);
85+
}
86+
87+
function token()
88+
external
89+
view
90+
returns (uint256 chainId, address tokenContract, uint256 tokenId)
91+
{}
92+
93+
/// @notice Checks whether the caller is the EntryPoint contract or the token owner.
94+
modifier onlyOwnerOrEntrypoint() {
95+
require(
96+
msg.sender == address(entryPoint()) || owner() == msg.sender,
97+
"Account: not admin or EntryPoint."
98+
);
99+
_;
100+
}
101+
}

src/TokenGatedAccountFactory.sol

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: GPL-3.0
2+
pragma solidity ^0.8.12;
3+
4+
// Utils
5+
import "@thirdweb-dev/contracts/smart-wallet/utils/BaseAccountFactory.sol";
6+
7+
// Smart wallet implementation
8+
import {TokenGatedAccount} from "./TokenGatedAccount.sol";
9+
10+
contract TokenGatedAccountFactory is BaseAccountFactory {
11+
/// @notice Emitted when a new Token Gated Account is created.
12+
event TokenGatedAccountCreated(address indexed account, bytes indexed data);
13+
14+
/*///////////////////////////////////////////////////////////////
15+
Constructor
16+
//////////////////////////////////////////////////////////////*/
17+
18+
/**
19+
* @notice Executes once when a contract is created to initialize state variables
20+
*
21+
* @param _entrypoint - 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789
22+
*/
23+
constructor(
24+
IEntryPoint _entrypoint
25+
)
26+
BaseAccountFactory(
27+
address(new TokenGatedAccount(_entrypoint, address(this)))
28+
)
29+
{}
30+
31+
/*///////////////////////////////////////////////////////////////
32+
Internal functions
33+
//////////////////////////////////////////////////////////////*/
34+
35+
/// @notice Generates a salt for the new Account using the erc-721 token data.
36+
function _generateSalt(
37+
address,
38+
bytes calldata _data
39+
) internal view virtual override returns (bytes32) {
40+
return keccak256(abi.encode(_data));
41+
}
42+
43+
/// @notice Deploys a new Account for admin.
44+
function _initializeAccount(
45+
address _account,
46+
address _admin,
47+
bytes calldata _data
48+
) internal override {
49+
TokenGatedAccount(payable(_account)).initialize(_admin, _data);
50+
emit TokenGatedAccountCreated(_account, _data);
51+
}
52+
}

0 commit comments

Comments
 (0)