-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* update dependencies. * adjust kyc test expected result. * refactor: structure imrpovement and utils. * add space passport contracts. * add utility script to calculate next deploy address. * add spacepass scripts deploy, mint and conteractURI get/set. * add spacepass metadata jsons. * refactor: * import order * sc deploy log. * add block number to `deployDetails`. * wip cps. * update metadata and tasks&scripts. * refactor.
- Loading branch information
Showing
108 changed files
with
9,613 additions
and
15,643 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
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 |
---|---|---|
|
@@ -25,6 +25,5 @@ cache | |
# typechain | ||
typechain | ||
|
||
secret_local.ts | ||
|
||
out | ||
secret.json |
This file was deleted.
Oops, something went wrong.
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,128 @@ | ||
// SPDX-License-Identifier: private | ||
pragma solidity ^0.8.18; | ||
|
||
import '@openzeppelin/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol'; | ||
import '@openzeppelin/contracts/access/AccessControl.sol'; | ||
import '@openzeppelin/contracts/access/Ownable.sol'; | ||
|
||
import '../../utils/GeneratorID.sol'; | ||
import '../../common/abstract/KYC.sol'; | ||
|
||
contract SpacepassAsset is ERC1155URIStorage, GeneratorID, AccessControl, Ownable, KYC { | ||
event Royalties(uint256 indexed id, uint256 indexed royalties); | ||
event PermanentURI(string _value, uint256 indexed _id); | ||
|
||
string public name; | ||
string public symbol; | ||
|
||
mapping(uint256 => uint256) private _royalties; | ||
mapping(uint256 => address) private _creators; | ||
|
||
bool public openCreate = false; | ||
bool public openSale = false; | ||
|
||
bytes32 public constant CREATOR_ROLE = keccak256('CREATOR_ROLE'); | ||
string private metadataCID; | ||
|
||
constructor(string memory uri, address kycAddress) ERC1155(uri) { | ||
name = 'Copernic Space Passports'; | ||
symbol = 'CSP'; | ||
_setBaseURI(uri); | ||
_setupRole(DEFAULT_ADMIN_ROLE, msg.sender); | ||
setupKyc(kycAddress); | ||
} | ||
|
||
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC1155, AccessControl) returns (bool) { | ||
return super.supportsInterface(interfaceId); | ||
} | ||
|
||
function contractURI() public view returns (string memory) { | ||
return metadataCID; | ||
} | ||
|
||
function setContractURI(string memory _contractURI) public onlyAdmin { | ||
metadataCID = _contractURI; | ||
} | ||
|
||
function mint(string memory cid, uint256 balance, uint256 royalties, bytes memory data) public { | ||
bool senderHasCreatorRole = hasRole(DEFAULT_ADMIN_ROLE, msg.sender) || hasRole(CREATOR_ROLE, msg.sender); | ||
require(openCreate || senderHasCreatorRole, 'You are not allowed to create new Spaceible Asset'); | ||
uint256 id = generateId(); | ||
_mint(msg.sender, id, balance, data); | ||
_setURI(id, cid); | ||
_setRoyalties(id, royalties); | ||
_setCreator(id, msg.sender); | ||
} | ||
|
||
function setURI(uint256 id, string memory cid) public { | ||
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender) || hasRole(CREATOR_ROLE, msg.sender), 'You are not allowed to set URI'); | ||
_setURI(id, cid); | ||
} | ||
|
||
function _setRoyalties(uint256 id, uint256 royalties) internal { | ||
_royalties[id] = royalties; | ||
emit Royalties(id, royalties); | ||
} | ||
|
||
function getRoyalties(uint256 id) public view returns (uint256) { | ||
return _royalties[id]; | ||
} | ||
|
||
function _setCreator(uint256 id, address creator) internal { | ||
_creators[id] = creator; | ||
} | ||
|
||
function getCreator(uint256 id) public view returns (address) { | ||
return _creators[id]; | ||
} | ||
|
||
function grantCreatorRole(address target) external onlyAdmin { | ||
grantRole(CREATOR_ROLE, target); | ||
} | ||
|
||
function revokeCreatorRole(address target) external onlyAdmin { | ||
revokeRole(CREATOR_ROLE, target); | ||
} | ||
|
||
function hasCreatorRole(address target) public view returns (bool) { | ||
return openCreate || hasRole(CREATOR_ROLE, target) || hasRole(DEFAULT_ADMIN_ROLE, target); | ||
} | ||
|
||
function toggleOpenCreate() external onlyAdmin { | ||
openCreate = !openCreate; | ||
} | ||
|
||
function toggleOpenSale() external onlyAdmin { | ||
openSale = !openSale; | ||
} | ||
|
||
function burn(uint256 id, uint256 amount) external { | ||
_burn(msg.sender, id, amount); | ||
} | ||
|
||
modifier onlyAdmin() { | ||
require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), 'Only admin can perform this action'); | ||
_; | ||
} | ||
|
||
function _beforeTokenTransfer( | ||
address operator, | ||
address from, | ||
address to, | ||
uint256[] memory ids, | ||
uint256[] memory amounts, | ||
bytes memory data | ||
) internal virtual override(ERC1155) { | ||
// if not open sale - check for sender/receiver to be on WL (KYC register) | ||
if (!openSale) { | ||
if (from != address(0)) { | ||
require(kycRegister.getKycStatusInfo(from), 'from adress is not on WL'); | ||
} | ||
if (to != address(0)) { | ||
require(kycRegister.getKycStatusInfo(to), 'to address is not on WL'); | ||
} | ||
} | ||
|
||
super._beforeTokenTransfer(operator, from, to, ids, amounts, data); | ||
} | ||
} |
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,165 @@ | ||
// SPDX-License-Identifier: private | ||
pragma solidity ^0.8.18; | ||
|
||
import '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol'; | ||
import '@openzeppelin/contracts/token/ERC1155/IERC1155.sol'; | ||
import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; | ||
|
||
import '../../utils/ERC20Percentage.sol'; | ||
import '../../utils/GeneratorID.sol'; | ||
import './SpacepassAsset.sol'; | ||
|
||
contract SpacepassListing is GeneratorID { | ||
using SafeERC20 for IERC20; | ||
|
||
struct Listing { | ||
uint256 id; | ||
address seller; | ||
uint256 assetId; | ||
uint256 amount; | ||
uint256 price; | ||
address money; | ||
} | ||
|
||
address public operator; | ||
uint256 public operatorFee; | ||
address public assetAddress; | ||
|
||
mapping(uint256 => Listing) private _listings; | ||
mapping(uint256 => bool) private _paused; | ||
mapping(uint256 => bool) private _canceled; | ||
|
||
mapping(address => mapping(uint256 => uint256)) private balancesOnListings; | ||
|
||
event Sell(uint256 indexed id); | ||
event Buy(uint256 indexed id, uint256 amount, uint256 sellerFee, uint256 royaltiesFee, uint256 platformFee); | ||
event Pause(uint256 indexed id); | ||
event Unpause(uint256 indexed id); | ||
event Cancel(uint256 indexed id); | ||
event Edit(uint256 indexed id, uint256 amount, uint256 price, address money); | ||
|
||
constructor(address _operator, uint256 _operatorFee, address _assetAddress) { | ||
operator = _operator; | ||
operatorFee = _operatorFee; | ||
assetAddress = _assetAddress; | ||
} | ||
|
||
function sell(uint256 assetId, uint256 amount, uint256 price, address money) public returns (uint256 id) { | ||
require( | ||
IERC1155(assetAddress).balanceOf(msg.sender, assetId) >= balancesOnListings[msg.sender][assetId] + amount, | ||
'Asset amount across listings exceeds available balance' | ||
); | ||
|
||
require( | ||
IERC1155(assetAddress).isApprovedForAll(msg.sender, address(this)) == true, | ||
'This contract has no approval to operate sellers assets' | ||
); | ||
|
||
id = generateId(); | ||
|
||
Listing storage listing = _listings[id]; | ||
listing.id = id; | ||
listing.seller = msg.sender; | ||
listing.assetId = assetId; | ||
listing.amount = amount; | ||
listing.price = price; | ||
listing.money = money; | ||
|
||
balancesOnListings[msg.sender][assetId] += amount; | ||
|
||
emit Sell(id); | ||
} | ||
|
||
function get( | ||
uint256 id | ||
) public view returns (address seller, uint256 assetId, uint256 amount, uint256 price, address money) { | ||
Listing memory listing = _listings[id]; | ||
return (listing.seller, listing.assetId, listing.amount, listing.price, listing.money); | ||
} | ||
|
||
function edit(uint256 id, uint256 amount, uint256 price, address money) public notCanceled(id) { | ||
Listing storage listing = _listings[id]; | ||
require(msg.sender == listing.seller, 'Only listing creator can edit'); | ||
|
||
cancel(listing.id); | ||
sell(listing.assetId, amount, price, money); | ||
} | ||
|
||
function buy(uint256 id, uint256 amount) public notCanceled(id) { | ||
require(_paused[id] != true, 'Listing is paused'); | ||
Listing storage listing = _listings[id]; | ||
SpacepassAsset asset = SpacepassAsset(assetAddress); | ||
require(listing.amount >= amount, 'Not enough asset balance on sale'); | ||
IERC20 money = IERC20(listing.money); | ||
uint256 amountPrice = amount * listing.price; | ||
require( | ||
money.allowance(msg.sender, address(this)) >= amountPrice, | ||
'Insufficient balance via allowance to purchase' | ||
); | ||
uint256 royalties = asset.getRoyalties(listing.assetId); | ||
address creator = asset.getCreator(listing.assetId); | ||
|
||
uint256 royaltiesFeeAmount; | ||
if (royalties == 0 || listing.seller == creator) { | ||
royaltiesFeeAmount = 0; | ||
} else { | ||
royaltiesFeeAmount = (amountPrice * royalties) / 1e4; | ||
} | ||
uint256 operatorFeeAmount = (amountPrice * operatorFee) / 1e4; | ||
|
||
uint256 sellerFeeAmount = amountPrice - royaltiesFeeAmount - operatorFeeAmount; | ||
|
||
// do not send 0 amount ERC20 transfer | ||
if (royaltiesFeeAmount > 0) { | ||
money.safeTransferFrom(msg.sender, creator, royaltiesFeeAmount); | ||
} | ||
money.safeTransferFrom(msg.sender, operator, operatorFeeAmount); | ||
money.safeTransferFrom(msg.sender, listing.seller, sellerFeeAmount); | ||
|
||
listing.amount -= amount; | ||
balancesOnListings[listing.seller][listing.assetId] -= amount; | ||
|
||
asset.safeTransferFrom(listing.seller, msg.sender, listing.assetId, amount, ''); | ||
|
||
emit Buy(id, amount, sellerFeeAmount, royaltiesFeeAmount, operatorFeeAmount); | ||
} | ||
|
||
function getAvailableBalance(address user, uint256 assetId) public view returns (uint256 availableBalance) { | ||
return IERC1155(assetAddress).balanceOf(user, assetId) - balancesOnListings[user][assetId]; | ||
} | ||
|
||
function pause(uint256 id) public notCanceled(id) { | ||
Listing memory listing = _listings[id]; | ||
require(msg.sender == listing.seller, 'Only listing seller can pause'); | ||
_paused[id] = true; | ||
emit Pause(id); | ||
} | ||
|
||
function unpause(uint256 id) public notCanceled(id) { | ||
Listing memory listing = _listings[id]; | ||
require(msg.sender == listing.seller, 'Only listing seller can unpause'); | ||
_paused[id] = false; | ||
emit Unpause(id); | ||
} | ||
|
||
function isPaused(uint256 id) public view returns (bool) { | ||
return _paused[id]; | ||
} | ||
|
||
function cancel(uint256 id) public notCanceled(id) { | ||
Listing memory listing = _listings[id]; | ||
require(msg.sender == listing.seller, 'Only listing seller can cancel'); | ||
_canceled[id] = true; | ||
balancesOnListings[listing.seller][listing.assetId] -= listing.amount; | ||
emit Cancel(id); | ||
} | ||
|
||
function isCanceled(uint256 id) public view returns (bool) { | ||
return _canceled[id]; | ||
} | ||
|
||
modifier notCanceled(uint256 id) { | ||
require(_canceled[id] != true, 'Listing is canceled'); | ||
_; | ||
} | ||
} |
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+174 KB
contracts/spaceibles/Spacepass/metadata/media/CS_LOGO_BLACK_WITH_LABEL.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+35.6 KB
contracts/spaceibles/Spacepass/metadata/media/CS_LOGO_BLUE_CIRCLE.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions
19
contracts/spaceibles/Spacepass/metadata/mumbai/ambassador.json
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,19 @@ | ||
{ | ||
"name": "Ambassador Passport", | ||
"description": "Unleash the full potential of the space economy and be a part of the future of space exploration with the Copernic Space Passport NFT Collection.\n\n \n\nGet exclusive access to a range of benefits on the Copernic Space Marketplace, from earning potential to discounted prices on space-related products and services. Participate in IRL space experiences like rocket launches and sending personal assets to the Moon + more!\n\n \n\nWith a limited collection of Passports, be one of the few who can take advantage of tangible value and access to space.\n\n---\n\nBecome a recognized representative in space and unlock unparalleled benefits with the **Ambassador Passport** from Copernic Space. Get all the benefits of the Citizen Passport plus earn even more revenue and savings, become a part of our exclusive Ambassador Program for added recognition and opportunities + more. Don't miss out on this limited opportunity to take your space exploration journey to the next level.\n\n \n\n* All Citizen Passport benefits +\n* Make more money by sharing your Citizen link - EARN 75% of platform revenue generated by any sales that come through it\n* Keep even more of your earnings - SAVE 75% off platform fees for all purchases you make\n* Ambassador Program Membership — be featured on our website and gain access to internal initiatives\n \n**[Join our Discord](https://discord.gg/XdAJGRJpSh)**\n[![logo](https://firebasestorage.googleapis.com/v0/b/cprs-platform-prod/o/discord-logo.gif?alt=media&token=fd9e9445-53fa-403b-bbd1-8e3b6ce32984)](https://discord.gg/XdAJGRJpSh)", | ||
"image": "ipfs://bafybeibxdgwwhoyajblbhkmc4zechnxnuo4af3gwd2ex3fzsz7537x2hhi/CSP_AMBASSADOR.mp4", | ||
"external_url": "https://app.copernicspace.com/spaceibles/asset/CSP-2", | ||
"tags": ["space", "economy", "business", "art", "blockchain"], | ||
"creator": { | ||
"name": "Copernic Space", | ||
"description": "Copernic Space is a decentralized marketplace for the space economy that leverages blockchain technology to enable access to space assets and related data. The platform connects providers and buyers of space-related products and services, making it easier for them to exchange and monetize space assets such as satellite data, space debris, and even payloads headed for the moon.", | ||
"image": "ipfs://bafybeibxdgwwhoyajblbhkmc4zechnxnuo4af3gwd2ex3fzsz7537x2hhi/CS_LOGO_BLACK_WITH_LABEL.png", | ||
"external_link": "https://copernicspace.com" | ||
}, | ||
"collection": { | ||
"name": "Copernic Space Passports", | ||
"description": "Copernic Space is the door to access Space ... and our passport is your key. The Copernic Space Passport gives you on-platform utilities, financial benefits, and access to IRL Space opportunities.", | ||
"external_link": "https://app.copernicspace.com/spaceibles?collection=spacepassport", | ||
"banner_image": "ipfs://bafybeibxdgwwhoyajblbhkmc4zechnxnuo4af3gwd2ex3fzsz7537x2hhi/CS_BANNER.png" | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
contracts/spaceibles/Spacepass/metadata/mumbai/astronaut.json
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,19 @@ | ||
{ | ||
"name": "Astronaut Passport", | ||
"description": "Unleash the full potential of the space economy and be a part of the future of space exploration with the Copernic Space Passport NFT Collection.\n\n \n\nGet exclusive access to a range of benefits on the Copernic Space Marketplace, from earning potential to discounted prices on space-related products and services. Participate in IRL space experiences like rocket launches and sending personal assets to the Moon + more!\n\n \n\nWith a limited collection of Passports, be one of the few who can take advantage of tangible value and access to space.\n\n---\n\nExperience the ultimate space adventure and make your mark on the universe with the **Astronaut Passport** from Copernic Space. Gain access to exclusive benefits like building your own IRL space mission, creating and selling your own space assets, and showcasing your collection at partnered physical galleries worldwide + more.\n\n \n\n* All Ambassador Passport benefits +\n* Build Your IRL Space Mission Send a physical or digital payload such as a commercial product to the ISS or the Moon.", | ||
"image": "ipfs://bafybeibxdgwwhoyajblbhkmc4zechnxnuo4af3gwd2ex3fzsz7537x2hhi/CSP_ASTRONAUT.mp4", | ||
"external_url": "https://app.copernicspace.com/spaceibles/asset/CSP-3", | ||
"tags": ["space", "economy", "business", "art", "blockchain"], | ||
"creator": { | ||
"name": "Copernic Space", | ||
"description": "Copernic Space is a decentralized marketplace for the space economy that leverages blockchain technology to enable access to space assets and related data. The platform connects providers and buyers of space-related products and services, making it easier for them to exchange and monetize space assets such as satellite data, space debris, and even payloads headed for the moon.", | ||
"image": "ipfs://bafybeibxdgwwhoyajblbhkmc4zechnxnuo4af3gwd2ex3fzsz7537x2hhi/CS_LOGO_BLACK_WITH_LABEL.png", | ||
"external_link": "https://copernicspace.com" | ||
}, | ||
"collection": { | ||
"name": "Copernic Space Passports", | ||
"description": "Copernic Space is the door to access Space ... and our passport is your key. The Copernic Space Passport gives you on-platform utilities, financial benefits, and access to IRL Space opportunities.", | ||
"external_link": "https://app.copernicspace.com/spaceibles?collection=spacepassport", | ||
"banner_image": "ipfs://bafybeibxdgwwhoyajblbhkmc4zechnxnuo4af3gwd2ex3fzsz7537x2hhi/CS_BANNER.png" | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
contracts/spaceibles/Spacepass/metadata/mumbai/citizen.json
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,19 @@ | ||
{ | ||
"name": "Citizen Passport", | ||
"description": "Unleash the full potential of the space economy and be a part of the future of space exploration with the Copernic Space Passport NFT Collection.\n\n \n\nGet exclusive access to a range of benefits on the Copernic Space Marketplace, from earning potential to discounted prices on space-related products and services. Participate in IRL space experiences like rocket launches and sending personal assets to the Moon + more!\n\n \n\nWith a limited collection of Passports, be one of the few who can take advantage of tangible value and access to space.\n\n---\n\nGet your foot in the door of the space economy with the **Citizen Passport** from Copernic Space. With exclusive access to benefits like earning potential, discounted prices on space-related products, chances to win space-related experiences and gain access to our space partner opportunities, the Citizen Passport is your ticket to the future of space exploration.\n\n \n\n* Make money by sharing your Citizen link - EARN 50% of platform revenue generated by any sales that come through it\n* Keep more of your earnings - SAVE 50% off platform fees for all purchases you make\n* Automatic entry to win amazing space-related prizes and experiences such as rocket launches\n* 2 for 1 Starborn offer for Copernic Space holders\n* Free tickets to our Metaverse space events with [Adshares](https://cryptoslate.com/this-new-deal-introduces-true-space-assets-and-experiences-into-the-metaverse/) & [WEOM](https://copernicspace.com/weomxcopernicspace)\n* Chance to send your digital assets such as art or music to the Moon\n\n**[Join our Discord](https://discord.gg/XdAJGRJpSh)**\n[![logo](https://firebasestorage.googleapis.com/v0/b/cprs-platform-prod/o/discord-logo.gif?alt=media&token=fd9e9445-53fa-403b-bbd1-8e3b6ce32984)](https://discord.gg/XdAJGRJpSh)", | ||
"image": "ipfs://bafybeibxdgwwhoyajblbhkmc4zechnxnuo4af3gwd2ex3fzsz7537x2hhi/CSP_CITIZEN.mp4", | ||
"external_url": "https://app.copernicspace.com/spaceibles/asset/CSP-1", | ||
"tags": ["space", "economy", "business", "art", "blockchain"], | ||
"creator": { | ||
"name": "Copernic Space", | ||
"description": "Copernic Space is a decentralized marketplace for the space economy that leverages blockchain technology to enable access to space assets and related data. The platform connects providers and buyers of space-related products and services, making it easier for them to exchange and monetize space assets such as satellite data, space debris, and even payloads headed for the moon.", | ||
"image": "ipfs://bafybeibxdgwwhoyajblbhkmc4zechnxnuo4af3gwd2ex3fzsz7537x2hhi/CS_LOGO_BLACK_WITH_LABEL.png", | ||
"external_link": "https://copernicspace.com" | ||
}, | ||
"collection": { | ||
"name": "Copernic Space Passports", | ||
"description": "Copernic Space is the door to access Space ... and our passport is your key. The Copernic Space Passport gives you on-platform utilities, financial benefits, and access to IRL Space opportunities.", | ||
"external_link": "https://app.copernicspace.com/spaceibles?collection=spacepassport", | ||
"banner_image": "ipfs://bafybeibxdgwwhoyajblbhkmc4zechnxnuo4af3gwd2ex3fzsz7537x2hhi/CS_BANNER.png" | ||
} | ||
} |
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,7 @@ | ||
{ | ||
"name": "Copernic Space Passports", | ||
"description": "Copernic Space is the door to access Space ... and our passport is your key. The Copernic Space Passport gives you on-platform utilities, financial benefits, and access to IRL Space opportunities.", | ||
"image": "ipfs://bafybeibxdgwwhoyajblbhkmc4zechnxnuo4af3gwd2ex3fzsz7537x2hhi/CSP_LOGO.png", | ||
"external_link": "https://app.copernicspace.com/spaceibles?collection=spacepassport", | ||
"seller_fee_basis_points": 300 | ||
} |
Oops, something went wrong.