Skip to content

Commit

Permalink
Merge pull request #4 from near-examples/8.marketplace
Browse files Browse the repository at this point in the history
8.marketplace
  • Loading branch information
BenKurrek authored Aug 7, 2022
2 parents 66a2bce + 0d63a8a commit 4e29dc8
Show file tree
Hide file tree
Showing 15 changed files with 316 additions and 146 deletions.
2 changes: 1 addition & 1 deletion neardev/dev-account
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dev-1659724732366-66506674516641
dev-1659879671058-26952029212531
2 changes: 1 addition & 1 deletion neardev/dev-account.env
Original file line number Diff line number Diff line change
@@ -1 +1 @@
CONTRACT_NAME=dev-1659724732366-66506674516641
CONTRACT_NAME=dev-1659879671058-26952029212531
26 changes: 13 additions & 13 deletions src/market-contract/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { NearContract, NearBindgen, near, call, view, LookupMap, UnorderedMap, Vector, UnorderedSet, assert } from 'near-sdk-js'
import { assertOneYocto, restoreOwners } from '../nft-contract/internals';
import { assertOneYocto, restoreOwners } from './internal';
import { internalNftOnApprove } from './nft_callbacks';
import { internalOffer, internalRemoveSale, internalResolvePurchase, internalUpdatePrice, Sale } from './sale';
import { internalGetSale, internalSalesByNftContractId, internalSalesByOwnerId, internalSupplyByNftContractId, internalSupplyByOwnerId, internalSupplySales } from './sale_views';
Expand Down Expand Up @@ -138,64 +138,64 @@ export class Contract extends NearContract {
@call
//removes a sale from the market.
remove_sale({nft_contract_id, token_id}:{nft_contract_id: string, token_id: string}) {
return internalRemoveSale(this, nft_contract_id, token_id);
return internalRemoveSale({contract: this, nftContractId: nft_contract_id, tokenId: token_id});
}

@call
//updates the price for a sale on the market
update_price({nft_contract_id, token_id, price}:{nft_contract_id: string, token_id: string, price: string}) {
return internalUpdatePrice(this, nft_contract_id, token_id, price);
return internalUpdatePrice({contract: this, nftContractId: nft_contract_id, tokenId: token_id, price: price});
}

@call
//place an offer on a specific sale. The sale will go through as long as your deposit is greater than or equal to the list price
offer({nft_contract_id, token_id}:{nft_contract_id: string, token_id: string}) {
return internalOffer(this, nft_contract_id, token_id);
return internalOffer({contract: this, nftContractId: nft_contract_id, tokenId: token_id});
}

@call
//place an offer on a specific sale. The sale will go through as long as your deposit is greater than or equal to the list price
resolve_purchase({buyer_id, price}:{buyer_id: string, price: string}) {
return internalResolvePurchase(buyer_id, price);
}
return internalResolvePurchase({buyerId: buyer_id, price: price});
}

/*
SALE VIEWS
*/
@view
//returns the number of sales the marketplace has up (as a string)
get_supply_sales(): string {
return internalSupplySales(this);
return internalSupplySales({contract: this});
}

@view
//returns the number of sales for a given account (result is a string)
get_supply_by_owner_id({account_id}:{account_id: string}): string {
return internalSupplyByOwnerId(this, account_id);
return internalSupplyByOwnerId({contract: this, accountId: account_id});
}

@view
//returns paginated sale objects for a given account. (result is a vector of sales)
get_sales_by_owner_id({account_id, from_index, limit}:{account_id: string, from_index?: string, limit?: number}): Sale[] {
return internalSalesByOwnerId(this, account_id, from_index, limit);
return internalSalesByOwnerId({contract: this, accountId: account_id, fromIndex: from_index, limit: limit});
}

@view
//returns paginated sale objects for a given account. (result is a vector of sales)
get_supply_by_nft_contract_id({nft_contract_id}:{nft_contract_id: string}): string {
return internalSupplyByNftContractId(this, nft_contract_id);
return internalSupplyByNftContractId({contract: this, nftContractId: nft_contract_id});
}

@view
//returns paginated sale objects associated with a given nft contract. (result is a vector of sales)
get_sales_by_nft_contract_id({nft_contract_id, from_index, limit}:{nft_contract_id: string, from_index?: string, limit?: number}): Sale[] {
return internalSalesByNftContractId(this, nft_contract_id, from_index, limit);
return internalSalesByNftContractId({contract: this, accountId: nft_contract_id, fromIndex: from_index, limit: limit});
}

@view
//get a sale information for a given unique sale ID (contract + DELIMITER + token ID)
get_sale({nft_contract_token}:{nft_contract_token: string}): Sale {
return internalGetSale(this, nft_contract_token);
return internalGetSale({contract: this, nftContractToken: nft_contract_token});
}

