Skip to content

Commit 6d23c6d

Browse files
authored
Merge pull request #8 from dev-protocol/feat-initial-implementation
feat: add SBT contracts
2 parents 833c200 + c6795d3 commit 6d23c6d

File tree

17 files changed

+395
-330
lines changed

17 files changed

+395
-330
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@
1010
}
1111
],
1212
"editor.codeActionsOnSave": {
13-
"source.fixAll.eslint": true
13+
"source.fixAll.eslint": "explicit"
1414
}
1515
}

contracts/Admin.sol

Lines changed: 0 additions & 6 deletions
This file was deleted.

contracts/Example.sol

Lines changed: 0 additions & 16 deletions
This file was deleted.

contracts/SBT.sol

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
// SPDX-License-Identifier: MPL-2.0
2+
pragma solidity =0.8.9;
3+
4+
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
5+
import {Base64} from "@devprotocol/util-contracts/contracts/utils/Base64.sol";
6+
import {ProxyAdmin} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
7+
import {ERC721EnumerableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";
8+
9+
import {ISBT} from "./interfaces/ISBT.sol";
10+
11+
contract SBT is ISBT, ERC721EnumerableUpgradeable {
12+
using Base64 for bytes;
13+
using Strings for uint256;
14+
15+
/// @dev Account with proxy adming rights.
16+
address private _proxyAdmin;
17+
/// @dev EOA with rights to allow(add)/disallow(remove) minter.
18+
address private _minterUpdater;
19+
20+
/// @dev EOA with minting rights.
21+
mapping(address => bool) private _minters;
22+
/// @dev Holds the encoded metadata of a SBT token.
23+
mapping(uint256 => bytes) private _sbtdata;
24+
25+
modifier onlyMinter() {
26+
require(_minters[_msgSender()], "Illegal access");
27+
_;
28+
}
29+
30+
modifier onlyMinterUpdater() {
31+
require(_msgSender() == _minterUpdater, "Not minter updater");
32+
_;
33+
}
34+
35+
function _setTokenURI(uint256 tokenId, bytes memory metadata) private {
36+
_sbtdata[tokenId] = metadata;
37+
emit SetSBTTokenURI(tokenId, metadata);
38+
}
39+
40+
function _beforeTokenTransfer(
41+
address from,
42+
address to,
43+
uint256 tokenId,
44+
uint256 batchSize
45+
) internal virtual override {
46+
if (from == address(0)) {
47+
// allow mint
48+
super._beforeTokenTransfer(from, to, tokenId, batchSize);
49+
} else if (to == address(0)) {
50+
// disallow burn
51+
revert("SBT can not burn");
52+
} else if (to != from) {
53+
// disallow transfer
54+
revert("SBT can not transfer");
55+
} else {
56+
// disallow other
57+
revert("Illegal operation");
58+
}
59+
}
60+
61+
function initialize(
62+
address minterUpdater,
63+
address[] memory minters
64+
) external initializer {
65+
__ERC721_init("Dev Protocol SBT V1", "DEV-SBT-V1");
66+
67+
_minterUpdater = minterUpdater;
68+
for (uint256 i = 0; i < minters.length; i++) {
69+
_minters[minters[i]] = true;
70+
}
71+
}
72+
73+
function setProxyAdmin(address proxyAdmin) external {
74+
require(_proxyAdmin == address(0), "Already set");
75+
_proxyAdmin = proxyAdmin;
76+
emit SetProxyAdmin(proxyAdmin);
77+
}
78+
79+
function addMinter(address minter) external override onlyMinterUpdater {
80+
_minters[minter] = true;
81+
emit MinterAdded(minter);
82+
}
83+
84+
function removeMinter(address minter) external override onlyMinterUpdater {
85+
_minters[minter] = false;
86+
emit MinterRemoved(minter);
87+
}
88+
89+
function setTokenURI(
90+
uint256 tokenId,
91+
bytes memory metadata
92+
) external override onlyMinter {
93+
require(tokenId < currentIndex(), "Token not found");
94+
_setTokenURI(tokenId, metadata);
95+
}
96+
97+
function mint(
98+
address to,
99+
bytes memory metadata
100+
) external override onlyMinter returns (uint256 tokenId_) {
101+
uint256 currentId = currentIndex();
102+
_mint(to, currentId);
103+
emit Minted(currentId, to);
104+
_setTokenURI(currentId, metadata);
105+
return currentId;
106+
}
107+
108+
function _tokenURI(uint256 tokenId) private view returns (string memory) {
109+
(
110+
string memory name,
111+
string memory description,
112+
string memory tokenUriImage,
113+
StringAttribute[] memory stringAttributes,
114+
NumberAttribute[] memory numberAttributes
115+
) = abi.decode(
116+
_sbtdata[tokenId],
117+
(string, string, string, StringAttribute[], NumberAttribute[])
118+
);
119+
120+
bool isStringDataPresent = false;
121+
string memory sbtAttributes = "";
122+
123+
for (uint256 i = 0; i < stringAttributes.length; i++) {
124+
string memory attributeInString = string(
125+
abi.encodePacked(
126+
// solhint-disable-next-line quotes
127+
'{"trait_type": "',
128+
stringAttributes[i].trait_type,
129+
// solhint-disable-next-line quotes
130+
'",',
131+
// solhint-disable-next-line quotes
132+
' "value": "',
133+
stringAttributes[i].value,
134+
// solhint-disable-next-line quotes
135+
'"}'
136+
)
137+
);
138+
139+
if (i == 0) {
140+
isStringDataPresent = true;
141+
sbtAttributes = attributeInString;
142+
} else {
143+
sbtAttributes = string(
144+
abi.encodePacked(sbtAttributes, ", ", attributeInString)
145+
);
146+
}
147+
}
148+
149+
for (uint256 i = 0; i < numberAttributes.length; i++) {
150+
string memory attributeInString = string(
151+
abi.encodePacked(
152+
// solhint-disable-next-line quotes
153+
'{"trait_type": "',
154+
numberAttributes[i].trait_type,
155+
// solhint-disable-next-line quotes
156+
'",',
157+
// solhint-disable-next-line quotes
158+
' "display_type": "',
159+
numberAttributes[i].display_type,
160+
// solhint-disable-next-line quotes
161+
'",',
162+
// solhint-disable-next-line quotes
163+
' "value": "',
164+
numberAttributes[i].value.toString(),
165+
// solhint-disable-next-line quotes
166+
'"}'
167+
)
168+
);
169+
170+
if (i == 0 && !isStringDataPresent) {
171+
sbtAttributes = attributeInString;
172+
} else {
173+
sbtAttributes = string(
174+
abi.encodePacked(sbtAttributes, ", ", attributeInString)
175+
);
176+
}
177+
}
178+
179+
sbtAttributes = string(abi.encodePacked("[", sbtAttributes, "]"));
180+
181+
return
182+
string(
183+
abi.encodePacked(
184+
"data:application/json;base64,",
185+
abi
186+
.encodePacked(
187+
// solhint-disable-next-line quotes
188+
'{"name":"',
189+
name,
190+
// solhint-disable-next-line quotes
191+
'", "description":"',
192+
description,
193+
// solhint-disable-next-line quotes
194+
'", "image": "',
195+
tokenUriImage,
196+
// solhint-disable-next-line quotes
197+
'", "attributes":',
198+
sbtAttributes,
199+
"}"
200+
)
201+
.encode()
202+
)
203+
);
204+
}
205+
206+
function encodeMetadata(
207+
string memory name,
208+
string memory description,
209+
StringAttribute[] memory stringAttributes,
210+
NumberAttribute[] memory numberAttributes,
211+
string memory tokenUriImage
212+
) public pure override returns (bytes memory) {
213+
return
214+
abi.encode(
215+
name,
216+
description,
217+
tokenUriImage,
218+
stringAttributes,
219+
numberAttributes
220+
);
221+
}
222+
223+
function tokenURI(
224+
uint256 tokenId
225+
) public view override returns (string memory) {
226+
return _tokenURI(tokenId);
227+
}
228+
229+
function currentIndex() public view override returns (uint256) {
230+
return super.totalSupply();
231+
}
232+
233+
function owner() external view returns (address) {
234+
return ProxyAdmin(_proxyAdmin).owner();
235+
}
236+
237+
function tokensOfOwner(
238+
address tokenOwner
239+
) external view override returns (uint256[] memory) {
240+
uint256 length = super.balanceOf(tokenOwner);
241+
uint256[] memory tokens = new uint256[](length);
242+
for (uint256 i = 0; i < length; i++) {
243+
tokens[i] = super.tokenOfOwnerByIndex(tokenOwner, i);
244+
}
245+
return tokens;
246+
}
247+
}

contracts/UpgradeableProxy.sol

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)