diff --git a/crates/ark-contracts/common/Scarb.lock b/crates/ark-contracts/common/Scarb.lock index 8c3df6429..cfe24e4dc 100644 --- a/crates/ark-contracts/common/Scarb.lock +++ b/crates/ark-contracts/common/Scarb.lock @@ -5,9 +5,15 @@ version = 1 name = "ark_common" version = "0.1.0" dependencies = [ + "openzeppelin", "snforge_std", ] +[[package]] +name = "openzeppelin" +version = "0.8.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.8.0#c23e8e96de60e6e3159b1ff8591a1187269c0eb7" + [[package]] name = "snforge_std" version = "0.1.0" diff --git a/crates/ark-contracts/common/Scarb.toml b/crates/ark-contracts/common/Scarb.toml index 3c8562976..ec3c945b9 100644 --- a/crates/ark-contracts/common/Scarb.toml +++ b/crates/ark-contracts/common/Scarb.toml @@ -7,5 +7,10 @@ version = "0.1.0" [dependencies] starknet = "2.3.1" snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.9.1" } +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag = "v0.8.0" } [lib] + +[[target.starknet-contract]] +sierra = true +casm = true diff --git a/crates/ark-contracts/common/src/lib.cairo b/crates/ark-contracts/common/src/lib.cairo index df7faa23c..bb4e1da56 100644 --- a/crates/ark-contracts/common/src/lib.cairo +++ b/crates/ark-contracts/common/src/lib.cairo @@ -6,3 +6,7 @@ mod crypto { mod hash; mod signer; } + +mod tokens { + mod nft; +} diff --git a/crates/ark-contracts/common/src/tokens/nft.cairo b/crates/ark-contracts/common/src/tokens/nft.cairo new file mode 100644 index 000000000..8955d5040 --- /dev/null +++ b/crates/ark-contracts/common/src/tokens/nft.cairo @@ -0,0 +1,63 @@ +use starknet::ContractAddress; + +#[starknet::interface] +trait IFreeMint { + fn mint(ref self: T, recipient: ContractAddress, token_id: u256); +} + +#[starknet::contract] +mod FreeMintNFT { + use super::IFreeMint; + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::ERC721Component; + use starknet::ContractAddress; + + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC721 + #[abi(embed_v0)] + impl ERC721Impl = ERC721Component::ERC721Impl; + #[abi(embed_v0)] + impl ERC721MetadataImpl = ERC721Component::ERC721MetadataImpl; + #[abi(embed_v0)] + impl ERC721CamelOnly = ERC721Component::ERC721CamelOnlyImpl; + #[abi(embed_v0)] + impl ERC721MetadataCamelOnly = + ERC721Component::ERC721MetadataCamelOnlyImpl; + impl ERC721InternalImpl = ERC721Component::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc721: ERC721Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, name: felt252, symbol: felt252) { + self.erc721.initializer(name, symbol); + } + + #[external(v0)] + impl ImplFreeMint of IFreeMint { + fn mint(ref self: ContractState, recipient: ContractAddress, token_id: u256) { + self.erc721._mint(recipient, token_id); + // self.erc721._set_token_uri(token_id, 0); + } + } +} diff --git a/crates/ark-contracts/common/tests/test_hash.cairo b/crates/ark-contracts/common/tests/test_hash.cairo index 5488891fe..a4b8ed006 100644 --- a/crates/ark-contracts/common/tests/test_hash.cairo +++ b/crates/ark-contracts/common/tests/test_hash.cairo @@ -29,4 +29,4 @@ fn test_create_listing() { let poseidon_hash = serialized_hash(order_hash); poseidon_hash.print(); let signer = sign_mock(poseidon_hash, Option::None); -} \ No newline at end of file +} diff --git a/crates/ark-contracts/starknet/Scarb.lock b/crates/ark-contracts/starknet/Scarb.lock index 13ccf24d4..628aba84d 100644 --- a/crates/ark-contracts/starknet/Scarb.lock +++ b/crates/ark-contracts/starknet/Scarb.lock @@ -5,6 +5,7 @@ version = 1 name = "ark_common" version = "0.1.0" dependencies = [ + "openzeppelin", "snforge_std", ] diff --git a/crates/ark-contracts/starknet/src/executor.cairo b/crates/ark-contracts/starknet/src/executor.cairo index 62925d781..b519b6cda 100644 --- a/crates/ark-contracts/starknet/src/executor.cairo +++ b/crates/ark-contracts/starknet/src/executor.cairo @@ -18,7 +18,11 @@ mod executor { use ark_starknet::appchain_messaging::{ IAppchainMessagingDispatcher, IAppchainMessagingDispatcherTrait, }; - use openzeppelin::token::erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait}; + + use openzeppelin::token::{ + erc721::interface::{IERC721, IERC721Dispatcher, IERC721DispatcherTrait}, + erc20::interface::{IERC20, IERC20Dispatcher, IERC20DispatcherTrait} + }; #[storage] struct Storage { @@ -61,7 +65,6 @@ mod executor { #[external(v0)] impl ExecutorImpl of IExecutor { - fn get_test_address(ref self: ContractState) -> ContractAddress { self.test_address.read() } @@ -120,7 +123,6 @@ mod executor { } fn execute_order(ref self: ContractState, execution_info: ExecutionInfo) { - self.test_address.write(starknet::get_caller_address()); // assert( @@ -132,8 +134,8 @@ mod executor { contract_address: self.eth_contract_address.read() }; - //let nft_contract = IERCDispatcher { contract_address: execution_info.token_address }; - //nft_contract.transfer_from(execution_info.maker_address, execution_info.taker_address, execution_info.token_id); + let nft_contract = IERC721Dispatcher { contract_address: execution_info.token_address }; + // nft_contract.transfer_from(execution_info.fulfiller_address, execution_info.offerer_address, execution_info.token_id); // self._transfer_royalties(execution_info, eth_contract); diff --git a/crates/ark-contracts/starknet/src/lib.cairo b/crates/ark-contracts/starknet/src/lib.cairo index 28a04b94f..7c4d45260 100644 --- a/crates/ark-contracts/starknet/src/lib.cairo +++ b/crates/ark-contracts/starknet/src/lib.cairo @@ -7,3 +7,4 @@ mod executor; //mod tests; + diff --git a/packages/core/examples/fulfillListing.ts b/packages/core/examples/fulfillListing.ts index 1081ca019..9f3ba98e3 100644 --- a/packages/core/examples/fulfillListing.ts +++ b/packages/core/examples/fulfillListing.ts @@ -4,18 +4,23 @@ * submitting a listing order and cancelling it. */ -import { RpcProvider, shortString } from "starknet"; +import { Account, cairo, CallData, RpcProvider, shortString } from "starknet"; import { createAccount } from "../src/actions/account/account"; import { createListing, fulfillListing } from "../src/actions/order"; import { getOrderHash, getOrderStatus } from "../src/actions/read"; +import { EVERAI_NFT_ADDRESS } from "../src/constants"; import { ListingV1 } from "../src/types"; // Initialize the RPC provider with the ArkChain node URL -const provider = new RpcProvider({ +const arkchainProvider = new RpcProvider({ nodeUrl: "http://0.0.0.0:7777" }); +const katanaProvider = new RpcProvider({ + nodeUrl: "http://0.0.0.0:5050" +}); + /** * Creates a listing on the blockchain using provided order details. * @@ -24,13 +29,41 @@ const provider = new RpcProvider({ (async (provider: RpcProvider) => { // Create a new account for the listing using the provider const { account: listing_account } = await createAccount(provider); + console.log("Created account: ", listing_account.address); + + const katana0 = { + privateKey: "0x1800000000300000180000000000030000000000003006001800006600", + publicKey: + "0x2b191c2f3ecf685a91af7cf72a43e7b90e2e41220175de5c4f7498981b10053", + accountAddress: + "0x517ececd29116499f4a1b64b094da79ba08dfd54a3edaa316134c41f8160973" + }; + + // Get katana0 account + const katana0Account = new Account( + katanaProvider, + katana0.accountAddress, + katana0.privateKey + ); + + const tokenId = 3; + + await katana0Account.execute({ + contractAddress: EVERAI_NFT_ADDRESS, + entrypoint: "mint", + calldata: CallData.compile({ + recipient: katana0Account.address, + token_id: cairo.uint256(tokenId) + }) + }); + + console.log("Minted everai token: ", tokenId); // Define the order details let order: ListingV1 = { brokerId: 123, // The broker ID - tokenAddress: - "0x01435498bf393da86b4733b9264a86b58a42b31f8d8b8ba309593e5c17847672", // The token address - tokenId: 16, // The ID of the token + tokenAddress: EVERAI_NFT_ADDRESS, + tokenId: tokenId, // The ID of the token startAmount: 600000000000000000 // The starting amount for the order }; @@ -73,4 +106,4 @@ const provider = new RpcProvider({ provider ); console.log("orderStatus", shortString.decodeShortString(orderStatusAfter)); -})(provider); +})(arkchainProvider); diff --git a/packages/core/src/constants.ts b/packages/core/src/constants.ts index 1ad29de44..ae8a400c1 100644 --- a/packages/core/src/constants.ts +++ b/packages/core/src/constants.ts @@ -1,5 +1,6 @@ -const ORDER_BOOK_ADDRESS = +export const ORDER_BOOK_ADDRESS = process.env.ORDERBOOK_CONTRACT || "0x46ddb3a4b23520a9944ae5213f27660115d5dd24ac47fb95c9f3a799bcdd1ca"; -export { ORDER_BOOK_ADDRESS }; +export const EVERAI_NFT_ADDRESS = + "0x7a39fd46a05526515cb577a52b45a14818585424b1eff7bbe76601369e580fe"; diff --git a/scripts/deployer/contracts/freemint-erc721.js b/scripts/deployer/contracts/freemint-erc721.js new file mode 100644 index 000000000..daf9747e2 --- /dev/null +++ b/scripts/deployer/contracts/freemint-erc721.js @@ -0,0 +1,35 @@ +import * as sn from "starknet"; + +import * as common from "./common.js"; + +export async function declareDeploy( + common_artifacts_path, + account, + provider, + name, + symbol +) { + const artifacts = common.load_artifacts( + common_artifacts_path, + "ark_common_FreeMintNFT" + ); + + const contractCallData = new sn.CallData(artifacts.sierra.abi); + const contractConstructor = contractCallData.compile("constructor", { + name, + symbol + }); + + const deployR = await account.declareAndDeploy({ + contract: artifacts.sierra, + casm: artifacts.casm, + constructorCalldata: contractConstructor, + salt: 0x7777 + }); + + return new sn.Contract( + artifacts.sierra.abi, + deployR.deploy.contract_address, + provider + ); +} diff --git a/scripts/deployer/index.js b/scripts/deployer/index.js index 0f2939f59..221a2bcc4 100644 --- a/scripts/deployer/index.js +++ b/scripts/deployer/index.js @@ -7,6 +7,7 @@ import * as sn from "starknet"; import * as appmsg from "./contracts/appchain_messaging.js"; import * as executor from "./contracts/executor.js"; +import * as erc721 from "./contracts/freemint-erc721.js"; import * as orderbook from "./contracts/orderbook.js"; const STARKGATE = @@ -28,6 +29,7 @@ const solis_account0 = new sn.Account(solis, account0, privkey0); const sn_artifacts_path = "../../crates/ark-contracts/starknet/target/dev/"; const arkchain_artifacts_path = "../../crates/ark-contracts/arkchain/target/dev/"; +const common_artifacts_path = "../../crates/ark-contracts/common/target/dev/"; let orderbook_contract = await orderbook.declareDeploy( arkchain_artifacts_path, @@ -37,7 +39,20 @@ let orderbook_contract = await orderbook.declareDeploy( admin: account0 } ); -console.log("orderbook (ark)", orderbook_contract.address); + +console.log("šŸ’  ARKCHAIN CONTRACTS"); +console.log("- orderbook: ", orderbook_contract.address); + +let nft = await erc721.declareDeploy( + common_artifacts_path, + katana_account0, + katana, + "Everai", + "Everai" +); + +console.log("\nšŸ’… STARKNET CONTRACTS"); +console.log("- everai nft: ", nft.address); let appmsg_contract = await appmsg.declareDeploy( sn_artifacts_path, @@ -49,6 +64,8 @@ let appmsg_contract = await appmsg.declareDeploy( } ); +console.log("- appmsg: ", appmsg_contract.address); + let executor_contract = await executor.declareDeploy( sn_artifacts_path, katana_account0, @@ -60,17 +77,18 @@ let executor_contract = await executor.declareDeploy( messaging_address: appmsg_contract.address } ); -console.log("executor (sn)", executor_contract.address); +console.log("- executor: ", executor_contract.address); -let calldata = sn.CallData.compile({ - value: executor_contract.address -}); +console.log("---"); await solis_account0.execute({ contractAddress: orderbook_contract.address, entrypoint: "update_starknet_executor_address", - calldata: calldata + calldata: sn.CallData.compile({ + value: executor_contract.address + }) }); +console.log("Updated executor address in orderbook"); // TODO: Solis requires the address of the orderbook + the executor address, // So if the code changes for any of those contracts, the address must be pre-computed