/*
Expand All @@ -204,7 +204,7 @@ export class Contract extends NearContract {
@call
/// where we add the sale because we know nft owner can only call nft_approve
nft_on_approve({token_id, owner_id, approval_id, msg}:{token_id: string, owner_id: string, approval_id: number, msg: string}) {
return internalNftOnApprove(this, token_id, owner_id, approval_id, msg);
return internalNftOnApprove({contract: this, tokenId: token_id, ownerId: owner_id, approvalId: approval_id, msg: msg});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,4 @@ export function internallyRemoveSale(contract: Contract, nftContractId: string,

//return the sale object
return sale;
}

}
16 changes: 14 additions & 2 deletions src/market-contract/nft_callbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@ import { Sale } from "./sale";
import { internalSupplyByOwnerId } from "./sale_views";

/// where we add the sale because we know nft owner can only call nft_approve
export function internalNftOnApprove(contract: Contract, tokenId: string, ownerId: string, approvalId: number, msg: string) {
export function internalNftOnApprove({
contract,
tokenId,
ownerId,
approvalId,
msg
}:{
contract: Contract,
tokenId: string,
ownerId: string,
approvalId: number,
msg: string
}) {
// get the contract ID which is the predecessor
let contractId = near.predecessorAccountId();
//get the signer which is the person who initiated the transaction
Expand All @@ -21,7 +33,7 @@ export function internalNftOnApprove(contract: Contract, tokenId: string, ownerI
//get the total storage paid by the owner
let ownerPaidStorage = contract.storageDeposits.get(signerId) || BigInt(0);
//get the storage required which is simply the storage for the number of sales they have + 1
let signerStorageRequired = (BigInt(internalSupplyByOwnerId(contract, signerId)) + BigInt(1)) * BigInt(storageAmount);
let signerStorageRequired = (BigInt(internalSupplyByOwnerId({contract, accountId: signerId})) + BigInt(1)) * BigInt(storageAmount);

//make sure that the total paid is >= the required storage
assert(ownerPaidStorage >= signerStorageRequired, "the owner does not have enough storage to approve this token");
Expand Down
69 changes: 55 additions & 14 deletions src/market-contract/sale.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { assert, bytes, near } from "near-sdk-js";
import { Contract, DELIMETER } from ".";
import { assertOneYocto, internallyRemoveSale } from "./internals";
import { assertOneYocto, internallyRemoveSale } from "./internal";

//GAS constants to attach to calls
const GAS_FOR_ROYALTIES = 115_000_000_000_000;
Expand Down Expand Up @@ -42,12 +42,20 @@ export class Sale {
}

//removes a sale from the market.
export function internalRemoveSale(contract: Contract, nftContactId: string, tokenId: string) {
export function internalRemoveSale({
contract,
nftContractId,
tokenId
}:{
contract: Contract,
nftContractId: string,
tokenId: string
}) {
//assert that the user has attached exactly 1 yoctoNEAR (for security reasons)
assertOneYocto();

//get the sale object as the return value from removing the sale internally
let sale = internallyRemoveSale(contract, nftContactId, tokenId);
let sale = internallyRemoveSale(contract, nftContractId, tokenId);

//get the predecessor of the call and make sure they're the owner of the sale
let ownerId = near.predecessorAccountId();
Expand All @@ -57,12 +65,22 @@ export function internalRemoveSale(contract: Contract, nftContactId: string, tok
}

//updates the price for a sale on the market
export function internalUpdatePrice(contract: Contract, nftContactId: string, tokenId: string, price: string) {
export function internalUpdatePrice({
contract,
nftContractId,
tokenId,
price
}:{
contract: Contract,
nftContractId: string,
tokenId: string,
price: string
}) {
//assert that the user has attached exactly 1 yoctoNEAR (for security reasons)
assertOneYocto();

//create the unique sale ID from the nft contract and token
let contractAndTokenId = `${nftContactId}${DELIMETER}${tokenId}`;
let contractAndTokenId = `${nftContractId}${DELIMETER}${tokenId}`;

//get the sale object from the unique sale ID. If there is no token, panic.
let sale = contract.sales.get(contractAndTokenId) as Sale;
Expand All @@ -78,13 +96,21 @@ export function internalUpdatePrice(contract: Contract, nftContactId: string, to
}

//place an offer on a specific sale. The sale will go through as long as your deposit is greater than or equal to the list price
export function internalOffer(contract: Contract, nftContactId: string, tokenId: string) {
export function internalOffer({
contract,
nftContractId,
tokenId
}:{
contract: Contract,
nftContractId: string,
tokenId: string
}) {
//get the attached deposit and make sure it's greater than 0
let deposit = near.attachedDeposit().valueOf();
assert(deposit > 0, "deposit must be greater than 0");

//get the unique sale ID (contract + DELIMITER + token ID)
let contractAndTokenId = `${nftContactId}${DELIMETER}${tokenId}`;
let contractAndTokenId = `${nftContractId}${DELIMETER}${tokenId}`;
//get the sale object from the unique sale ID. If the sale doesn't exist, panic.
let sale = contract.sales.get(contractAndTokenId) as Sale;
if (sale == null) {
Expand All @@ -101,18 +127,30 @@ export function internalOffer(contract: Contract, nftContactId: string, tokenId:
assert(deposit >= price, "deposit must be greater than or equal to price");

//process the purchase (which will remove the sale, transfer and get the payout from the nft contract, and then distribute royalties)
processPurchase(contract, nftContactId, tokenId, deposit.toString(), buyerId);
processPurchase({contract, nftContractId, tokenId, price: deposit.toString(), buyerId});
}

//private function used when a sale is purchased.
//this will remove the sale, transfer and get the payout from the nft contract, and then distribute royalties
export function processPurchase(contract: Contract, nftContactId: string, tokenId: string, price: string, buyerId: string) {
export function processPurchase({
contract,
nftContractId,
tokenId,
price,
buyerId
}:{
contract: Contract,
nftContractId: string,
tokenId: string,
price: string,
buyerId: string
}) {
//get the sale object by removing the sale
let sale = internallyRemoveSale(contract, nftContactId, tokenId);
let sale = internallyRemoveSale(contract, nftContractId, tokenId);

//initiate a cross contract call to the nft contract. This will transfer the token to the buyer and return
//a payout object used for the market to distribute funds to the appropriate accounts.
const promise = near.promiseBatchCreate(nftContactId);
const promise = near.promiseBatchCreate(nftContractId);
near.promiseBatchActionFunctionCall(
promise,
"nft_transfer_payout",
Expand Down Expand Up @@ -153,10 +191,13 @@ export function processPurchase(contract: Contract, nftContactId: string, tokenI
check to see if it's authentic and there's no problems. If everything is fine, it will pay the accounts. If there's a problem,
it will refund the buyer for the price.
*/
export function internalResolvePurchase(
buyerId: string,
export function internalResolvePurchase({
buyerId,
price
}:{
buyerId: string,
price: string
) {
}) {
assert(near.currentAccountId() === near.predecessorAccountId(), "Only the contract itself can call this method");

// checking for payout information returned from the nft_transfer_payout method
Expand Down
60 changes: 48 additions & 12 deletions src/market-contract/sale_views.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
import { Contract } from ".";
import { restoreOwners } from "./internals";
import { restoreOwners } from "./internal";
import { Sale } from "./sale";

//returns the number of sales the marketplace has up (as a string)
export function internalSupplySales(contract: Contract): string {
export function internalSupplySales({
contract
}:{
contract: Contract
}): string {
//returns the sales object length wrapped as a string
return contract.sales.len().toString();
}

//returns the number of sales for a given account (result is a string)
export function internalSupplyByOwnerId(contract: Contract, accountId: string): string {
export function internalSupplyByOwnerId({
contract,
accountId
}:{
contract: Contract,
accountId: string
}): string {
//get the set of sales for the given owner Id
let byOwnerId = restoreOwners(contract.byOwnerId.get(accountId));
//if there as some set, we return the length but if there wasn't a set, we return 0
Expand All @@ -21,7 +31,17 @@ export function internalSupplyByOwnerId(contract: Contract, accountId: string):
}

//returns paginated sale objects for a given account. (result is a vector of sales)
export function internalSalesByOwnerId(contract: Contract, accountId: string, fromIndex?: string, limit?: number): Sale[] {
export function internalSalesByOwnerId({
contract,
accountId,
fromIndex,
limit
}:{
contract: Contract,
accountId: string,
fromIndex?: string,
limit?: number
}): Sale[] {
//get the set of token IDs for sale for the given account ID
let tokenSet = restoreOwners(contract.byOwnerId.get(accountId));

Expand Down Expand Up @@ -50,7 +70,13 @@ export function internalSalesByOwnerId(contract: Contract, accountId: string, fr
}

//get the number of sales for an nft contract. (returns a string)
export function internalSupplyByNftContractId(contract: Contract, nftContractId: string): string {
export function internalSupplyByNftContractId({
contract,
nftContractId
}:{
contract: Contract,
nftContractId: string
}): string {
//get the set of tokens for associated with the given nft contract
let byNftContractId = restoreOwners(contract.byNftContractId.get(nftContractId));
//if there as some set, we return the length but if there wasn't a set, we return 0
Expand All @@ -62,7 +88,17 @@ export function internalSupplyByNftContractId(contract: Contract, nftContractId:
}

//returns paginated sale objects associated with a given nft contract. (result is a vector of sales)
export function internalSalesByNftContractId(contract: Contract, accountId: string, fromIndex?: string, limit?: number): Sale[] {
export function internalSalesByNftContractId({
contract,
accountId,
fromIndex,
limit
}:{
contract: Contract,
accountId: string,
fromIndex?: string,
limit?: number
}): Sale[] {
//get the set of token IDs for sale for the given contract ID
let tokenSet = restoreOwners(contract.byNftContractId.get(accountId));

Expand Down Expand Up @@ -91,14 +127,14 @@ export function internalSalesByNftContractId(contract: Contract, accountId: stri
}

//get a sale information for a given unique sale ID (contract + DELIMITER + token ID)
export function internalGetSale(
export function internalGetSale({
contract,
nftContractToken,
}:{
contract: Contract,
nftContractToken: string
): Sale {
}): Sale {
//try and get the sale object for the given unique sale ID. Will return an option since
//we're not guaranteed that the unique sale ID passed in will be valid.n);
return contract.sales.get(nftContractToken) as Sale;
}



}
Loading

0 comments on commit 4e29dc8

Please sign in to comment.