UNPKG

@silvana-one/nft

Version:
258 lines 11.6 kB
import { __decorate, __metadata } from "tslib"; import { AccountUpdate, method, Permissions, PublicKey, State, state, UInt64, SmartContract, Bool, Field, Struct, Poseidon, Provable, } from "o1js"; import { Whitelist, OffChainList, Storage } from "@silvana-one/storage"; import { NFTAddress, SellEvent, DepositEvent, WithdrawEvent, BidEvent, } from "./types.js"; import { TransferBySignatureParams, UInt64Option, NFTTransactionContext, } from "../interfaces/index.js"; export class Bid extends Struct({ price: UInt64, points: UInt64, }) { pack() { return Field.fromBits([ ...this.price.value.toBits(64), ...this.points.value.toBits(64), ]); } static unpack(field) { const bits = field.toBits(64 + 64); const price = UInt64.Unsafe.fromField(Field.fromBits(bits.slice(0, 64))); const points = UInt64.Unsafe.fromField(Field.fromBits(bits.slice(64, 64 + 64))); return new Bid({ price, points, }); } } export function BidFactory(params) { const { collectionContract } = params; class NonFungibleTokenBidContract extends SmartContract { constructor() { super(...arguments); this.buyer = State(); this.whitelist = State(); this.bids = State(); this.storage = State(); this.maxPoints = State(); // We do not have concurrency here, but given that it is a single-user contract, // we can use state to track the points that have been consumed // In case of concurrency, we can use the tokens to track the points this.consumedPoints = State(); this.events = { deposit: DepositEvent, withdraw: WithdrawEvent, sell: SellEvent, updateWhitelist: Whitelist, bid: BidEvent, }; } async deploy(args) { await super.deploy(args); this.whitelist.set(args.whitelist); this.bids.set(args.bids); this.storage.set(args.storage); this.account.permissions.set({ ...Permissions.default(), send: Permissions.proof(), setVerificationKey: Permissions.VerificationKey.impossibleDuringCurrentVersion(), setPermissions: Permissions.impossible(), }); } async initialize(amount, maxPoints) { this.account.provedState.requireEquals(Bool(false)); const buyer = this.sender.getUnconstrained(); const buyerUpdate = AccountUpdate.createSigned(buyer); // We use low-level subInPlace and addInPlace to decrease the number of AccountUpdates buyerUpdate.balance.subInPlace(amount.add(UInt64.from(1_000_000_000))); this.self.balance.addInPlace(amount); buyerUpdate.body.useFullCommitment = Bool(true); this.buyer.set(buyer); this.maxPoints.set(maxPoints); this.emitEvent("deposit", new DepositEvent({ buyer, amount, maxPoints, })); } getCollectionContract(address) { const CollectionContract = collectionContract(); return new CollectionContract(address); } async deposit(amount, maxPoints) { amount.equals(UInt64.from(0)).assertFalse(); const sender = this.sender.getUnconstrained(); const buyer = this.buyer.getAndRequireEquals(); sender.assertEquals(buyer); const buyerUpdate = AccountUpdate.createSigned(buyer); buyerUpdate.send({ to: this.address, amount }); buyerUpdate.body.useFullCommitment = Bool(true); this.maxPoints.set(maxPoints); this.emitEvent("deposit", new DepositEvent({ buyer, amount, maxPoints, })); } async withdraw(amount, maxPoints) { amount.equals(UInt64.from(0)).assertFalse(); this.account.balance.requireBetween(amount, UInt64.MAXINT()); const buyer = this.buyer.getAndRequireEquals(); const sender = this.sender.getUnconstrained(); const senderUpdate = AccountUpdate.createSigned(sender); senderUpdate.body.useFullCommitment = Bool(true); sender.assertEquals(buyer); let bidUpdate = this.send({ to: senderUpdate, amount }); bidUpdate.body.useFullCommitment = Bool(true); this.maxPoints.set(maxPoints); this.emitEvent("withdraw", new WithdrawEvent({ buyer, amount, maxPoints, })); } async sell(nftAddress, price) { await this._sell(nftAddress, price); const buyer = this.buyer.getAndRequireEquals(); const Collection = collectionContract(); const collection = new Collection(nftAddress.collection); await collection.transferBySignature(new TransferBySignatureParams({ address: nftAddress.nft, to: buyer, price: UInt64Option.fromValue(price), context: new NFTTransactionContext({ custom: [Field(0), Field(0), Field(0)], }), })); } async approvedSell(nftAddress, price) { await this._sell(nftAddress, price); const buyer = this.buyer.getAndRequireEquals(); const Collection = collectionContract(); const collection = new Collection(nftAddress.collection); await collection.adminApprovedTransferBySignature(new TransferBySignatureParams({ address: nftAddress.nft, to: buyer, price: UInt64Option.fromValue(price), context: new NFTTransactionContext({ custom: [Field(0), Field(0), Field(0)], }), })); } async _sell(nftAddress, price) { price.equals(UInt64.from(0)).assertFalse(); const key = Poseidon.hashPacked(NFTAddress, nftAddress); const storage = this.storage.getAndRequireEquals(); const bids = new OffChainList({ root: this.bids.getAndRequireEquals(), storage, }); const bid = Bid.unpack((await bids.getValue(key, "bids")).assertSome("bid not found")); // We do not require the price to be equal to the bid price, // because the price can be lower than the bid price // and the seller can still be willing to sell the NFT // as the deposit remaining is less than bid price price.assertLessThanOrEqual(bid.price, "price is too high"); this.account.balance.requireBetween(price, UInt64.MAXINT()); const consumedPoints = this.consumedPoints.getAndRequireEquals(); const maxPoints = this.maxPoints.getAndRequireEquals(); const newConsumedPoints = consumedPoints.add(bid.points); newConsumedPoints.assertLessThanOrEqual(maxPoints, "consumed points exceed max points"); this.consumedPoints.set(newConsumedPoints); const seller = this.sender.getUnconstrained(); const sellerUpdate = AccountUpdate.createSigned(seller); sellerUpdate.balance.addInPlace(price); this.self.balance.subInPlace(price); sellerUpdate.body.useFullCommitment = Bool(true); const whitelist = new Whitelist({ list: new OffChainList({ root: this.whitelist.getAndRequireEquals(), storage, }), }); const whitelistedAmount = await whitelist.getWhitelistedAmount(seller, "whitelist"); const whitelistDisabled = whitelist.isNone(); whitelistedAmount.isSome .or(whitelistDisabled) .assertTrue("Cannot buy from non-whitelisted address"); const maxPrice = Provable.if(whitelistDisabled, UInt64.MAXINT(), whitelistedAmount.value); price.assertLessThanOrEqual(maxPrice, "price is higher than whitelisted price"); this.emitEvent("sell", new SellEvent({ collection: nftAddress.collection, nft: nftAddress.nft, price, })); } async bid(bids, whitelist, storage) { const buyer = this.buyer.getAndRequireEquals(); const sender = this.sender.getUnconstrained(); const senderUpdate = AccountUpdate.createSigned(sender); senderUpdate.body.useFullCommitment = Bool(true); sender.assertEquals(buyer); this.bids.set(bids); this.whitelist.set(whitelist); this.storage.set(storage); this.emitEvent("bid", new BidEvent({ bids, whitelist, storage })); } } __decorate([ state(PublicKey), __metadata("design:type", Object) ], NonFungibleTokenBidContract.prototype, "buyer", void 0); __decorate([ state(Field), __metadata("design:type", Object) ], NonFungibleTokenBidContract.prototype, "whitelist", void 0); __decorate([ state(Field), __metadata("design:type", Object) ], NonFungibleTokenBidContract.prototype, "bids", void 0); __decorate([ state(Storage), __metadata("design:type", Object) ], NonFungibleTokenBidContract.prototype, "storage", void 0); __decorate([ state(UInt64), __metadata("design:type", Object) ], NonFungibleTokenBidContract.prototype, "maxPoints", void 0); __decorate([ state(UInt64), __metadata("design:type", Object) ], NonFungibleTokenBidContract.prototype, "consumedPoints", void 0); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [UInt64, UInt64]), __metadata("design:returntype", Promise) ], NonFungibleTokenBidContract.prototype, "initialize", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [UInt64, UInt64]), __metadata("design:returntype", Promise) ], NonFungibleTokenBidContract.prototype, "deposit", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [UInt64, UInt64]), __metadata("design:returntype", Promise) ], NonFungibleTokenBidContract.prototype, "withdraw", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [NFTAddress, UInt64]), __metadata("design:returntype", Promise) ], NonFungibleTokenBidContract.prototype, "sell", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [NFTAddress, UInt64]), __metadata("design:returntype", Promise) ], NonFungibleTokenBidContract.prototype, "approvedSell", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [Field, Field, Storage]), __metadata("design:returntype", Promise) ], NonFungibleTokenBidContract.prototype, "bid", null); return NonFungibleTokenBidContract; } //# sourceMappingURL=bid.js.map