UNPKG

@silvana-one/nft

Version:
399 lines 16.6 kB
import { __decorate, __metadata } from "tslib"; import { AccountUpdate, Bool, method, Permissions, PublicKey, SmartContract, State, state, VerificationKey, UInt64, Field, Struct, TokenId, } from "o1js"; import { FungibleTokenContract, } from "@silvana-one/token"; import { TransferExtendedParams } from "../interfaces/index.js"; import { mulDiv } from "../util/index.js"; export class NFTSharesDataPacked extends Struct({ adminX: Field, ownerX: Field, collectionX: Field, nftX: Field, auctionX: Field, data: Field, }) { } export class NFTSharesData extends Struct({ admin: PublicKey, owner: PublicKey, collection: PublicKey, nft: PublicKey, auction: PublicKey, maxBuyPrice: UInt64, minSellPrice: UInt64, }) { pack() { const data = Field.fromBits([ ...this.maxBuyPrice.value.toBits(64), ...this.minSellPrice.value.toBits(64), this.admin.isOdd, this.owner.isOdd, this.collection.isOdd, this.nft.isOdd, this.auction.isOdd, ]); return new NFTSharesDataPacked({ adminX: this.admin.x, ownerX: this.owner.x, collectionX: this.collection.x, nftX: this.nft.x, auctionX: this.auction.x, data, }); } static unpack(packed) { const bits = packed.data.toBits(64 + 64 + 5); const adminIsOdd = bits[64 + 64]; const ownerIsOdd = bits[64 + 64 + 1]; const collectionIsOdd = bits[64 + 64 + 2]; const nftIsOdd = bits[64 + 64 + 3]; const auctionIsOdd = bits[64 + 64 + 4]; const adminX = packed.adminX; const ownerX = packed.ownerX; const collectionX = packed.collectionX; const nftX = packed.nftX; const auctionX = packed.auctionX; const admin = PublicKey.from({ x: adminX, isOdd: adminIsOdd }); const owner = PublicKey.from({ x: ownerX, isOdd: ownerIsOdd }); const collection = PublicKey.from({ x: collectionX, isOdd: collectionIsOdd, }); const nft = PublicKey.from({ x: nftX, isOdd: nftIsOdd }); const auction = PublicKey.from({ x: auctionX, isOdd: auctionIsOdd }); const maxBuyPrice = UInt64.Unsafe.fromField(Field.fromBits(bits.slice(0, 64))); const minSellPrice = UInt64.Unsafe.fromField(Field.fromBits(bits.slice(64, 64 + 64))); return new NFTSharesData({ admin, owner, collection, nft, auction, maxBuyPrice, minSellPrice, }); } } export function NFTSharesFactory(params) { const { auctionContract } = params; class NFTSharesAdmin extends SmartContract { constructor() { super(...arguments); this.admin = State(); this.owner = State(); } async deploy(props) { await super.deploy(props); this.admin.set(props.admin); this.owner.set(props.owner); this.account.permissions.set({ ...Permissions.default(), setVerificationKey: Permissions.VerificationKey.proofDuringCurrentVersion(), setPermissions: Permissions.impossible(), }); } async updateVerificationKey(vk) { this.ensureAdminSignature(); this.account.verificationKey.set(vk); } ensureAdminSignature() { const admin = this.admin.getAndRequireEquals(); return AccountUpdate.createSigned(admin); } getOwner() { return new NFTSharesOwner(this.owner.getAndRequireEquals()); } async canMint(_accountUpdate) { const owner = this.getOwner(); return await owner.canMint(_accountUpdate); } async canChangeAdmin(_admin) { this.ensureAdminSignature(); return Bool(true); } async canPause() { this.ensureAdminSignature(); return Bool(true); } async canResume() { this.ensureAdminSignature(); return Bool(true); } async canChangeVerificationKey(_vk) { this.ensureAdminSignature(); return Bool(true); } } __decorate([ state(PublicKey), __metadata("design:type", Object) ], NFTSharesAdmin.prototype, "admin", void 0); __decorate([ state(PublicKey), __metadata("design:type", Object) ], NFTSharesAdmin.prototype, "owner", void 0); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [VerificationKey]), __metadata("design:returntype", Promise) ], NFTSharesAdmin.prototype, "updateVerificationKey", null); __decorate([ method.returns(Bool), __metadata("design:type", Function), __metadata("design:paramtypes", [AccountUpdate]), __metadata("design:returntype", Promise) ], NFTSharesAdmin.prototype, "canMint", null); __decorate([ method.returns(Bool), __metadata("design:type", Function), __metadata("design:paramtypes", [PublicKey]), __metadata("design:returntype", Promise) ], NFTSharesAdmin.prototype, "canChangeAdmin", null); __decorate([ method.returns(Bool), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], NFTSharesAdmin.prototype, "canPause", null); __decorate([ method.returns(Bool), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], NFTSharesAdmin.prototype, "canResume", null); __decorate([ method.returns(Bool), __metadata("design:type", Function), __metadata("design:paramtypes", [VerificationKey]), __metadata("design:returntype", Promise) ], NFTSharesAdmin.prototype, "canChangeVerificationKey", null); const FungibleToken = FungibleTokenContract(NFTSharesAdmin); class NFTSharesOwner extends SmartContract { constructor() { super(...arguments); /** * The public key of the contract's administrator. * This account has the authority to perform administrative actions such as pausing the contract or upgrading the verification key. */ this.data = State(); // FungibleTokenAdmin this.subscriptionOpen = State(Bool(true)); this.sharesOutstanding = State(UInt64.zero); } /** * Deploys the contract with initial settings. * @param props - Deployment properties including admin, upgradeAuthority, uri, canPause, and isPaused. */ async deploy(props) { await super.deploy(props); this.data.set(new NFTSharesData({ admin: props.admin, owner: props.owner, collection: props.collection, nft: props.nft, auction: props.auction, maxBuyPrice: props.maxBuyPrice, minSellPrice: props.minSellPrice, }).pack()); this.account.zkappUri.set(props.uri); this.subscriptionOpen.set(Bool(true)); this.sharesOutstanding.set(UInt64.zero); this.account.permissions.set({ ...Permissions.default(), // Allow the upgrade authority to set the verification key even without a protocol upgrade, // enabling upgrades in case of o1js breaking changes. setVerificationKey: Permissions.VerificationKey.proofDuringCurrentVersion(), setPermissions: Permissions.impossible(), }); } /** * Ensures that the transaction is authorized by the contract owner. * @returns A signed `AccountUpdate` from the admin. */ ensureOwnerSignature() { const data = NFTSharesData.unpack(this.data.getAndRequireEquals()); const adminUpdate = AccountUpdate.createSigned(data.admin); adminUpdate.body.useFullCommitment = Bool(true); // Prevent memo and fee change return data; } getAuction(auction) { return new (auctionContract())(auction); } /** * Allows the owner to mint shares. * This method should NOT called directly, but through the FungibleToken.mint() * * @param _accountUpdate - The account update containing the sender's information. * @returns A boolean indicating if the minting is allowed. */ async canMint(_accountUpdate) { const subscriptionOpen = this.subscriptionOpen.getAndRequireEquals(); subscriptionOpen.assertEquals(Bool(true), "Subscription is closed"); const address = _accountUpdate.publicKey; const amount = _accountUpdate.balanceChange.magnitude; const senderUpdate = AccountUpdate.createSigned(address); // 1 share = 1 MINA senderUpdate.balance.subInPlace(amount); this.balance.addInPlace(amount); const sharesOutstanding = this.sharesOutstanding.getAndRequireEquals(); this.sharesOutstanding.set(sharesOutstanding.add(amount)); return Bool(true); } async withdraw(shares) { const subscriptionOpen = this.subscriptionOpen.getAndRequireEquals(); subscriptionOpen.assertEquals(Bool(false), "Subscription is not closed, cannot withdraw"); const sharesOutstanding = this.sharesOutstanding.getAndRequireEquals(); const balance = this.account.balance.getAndRequireEquals(); balance .equals(UInt64.zero) .assertFalse("Balance is zero, nothing to withdraw"); const amountInMina = mulDiv({ value: shares, multiplier: balance, denominator: sharesOutstanding, }).result; const sender = this.sender.getUnconstrained(); const senderUpdate = AccountUpdate.createSigned(sender); senderUpdate.balance.addInPlace(amountInMina); this.balance.subInPlace(amountInMina); senderUpdate.body.useFullCommitment = Bool(true); this.sharesOutstanding.set(sharesOutstanding.sub(shares)); const data = NFTSharesData.unpack(this.data.getAndRequireEquals()); const token = new FungibleToken(data.owner); await token.burn(sender, shares); } async closeSubscription() { const data = this.ensureOwnerSignature(); const maxBuyPrice = data.maxBuyPrice; this.account.balance.requireBetween(maxBuyPrice, UInt64.MAXINT()); this.subscriptionOpen.set(Bool(false)); } async bid(price) { const data = NFTSharesData.unpack(this.data.getAndRequireEquals()); const maxBuyPrice = data.maxBuyPrice; price.assertLessThanOrEqual(maxBuyPrice, "Price is too high"); this.account.balance.requireBetween(price, UInt64.MAXINT()); this.subscriptionOpen.set(Bool(false)); const sharesOutstanding = this.sharesOutstanding.getAndRequireEquals(); const auction = this.getAuction(data.auction); const sender = this.sender.getUnconstrained(); const senderUpdate = AccountUpdate.createSigned(sender); const tokenId = TokenId.derive(data.owner); const tokenUpdate = AccountUpdate.create(sender, tokenId); const tokenBalance = tokenUpdate.account.balance.getAndRequireEquals(); const token = new FungibleToken(data.owner); await token.approveAccountUpdate(tokenUpdate); tokenBalance .mul(4) .assertGreaterThanOrEqual(sharesOutstanding, "Not enough shares to bid, minimum is 25% of the shares outstanding"); senderUpdate.balance.addInPlace(price); this.balance.subInPlace(price); senderUpdate.body.useFullCommitment = Bool(true); await auction.bid(price, this.address); } async canTransfer(params) { const data = NFTSharesData.unpack(this.data.getAndRequireEquals()); params.collection.assertEquals(data.collection); params.nft.assertEquals(data.nft); const amount = params.price.assertSome(); amount.assertGreaterThanOrEqual(data.minSellPrice, "Price is too low"); const sender = this.sender.getUnconstrained(); const senderUpdate = AccountUpdate.createSigned(sender); senderUpdate.balance.subInPlace(amount); senderUpdate.body.useFullCommitment = Bool(true); this.balance.addInPlace(amount); return Bool(true); } async canPause(collection, nft) { this.ensureOwnerSignature(); return Bool(true); } async canResume(collection, nft) { this.ensureOwnerSignature(); return Bool(true); } async canChangeVerificationKey(collection, nft, vk) { this.ensureOwnerSignature(); return Bool(true); } async canApproveAddress(collection, nft, approved) { this.ensureOwnerSignature(); return Bool(true); } } __decorate([ state(NFTSharesDataPacked), __metadata("design:type", Object) ], NFTSharesOwner.prototype, "data", void 0); __decorate([ state(Bool), __metadata("design:type", Object) ], NFTSharesOwner.prototype, "subscriptionOpen", void 0); __decorate([ state(UInt64), __metadata("design:type", Object) ], NFTSharesOwner.prototype, "sharesOutstanding", void 0); __decorate([ method.returns(Bool), __metadata("design:type", Function), __metadata("design:paramtypes", [AccountUpdate]), __metadata("design:returntype", Promise) ], NFTSharesOwner.prototype, "canMint", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [UInt64]), __metadata("design:returntype", Promise) ], NFTSharesOwner.prototype, "withdraw", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", Promise) ], NFTSharesOwner.prototype, "closeSubscription", null); __decorate([ method, __metadata("design:type", Function), __metadata("design:paramtypes", [UInt64]), __metadata("design:returntype", Promise) ], NFTSharesOwner.prototype, "bid", null); __decorate([ method.returns(Bool), __metadata("design:type", Function), __metadata("design:paramtypes", [TransferExtendedParams]), __metadata("design:returntype", Promise) ], NFTSharesOwner.prototype, "canTransfer", null); __decorate([ method.returns(Bool), __metadata("design:type", Function), __metadata("design:paramtypes", [PublicKey, PublicKey]), __metadata("design:returntype", Promise) ], NFTSharesOwner.prototype, "canPause", null); __decorate([ method.returns(Bool), __metadata("design:type", Function), __metadata("design:paramtypes", [PublicKey, PublicKey]), __metadata("design:returntype", Promise) ], NFTSharesOwner.prototype, "canResume", null); __decorate([ method.returns(Bool), __metadata("design:type", Function), __metadata("design:paramtypes", [PublicKey, PublicKey, VerificationKey]), __metadata("design:returntype", Promise) ], NFTSharesOwner.prototype, "canChangeVerificationKey", null); __decorate([ method.returns(Bool), __metadata("design:type", Function), __metadata("design:paramtypes", [PublicKey, PublicKey, PublicKey]), __metadata("design:returntype", Promise) ], NFTSharesOwner.prototype, "canApproveAddress", null); return { NFTSharesAdmin, NFTSharesOwner, FungibleToken, }; } //# sourceMappingURL=nft-shares.js.map