@silvana-one/nft
Version:
Mina NFT library
340 lines (309 loc) • 8.68 kB
text/typescript
import {
AccountUpdate,
DeployArgs,
method,
Permissions,
PublicKey,
State,
state,
UInt64,
SmartContract,
Bool,
Field,
Struct,
VerificationKey,
} from "o1js";
/**
* The BulletinBoard contract serves as a centralized event emitter for NFT marketplace activities.
* It provides a standardized way to broadcast and track various marketplace events such as:
* - New collection listings
* - Offers made on NFTs
* - Offer cancellations
* - Bids placed on NFTs
* - Bid cancellations
* - Completed sales
*
* While anyone can emit events through this contract, all events are prefixed with "BB_" to
* distinguish them from events emitted directly by NFT contracts. This helps maintain clarity
* in event tracking and marketplace activity monitoring.
*
* The BulletinBoard does not handle any NFT transfers or escrow - it purely exists as an
* event broadcasting mechanism to help indexers and UIs track marketplace activity.
*/
export {
BB_NewCollectionEvent,
BB_OfferEvent,
BB_CancelOfferEvent,
BB_BidEvent,
BB_CancelBidEvent,
BB_SaleEvent,
BB_UpgradeVerificationKeyEvent,
BB_ChangeAdminEvent,
BulletinBoard,
BulletinBoardDeployProps,
};
class BB_NewCollectionEvent extends Struct({
/** The collection address. */
collection: PublicKey,
}) {}
class BB_OfferEvent extends Struct({
/** The collection address. */
collection: PublicKey,
/** The NFT address. */
nft: PublicKey,
/** The offer address. */
offer: PublicKey,
/** The price. */
price: UInt64,
}) {}
class BB_CancelOfferEvent extends Struct({
/** The collection address. */
collection: PublicKey,
/** The NFT address. */
nft: PublicKey,
}) {}
class BB_BidEvent extends Struct({
/** The collection address. */
collection: PublicKey,
/** The NFT address. */
nft: PublicKey,
/** The bid address. */
bid: PublicKey,
/** The price. */
price: UInt64,
}) {}
class BB_CancelBidEvent extends Struct({
/** The collection address. */
collection: PublicKey,
/** The NFT address. */
nft: PublicKey,
/** The bid address. */
bid: PublicKey,
}) {}
class BB_SaleEvent extends Struct({
/** The collection address. */
collection: PublicKey,
/** The NFT address. */
nft: PublicKey,
/** The buyer address. */
buyer: PublicKey,
/** The price. */
price: UInt64,
}) {}
class BB_UpgradeVerificationKeyEvent extends Struct({
/** The new verification key. */
vk: Field,
}) {}
class BB_ChangeAdminEvent extends Struct({
/** The new admin. */
admin: PublicKey,
}) {}
interface BulletinBoardDeployProps extends Exclude<DeployArgs, undefined> {
/** The admin. */
admin: PublicKey;
/** The fee. */
fee?: UInt64;
}
/**
* The BulletinBoard contract serves as a centralized event emitter for NFT marketplace activities.
* It provides a standardized way to broadcast and track various marketplace events such as:
* - New collection listings
* - Offers made on NFTs
* - Offer cancellations
* - Bids placed on NFTs
* - Bid cancellations
* - Completed sales
*
* While anyone can emit events through this contract, all events are prefixed with "BB_" to
* distinguish them from events emitted directly by NFT contracts. This helps maintain clarity
* in event tracking and marketplace activity monitoring.
*/
class BulletinBoard extends SmartContract {
admin = State<PublicKey>();
fee = State<UInt64>();
async deploy(args: BulletinBoardDeployProps) {
await super.deploy(args);
this.admin.set(args.admin);
this.fee.set(args.fee ?? UInt64.from(100_000_000));
this.account.permissions.set({
...Permissions.default(),
send: Permissions.proof(),
setVerificationKey:
Permissions.VerificationKey.proofDuringCurrentVersion(),
setPermissions: Permissions.impossible(),
});
}
events = {
newCollection: BB_NewCollectionEvent,
offer: BB_OfferEvent,
cancelOffer: BB_CancelOfferEvent,
bid: BB_BidEvent,
cancelBid: BB_CancelBidEvent,
sale: BB_SaleEvent,
upgradeVerificationKey: BB_UpgradeVerificationKeyEvent,
changeAdmin: BB_ChangeAdminEvent,
withdraw: UInt64,
setFee: UInt64,
};
/**
* Pays the fee to prevent spamming the BulletinBoard with fake events.
*/
async payFee() {
const fee = this.fee.getAndRequireEquals();
const sender = this.sender.getUnconstrained();
const feeUpdate = AccountUpdate.createSigned(sender);
feeUpdate.body.useFullCommitment = Bool(true); // Prevent memo and fee change
feeUpdate.balance.subInPlace(fee);
this.balance.addInPlace(fee);
return feeUpdate;
}
/**
* Emits a new collection event.
* @param collection - The collection address.
*/
async newCollection(collection: PublicKey) {
await this.payFee();
this.emitEvent(
"newCollection",
new BB_NewCollectionEvent({
collection,
})
);
}
/**
* Emits an offer event.
* @param collection - The collection address.
* @param nft - The NFT address.
* @param offer - The offer address.
* @param price - The price.
*/
async offer(
collection: PublicKey,
nft: PublicKey,
offer: PublicKey,
price: UInt64
) {
await this.payFee();
this.emitEvent(
"offer",
new BB_OfferEvent({ collection, nft, offer, price })
);
}
/**
* Emits a cancel offer event.
* @param collection - The collection address.
* @param nft - The NFT address.
*/
async cancelOffer(collection: PublicKey, nft: PublicKey) {
await this.payFee();
this.emitEvent("cancelOffer", new BB_CancelOfferEvent({ collection, nft }));
}
/**
* Emits a bid event.
* @param collection - The collection address.
* @param nft - The NFT address.
* @param bid - The bid address.
* @param price - The price.
*/
async bid(
collection: PublicKey,
nft: PublicKey,
bid: PublicKey,
price: UInt64
) {
await this.payFee();
this.emitEvent("bid", new BB_BidEvent({ collection, nft, bid, price }));
}
/**
* Emits a cancel bid event.
* @param collection - The collection address.
* @param nft - The NFT address.
* @param bid - The bid address.
*/
async cancelBid(
collection: PublicKey,
nft: PublicKey,
bid: PublicKey
) {
await this.payFee();
this.emitEvent(
"cancelBid",
new BB_CancelBidEvent({ collection, nft, bid })
);
}
/**
* Emits a sale event.
* @param collection - The collection address.
* @param nft - The NFT address.
* @param buyer - The buyer address.
* @param price - The price.
*/
async sale(
collection: PublicKey,
nft: PublicKey,
buyer: PublicKey,
price: UInt64
) {
await this.payFee();
this.emitEvent("sale", new BB_SaleEvent({ collection, nft, buyer, price }));
}
/**
* Ensures that the transaction is authorized by the contract owner.
* @returns A signed `AccountUpdate` from the admin.
*/
async ensureOwnerSignature(): Promise<AccountUpdate> {
const admin = this.admin.getAndRequireEquals();
const adminUpdate = AccountUpdate.createSigned(admin);
adminUpdate.body.useFullCommitment = Bool(true); // Prevent memo and fee change
return adminUpdate;
}
/**
* Changes the contract's admin
* @param admin - The new admin.
*/
async changeAdmin(admin: PublicKey) {
await this.ensureOwnerSignature();
// Set the new admin
this.admin.set(admin);
// Emit the change admin event
this.emitEvent("changeAdmin", new BB_ChangeAdminEvent({ admin }));
}
/**
* Changes the contract's fee
* @param fee - The new fee.
*/
async setFee(fee: UInt64) {
await this.ensureOwnerSignature();
// Set the new fee
this.fee.set(fee);
// Emit the change fee event
this.emitEvent("setFee", fee);
}
/**
* Upgrades the contract's verification key after validating with the upgrade authority.
* @param vk - The new verification key to upgrade to.
*/
async upgradeVerificationKey(vk: VerificationKey) {
await this.ensureOwnerSignature();
// Set the new verification key
this.account.verificationKey.set(vk);
// Emit the upgrade event
this.emitEvent(
"upgradeVerificationKey",
new BB_UpgradeVerificationKeyEvent({ vk: vk.hash })
);
}
/**
* Withdraws the fee by admin
*/
async withdraw(amount: UInt64) {
const adminUpdate = await this.ensureOwnerSignature();
adminUpdate.balance.addInPlace(amount);
this.balance.subInPlace(amount);
this.emitEvent("withdraw", amount);
}
}