From 0eda8f45b1d11f4c4223fe7ad366a0889cd93665 Mon Sep 17 00:00:00 2001 From: kiok46 Date: Sat, 26 Jul 2025 17:48:08 +0530 Subject: [PATCH 01/35] update cashscript and cashc version, change contract names, add decay logi --- README.md | 24 +- contracts/Accumulator.cash | 9 +- contracts/Auction.cash | 32 ++- contracts/Bid.cash | 4 +- ...ictResolver.cash => ConflictResolver.cash} | 4 +- .../{DomainFactory.cash => Factory.cash} | 110 ++++---- contracts/{Domain.cash => Name.cash} | 70 +++--- ...ionNameEnforcer.cash => NameEnforcer.cash} | 8 +- ...wnershipGuard.cash => OwnershipGuard.cash} | 44 ++-- contracts/Registry.cash | 26 +- lib/compiled/Accumulator.ts | 22 +- lib/compiled/Auction.ts | 60 ++--- lib/compiled/AuctionConflictResolver.ts | 80 ------ lib/compiled/AuctionNameEnforcer.ts | 81 ------ lib/compiled/Bid.ts | 6 +- lib/compiled/ConflictResolver.ts | 80 ++++++ lib/compiled/Domain.ts | 226 ----------------- lib/compiled/DomainFactory.ts | 197 --------------- lib/compiled/DomainOwnershipGuard.ts | 89 ------- lib/compiled/Factory.ts | 185 ++++++++++++++ lib/compiled/Name.ts | 234 ++++++++++++++++++ lib/compiled/NameEnforcer.ts | 81 ++++++ lib/compiled/OwnershipGuard.ts | 93 +++++++ lib/compiled/Registry.ts | 10 +- lib/index.ts | 20 +- package-lock.json | 46 ++-- package.json | 16 +- test/common.ts | 7 +- test/e2e/auction.test.ts | 10 +- test/unit/import.test.ts | 10 +- test/utils.ts | 9 + 31 files changed, 965 insertions(+), 928 deletions(-) rename contracts/{AuctionConflictResolver.cash => ConflictResolver.cash} (98%) rename contracts/{DomainFactory.cash => Factory.cash} (57%) rename contracts/{Domain.cash => Name.cash} (71%) rename contracts/{AuctionNameEnforcer.cash => NameEnforcer.cash} (90%) rename contracts/{DomainOwnershipGuard.cash => OwnershipGuard.cash} (63%) delete mode 100644 lib/compiled/AuctionConflictResolver.ts delete mode 100644 lib/compiled/AuctionNameEnforcer.ts create mode 100644 lib/compiled/ConflictResolver.ts delete mode 100644 lib/compiled/Domain.ts delete mode 100644 lib/compiled/DomainFactory.ts delete mode 100644 lib/compiled/DomainOwnershipGuard.ts create mode 100644 lib/compiled/Factory.ts create mode 100644 lib/compiled/Name.ts create mode 100644 lib/compiled/NameEnforcer.ts create mode 100644 lib/compiled/OwnershipGuard.ts diff --git a/README.md b/README.md index 2758469..69e276a 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ The Registry contract functions as the control and storage hub. Operational, Gua This contract holds [RegistrationNFTs](#registrationnfts), [AuctionNFTs](#auctionnft), and [AuthorizedThreadNFTs](#authorizedthreadnfts). Constructor: -- `domainCategory`: The category of the domain. All the NFTs in the system belong to this category. +- `nameCategory`: The category of the domain. All the NFTs in the system belong to this category. Transaction Structure: | # | Inputs | Outputs | @@ -253,7 +253,7 @@ The Domain contract allows the owner to perform a few operations after [DomainNF Constructor: - `inactivityExpiryTime`: The time after which the domain is considered abandoned. - `name`: The name of the domain. -- `domainCategory`: The category of the domain. +- `nameCategory`: The category of the domain. There are 3 functions in each Domain Contract: @@ -318,11 +318,11 @@ The contracts talk to each other through cashtokens. There are 4 types in this s #### RegistrationNFTs A pair of minting NFTs that exist as UTXOs within the [Registry.cash](#registry) contract, consisting of: - **CounterNFT**: This minting hybrid NFT has nftCommitment that starts from 0 and increments by 1 with each new registration. It is also initialized with the maximum possible token amount of `9223372036854775807` that interacts with [Auction.cash](#auction) to facilitate the creation of new auction NFTs. Based on the value of the new registrationID from it's own commitment, the new minted AuctionNFT gets the exact tokenAmount. [FAQ](#what-if-the-tokenamount-in-the-counternft-runs-out) - - `category`: domainCategory + - `category`: nameCategory - `commitment`: registrationID < 8 bytes > - `tokenAmount`: Keeps reducing with each new registration. - **DomainMintingNFT**: A minting NFT that works with [DomainFactory.cash](#domainfactory) to issue new Domain NFTs. This has no nftCommitment or tokenAmount. - - `category`: domainCategory + - `category`: nameCategory #### AuctionNFT A mutable hybrid NFT created for each new auction that remains within [Registry.cash](#registry), containing comprehensive auction information through the following attributes: @@ -330,14 +330,14 @@ A mutable hybrid NFT created for each new auction that remains within [Registry. - `tokenAmount`: This represents the registrationID - `capability`: Mutable - `satoshis`: The latest bid amount - - `category`: The designated domainCategory + - `category`: The designated nameCategory A new bid simply updates the `pkh` in the `nftCommitment` and updates the `satoshisValue` to the new amount. #### AuthorizedThreadNFTs Each authorized contract's lockingbytecode(Excluding [Domain.cash](#domain)) is added to an immutable NFT commitment and sent to the [Registry.cash](#registry) at the time of genesis. These immutable NFTs stay with `Registry.cash` forever. Any interaction with the registry must include one of these thread NFTs to create a transaction. Structure: - - `category`: domainCategory + - `category`: nameCategory - `commitment`: lockingbytecode of authorized contract <35 bytes> The Registry Contract has a designated number of threads for authorized contracts: @@ -355,15 +355,15 @@ x = number of threads [The exact value can be anything. It must be decided at th #### DomainNFTs A set of 3 immutable NFTs minted when an auction ends: - **OwnershipNFT**: This NFT proves ownership of a specific domain. - - `category`: domainCategory + - `category`: nameCategory - `commitment`: registrationID < 8 bytes > + name < bytes > - **InternalAuthNFT**: A specialized authorization NFT that resides within the Domain contract and must be used together with the OwnershipNFT to enable the owner's interaction with [Domain.cash](#domain). - - `category`: domainCategory + - `category`: nameCategory - `commitment`: registrationID < 8 bytes > - **ExternalAuthNFT**: A specialized authorization NFT that resides within the Domain Contract but can be attached to any transaction, particularly utilized by [DomainOwnershipGuard.cash](#domainownershipguard) to prove existing domain ownership and enforce penalties on illegal auction attempts. - - `category`: domainCategory + - `category`: nameCategory If the domain has been inactive for > `inactivityExpiryTime` then the domain is considered abandoned and anyone can prove the inactivity and burn the Internal and External Auth NFTs to make the domain available for auction. @@ -372,15 +372,15 @@ If the domain has been inactive for > `inactivityExpiryTime` then the domain is Top Level Domains (TLDs) like `.bch` and `.sat` do not exist within the contract system directly as a `value`. The names as part of the commitment in any of the NFTs in the system do not have the TLD in them. Instead, it exists in the AuthChain. This is done to allow bigger names and reduce the contract size and complexity. -During the genesis phase, the Registry.cash contract is initialized with the `domainCategory`. The `authHead` for this category must include the symbol and name as the TLD, making it accessible to all applications. This entry will be the first and only one in the `authChain`. After this step, the `authHead` must be permanently removed by creating an OP_RETURN output as the first output. +During the genesis phase, the Registry.cash contract is initialized with the `nameCategory`. The `authHead` for this category must include the symbol and name as the TLD, making it accessible to all applications. This entry will be the first and only one in the `authChain`. After this step, the `authHead` must be permanently removed by creating an OP_RETURN output as the first output. ## Genesis To ensure the system operates as expected, the following steps must be followed : -- Mint a new hybrid token with an NFT commitment set to 0 (8 bytes) and the maximum possible token amount of `9223372036854775807`, the tokenCategory of this NFT will be `domainCategory`. -- Using the `tokenCategory` i.e domainCategory, create the locking bytecode for `Registry.cash`. +- Mint a new hybrid token with an NFT commitment set to 0 (8 bytes) and the maximum possible token amount of `9223372036854775807`, the tokenCategory of this NFT will be `nameCategory`. +- Using the `tokenCategory` i.e nameCategory, create the locking bytecode for `Registry.cash`. - Mint a mintingNFT i.e `DomainMintingNFT` and send it to the `Registry.cash` - Determine the following parameters and generate the locking bytecode of all the other authorized contracts: - `inactivityExpiryTime` diff --git a/contracts/Accumulator.cash b/contracts/Accumulator.cash index 7a31511..cd21daa 100644 --- a/contracts/Accumulator.cash +++ b/contracts/Accumulator.cash @@ -1,8 +1,8 @@ -pragma cashscript 0.11.2; +pragma cashscript 0.11.3; contract Accumulator() { /** - * Once enough auctions have happened, there might come a time when the counterNFT's tokenAmount is not enough. + * Once enough auctions have happened, there will come a time when the counterNFT's tokenAmount is not enough. * Since the amount would be accumulating in the thread NFTs, this function can be used to transfer them back to the * Counter NFT to keep the system functioning smoothly. * @@ -51,13 +51,14 @@ contract Accumulator() { bytes counterCategory, bytes counterCapability = tx.inputs[2].tokenCategory.split(32); require(counterCategory == registryInputCategory); - require(counterCapability == 0x02); // Minting + // Minting + require(counterCapability == 0x02); // Locking bytecode of the authorized contract is 35 bytes long. require(tx.inputs[3].nftCommitment.length == 35); // Since the nftCommitment of counterNFT is registrationID so it must not be null - // as the DomainMintingNFT has no nftCommitment nor tokenAmount + // as the NameMintingNFT has no nftCommitment nor tokenAmount require(tx.inputs[2].nftCommitment != 0x); require(tx.inputs[2].tokenAmount > 0); // Ensure that the counter minting NFT is used. require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount + tx.inputs[3].tokenAmount); diff --git a/contracts/Auction.cash b/contracts/Auction.cash index 8956f30..3dae309 100644 --- a/contracts/Auction.cash +++ b/contracts/Auction.cash @@ -1,11 +1,11 @@ -pragma cashscript 0.11.2; +pragma cashscript 0.11.3; /** * @param minStartingBid The minimum starting bid for the auction. */ contract Auction(int minStartingBid) { /** - * Starts a new domain registration auction. + * Starts a new name registration auction. * @param name The name being registered. * * The function creates a new auction with: @@ -38,7 +38,7 @@ contract Auction(int minStartingBid) { // This contract can only be used at input1 and it should return the input1 back to itself. require(this.activeInputIndex == 1); require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode); - // Ensure that the domainCategory in not minted here. + // Ensure that the nameCategory in not minted here. require(tx.outputs[this.activeInputIndex].tokenCategory == 0x); // This contract can only be used with the 'lockingbytecode' used in the 0th input. @@ -60,8 +60,16 @@ contract Auction(int minStartingBid) { // tokenAmount in the auctionNFT is the registrationId. require(tx.outputs[3].tokenAmount == nextRegistrationId); - // Every auction begins with a min base value of at least minStartingBid satoshis. - require(tx.outputs[3].value >= minStartingBid); + // Dual Decay mechanism, auction price decays linearly with the step. + // 1. Decay percentage to the step 0.0003% + int decayPercentageToTheStep = nextRegistrationId * 3 / 1000000; + // 2. Get auction price for current step with linear decay + int currentAuctionPrice = minStartingBid * (1 - decayPercentageToTheStep); + // Set the minimum auction price to 20000 satoshis. + currentAuctionPrice = max(currentAuctionPrice, 20000); + + // Every auction begins with a min base value of at least currentAuctionPrice satoshis. + require(tx.outputs[3].value >= currentAuctionPrice); // Funding UTXO/ Bid UTXO require(tx.inputs[3].tokenCategory == 0x); @@ -73,21 +81,27 @@ contract Auction(int minStartingBid) { bytes pkh = tx.inputs[3].lockingBytecode.split(3)[1].split(20)[0]; require(tx.outputs[3].nftCommitment == pkh + name); + // Ensure that the name is not too long, as of 2025 upgrade, the nftcommitment is 40 bytes. + // 20 bytes pkh + 16 bytes name + 4 bytes TLD + require(name.length <= 16); + // CounterNFT should keep the same category and capability. require(tx.outputs[2].tokenCategory == tx.inputs[2].tokenCategory); // All the token categories in the transaction should be the same. bytes registryInputCategory = tx.inputs[0].tokenCategory; - // CounterNFT should be minting and of the 'domainCategory' i.e registryInputCategory + // CounterNFT should be minting and of the 'nameCategory' i.e registryInputCategory bytes counterCategory, bytes counterCapability = tx.outputs[2].tokenCategory.split(32); require(counterCategory == registryInputCategory); - require(counterCapability == 0x02); // Minting + // Minting + require(counterCapability == 0x02); - // AuctionNFT should be mutable and of the 'domainCategory' i.e registryInputCategory + // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory bytes auctionCategory, bytes auctionCapability = tx.outputs[3].tokenCategory.split(32); require(auctionCategory == registryInputCategory); - require(auctionCapability == 0x01); // Mutable + // Mutable + require(auctionCapability == 0x01); // Enforce an OP_RETURN output that contains the name. require(tx.outputs[4].lockingBytecode == new LockingBytecodeNullData([name])); diff --git a/contracts/Bid.cash b/contracts/Bid.cash index 64db089..a0470f9 100644 --- a/contracts/Bid.cash +++ b/contracts/Bid.cash @@ -1,11 +1,11 @@ -pragma cashscript 0.11.2; +pragma cashscript 0.11.3; /** * @param minBidIncreasePercentage The minimum percentage increase required for a new bid over the previous bid. */ contract Bid(int minBidIncreasePercentage) { /** - * Places a new bid on an active domain registration auction. + * Places a new bid on an active name registration auction. * * The function allows placing a new bid with: * - A minimum `minBidIncreasePercentage` increase over the previous bid. diff --git a/contracts/AuctionConflictResolver.cash b/contracts/ConflictResolver.cash similarity index 98% rename from contracts/AuctionConflictResolver.cash rename to contracts/ConflictResolver.cash index 6215a76..0a59c7e 100644 --- a/contracts/AuctionConflictResolver.cash +++ b/contracts/ConflictResolver.cash @@ -1,6 +1,6 @@ -pragma cashscript 0.11.2; +pragma cashscript 0.11.3; -contract AuctionConflictResolver() { +contract ConflictResolver() { /** * Resolves a conflict between two competing registration auctions for the same name. * diff --git a/contracts/DomainFactory.cash b/contracts/Factory.cash similarity index 57% rename from contracts/DomainFactory.cash rename to contracts/Factory.cash index 0e9de61..1feeff9 100644 --- a/contracts/DomainFactory.cash +++ b/contracts/Factory.cash @@ -1,21 +1,23 @@ -pragma cashscript 0.11.2; +pragma cashscript 0.11.3; /** - * @param domainContractBytecode - Partial bytecode of the domain contract + * @param nameContractBytecode - Partial bytecode of the name contract * @param minWaitTime - Minimum wait time to consider an auction ended - * @param maxPlatformFeePercentage - Maximum platform fee percentage + * @param creatorIncentivePKH - PKH of the creator incentive + * @param tld - TLD of the name */ -contract DomainFactory( - bytes domainContractBytecode, +contract Factory( + bytes nameContractBytecode, int minWaitTime, - int maxPlatformFeePercentage + bytes20 creatorIncentivePKH, + bytes tld ) { /** - * This function finalizes a domain registration auction by: + * This function finalizes a name registration auction by: * - Verifying the auction has ended and the winner's bid is valid - * - Issuing an immutable externalAuthNFT to the Domain Contract - * - Issuing an immutable internalAuthNFT to the Domain Contract - * - Issuing an immutable domain NFT to the auction winner + * - Issuing an immutable externalAuthNFT to the Name Contract + * - Issuing an immutable internalAuthNFT to the Name Contract + * - Issuing an immutable name NFT to the auction winner * - Distributing auction fees between the platform and miners * - Burning the auctionNFT * - Pure BCH input from bidder is used to prevent miners from taking away the funds from any or all transactions in the future. @@ -25,29 +27,27 @@ contract DomainFactory( * @inputs * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract * - Input1: Any input from this contract - * - Input2: DomainMintingNFT from the Registry Contract + * - Input2: NameMintingNFT from the Registry Contract * - Input3: auctionNFT from the Registry Contract - * - Input4: Pure BCH from bidder * * @outputs * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract. * - Output1: Input1 back to this contract without any change - * - Output2: DomainMintingNFT back to the Registry contract - * - Output3: External Auth NFT to the domain contract - * - Output4: Internal Auth NFT to the domain contract - * - Output5: Domain NFT to the auction winner - * - Output6: Pure BCH back to the bidder - * - Output7: Platform fee + * - Output2: NameMintingNFT back to the Registry contract + * - Output3: External Auth NFT to the name contract + * - Output4: Internal Auth NFT to the name contract + * - Output5: Name NFT to the auction winner + * - Output6: Platform fee [Reduces and the not included] * */ function call(){ - require(tx.inputs.length == 5); - require(tx.outputs.length == 8); + require(tx.inputs.length == 4); + require(tx.outputs.length <= 7); // This contract can only be used at input1 and it should return to itself. require(this.activeInputIndex == 1); require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode); - // Ensure that the domainCategory in not minted here. + // Ensure that the nameCategory in not minted here. require(tx.outputs[this.activeInputIndex].tokenCategory == 0x); // Strict value checks to ensure the platform and miner get fee. require(tx.inputs[this.activeInputIndex].value == tx.outputs[this.activeInputIndex].value); @@ -67,23 +67,25 @@ contract DomainFactory( require(tx.outputs[4].tokenCategory == registryInputCategory); require(tx.outputs[5].tokenCategory == registryInputCategory); - // DomainMintingNFT should be minting and of the 'domainCategory' i.e registryInputCategory - bytes domainMintingCategory, bytes domainMintingCapability = tx.inputs[2].tokenCategory.split(32); - require(domainMintingCategory == registryInputCategory); - require(domainMintingCapability == 0x02); // Mutable - // DomainMintingNFT should keep the same category and capability + // NameMintingNFT should be minting and of the 'nameCategory' i.e registryInputCategory + bytes nameMintingCategory, bytes nameMintingCapability = tx.inputs[2].tokenCategory.split(32); + require(nameMintingCategory == registryInputCategory); + // Minting + require(nameMintingCapability == 0x02); + // NameMintingNFT should keep the same category and capability require(tx.inputs[2].tokenCategory == tx.outputs[2].tokenCategory); - // AuctionNFT should be mutable and of the 'domainCategory' i.e registryInputCategory + // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory bytes auctionCategory, bytes auctionCapability = tx.inputs[3].tokenCategory.split(32); require(auctionCategory == registryInputCategory); - require(auctionCapability == 0x01); // Mutable + // Mutable + require(auctionCapability == 0x01); - // Enforce strict restrictions on DomainMintingNFT + // Enforce strict restrictions on NameMintingNFT require(tx.inputs[2].nftCommitment == tx.outputs[2].nftCommitment); - // DomainMintingNFT has no nftCommitment + // NameMintingNFT has no nftCommitment require(tx.outputs[2].nftCommitment == 0x); - // DomainMintingNFT has no tokenAmount + // NameMintingNFT has no tokenAmount require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount); require(tx.outputs[2].tokenAmount == 0); @@ -97,18 +99,18 @@ contract DomainFactory( // Extract the PKH and name from the auctionNFT bytes20 bidderPKH, bytes name = tx.inputs[3].nftCommitment.split(20); - // Get the name length to generate the complete bytecode of the domain contract + // Get the name length to generate the complete bytecode of the name contract int nameLength = name.length; // category + name + bytecode. - // Note: `inactivityExpiryTime` in the domain is already added to the domainContractBytecode in the constructor. - bytes domainBytecode = 0x20 + registryInputCategory + bytes(nameLength) + name + domainContractBytecode; - bytes32 scriptHash = hash256(domainBytecode); - bytes35 domainLockingBytecode = new LockingBytecodeP2SH32(scriptHash); + // Note: `inactivityExpiryTime` in the name is already added to the nameContractBytecode in the constructor. + bytes nameBytecode = 0x20 + registryInputCategory + bytes(nameLength) + name + tld + nameContractBytecode; + bytes32 scriptHash = hash256(nameBytecode); + bytes35 nameLockingBytecode = new LockingBytecodeP2SH32(scriptHash); - // ExternalAuthNFT goes to the domain contract - require(tx.outputs[3].lockingBytecode == domainLockingBytecode); - // InternalAuthNFT goes to the domain contract - require(tx.outputs[4].lockingBytecode == domainLockingBytecode); + // ExternalAuthNFT goes to the name contract + require(tx.outputs[3].lockingBytecode == nameLockingBytecode); + // InternalAuthNFT goes to the name contract + require(tx.outputs[4].lockingBytecode == nameLockingBytecode); // ExternalAuthNFT does not have any commitment require(tx.outputs[3].nftCommitment == 0x); @@ -122,29 +124,27 @@ contract DomainFactory( // Strict value check require(tx.outputs[4].value == 1000); - // Send the domain ownership NFT to the bidder + // Send the name ownership NFT to the bidder require(tx.outputs[5].nftCommitment == registrationId + name); require(tx.outputs[5].lockingBytecode == new LockingBytecodeP2PKH(bidderPKH)); require(tx.outputs[5].value == 1000); - // Ensure that the bidder receiving the domain ownership NFT is also receiving the pure BCH back - require(tx.inputs[4].lockingBytecode == tx.outputs[5].lockingBytecode); - require(tx.inputs[4].lockingBytecode == tx.outputs[6].lockingBytecode); - // Ensure that the value of input from bidder is the same and goes back to the bidder - require(tx.inputs[4].value == tx.outputs[6].value); - - // Ensure that input and output to the bidder does not have any tokenCategory - require(tx.inputs[4].tokenCategory == 0x); - require(tx.outputs[6].tokenCategory == 0x); - // tokenAmount from the auctionNFT goes to the authorizedThreadNFT to be accumulated later // and merged back with the CounterNFT using the `Accumulator` Contract require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[3].tokenAmount); - // Output can be added by anyone (Mainly platforms) - require(tx.outputs[7].tokenCategory == 0x); - // Enforce that the other piece of the fee goes to the miners. - require(tx.outputs[7].value <= (tx.inputs[3].value / 100) * maxPlatformFeePercentage); + // Dual Decay mechanism, creator incentive decays linearly with the step. + // 1. Decay percentage to the step 0.001% + int decayPercentageToTheStep = tx.inputs[3].tokenAmount * 1 / 10000; + // 2. Get creator incentive for current step with linear decay + int creatorIncentive = tx.inputs[3].tokenAmount * (1 - decayPercentageToTheStep); + + if(creatorIncentive > 20000) { + require(tx.outputs[6].tokenCategory == 0x); + // Enforce that the other piece of the fee goes to the miners. + require(tx.outputs[6].value == creatorIncentive); + require(tx.outputs[6].lockingBytecode == new LockingBytecodeP2PKH(creatorIncentivePKH)); + } } } \ No newline at end of file diff --git a/contracts/Domain.cash b/contracts/Name.cash similarity index 71% rename from contracts/Domain.cash rename to contracts/Name.cash index 185bdab..a911576 100644 --- a/contracts/Domain.cash +++ b/contracts/Name.cash @@ -1,21 +1,21 @@ -pragma cashscript 0.11.2; +pragma cashscript 0.11.3; /** - * @param inactivityExpiryTime The time period after which the domain is considered inactive. - * @param name The name of the domain. - * @param domainCategory The category of the domain. + * @param inactivityExpiryTime The time period after which the name is considered inactive. + * @param fullName The full name of the name including the TLD. . + * @param nameCategory The category of the name. */ -contract Domain( +contract Name( int inactivityExpiryTime, - bytes name, - bytes domainCategory + bytes fullName, + bytes nameCategory ) { /** * This function can be used to perform a variety of actions. * * For example: - * - It can be used to prove the the ownership of the domain by other contracts. + * - It can be used to prove the the ownership of the name by other contracts. * - This function allows the owner to perform any actions in conjunction with other contracts. * - This function can be used to add records and invalidate multiple records in a single transaction. * @@ -25,11 +25,11 @@ contract Domain( * * @inputs * - Inputx: Internal/External Auth NFT - * - Inputx+1 (optional): Domain ownership NFT from the owner + * - Inputx+1 (optional): Name ownership NFT from the owner * * @outputs * - Outputx: Internal/External Auth NFT returned to this contract - * - Outputx+1 (optional): Domain NFT returned + * - Outputx+1 (optional): Name NFT returned * */ function useAuth(int authID) { @@ -39,27 +39,33 @@ contract Domain( // The activeInputIndex can be anything as long as the utxo properties are preserved and comes back to the // contract without alteration. require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode); - require(tx.inputs[this.activeInputIndex].tokenCategory == domainCategory); - require(tx.outputs[this.activeInputIndex].tokenCategory == domainCategory); + require(tx.inputs[this.activeInputIndex].tokenCategory == nameCategory); + require(tx.outputs[this.activeInputIndex].tokenCategory == nameCategory); require(tx.inputs[this.activeInputIndex].nftCommitment == tx.outputs[this.activeInputIndex].nftCommitment); if(authID == 1) { // The next input from the InternalAuthNFT must be the ownershipNFT. - require(tx.inputs[this.activeInputIndex + 1].tokenCategory == domainCategory); + require(tx.inputs[this.activeInputIndex + 1].tokenCategory == nameCategory); bytes registrationId, bytes nameFromOwnerNFT = tx.inputs[this.activeInputIndex + 1].nftCommitment.split(8); - require(nameFromOwnerNFT == name); + require(nameFromOwnerNFT == fullName); require(tx.inputs[this.activeInputIndex].nftCommitment == registrationId); } else { - // One known use of ExternalAuthNFT in the `DomainOwnershipGuard` contract. ExternalAuthNFT is + // One known use of ExternalAuthNFT in the `NameOwnershipGuard` contract. ExternalAuthNFT is // used to prove that an owner exists. require(tx.inputs[this.activeInputIndex].nftCommitment == 0x); } } + function penaliseInvalidName() { + // Allow anyone to call only when the name registered is invalid, and the incentive system was not able to prevent it. + + require(tx.version == 2); + } + /** - * If the incentive system fails, i.e `DomainOwnershipGuard` or `AuctionConflictResolver` fails to prevent a - * a owner conflict. When this happens there will be > 1 owner for this domain. - * The owner with the lowest registrationID must be the only owner for this domain. + * If the incentive system fails, i.e `NameOwnershipGuard` or `AuctionConflictResolver` fails to prevent a + * a owner conflict. When this happens there will be > 1 owner for this name. + * The owner with the lowest registrationID must be the only owner for this name. * To help enforce this rule, this function will allow anyone to burn both the Auth NFTs of the NEW owner. * * @inputs @@ -102,21 +108,21 @@ contract Domain( require(tx.outputs[0].nftCommitment == 0x); require(tx.outputs[1].nftCommitment == tx.inputs[1].nftCommitment); - // Ensure that all the token inputs and outputs have domainCategory - require(tx.inputs[0].tokenCategory == domainCategory); - require(tx.inputs[1].tokenCategory == domainCategory); - require(tx.inputs[2].tokenCategory == domainCategory); - require(tx.inputs[3].tokenCategory == domainCategory); + // Ensure that all the token inputs and outputs have nameCategory + require(tx.inputs[0].tokenCategory == nameCategory); + require(tx.inputs[1].tokenCategory == nameCategory); + require(tx.inputs[2].tokenCategory == nameCategory); + require(tx.inputs[3].tokenCategory == nameCategory); - require(tx.outputs[0].tokenCategory == domainCategory); - require(tx.outputs[1].tokenCategory == domainCategory); + require(tx.outputs[0].tokenCategory == nameCategory); + require(tx.outputs[1].tokenCategory == nameCategory); // Compare the registrationID require(int(tx.inputs[1].nftCommitment.reverse()) < int(tx.inputs[3].nftCommitment.reverse())); } /** - * Allows the domain owner or anyone to burn the InternalAuthNFT and externalAuthNFT making this domain available + * Allows the name owner or anyone to burn the InternalAuthNFT and externalAuthNFT making this name available * for auction. * * - Owner can burn the AuthNFTs anytime. @@ -125,7 +131,7 @@ contract Domain( * @inputs * - Input0: External Auth NFT * - Input1: Internal Auth NFT - * - Input2: Pure BCH or Domain ownership NFT from the owner + * - Input2: Pure BCH or Name ownership NFT from the owner * * @outputs * - Output0: BCH change @@ -144,9 +150,9 @@ contract Domain( // If pure BCH input, then allow anyone to burn given the time limit has passed. require(tx.inputs[1].sequenceNumber == inactivityExpiryTime); } else { - // If domain ownership NFT input, then allow the owner to burn anytime. - require(tx.inputs[2].tokenCategory == domainCategory); - // Make sure that the registrationID in the domainOwnershipNFT and the internalAuthNFT are the same. + // If name ownership NFT input, then allow the owner to burn anytime. + require(tx.inputs[2].tokenCategory == nameCategory); + // Make sure that the registrationID in the nameOwnershipNFT and the internalAuthNFT are the same. require(tx.inputs[2].nftCommitment.split(8)[0] == tx.inputs[0].nftCommitment); } @@ -158,8 +164,8 @@ contract Domain( require(tx.inputs[0].nftCommitment == 0x); // Both InternalAuthNFT and externalAuthNFT are immutable and have the same tokenCategory require(tx.inputs[0].tokenCategory == tx.inputs[1].tokenCategory); - require(tx.inputs[0].tokenCategory == domainCategory); - require(tx.inputs[1].tokenCategory == domainCategory); + require(tx.inputs[0].tokenCategory == nameCategory); + require(tx.inputs[1].tokenCategory == nameCategory); // Return the BCH as change. require(tx.outputs[0].tokenCategory == 0x); diff --git a/contracts/AuctionNameEnforcer.cash b/contracts/NameEnforcer.cash similarity index 90% rename from contracts/AuctionNameEnforcer.cash rename to contracts/NameEnforcer.cash index 0965cb6..95e722f 100644 --- a/contracts/AuctionNameEnforcer.cash +++ b/contracts/NameEnforcer.cash @@ -1,8 +1,8 @@ -pragma cashscript 0.11.2; +pragma cashscript 0.11.3; -contract AuctionNameEnforcer() { +contract NameEnforcer() { /** - * Proves that a domain name contains invalid characters, burns the auctionNFT, and takes away the funds as a reward. + * Proves that a name contains invalid characters, burns the auctionNFT, and takes away the funds as a reward. * During the entire auction, this can be called at any time by anyone. * * Rules: @@ -39,7 +39,7 @@ contract AuctionNameEnforcer() { // All the token categories in the transaction should be the same. bytes registryInputCategory = tx.inputs[0].tokenCategory; - // AuctionNFT should be mutable and of the 'domainCategory' i.e registryInputCategory + // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory bytes auctionCategory, bytes auctionCapability = tx.inputs[2].tokenCategory.split(32); require(auctionCategory == registryInputCategory); require(auctionCapability == 0x01); // Mutable diff --git a/contracts/DomainOwnershipGuard.cash b/contracts/OwnershipGuard.cash similarity index 63% rename from contracts/DomainOwnershipGuard.cash rename to contracts/OwnershipGuard.cash index b9b09d0..bb6bed5 100644 --- a/contracts/DomainOwnershipGuard.cash +++ b/contracts/OwnershipGuard.cash @@ -1,27 +1,28 @@ -pragma cashscript 0.11.2; +pragma cashscript 0.11.3; /** - * @param domainContractBytecode The the partial bytecode of the domain contract that has an Owner.. + * @param nameContractBytecode The the partial bytecode of the name contract that has an Owner. + * @param tld - TLD of the name */ -contract DomainOwnershipGuard(bytes domainContractBytecode) { +contract OwnershipGuard(bytes nameContractBytecode, bytes tld) { /** - * If the domain being auctioned already has an `externalAuthNFT` with the same category, then the auction is invalid. - * Because it means that an owner still exists. If it is known that the domain has been abandoned for > `inactivityExpiryTime` - * then one must use the `burn` method of the domain.cash to burn the internalAuthNFT and externalAuthNFT making the - * domain to be available for auction. + * If the name being auctioned already has an `externalAuthNFT` with the same category, then the auction is invalid. + * Because it means that an owner still exists. If it is known that the name has been abandoned for > `inactivityExpiryTime` + * then one must use the `burn` method of the name.cash to burn the internalAuthNFT and externalAuthNFT making the + * name to be available for auction. * - * Penalizes invalid domain registrations by allowing anyone to burn the auctionNFT and claim the funds as a reward. + * Penalizes invalid name registrations by allowing anyone to burn the auctionNFT and claim the funds as a reward. * * @inputs * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract * - Input1: Any input from this contract - * - Input2: External Auth NFT from the Domain Contract + * - Input2: External Auth NFT from the Name Contract * - Input3: auctionNFT from Registry Contract * * @outputs * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract. * - Output1: Input1 back to this contract without any change - * - Output2: External Auth NFT back to the Domain Contract + * - Output2: External Auth NFT back to the Name Contract * - Output3: BCH change/reward to caller */ function call(){ @@ -43,29 +44,30 @@ contract DomainOwnershipGuard(bytes domainContractBytecode) { require(tx.inputs[2].tokenCategory == registryInputCategory); require(tx.outputs[2].tokenCategory == registryInputCategory); - // AuctionNFT should be mutable and of the 'domainCategory' i.e registryInputCategory + // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory bytes auctionCategory, bytes auctionCapability = tx.inputs[3].tokenCategory.split(32); require(auctionCategory == registryInputCategory); - require(auctionCapability == 0x01); // Mutable + // Mutable + require(auctionCapability == 0x01); // nftCommiment of the externalAuthNFT must stay the same require(tx.inputs[2].nftCommitment == tx.outputs[2].nftCommitment); // Ensure that the externalAuth NFT is used and not the internalAuth NFT. require(tx.inputs[2].nftCommitment == 0x); - // Get the name of the domain from the auctionNFT + // Get the name of the name from the auctionNFT bytes name = tx.inputs[3].nftCommitment.split(20)[1]; - // Get the name length to generate the complete bytecode of the domain contract + // Get the name length to generate the complete bytecode of the name contract int nameLength = name.length; // category + name + bytecode. - // Note: `inactivityExpiryTime` in the domain is already added to the domainContractBytecode in the constructor. - bytes domainBytecode = 0x20 + registryInputCategory + bytes(nameLength) + name + domainContractBytecode; - bytes32 scriptHash = hash256(domainBytecode); - bytes35 domainLockingBytecode = new LockingBytecodeP2SH32(scriptHash); + // Note: `inactivityExpiryTime` in the name is already added to the nameContractBytecode in the constructor. + bytes nameBytecode = 0x20 + registryInputCategory + bytes(nameLength + tld.length) + name + tld + nameContractBytecode; + bytes32 scriptHash = hash256(nameBytecode); + bytes35 nameLockingBytecode = new LockingBytecodeP2SH32(scriptHash); - // Ensure that the externalAuthNFT is coming from the correct Domain Contract - require(tx.inputs[2].lockingBytecode == domainLockingBytecode); - require(tx.outputs[2].lockingBytecode == domainLockingBytecode); + // Ensure that the externalAuthNFT is coming from the correct Name Contract + require(tx.inputs[2].lockingBytecode == nameLockingBytecode); + require(tx.outputs[2].lockingBytecode == nameLockingBytecode); // tokenAmount from the auctionNFT goes to the authorizedThreadNFT to be accumulated later // and merged back with the CounterNFT using the `Accumulator` Contract diff --git a/contracts/Registry.cash b/contracts/Registry.cash index 58fdc35..3d2d52f 100644 --- a/contracts/Registry.cash +++ b/contracts/Registry.cash @@ -1,18 +1,18 @@ -pragma cashscript 0.11.2; +pragma cashscript 0.11.3; /** - * @param domainCategory - The category of the domain NFTs that are authorized to be registered. [In reverse order] + * @param nameCategory - The category of the name NFTs that are authorized to be registered. [In reverse order] * * The Registry has two minting NFTs: * 1. CounterMintingNFT, has tokenAmount and nftCommitment. - * 2. DomainMintingNFT, does not have any tokenAmount or nftCommitment. + * 2. NameMintingNFT, does not have any tokenAmount or nftCommitment. */ -contract Registry(bytes domainCategory) { +contract Registry(bytes nameCategory) { /** * The Registry contract serves as both a source and storage for authorized NFTs. * It holds: RegistrationNFTs, AuctionNFTs, and AuthorizedThreadNFTs * - * AuthorizedThreadNFTs are NFTs with immutable capability that share the same category as domainCategory. + * AuthorizedThreadNFTs are NFTs with immutable capability that share the same category as nameCategory. * These NFTs contain the lockingBytecode of authorized contracts. * Multiple copies of these NFTs enable parallel processing through multiple threads. * @@ -24,15 +24,15 @@ contract Registry(bytes domainCategory) { * for a given action. To use the code in these authorized contracts, a random UTXO is used and * sent back to itself to be used again in future. * - * All the utxos, except for the DomainNFTs (InternalAuth, ExternalAuth and DomainOwnershipNFT), + * All the utxos, except for the NameNFTs (InternalAuth, ExternalAuth and NameOwnershipNFT), * stay with the Registry contract. * * @note Authorized contracts and their thread counts: * - Auction: [1 thread] (Single-threaded registration) * - Bid: [~x threads] - * - DomainFactory: [~x threads] + * - NameFactory: [~x threads] * - AuctionNameEnforcer: [~x threads] - * - DomainOwnershipGuard: [~x threads] + * - NameOwnershipGuard: [~x threads] * - AuctionConflictResolver: [~x threads] * - Accumulator: [~x threads] * @@ -57,11 +57,11 @@ contract Registry(bytes domainCategory) { require(tx.inputs[0].lockingBytecode == selfLockingBytecode); require(tx.outputs[0].lockingBytecode == selfLockingBytecode); - // Immutable NFTs of domainCategory in Registry Contract will always be authorizedThreadNFTs - // Mutable NFTs of domainCategory in Registry Contract will always be auctionNFTs - // Minting NFTs of domainCategory in Registry Contract will always be counterMintingNFT or DomainMintingNFT - require(tx.inputs[0].tokenCategory == domainCategory); - require(tx.outputs[0].tokenCategory == domainCategory); + // Immutable NFTs of nameCategory in Registry Contract will always be authorizedThreadNFTs + // Mutable NFTs of nameCategory in Registry Contract will always be auctionNFTs + // Minting NFTs of nameCategory in Registry Contract will always be counterMintingNFT or NameMintingNFT + require(tx.inputs[0].tokenCategory == nameCategory); + require(tx.outputs[0].tokenCategory == nameCategory); // Keeping the value same to not influence any satoshi movement in authorized contracts require(tx.outputs[0].value == tx.inputs[0].value); // The commitment that has the lockingbytecode of the authorized contract should never change. diff --git a/lib/compiled/Accumulator.ts b/lib/compiled/Accumulator.ts index 9badcea..a9434ce 100644 --- a/lib/compiled/Accumulator.ts +++ b/lib/compiled/Accumulator.ts @@ -8,10 +8,10 @@ export default { }, ], 'bytecode': 'OP_TXINPUTCOUNT OP_5 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_5 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY OP_3 OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_3 OP_UTXOTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_3 OP_UTXOTOKENCOMMITMENT OP_SIZE OP_NIP 23 OP_NUMEQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_0 OP_EQUAL OP_NOT OP_VERIFY OP_2 OP_UTXOTOKENAMOUNT OP_0 OP_GREATERTHAN OP_VERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT OP_ADD OP_NUMEQUALVERIFY OP_4 OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_4 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUAL', - 'source': "pragma cashscript 0.11.2;\n\ncontract Accumulator() {\n /**\n * Once enough auctions have happened, there might come a time when the counterNFT's tokenAmount is not enough.\n * Since the amount would be accumulating in the thread NFTs, this function can be used to transfer them back to the\n * Counter NFT to keep the system functioning smoothly.\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract\n * - Input2: Minting CounterNFT + tokenAmount from Registry Contract\n * - Input3: authorizedThreadNFT with tokenAmount from Registry Contract\n * - Input4: Pure BCH\n * \n * @outputs\n * - Output0: Registry Contract's thread NFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Minting CounterNFT back to the Registry contract + tokenAmount\n * - Output3: authorizedThreadNFT without tokenAmount back to the Registry contract\n * - Output4: Change BCH\n */\n function call(){\n require(tx.inputs.length == 5);\n require(tx.outputs.length == 5);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Restriction on output category is important as minting NFT is used in this transaction.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.inputs[3].lockingBytecode == registryInputLockingBytecode);\n\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[3].lockingBytecode == registryInputLockingBytecode);\n\n require(tx.outputs[2].tokenCategory == tx.inputs[2].tokenCategory);\n require(tx.outputs[3].tokenCategory == tx.inputs[3].tokenCategory);\n\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n \n // authorizedThreadNFTs are immutable\n require(tx.inputs[3].tokenCategory == registryInputCategory);\n \n bytes counterCategory, bytes counterCapability = tx.inputs[2].tokenCategory.split(32);\n require(counterCategory == registryInputCategory);\n require(counterCapability == 0x02); // Minting\n\n // Locking bytecode of the authorized contract is 35 bytes long.\n require(tx.inputs[3].nftCommitment.length == 35);\n\n // Since the nftCommitment of counterNFT is registrationID so it must not be null\n // as the DomainMintingNFT has no nftCommitment nor tokenAmount\n require(tx.inputs[2].nftCommitment != 0x);\n require(tx.inputs[2].tokenAmount > 0); // Ensure that the counter minting NFT is used.\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount + tx.inputs[3].tokenAmount);\n\n // Pure BCH input and output.\n require(tx.inputs[4].tokenCategory == 0x);\n require(tx.outputs[4].tokenCategory == 0x);\n }\n}", + 'source': "pragma cashscript 0.11.3;\n\ncontract Accumulator() {\n /**\n * Once enough auctions have happened, there will come a time when the counterNFT's tokenAmount is not enough.\n * Since the amount would be accumulating in the thread NFTs, this function can be used to transfer them back to the\n * Counter NFT to keep the system functioning smoothly.\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract\n * - Input2: Minting CounterNFT + tokenAmount from Registry Contract\n * - Input3: authorizedThreadNFT with tokenAmount from Registry Contract\n * - Input4: Pure BCH\n * \n * @outputs\n * - Output0: Registry Contract's thread NFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Minting CounterNFT back to the Registry contract + tokenAmount\n * - Output3: authorizedThreadNFT without tokenAmount back to the Registry contract\n * - Output4: Change BCH\n */\n function call(){\n require(tx.inputs.length == 5);\n require(tx.outputs.length == 5);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Restriction on output category is important as minting NFT is used in this transaction.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.inputs[3].lockingBytecode == registryInputLockingBytecode);\n\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[3].lockingBytecode == registryInputLockingBytecode);\n\n require(tx.outputs[2].tokenCategory == tx.inputs[2].tokenCategory);\n require(tx.outputs[3].tokenCategory == tx.inputs[3].tokenCategory);\n\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n \n // authorizedThreadNFTs are immutable\n require(tx.inputs[3].tokenCategory == registryInputCategory);\n \n bytes counterCategory, bytes counterCapability = tx.inputs[2].tokenCategory.split(32);\n require(counterCategory == registryInputCategory);\n // Minting\n require(counterCapability == 0x02);\n\n // Locking bytecode of the authorized contract is 35 bytes long.\n require(tx.inputs[3].nftCommitment.length == 35);\n\n // Since the nftCommitment of counterNFT is registrationID so it must not be null\n // as the NameMintingNFT has no nftCommitment nor tokenAmount\n require(tx.inputs[2].nftCommitment != 0x);\n require(tx.inputs[2].tokenAmount > 0); // Ensure that the counter minting NFT is used.\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount + tx.inputs[3].tokenAmount);\n\n // Pure BCH input and output.\n require(tx.inputs[4].tokenCategory == 0x);\n require(tx.outputs[4].tokenCategory == 0x);\n }\n}", 'debug': { 'bytecode': 'c3559dc4559dc0519dc0c7c0cd88c0d1008800c752c7788853c7788852cd788853cd8852d152ce8853d153ce8800ce53ce788852ce01207f7c7b88528853cf827701239d52cf0087916952d000a06952d352d053d0939d54ce008854d10087', - 'sourceMap': '24:12:24:28;:32::33;:4::35:1;25:12:25:29:0;:33::34;:4::36:1;28:12:28:33:0;:37::38;:4::40:1;29:22:29:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;31:23:31:44:0;:12::59:1;:63::65:0;:4::67:1;37:51:37:52:0;:41::69:1;38:22:38:23:0;:12::40:1;:44::72:0;:4::74:1;39:22:39:23:0;:12::40:1;:44::72:0;:4::74:1;41:23:41:24:0;:12::41:1;:45::73:0;:4::75:1;42:23:42:24:0;:12::41:1;:4::75;44:23:44:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;45:23:45:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;47:44:47:45:0;:34::60:1;50:22:50:23:0;:12::38:1;:42::63:0;:4::65:1;52:63:52:64:0;:53::79:1;:86::88:0;:53::89:1;53:12:53:27:0;:31::52;:4::54:1;54:33:54:37:0;:4::39:1;57:22:57:23:0;:12::38:1;:::45;;:49::51:0;:4::53:1;61:22:61:23:0;:12::38:1;:42::44:0;:12:::1;;:4::46;62:22:62:23:0;:12::36:1;:39::40:0;:12:::1;:4::42;63:23:63:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;66:22:66:23:0;:12::38:1;:42::44:0;:4::46:1;67:23:67:24:0;:12::39:1;:43::45:0;:4::47:1', + 'sourceMap': '24:12:24:28;:32::33;:4::35:1;25:12:25:29:0;:33::34;:4::36:1;28:12:28:33:0;:37::38;:4::40:1;29:22:29:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;31:23:31:44:0;:12::59:1;:63::65:0;:4::67:1;37:51:37:52:0;:41::69:1;38:22:38:23:0;:12::40:1;:44::72:0;:4::74:1;39:22:39:23:0;:12::40:1;:44::72:0;:4::74:1;41:23:41:24:0;:12::41:1;:45::73:0;:4::75:1;42:23:42:24:0;:12::41:1;:4::75;44:23:44:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;45:23:45:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;47:44:47:45:0;:34::60:1;50:22:50:23:0;:12::38:1;:42::63:0;:4::65:1;52:63:52:64:0;:53::79:1;:86::88:0;:53::89:1;53:12:53:27:0;:31::52;:4::54:1;55:33:55:37:0;:4::39:1;58:22:58:23:0;:12::38:1;:::45;;:49::51:0;:4::53:1;62:22:62:23:0;:12::38:1;:42::44:0;:12:::1;;:4::46;63:22:63:23:0;:12::36:1;:39::40:0;:12:::1;:4::42;64:23:64:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;67:22:67:23:0;:12::38:1;:42::44:0;:4::46:1;68:23:68:24:0;:12::39:1;:43::45:0;:4::47:1', 'logs': [], 'requires': [ { @@ -68,37 +68,37 @@ export default { }, { 'ip': 59, - 'line': 54, + 'line': 55, }, { 'ip': 65, - 'line': 57, + 'line': 58, }, { 'ip': 71, - 'line': 61, + 'line': 62, }, { 'ip': 76, - 'line': 62, + 'line': 63, }, { 'ip': 84, - 'line': 63, + 'line': 64, }, { 'ip': 88, - 'line': 66, + 'line': 67, }, { 'ip': 93, - 'line': 67, + 'line': 68, }, ], }, 'compiler': { 'name': 'cashc', - 'version': '0.11.2', + 'version': '0.11.3', }, - 'updatedAt': '2025-07-15T19:31:10.154Z', + 'updatedAt': '2025-07-26T12:09:11.355Z', }; diff --git a/lib/compiled/Auction.ts b/lib/compiled/Auction.ts index cca4253..5cd4e93 100644 --- a/lib/compiled/Auction.ts +++ b/lib/compiled/Auction.ts @@ -17,11 +17,11 @@ export default { ], }, ], - 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_REVERSEBYTES OP_BIN2NUM OP_2 OP_OUTPUTTOKENCOMMITMENT OP_REVERSEBYTES OP_BIN2NUM OP_DUP OP_ROT OP_1ADD OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_2 OP_PICK OP_SUB OP_NUMEQUALVERIFY OP_3 OP_OUTPUTTOKENAMOUNT OP_NUMEQUALVERIFY OP_3 OP_OUTPUTVALUE OP_LESSTHANOREQUAL OP_VERIFY OP_3 OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_SIZE OP_NIP 19 OP_NUMEQUALVERIFY OP_3 OP_UTXOBYTECODE OP_3 OP_SPLIT OP_NIP 14 OP_SPLIT OP_DROP OP_3 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_2 OP_PICK OP_CAT OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_4 OP_OUTPUTBYTECODE 6a OP_ROT OP_SIZE OP_DUP 4b OP_GREATERTHAN OP_IF 4c OP_SWAP OP_CAT OP_ENDIF OP_SWAP OP_CAT OP_CAT OP_EQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_NUMEQUAL OP_IF OP_5 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_1', - 'source': "pragma cashscript 0.11.2;\n\n/**\n * @param minStartingBid The minimum starting bid for the auction.\n */\ncontract Auction(int minStartingBid) {\n /**\n * Starts a new domain registration auction.\n * @param name The name being registered.\n * \n * The function creates a new auction with:\n * - Starting bid >= `minStartingBid` BCH.\n * - A successful registration initiation results in an auctionNFT representing the auction state:\n * - capability: (Mutable)\n * - category: registryInputCategory\n * - tokenAmount: (Represents the registrationId)\n * - satoshiValue: (Represents the bid amount)\n * - commitment: bidder's PKH (20 bytes) + name (bytes)\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: Minting CounterNFT from Registry contract (Increases the registrationId by 1 in the output).\n * - Input3: Funding UTXO.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Minting CounterNFT going back to the Registry contract.\n * - Output3: auctionNFT to the Registry contract.\n * - Output4: OP_RETURN output containing the name.\n * - Output5: Optional change in BCH.\n */\n function call(bytes name) {\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 6);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the domainCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[3].lockingBytecode == registryInputLockingBytecode);\n\n // Registration ID increases by 1 with each transaction.\n int prevRegistrationId = int(tx.inputs[2].nftCommitment.reverse());\n int nextRegistrationId = int(tx.outputs[2].nftCommitment.reverse());\n require(nextRegistrationId == prevRegistrationId + 1);\n\n // Reduce the tokenAmount in the counterNFT as some amount is going to auctionNFT\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount - nextRegistrationId);\n // tokenAmount in the auctionNFT is the registrationId.\n require(tx.outputs[3].tokenAmount == nextRegistrationId);\n\n // Every auction begins with a min base value of at least minStartingBid satoshis.\n require(tx.outputs[3].value >= minStartingBid);\n // Funding UTXO/ Bid UTXO\n require(tx.inputs[3].tokenCategory == 0x);\n\n // Ensure that the funding happens from a P2PKH UTXO.\n require(tx.inputs[3].lockingBytecode.length == 25);\n\n // Extract the PKH from the lockingBytecode of the Funding UTXO.\n // + name > 20 bytes\n bytes pkh = tx.inputs[3].lockingBytecode.split(3)[1].split(20)[0];\n require(tx.outputs[3].nftCommitment == pkh + name);\n\n // CounterNFT should keep the same category and capability.\n require(tx.outputs[2].tokenCategory == tx.inputs[2].tokenCategory);\n \n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n \n // CounterNFT should be minting and of the 'domainCategory' i.e registryInputCategory\n bytes counterCategory, bytes counterCapability = tx.outputs[2].tokenCategory.split(32);\n require(counterCategory == registryInputCategory);\n require(counterCapability == 0x02); // Minting\n\n // AuctionNFT should be mutable and of the 'domainCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.outputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n require(auctionCapability == 0x01); // Mutable\n\n // Enforce an OP_RETURN output that contains the name.\n require(tx.outputs[4].lockingBytecode == new LockingBytecodeNullData([name]));\n\n if (tx.outputs.length == 6) {\n // If any change, then it must be pure BCH.\n require(tx.outputs[5].tokenCategory == 0x);\n }\n }\n}", + 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_REVERSEBYTES OP_BIN2NUM OP_2 OP_OUTPUTTOKENCOMMITMENT OP_REVERSEBYTES OP_BIN2NUM OP_DUP OP_ROT OP_1ADD OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_2 OP_PICK OP_SUB OP_NUMEQUALVERIFY OP_3 OP_OUTPUTTOKENAMOUNT OP_OVER OP_NUMEQUALVERIFY OP_3 OP_MUL 40420f OP_DIV OP_SWAP OP_1 OP_ROT OP_SUB OP_MUL OP_DUP 204e OP_MAX OP_3 OP_OUTPUTVALUE OP_LESSTHANOREQUAL OP_VERIFY OP_3 OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_SIZE OP_NIP 19 OP_NUMEQUALVERIFY OP_3 OP_UTXOBYTECODE OP_3 OP_SPLIT OP_NIP 14 OP_SPLIT OP_DROP OP_3 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_3 OP_PICK OP_CAT OP_EQUALVERIFY OP_OVER OP_SIZE OP_NIP OP_16 OP_LESSTHANOREQUAL OP_VERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_4 OP_OUTPUTBYTECODE 6a OP_3 OP_ROLL OP_SIZE OP_DUP 4b OP_GREATERTHAN OP_IF 4c OP_SWAP OP_CAT OP_ENDIF OP_SWAP OP_CAT OP_CAT OP_EQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_NUMEQUAL OP_IF OP_5 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_DROP OP_1', + 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param minStartingBid The minimum starting bid for the auction.\n */\ncontract Auction(int minStartingBid) {\n /**\n * Starts a new name registration auction.\n * @param name The name being registered.\n * \n * The function creates a new auction with:\n * - Starting bid >= `minStartingBid` BCH.\n * - A successful registration initiation results in an auctionNFT representing the auction state:\n * - capability: (Mutable)\n * - category: registryInputCategory\n * - tokenAmount: (Represents the registrationId)\n * - satoshiValue: (Represents the bid amount)\n * - commitment: bidder's PKH (20 bytes) + name (bytes)\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: Minting CounterNFT from Registry contract (Increases the registrationId by 1 in the output).\n * - Input3: Funding UTXO.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Minting CounterNFT going back to the Registry contract.\n * - Output3: auctionNFT to the Registry contract.\n * - Output4: OP_RETURN output containing the name.\n * - Output5: Optional change in BCH.\n */\n function call(bytes name) {\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 6);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the nameCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[3].lockingBytecode == registryInputLockingBytecode);\n\n // Registration ID increases by 1 with each transaction.\n int prevRegistrationId = int(tx.inputs[2].nftCommitment.reverse());\n int nextRegistrationId = int(tx.outputs[2].nftCommitment.reverse());\n require(nextRegistrationId == prevRegistrationId + 1);\n\n // Reduce the tokenAmount in the counterNFT as some amount is going to auctionNFT\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount - nextRegistrationId);\n // tokenAmount in the auctionNFT is the registrationId.\n require(tx.outputs[3].tokenAmount == nextRegistrationId);\n\n // Dual Decay mechanism, auction price decays linearly with the step.\n // 1. Decay percentage to the step 0.0003%\n int decayPercentageToTheStep = nextRegistrationId * 3 / 1000000;\n // 2. Get auction price for current step with linear decay\n int currentAuctionPrice = minStartingBid * (1 - decayPercentageToTheStep);\n // Set the minimum auction price to 20000 satoshis.\n currentAuctionPrice = max(currentAuctionPrice, 20000);\n\n // Every auction begins with a min base value of at least currentAuctionPrice satoshis.\n require(tx.outputs[3].value >= currentAuctionPrice);\n // Funding UTXO/ Bid UTXO\n require(tx.inputs[3].tokenCategory == 0x);\n\n // Ensure that the funding happens from a P2PKH UTXO.\n require(tx.inputs[3].lockingBytecode.length == 25);\n\n // Extract the PKH from the lockingBytecode of the Funding UTXO.\n // + name > 20 bytes\n bytes pkh = tx.inputs[3].lockingBytecode.split(3)[1].split(20)[0];\n require(tx.outputs[3].nftCommitment == pkh + name);\n\n // Ensure that the name is not too long, as of 2025 upgrade, the nftcommitment is 40 bytes.\n // 20 bytes pkh + 16 bytes name + 4 bytes TLD\n require(name.length <= 16);\n\n // CounterNFT should keep the same category and capability.\n require(tx.outputs[2].tokenCategory == tx.inputs[2].tokenCategory);\n \n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n \n // CounterNFT should be minting and of the 'nameCategory' i.e registryInputCategory\n bytes counterCategory, bytes counterCapability = tx.outputs[2].tokenCategory.split(32);\n require(counterCategory == registryInputCategory);\n // Minting\n require(counterCapability == 0x02);\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.outputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n // Mutable\n require(auctionCapability == 0x01);\n\n // Enforce an OP_RETURN output that contains the name.\n require(tx.outputs[4].lockingBytecode == new LockingBytecodeNullData([name]));\n\n if (tx.outputs.length == 6) {\n // If any change, then it must be pure BCH.\n require(tx.outputs[5].tokenCategory == 0x);\n }\n }\n}", 'debug': { - 'bytecode': 'c3549dc456a169c0519dc0c7c0cd88c0d1008800c752c7788852cd788853cd8852cfbc8152d2bc81767b8b9d52d352d05279949d53d39d53cca16953ce008853c7827701199d53c7537f7701147f7553d27c52797e8852d152ce8800ce52d101207f7c527988528853d101207f7c7b88518854cd016a7b8276014ba063014c7c7e687c7e7e88c4569c6355d100886851', - 'sourceMap': '35:12:35:28;:32::33;:4::35:1;36:12:36:29:0;:33::34;:12:::1;:4::36;39:12:39:33:0;:37::38;:4::40:1;40:22:40:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;42:23:42:44:0;:12::59:1;:63::65:0;:4::67:1;48:51:48:52:0;:41::69:1;49:22:49:23:0;:12::40:1;:44::72:0;:4::74:1;50:23:50:24:0;:12::41:1;:45::73:0;:4::75:1;51:23:51:24:0;:12::41:1;:4::75;54:43:54:44:0;:33::59:1;:::69;:29::70;55:44:55:45:0;:33::60:1;:::70;:29::71;56:12:56:30:0;:34::52;:::56:1;:4::58;59:23:59:24:0;:12::37:1;:51::52:0;:41::65:1;:68::86:0;;:41:::1;:4::88;61:23:61:24:0;:12::37:1;:4::61;64:23:64:24:0;:12::31:1;:::49;:4::51;66:22:66:23:0;:12::38:1;:42::44:0;:4::46:1;69:22:69:23:0;:12::40:1;:::47;;:51::53:0;:4::55:1;73:26:73:27:0;:16::44:1;:51::52:0;:16::53:1;:::56;:63::65:0;:16::66:1;:::69;74:23:74:24:0;:12::39:1;:43::46:0;:49::53;;:43:::1;:4::55;77:23:77:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;80:44:80:45:0;:34::60:1;83:64:83:65:0;:53::80:1;:87::89:0;:53::90:1;84:12:84:27:0;:31::52;;:4::54:1;85:33:85:37:0;:4::39:1;88:64:88:65:0;:53::80:1;:87::89:0;:53::90:1;89:12:89:27:0;:31::52;:4::54:1;90:33:90:37:0;:4::39:1;93:23:93:24:0;:12::41:1;:45::80:0;:74::78;::::1;;;;;;;;;;;;:4::82;95:8:95:25:0;:29::30;:8:::1;:32:98:5:0;97:25:97:26;:14::41:1;:45::47:0;:6::49:1;95:32:98:5;34:2:99:3', + 'bytecode': 'c3549dc456a169c0519dc0c7c0cd88c0d1008800c752c7788852cd788853cd8852cfbc8152d2bc81767b8b9d52d352d05279949d53d3789d53950340420f967c517b94957602204ea453cca16953ce008853c7827701199d53c7537f7701147f7553d27c53797e8878827760a16952d152ce8800ce52d101207f7c527988528853d101207f7c7b88518854cd016a537a8276014ba063014c7c7e687c7e7e88c4569c6355d10088687551', + 'sourceMap': '35:12:35:28;:32::33;:4::35:1;36:12:36:29:0;:33::34;:12:::1;:4::36;39:12:39:33:0;:37::38;:4::40:1;40:22:40:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;42:23:42:44:0;:12::59:1;:63::65:0;:4::67:1;48:51:48:52:0;:41::69:1;49:22:49:23:0;:12::40:1;:44::72:0;:4::74:1;50:23:50:24:0;:12::41:1;:45::73:0;:4::75:1;51:23:51:24:0;:12::41:1;:4::75;54:43:54:44:0;:33::59:1;:::69;:29::70;55:44:55:45:0;:33::60:1;:::70;:29::71;56:12:56:30:0;:34::52;:::56:1;:4::58;59:23:59:24:0;:12::37:1;:51::52:0;:41::65:1;:68::86:0;;:41:::1;:4::88;61:23:61:24:0;:12::37:1;:41::59:0;:4::61:1;65:56:65:57:0;:35:::1;:60::67:0;:35:::1;67:30:67:44:0;:48::49;:52::76;:48:::1;:30::77;69::69:49:0;:51::56;:26::57:1;72:23:72:24:0;:12::31:1;:::54;:4::56;74:22:74:23:0;:12::38:1;:42::44:0;:4::46:1;77:22:77:23:0;:12::40:1;:::47;;:51::53:0;:4::55:1;81:26:81:27:0;:16::44:1;:51::52:0;:16::53:1;:::56;:63::65:0;:16::66:1;:::69;82:23:82:24:0;:12::39:1;:43::46:0;:49::53;;:43:::1;:4::55;86:12:86:16:0;:::23:1;;:27::29:0;:12:::1;:4::31;89:23:89:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;92:44:92:45:0;:34::60:1;95:64:95:65:0;:53::80:1;:87::89:0;:53::90:1;96:12:96:27:0;:31::52;;:4::54:1;98:33:98:37:0;:4::39:1;101:64:101:65:0;:53::80:1;:87::89:0;:53::90:1;102:12:102:27:0;:31::52;:4::54:1;104:33:104:37:0;:4::39:1;107:23:107:24:0;:12::41:1;:45::80:0;:74::78;;::::1;;;;;;;;;;;;:4::82;109:8:109:25:0;:29::30;:8:::1;:32:112:5:0;111:25:111:26;:14::41:1;:45::47:0;:6::49:1;109:32:112:5;34:2:113:3;', 'logs': [], 'requires': [ { @@ -65,58 +65,62 @@ export default { 'line': 59, }, { - 'ip': 55, + 'ip': 56, 'line': 61, }, { - 'ip': 59, - 'line': 64, + 'ip': 72, + 'line': 72, }, { - 'ip': 63, - 'line': 66, - }, - { - 'ip': 69, - 'line': 69, - }, - { - 'ip': 84, + 'ip': 76, 'line': 74, }, { - 'ip': 89, + 'ip': 82, 'line': 77, }, { - 'ip': 99, - 'line': 84, + 'ip': 97, + 'line': 82, }, { - 'ip': 101, - 'line': 85, + 'ip': 103, + 'line': 86, }, { 'ip': 108, 'line': 89, }, { - 'ip': 110, - 'line': 90, + 'ip': 118, + 'line': 96, + }, + { + 'ip': 120, + 'line': 98, }, { 'ip': 127, - 'line': 93, + 'line': 102, + }, + { + 'ip': 129, + 'line': 104, + }, + { + 'ip': 147, + 'line': 107, }, { - 'ip': 135, - 'line': 97, + 'ip': 155, + 'line': 111, }, ], }, 'compiler': { 'name': 'cashc', - 'version': '0.11.2', + 'version': '0.11.3', }, - 'updatedAt': '2025-07-15T19:31:07.927Z', + 'updatedAt': '2025-07-26T12:09:09.240Z', }; diff --git a/lib/compiled/AuctionConflictResolver.ts b/lib/compiled/AuctionConflictResolver.ts deleted file mode 100644 index 3b9d29c..0000000 --- a/lib/compiled/AuctionConflictResolver.ts +++ /dev/null @@ -1,80 +0,0 @@ -export default { - 'contractName': 'AuctionConflictResolver', - 'constructorInputs': [], - 'abi': [ - { - 'name': 'call', - 'inputs': [], - }, - ], - 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_3 OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_NIP OP_3 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_NIP OP_EQUALVERIFY OP_2 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT OP_LESSTHAN OP_VERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_0 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT OP_ADD OP_NUMEQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUAL', - 'source': "pragma cashscript 0.11.2;\n\ncontract AuctionConflictResolver() {\n /**\n * Resolves a conflict between two competing registration auctions for the same name.\n * \n * RULE:\n * - If any new auction is created when an auction already exists, then the new auction is open for penalization.\n *\n * Anyone can provide proof of an active auction's existence and take away the funds from the \"new\" invalid auction\n * as a form of reward for keeping the system secure and predictable.\n * Therefore, it's the responsibility of the application to check for any running auctions for the same name.\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: Valid auctionNFT from Registry Contract.\n * - Input3: Invalid auctionNFT from Registry Contract.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Valid auctionNFT back to Registry Contract.\n * - Output3: BCH change/reward to caller.\n */\n function call() {\n require(tx.inputs.length == 4);\n require(tx.outputs.length == 4);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.inputs[3].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n\n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n\n // auctionNFT should be mutable\n bytes auctionCategory, bytes auctionCapability = tx.inputs[2].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n require(auctionCapability == 0x01); // Mutable\n\n // Invalid and valid auctionNFTs both should have the same category and capability.\n require(tx.inputs[2].tokenCategory == tx.inputs[3].tokenCategory);\n // Both auctionNFTs should also have the same 'name'\n require(tx.inputs[2].nftCommitment.split(20)[1] == tx.inputs[3].nftCommitment.split(20)[1]);\n // The valid auctionNFT will have a lower registrationID\n require(tx.inputs[2].tokenAmount < tx.inputs[3].tokenAmount);\n\n // tokenAmount from the invalid auctionNFT goes to the authorizedThreadNFT to be accumulated later\n // and merged back with the CounterNFT using the `Accumulator` Contract\n require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[3].tokenAmount);\n\n // Attach any output to take away the funds as reward\n require(tx.outputs[3].tokenCategory == 0x);\n }\n}", - 'debug': { - 'bytecode': 'c3549dc4549dc0519dc0c7c0cd8800c752c7788853c7788852cd8800ce52ce01207f7c7b88518852ce53ce8852cf01147f7753cf01147f778852d053d09f6900d300d053d0939d53d10087', - 'sourceMap': '27:12:27:28;:32::33;:4::35:1;28:12:28:29:0;:33::34;:4::36:1;31:12:31:33:0;:37::38;:4::40:1;32:22:32:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;38:51:38:52:0;:41::69:1;39:22:39:23:0;:12::40:1;:44::72:0;:4::74:1;40:22:40:23:0;:12::40:1;:44::72:0;:4::74:1;41:23:41:24:0;:12::41:1;:4::75;44:44:44:45:0;:34::60:1;47:63:47:64:0;:53::79:1;:86::88:0;:53::89:1;48:12:48:27:0;:31::52;:4::54:1;49:33:49:37:0;:4::39:1;52:22:52:23:0;:12::38:1;:52::53:0;:42::68:1;:4::70;54:22:54:23:0;:12::38:1;:45::47:0;:12::48:1;:::51;:65::66:0;:55::81:1;:88::90:0;:55::91:1;:::94;:4::96;56:22:56:23:0;:12::36:1;:49::50:0;:39::63:1;:12;:4::65;60:23:60:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;63:23:63:24:0;:12::39:1;:43::45:0;:4::47:1', - 'logs': [], - 'requires': [ - { - 'ip': 2, - 'line': 27, - }, - { - 'ip': 5, - 'line': 28, - }, - { - 'ip': 8, - 'line': 31, - }, - { - 'ip': 13, - 'line': 32, - }, - { - 'ip': 19, - 'line': 39, - }, - { - 'ip': 23, - 'line': 40, - }, - { - 'ip': 26, - 'line': 41, - }, - { - 'ip': 35, - 'line': 48, - }, - { - 'ip': 37, - 'line': 49, - }, - { - 'ip': 42, - 'line': 52, - }, - { - 'ip': 53, - 'line': 54, - }, - { - 'ip': 59, - 'line': 56, - }, - { - 'ip': 67, - 'line': 60, - }, - { - 'ip': 72, - 'line': 63, - }, - ], - }, - 'compiler': { - 'name': 'cashc', - 'version': '0.11.2', - }, - 'updatedAt': '2025-07-15T19:31:09.830Z', -}; diff --git a/lib/compiled/AuctionNameEnforcer.ts b/lib/compiled/AuctionNameEnforcer.ts deleted file mode 100644 index 4eb28c5..0000000 --- a/lib/compiled/AuctionNameEnforcer.ts +++ /dev/null @@ -1,81 +0,0 @@ -export default { - 'contractName': 'AuctionNameEnforcer', - 'constructorInputs': [], - 'abi': [ - { - 'name': 'call', - 'inputs': [ - { - 'name': 'characterNumber', - 'type': 'int', - }, - ], - }, - ], - 'bytecode': 'OP_TXINPUTCOUNT OP_3 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_3 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_NIP OP_OVER OP_SPLIT OP_DROP OP_OVER OP_1SUB OP_SPLIT OP_NIP OP_BIN2NUM OP_DUP 2d OP_NUMNOTEQUAL OP_VERIFY OP_DUP 61 7b OP_WITHIN OP_NOT OP_VERIFY OP_DUP 41 5b OP_WITHIN OP_NOT OP_VERIFY 30 3a OP_WITHIN OP_NOT OP_VERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_0 OP_UTXOTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_ADD OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUAL OP_NIP', - 'source': "pragma cashscript 0.11.2;\n\ncontract AuctionNameEnforcer() {\n /**\n * Proves that a domain name contains invalid characters, burns the auctionNFT, and takes away the funds as a reward.\n * During the entire auction, this can be called at any time by anyone.\n * \n * Rules:\n * 1. The name must consist of only these characters:\n * - Letters (a-z or A-Z)\n * - Numbers (0-9)\n * - Hyphens (-)\n *\n * @param characterNumber - Number of the character in the name that is invalid (starting from 1)\n *\n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: auctionNFT from Registry Contract.\n *\n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Reward to caller.\n *\n */\n function call(int characterNumber) {\n require(tx.inputs.length == 3);\n require(tx.outputs.length == 3);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n\n // Lock this contract to only be used with the registry type contract.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n\n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n\n // AuctionNFT should be mutable and of the 'domainCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.inputs[2].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n require(auctionCapability == 0x01); // Mutable\n\n bytes name = tx.inputs[2].nftCommitment.split(20)[1];\n bytes characterSplitBytes = name.split(characterNumber)[0];\n characterNumber = characterNumber - 1;\n bytes character = characterSplitBytes.split(characterNumber)[1];\n int charVal = int(character);\n\n // Character is not a hyphen.\n require(charVal != 45); \n // Character is not from a-z.\n require(!within(charVal, 97, 123));\n // Character is not from A-Z.\n require(!within(charVal, 65, 91));\n // Character is not from 0-9.\n require(!within(charVal, 48, 58));\n\n // tokenAmount from the invalid auctionNFT goes to the authorizedThreadNFT to be accumulated later\n // and merged back with the CounterNFT using the `Accumulator` Contract\n require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[2].tokenAmount);\n\n // Pure BCH.\n require(tx.outputs[2].tokenCategory == 0x);\n }\n}", - 'debug': { - 'bytecode': 'c3539dc4539dc0519dc0c7c0cd8800c752c78800ce52ce01207f7c7b88518852cf01147f77787f75788c7f778176012d9e69760161017ba59169760141015ba591690130013aa5916900d300d052d0939d52d1008777', - 'sourceMap': '28:12:28:28;:32::33;:4::35:1;29:12:29:29:0;:33::34;:4::36:1;32:12:32:33:0;:37::38;:4::40:1;33:22:33:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;36:51:36:52:0;:41::69:1;37:22:37:23:0;:12::40:1;:4::74;40:44:40:45:0;:34::60:1;43:63:43:64:0;:53::79:1;:86::88:0;:53::89:1;44:12:44:27:0;:31::52;:4::54:1;45:33:45:37:0;:4::39:1;47:27:47:28:0;:17::43:1;:50::52:0;:17::53:1;:::56;48:43:48:58:0;:32::59:1;:::62;49:22:49:37:0;:::41:1;50::50:64;:::67;51:18:51:32;54:12:54:19:0;:23::25;:12:::1;:4::27;56:20:56::0;:29::31;:33::36;:13::37:1;:12;:4::39;58:20:58:27:0;:29::31;:33::35;:13::36:1;:12;:4::38;60:29:60:31:0;:33::35;:13::36:1;:12;:4::38;64:23:64:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;67:23:67:24:0;:12::39:1;:43::45:0;:4::47:1;27:2:68:3', - 'logs': [], - 'requires': [ - { - 'ip': 2, - 'line': 28, - }, - { - 'ip': 5, - 'line': 29, - }, - { - 'ip': 8, - 'line': 32, - }, - { - 'ip': 13, - 'line': 33, - }, - { - 'ip': 18, - 'line': 37, - }, - { - 'ip': 27, - 'line': 44, - }, - { - 'ip': 29, - 'line': 45, - }, - { - 'ip': 46, - 'line': 54, - }, - { - 'ip': 52, - 'line': 56, - }, - { - 'ip': 58, - 'line': 58, - }, - { - 'ip': 63, - 'line': 60, - }, - { - 'ip': 71, - 'line': 64, - }, - { - 'ip': 76, - 'line': 67, - }, - ], - }, - 'compiler': { - 'name': 'cashc', - 'version': '0.11.2', - }, - 'updatedAt': '2025-07-15T19:31:09.230Z', -}; diff --git a/lib/compiled/Bid.ts b/lib/compiled/Bid.ts index 214f17b..5706a79 100644 --- a/lib/compiled/Bid.ts +++ b/lib/compiled/Bid.ts @@ -13,7 +13,7 @@ export default { }, ], 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_5 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_SIZE OP_NIP 19 OP_NUMEQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_3 OP_UTXOBYTECODE OP_3 OP_SPLIT OP_NIP 14 OP_SPLIT OP_DROP OP_2 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_ROT OP_CAT OP_EQUALVERIFY OP_2 OP_UTXOTOKENAMOUNT OP_2 OP_OUTPUTTOKENAMOUNT OP_NUMEQUALVERIFY OP_2 OP_OUTPUTVALUE 64 OP_MUL OP_2 OP_UTXOVALUE 64 OP_4 OP_ROLL OP_ADD OP_MUL OP_GREATERTHANOREQUAL OP_VERIFY OP_3 OP_OUTPUTBYTECODE 76a914 OP_ROT OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_3 OP_OUTPUTVALUE OP_2 OP_UTXOVALUE OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_5 OP_NUMEQUAL OP_IF OP_4 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_1', - 'source': "pragma cashscript 0.11.2;\n\n/**\n * @param minBidIncreasePercentage The minimum percentage increase required for a new bid over the previous bid.\n */\ncontract Bid(int minBidIncreasePercentage) {\n /**\n * Places a new bid on an active domain registration auction.\n * \n * The function allows placing a new bid with:\n * - A minimum `minBidIncreasePercentage` increase over the previous bid.\n * - The previous bidder receives their bid amount back in the same transaction.\n * - A successful bid updates the auctionNFT by updating the PKH in the nftCommitment and satoshiValue.\n * capability: Mutable\n * category: registryInputCategory\n * tokenAmount: Represents the registrationId\n * satoshiValue: Represents the bid amount\n * commitment: new Bidder's PKH (20 bytes) + name (bytes)\n *\n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: auctionNFT from the Registry contract.\n * - Input3: Funding UTXO from the new bidder.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Updated auctionNFT back to the Registry contract.\n * - Output3: Previous bid amount to the previous bidder.\n * - Output4: Optional change in BCH to the new bidder.\n */\n function call() {\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 5);\n \n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode); \n\n // AuctionNFT should keep the same category and capability.\n require(tx.inputs[2].tokenCategory == tx.outputs[2].tokenCategory);\n\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n // The second part of the pair changes with each new bid, hence it's marked as mutable.\n // Enforcing the structure of the pair results in predictable behavior.\n bytes auctionCategory, bytes auctionCapability = tx.outputs[2].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n require(auctionCapability == 0x01); // Mutable\n\n // Ensure that the funding happens from a P2PKH UTXO.\n require(tx.inputs[3].lockingBytecode.length == 25);\n\n bytes20 previousPKH, bytes name = tx.inputs[2].nftCommitment.split(20);\n // Extract the PKH from the lockingBytecode of the Funding UTXO.\n // + name > 20 bytes\n bytes pkh = tx.inputs[3].lockingBytecode.split(3)[1].split(20)[0];\n \n // AuctionNFT should have updated PKH in it's commitment.\n require(tx.outputs[2].nftCommitment == pkh + name);\n\n // Since tokenAmount is registrationID, make sure that it's not changing.\n require(tx.inputs[2].tokenAmount == tx.outputs[2].tokenAmount);\n\n // Ensure that the bid amount is greater than or equal to the previous bid amount + minBidIncreasePercentage.\n require(tx.outputs[2].value * 100 >= tx.inputs[2].value * (100 + minBidIncreasePercentage));\n\n // Locking bytecode of the previous bidder.\n require(tx.outputs[3].lockingBytecode == new LockingBytecodeP2PKH(previousPKH));\n // The amount being sent back to the previous bidder.\n require(tx.outputs[3].value == tx.inputs[2].value);\n\n if (tx.outputs.length == 5) {\n // If any change, then it must be pure BCH.\n require(tx.outputs[4].tokenCategory == 0x);\n }\n }\n}", + 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param minBidIncreasePercentage The minimum percentage increase required for a new bid over the previous bid.\n */\ncontract Bid(int minBidIncreasePercentage) {\n /**\n * Places a new bid on an active name registration auction.\n * \n * The function allows placing a new bid with:\n * - A minimum `minBidIncreasePercentage` increase over the previous bid.\n * - The previous bidder receives their bid amount back in the same transaction.\n * - A successful bid updates the auctionNFT by updating the PKH in the nftCommitment and satoshiValue.\n * capability: Mutable\n * category: registryInputCategory\n * tokenAmount: Represents the registrationId\n * satoshiValue: Represents the bid amount\n * commitment: new Bidder's PKH (20 bytes) + name (bytes)\n *\n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: auctionNFT from the Registry contract.\n * - Input3: Funding UTXO from the new bidder.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Updated auctionNFT back to the Registry contract.\n * - Output3: Previous bid amount to the previous bidder.\n * - Output4: Optional change in BCH to the new bidder.\n */\n function call() {\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 5);\n \n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode); \n\n // AuctionNFT should keep the same category and capability.\n require(tx.inputs[2].tokenCategory == tx.outputs[2].tokenCategory);\n\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n // The second part of the pair changes with each new bid, hence it's marked as mutable.\n // Enforcing the structure of the pair results in predictable behavior.\n bytes auctionCategory, bytes auctionCapability = tx.outputs[2].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n require(auctionCapability == 0x01); // Mutable\n\n // Ensure that the funding happens from a P2PKH UTXO.\n require(tx.inputs[3].lockingBytecode.length == 25);\n\n bytes20 previousPKH, bytes name = tx.inputs[2].nftCommitment.split(20);\n // Extract the PKH from the lockingBytecode of the Funding UTXO.\n // + name > 20 bytes\n bytes pkh = tx.inputs[3].lockingBytecode.split(3)[1].split(20)[0];\n \n // AuctionNFT should have updated PKH in it's commitment.\n require(tx.outputs[2].nftCommitment == pkh + name);\n\n // Since tokenAmount is registrationID, make sure that it's not changing.\n require(tx.inputs[2].tokenAmount == tx.outputs[2].tokenAmount);\n\n // Ensure that the bid amount is greater than or equal to the previous bid amount + minBidIncreasePercentage.\n require(tx.outputs[2].value * 100 >= tx.inputs[2].value * (100 + minBidIncreasePercentage));\n\n // Locking bytecode of the previous bidder.\n require(tx.outputs[3].lockingBytecode == new LockingBytecodeP2PKH(previousPKH));\n // The amount being sent back to the previous bidder.\n require(tx.outputs[3].value == tx.inputs[2].value);\n\n if (tx.outputs.length == 5) {\n // If any change, then it must be pure BCH.\n require(tx.outputs[4].tokenCategory == 0x);\n }\n }\n}", 'debug': { 'bytecode': 'c3549dc455a169c0519dc0c7c0cd8800c752c7788852cd8852ce52d18800ce52d101207f7c7b88518853c7827701199d52cf01147f53c7537f7701147f7552d27c7b7e8852d052d39d52cc01649552c60164547a9395a26953cd0376a9147b7e0288ac7e8853cc52c69dc4559c6354d100886851', 'sourceMap': '34:12:34:28;:32::33;:4::35:1;35:12:35:29:0;:33::34;:12:::1;:4::36;38:12:38:33:0;:37::38;:4::40:1;39:22:39:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;45:51:45:52:0;:41::69:1;46:22:46:23:0;:12::40:1;:44::72:0;:4::74:1;47:23:47:24:0;:12::41:1;:4::75;50:22:50:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;52:44:52:45:0;:34::60:1;55:64:55:65:0;:53::80:1;:87::89:0;:53::90:1;56:12:56:27:0;:31::52;:4::54:1;57:33:57:37:0;:4::39:1;60:22:60:23:0;:12::40:1;:::47;;:51::53:0;:4::55:1;62:48:62:49:0;:38::64:1;:71::73:0;:38::74:1;65:26:65:27:0;:16::44:1;:51::52:0;:16::53:1;:::56;:63::65:0;:16::66:1;:::69;68:23:68:24:0;:12::39:1;:43::46:0;:49::53;:43:::1;:4::55;71:22:71:23:0;:12::36:1;:51::52:0;:40::65:1;:4::67;74:23:74:24:0;:12::31:1;:34::37:0;:12:::1;:51::52:0;:41::59:1;:63::66:0;:69::93;;:63:::1;:41::94;:12;:4::96;77:23:77:24:0;:12::41:1;:45::82:0;:70::81;:45::82:1;;;:4::84;79:23:79:24:0;:12::31:1;:45::46:0;:35::53:1;:4::55;81:8:81:25:0;:29::30;:8:::1;:32:84:5:0;83:25:83:26;:14::41:1;:45::47:0;:6::49:1;81:32:84:5;33:2:85:3', @@ -87,7 +87,7 @@ export default { }, 'compiler': { 'name': 'cashc', - 'version': '0.11.2', + 'version': '0.11.3', }, - 'updatedAt': '2025-07-15T19:31:08.613Z', + 'updatedAt': '2025-07-26T12:09:09.858Z', }; diff --git a/lib/compiled/ConflictResolver.ts b/lib/compiled/ConflictResolver.ts new file mode 100644 index 0000000..e715a96 --- /dev/null +++ b/lib/compiled/ConflictResolver.ts @@ -0,0 +1,80 @@ +export default { + 'contractName': 'ConflictResolver', + 'constructorInputs': [], + 'abi': [ + { + 'name': 'call', + 'inputs': [], + }, + ], + 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_3 OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_NIP OP_3 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_NIP OP_EQUALVERIFY OP_2 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT OP_LESSTHAN OP_VERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_0 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT OP_ADD OP_NUMEQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUAL', + 'source': "pragma cashscript 0.11.3;\n\ncontract ConflictResolver() {\n /**\n * Resolves a conflict between two competing registration auctions for the same name.\n * \n * RULE:\n * - If any new auction is created when an auction already exists, then the new auction is open for penalization.\n *\n * Anyone can provide proof of an active auction's existence and take away the funds from the \"new\" invalid auction\n * as a form of reward for keeping the system secure and predictable.\n * Therefore, it's the responsibility of the application to check for any running auctions for the same name.\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: Valid auctionNFT from Registry Contract.\n * - Input3: Invalid auctionNFT from Registry Contract.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Valid auctionNFT back to Registry Contract.\n * - Output3: BCH change/reward to caller.\n */\n function call() {\n require(tx.inputs.length == 4);\n require(tx.outputs.length == 4);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.inputs[3].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n\n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n\n // auctionNFT should be mutable\n bytes auctionCategory, bytes auctionCapability = tx.inputs[2].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n require(auctionCapability == 0x01); // Mutable\n\n // Invalid and valid auctionNFTs both should have the same category and capability.\n require(tx.inputs[2].tokenCategory == tx.inputs[3].tokenCategory);\n // Both auctionNFTs should also have the same 'name'\n require(tx.inputs[2].nftCommitment.split(20)[1] == tx.inputs[3].nftCommitment.split(20)[1]);\n // The valid auctionNFT will have a lower registrationID\n require(tx.inputs[2].tokenAmount < tx.inputs[3].tokenAmount);\n\n // tokenAmount from the invalid auctionNFT goes to the authorizedThreadNFT to be accumulated later\n // and merged back with the CounterNFT using the `Accumulator` Contract\n require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[3].tokenAmount);\n\n // Attach any output to take away the funds as reward\n require(tx.outputs[3].tokenCategory == 0x);\n }\n}", + 'debug': { + 'bytecode': 'c3549dc4549dc0519dc0c7c0cd8800c752c7788853c7788852cd8800ce52ce01207f7c7b88518852ce53ce8852cf01147f7753cf01147f778852d053d09f6900d300d053d0939d53d10087', + 'sourceMap': '27:12:27:28;:32::33;:4::35:1;28:12:28:29:0;:33::34;:4::36:1;31:12:31:33:0;:37::38;:4::40:1;32:22:32:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;38:51:38:52:0;:41::69:1;39:22:39:23:0;:12::40:1;:44::72:0;:4::74:1;40:22:40:23:0;:12::40:1;:44::72:0;:4::74:1;41:23:41:24:0;:12::41:1;:4::75;44:44:44:45:0;:34::60:1;47:63:47:64:0;:53::79:1;:86::88:0;:53::89:1;48:12:48:27:0;:31::52;:4::54:1;49:33:49:37:0;:4::39:1;52:22:52:23:0;:12::38:1;:52::53:0;:42::68:1;:4::70;54:22:54:23:0;:12::38:1;:45::47:0;:12::48:1;:::51;:65::66:0;:55::81:1;:88::90:0;:55::91:1;:::94;:4::96;56:22:56:23:0;:12::36:1;:49::50:0;:39::63:1;:12;:4::65;60:23:60:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;63:23:63:24:0;:12::39:1;:43::45:0;:4::47:1', + 'logs': [], + 'requires': [ + { + 'ip': 2, + 'line': 27, + }, + { + 'ip': 5, + 'line': 28, + }, + { + 'ip': 8, + 'line': 31, + }, + { + 'ip': 13, + 'line': 32, + }, + { + 'ip': 19, + 'line': 39, + }, + { + 'ip': 23, + 'line': 40, + }, + { + 'ip': 26, + 'line': 41, + }, + { + 'ip': 35, + 'line': 48, + }, + { + 'ip': 37, + 'line': 49, + }, + { + 'ip': 42, + 'line': 52, + }, + { + 'ip': 53, + 'line': 54, + }, + { + 'ip': 59, + 'line': 56, + }, + { + 'ip': 67, + 'line': 60, + }, + { + 'ip': 72, + 'line': 63, + }, + ], + }, + 'compiler': { + 'name': 'cashc', + 'version': '0.11.3', + }, + 'updatedAt': '2025-07-26T12:09:11.059Z', +}; diff --git a/lib/compiled/Domain.ts b/lib/compiled/Domain.ts deleted file mode 100644 index 9e3bc73..0000000 --- a/lib/compiled/Domain.ts +++ /dev/null @@ -1,226 +0,0 @@ -export default { - 'contractName': 'Domain', - 'constructorInputs': [ - { - 'name': 'inactivityExpiryTime', - 'type': 'int', - }, - { - 'name': 'name', - 'type': 'bytes', - }, - { - 'name': 'domainCategory', - 'type': 'bytes', - }, - ], - 'abi': [ - { - 'name': 'useAuth', - 'inputs': [ - { - 'name': 'authID', - 'type': 'int', - }, - ], - }, - { - 'name': 'resolveOwnerConflict', - 'inputs': [], - }, - { - 'name': 'burn', - 'inputs': [], - }, - ], - 'bytecode': 'OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_TXVERSION OP_2 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOTOKENCOMMITMENT OP_INPUTINDEX OP_OUTPUTTOKENCOMMITMENT OP_EQUALVERIFY OP_4 OP_ROLL OP_1 OP_NUMEQUAL OP_IF OP_INPUTINDEX OP_1ADD OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_INPUTINDEX OP_1ADD OP_UTXOTOKENCOMMITMENT OP_8 OP_SPLIT OP_DUP OP_4 OP_PICK OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOTOKENCOMMITMENT OP_2 OP_PICK OP_EQUALVERIFY OP_2DROP OP_ELSE OP_INPUTINDEX OP_UTXOTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_ENDIF OP_2DROP OP_2DROP OP_1 OP_ELSE OP_3 OP_PICK OP_1 OP_NUMEQUAL OP_IF OP_TXVERSION OP_2 OP_NUMEQUALVERIFY OP_TXINPUTCOUNT OP_5 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_3 OP_NUMEQUALVERIFY OP_4 OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_0 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_1 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_0 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_1 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENCOMMITMENT OP_1 OP_UTXOTOKENCOMMITMENT OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_1 OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_3 OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENCATEGORY OP_3 OP_ROLL OP_EQUALVERIFY OP_1 OP_UTXOTOKENCOMMITMENT OP_REVERSEBYTES OP_BIN2NUM OP_3 OP_UTXOTOKENCOMMITMENT OP_REVERSEBYTES OP_BIN2NUM OP_LESSTHAN OP_NIP OP_NIP OP_NIP OP_ELSE OP_3 OP_ROLL OP_2 OP_NUMEQUALVERIFY OP_TXVERSION OP_2 OP_NUMEQUALVERIFY OP_TXINPUTCOUNT OP_3 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_1 OP_NUMEQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_0 OP_EQUAL OP_IF OP_1 OP_INPUTSEQUENCENUMBER OP_OVER OP_NUMEQUALVERIFY OP_ELSE OP_2 OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_8 OP_SPLIT OP_DROP OP_0 OP_UTXOTOKENCOMMITMENT OP_EQUALVERIFY OP_ENDIF OP_INPUTINDEX OP_UTXOBYTECODE OP_0 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_1 OP_UTXOBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_1 OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_1 OP_UTXOTOKENCATEGORY OP_3 OP_ROLL OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUAL OP_NIP OP_NIP OP_ENDIF OP_ENDIF', - 'source': 'pragma cashscript 0.11.2;\n\n/**\n * @param inactivityExpiryTime The time period after which the domain is considered inactive.\n * @param name The name of the domain.\n * @param domainCategory The category of the domain.\n */\ncontract Domain(\n int inactivityExpiryTime,\n bytes name,\n bytes domainCategory\n ) {\n \n /**\n * This function can be used to perform a variety of actions.\n *\n * For example:\n * - It can be used to prove the the ownership of the domain by other contracts.\n * - This function allows the owner to perform any actions in conjunction with other contracts.\n * - This function can be used to add records and invalidate multiple records in a single transaction.\n *\n * Records are created using OP_RETURN outputs. To add a record, include the record data directly in the OP_RETURN output.\n * To invalidate a record, prefix "RMV" followed by the hash of the record content in the OP_RETURN output. This will signal\n * the library/indexers to exclude the record from the valid records.\n * \n * @inputs\n * - Inputx: Internal/External Auth NFT\n * - Inputx+1 (optional): Domain ownership NFT from the owner\n * \n * @outputs\n * - Outputx: Internal/External Auth NFT returned to this contract\n * - Outputx+1 (optional): Domain NFT returned\n * \n */\n function useAuth(int authID) {\n // Need transaction version 2 to prevent any vulnerabilities caused due to future versions.\n require(tx.version == 2);\n\n // The activeInputIndex can be anything as long as the utxo properties are preserved and comes back to the\n // contract without alteration.\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n require(tx.inputs[this.activeInputIndex].tokenCategory == domainCategory);\n require(tx.outputs[this.activeInputIndex].tokenCategory == domainCategory);\n require(tx.inputs[this.activeInputIndex].nftCommitment == tx.outputs[this.activeInputIndex].nftCommitment);\n\n if(authID == 1) {\n // The next input from the InternalAuthNFT must be the ownershipNFT.\n require(tx.inputs[this.activeInputIndex + 1].tokenCategory == domainCategory);\n bytes registrationId, bytes nameFromOwnerNFT = tx.inputs[this.activeInputIndex + 1].nftCommitment.split(8);\n require(nameFromOwnerNFT == name);\n require(tx.inputs[this.activeInputIndex].nftCommitment == registrationId);\n } else {\n // One known use of ExternalAuthNFT in the `DomainOwnershipGuard` contract. ExternalAuthNFT is\n // used to prove that an owner exists.\n require(tx.inputs[this.activeInputIndex].nftCommitment == 0x);\n }\n }\n\n /**\n * If the incentive system fails, i.e `DomainOwnershipGuard` or `AuctionConflictResolver` fails to prevent a\n * a owner conflict. When this happens there will be > 1 owner for this domain.\n * The owner with the lowest registrationID must be the only owner for this domain.\n * To help enforce this rule, this function will allow anyone to burn both the Auth NFTs of the NEW owner.\n *\n * @inputs\n * - Input0: Valid External Auth NFT from self\n * - Input1: Valid Internal Auth NFT from self\n * - Input2: Invalid External Auth NFT from self\n * - Input3: Invalid Internal Auth NFT from self\n * - Input4: BCH input from anyone\n * \n * @outputs \n * - Output0: Valid External Auth NFT back to self\n * - Output1: Valid Internal Auth NFT back to self\n * - Output3: BCH change output\n */\n function resolveOwnerConflict(){\n // Need transaction version 2 to prevent any vulnerabilities caused due to future versions.\n require(tx.version == 2);\n\n require(tx.inputs.length == 5);\n require(tx.outputs.length == 3);\n\n // Pure BCH input and output to fund the transaction\n require(tx.inputs[4].tokenCategory == 0x);\n require(tx.outputs[2].tokenCategory == 0x);\n\n bytes selfLockingBytecode = tx.inputs[this.activeInputIndex].lockingBytecode;\n require(tx.inputs[0].lockingBytecode == selfLockingBytecode);\n require(tx.inputs[1].lockingBytecode == selfLockingBytecode);\n require(tx.inputs[2].lockingBytecode == selfLockingBytecode);\n require(tx.inputs[3].lockingBytecode == selfLockingBytecode);\n\n require(tx.outputs[0].lockingBytecode == selfLockingBytecode);\n require(tx.outputs[1].lockingBytecode == selfLockingBytecode);\n\n // External Auth NFTs\n require(tx.inputs[0].nftCommitment == 0x);\n require(tx.inputs[2].nftCommitment == 0x);\n\n // Commitments of Valid Auth NFts back to self\n require(tx.outputs[0].nftCommitment == 0x);\n require(tx.outputs[1].nftCommitment == tx.inputs[1].nftCommitment);\n\n // Ensure that all the token inputs and outputs have domainCategory\n require(tx.inputs[0].tokenCategory == domainCategory);\n require(tx.inputs[1].tokenCategory == domainCategory);\n require(tx.inputs[2].tokenCategory == domainCategory);\n require(tx.inputs[3].tokenCategory == domainCategory);\n\n require(tx.outputs[0].tokenCategory == domainCategory);\n require(tx.outputs[1].tokenCategory == domainCategory);\n\n // Compare the registrationID\n require(int(tx.inputs[1].nftCommitment.reverse()) < int(tx.inputs[3].nftCommitment.reverse()));\n }\n\n /**\n * Allows the domain owner or anyone to burn the InternalAuthNFT and externalAuthNFT making this domain available\n * for auction.\n * \n * - Owner can burn the AuthNFTs anytime.\n * - External party can burn the AuthNFTs when the internalAuth NFT has not been used for more than `inactivityExpiryTime`.\n *\n * @inputs\n * - Input0: External Auth NFT\n * - Input1: Internal Auth NFT\n * - Input2: Pure BCH or Domain ownership NFT from the owner\n *\n * @outputs \n * - Output0: BCH change\n *\n */\n function burn() {\n // Need transaction version 2 to prevent any vulnerabilities caused due to future versions.\n // Need version 2 enforcement for relative timelocks.\n require(tx.version == 2);\n\n require(tx.inputs.length == 3);\n require(tx.outputs.length == 1);\n\n // If an external party is attempting to burn the authNFTs\n if (tx.inputs[2].tokenCategory == 0x) {\n // If pure BCH input, then allow anyone to burn given the time limit has passed.\n require(tx.inputs[1].sequenceNumber == inactivityExpiryTime);\n } else {\n // If domain ownership NFT input, then allow the owner to burn anytime.\n require(tx.inputs[2].tokenCategory == domainCategory);\n // Make sure that the registrationID in the domainOwnershipNFT and the internalAuthNFT are the same.\n require(tx.inputs[2].nftCommitment.split(8)[0] == tx.inputs[0].nftCommitment);\n }\n\n bytes selfLockingBytecode = tx.inputs[this.activeInputIndex].lockingBytecode;\n require(tx.inputs[0].lockingBytecode == selfLockingBytecode);\n require(tx.inputs[1].lockingBytecode == selfLockingBytecode);\n\n // ExternalAuthNFT\n require(tx.inputs[0].nftCommitment == 0x);\n // Both InternalAuthNFT and externalAuthNFT are immutable and have the same tokenCategory\n require(tx.inputs[0].tokenCategory == tx.inputs[1].tokenCategory);\n require(tx.inputs[0].tokenCategory == domainCategory);\n require(tx.inputs[1].tokenCategory == domainCategory);\n\n // Return the BCH as change.\n require(tx.outputs[0].tokenCategory == 0x);\n }\n}\n', - 'debug': { - 'bytecode': '5379009c63c2529dc0c7c0cd88c0ce537988c0d1537988c0cfc0d288547a519c63c08bce537988c08bcf587f76547988c0cf5279886d67c0cf0088686d6d51675379519c63c2529dc3559dc4539d54ce008852d10088c0c700c7788851c7788852c7788853c7788800cd788851cd8800cf008852cf008800d2008851d251cf8800ce53798851ce53798852ce53798853ce53798800d153798851d1537a8851cfbc8153cfbc819f77777767537a529dc2529dc3539dc4519d52ce00876351cb789d6752ce53798852cf587f7500cf8868c0c700c7788851c78800cf008800ce51ce8800ce53798851ce537a8800d1008777776868', - 'sourceMap': '35:2:57:3;;;;;37:12:37:22;:26::27;:4::29:1;41:22:41:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;42:22:42:43:0;:12::58:1;:62::76:0;;:4::78:1;43:23:43:44:0;:12::59:1;:63::77:0;;:4::79:1;44:22:44:43:0;:12::58:1;:73::94:0;:62::109:1;:4::111;46:7:46:13:0;;:17::18;:7:::1;:20:52:5:0;48:24:48:45;:::49:1;:14::64;:68::82:0;;:6::84:1;49:63:49::0;:::88:1;:53::103;:110::111:0;:53::112:1;50:14:50:30:0;:34::38;;:6::40:1;51:24:51:45:0;:14::60:1;:64::78:0;;:6::80:1;46:20:52:5;52:11:56::0;55:24:55:45;:14::60:1;:64::66:0;:6::68:1;52:11:56:5;35:2:57:3;;;;77::116::0;;;;;79:12:79:22;:26::27;:4::29:1;81:12:81:28:0;:32::33;:4::35:1;82:12:82:29:0;:33::34;:4::36:1;85:22:85:23:0;:12::38:1;:42::44:0;:4::46:1;86:23:86:24:0;:12::39:1;:43::45:0;:4::47:1;88:42:88:63:0;:32::80:1;89:22:89:23:0;:12::40:1;:44::63:0;:4::65:1;90:22:90:23:0;:12::40:1;:44::63:0;:4::65:1;91:22:91:23:0;:12::40:1;:44::63:0;:4::65:1;92:22:92:23:0;:12::40:1;:44::63:0;:4::65:1;94:23:94:24:0;:12::41:1;:45::64:0;:4::66:1;95:23:95:24:0;:12::41:1;:4::66;98:22:98:23:0;:12::38:1;:42::44:0;:4::46:1;99:22:99:23:0;:12::38:1;:42::44:0;:4::46:1;102:23:102:24:0;:12::39:1;:43::45:0;:4::47:1;103:23:103:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;106:22:106:23:0;:12::38:1;:42::56:0;;:4::58:1;107:22:107:23:0;:12::38:1;:42::56:0;;:4::58:1;108:22:108:23:0;:12::38:1;:42::56:0;;:4::58:1;109:22:109:23:0;:12::38:1;:42::56:0;;:4::58:1;111:23:111:24:0;:12::39:1;:43::57:0;;:4::59:1;112:23:112:24:0;:12::39:1;:43::57:0;;:4::59:1;115:26:115:27:0;:16::42:1;:::52;:12::53;:70::71:0;:60::86:1;:::96;:56::97;:4::99;77:2:116:3;;;;134::166::0;;;;137:12:137:22;:26::27;:4::29:1;139:12:139:28:0;:32::33;:4::35:1;140:12:140:29:0;:33::34;:4::36:1;143:18:143:19:0;:8::34:1;:38::40:0;:8:::1;:42:146:5:0;145:24:145:25;:14::41:1;:45::65:0;:6::67:1;146:11:151:5:0;148:24:148:25;:14::40:1;:44::58:0;;:6::60:1;150:24:150:25:0;:14::40:1;:47::48:0;:14::49:1;:::52;:66::67:0;:56::82:1;:6::84;146:11:151:5;153:42:153:63:0;:32::80:1;154:22:154:23:0;:12::40:1;:44::63:0;:4::65:1;155:22:155:23:0;:12::40:1;:4::65;158:22:158:23:0;:12::38:1;:42::44:0;:4::46:1;160:22:160:23:0;:12::38:1;:52::53:0;:42::68:1;:4::70;161:22:161:23:0;:12::38:1;:42::56:0;;:4::58:1;162:22:162:23:0;:12::38:1;:42::56:0;;:4::58:1;165:23:165:24:0;:12::39:1;:43::45:0;:4::47:1;134:2:166:3;;8:0:167:1;', - 'logs': [], - 'requires': [ - { - 'ip': 10, - 'line': 37, - }, - { - 'ip': 15, - 'line': 41, - }, - { - 'ip': 20, - 'line': 42, - }, - { - 'ip': 25, - 'line': 43, - }, - { - 'ip': 30, - 'line': 44, - }, - { - 'ip': 41, - 'line': 48, - }, - { - 'ip': 50, - 'line': 50, - }, - { - 'ip': 55, - 'line': 51, - }, - { - 'ip': 61, - 'line': 55, - }, - { - 'ip': 74, - 'line': 79, - }, - { - 'ip': 77, - 'line': 81, - }, - { - 'ip': 80, - 'line': 82, - }, - { - 'ip': 84, - 'line': 85, - }, - { - 'ip': 88, - 'line': 86, - }, - { - 'ip': 94, - 'line': 89, - }, - { - 'ip': 98, - 'line': 90, - }, - { - 'ip': 102, - 'line': 91, - }, - { - 'ip': 106, - 'line': 92, - }, - { - 'ip': 110, - 'line': 94, - }, - { - 'ip': 113, - 'line': 95, - }, - { - 'ip': 117, - 'line': 98, - }, - { - 'ip': 121, - 'line': 99, - }, - { - 'ip': 125, - 'line': 102, - }, - { - 'ip': 130, - 'line': 103, - }, - { - 'ip': 135, - 'line': 106, - }, - { - 'ip': 140, - 'line': 107, - }, - { - 'ip': 145, - 'line': 108, - }, - { - 'ip': 150, - 'line': 109, - }, - { - 'ip': 155, - 'line': 111, - }, - { - 'ip': 160, - 'line': 112, - }, - { - 'ip': 170, - 'line': 115, - }, - { - 'ip': 180, - 'line': 137, - }, - { - 'ip': 183, - 'line': 139, - }, - { - 'ip': 186, - 'line': 140, - }, - { - 'ip': 195, - 'line': 145, - }, - { - 'ip': 201, - 'line': 148, - }, - { - 'ip': 209, - 'line': 150, - }, - { - 'ip': 216, - 'line': 154, - }, - { - 'ip': 219, - 'line': 155, - }, - { - 'ip': 223, - 'line': 158, - }, - { - 'ip': 228, - 'line': 160, - }, - { - 'ip': 233, - 'line': 161, - }, - { - 'ip': 238, - 'line': 162, - }, - { - 'ip': 243, - 'line': 165, - }, - ], - }, - 'compiler': { - 'name': 'cashc', - 'version': '0.11.2', - }, - 'updatedAt': '2025-07-15T19:31:08.262Z', -}; diff --git a/lib/compiled/DomainFactory.ts b/lib/compiled/DomainFactory.ts deleted file mode 100644 index b9ae010..0000000 --- a/lib/compiled/DomainFactory.ts +++ /dev/null @@ -1,197 +0,0 @@ -export default { - 'contractName': 'DomainFactory', - 'constructorInputs': [ - { - 'name': 'domainContractBytecode', - 'type': 'bytes', - }, - { - 'name': 'minWaitTime', - 'type': 'int', - }, - { - 'name': 'maxPlatformFeePercentage', - 'type': 'int', - }, - ], - 'abi': [ - { - 'name': 'call', - 'inputs': [], - }, - ], - 'bytecode': 'OP_TXINPUTCOUNT OP_5 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_8 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOVALUE OP_INPUTINDEX OP_OUTPUTVALUE OP_NUMEQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_3 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_5 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY OP_EQUALVERIFY OP_3 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_2 OP_OUTPUTTOKENCOMMITMENT OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_0 OP_NUMEQUALVERIFY OP_2 OP_OUTPUTVALUE OP_2 OP_UTXOVALUE OP_NUMEQUALVERIFY OP_3 OP_INPUTSEQUENCENUMBER OP_3 OP_ROLL OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_DUP OP_SIZE OP_NIP 20 OP_4 OP_ROLL OP_CAT OP_SWAP OP_CAT OP_OVER OP_CAT OP_3 OP_ROLL OP_CAT OP_HASH256 aa20 OP_SWAP OP_CAT 87 OP_CAT OP_3 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_3 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENAMOUNT OP_8 OP_NUM2BIN OP_REVERSEBYTES OP_4 OP_OUTPUTTOKENCOMMITMENT OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_5 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_ROT OP_CAT OP_EQUALVERIFY OP_5 OP_OUTPUTBYTECODE 76a914 OP_ROT OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_5 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_4 OP_UTXOBYTECODE OP_5 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_4 OP_UTXOBYTECODE OP_6 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_4 OP_UTXOVALUE OP_6 OP_OUTPUTVALUE OP_NUMEQUALVERIFY OP_4 OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_6 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_0 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT OP_ADD OP_NUMEQUALVERIFY OP_7 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_7 OP_OUTPUTVALUE OP_3 OP_UTXOVALUE 64 OP_DIV OP_ROT OP_MUL OP_LESSTHANOREQUAL', - 'source': "pragma cashscript 0.11.2;\n\n/**\n * @param domainContractBytecode - Partial bytecode of the domain contract\n * @param minWaitTime - Minimum wait time to consider an auction ended\n * @param maxPlatformFeePercentage - Maximum platform fee percentage\n */\ncontract DomainFactory(\n bytes domainContractBytecode,\n int minWaitTime,\n int maxPlatformFeePercentage\n) {\n /**\n * This function finalizes a domain registration auction by:\n * - Verifying the auction has ended and the winner's bid is valid\n * - Issuing an immutable externalAuthNFT to the Domain Contract\n * - Issuing an immutable internalAuthNFT to the Domain Contract\n * - Issuing an immutable domain NFT to the auction winner\n * - Distributing auction fees between the platform and miners\n * - Burning the auctionNFT\n * - Pure BCH input from bidder is used to prevent miners from taking away the funds from any or all transactions in the future.\n * Out of many possible ways, this method will be suitable to easily implement by applications.\n *\n *\n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract\n * - Input2: DomainMintingNFT from the Registry Contract\n * - Input3: auctionNFT from the Registry Contract\n * - Input4: Pure BCH from bidder\n *\n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change\n * - Output2: DomainMintingNFT back to the Registry contract\n * - Output3: External Auth NFT to the domain contract\n * - Output4: Internal Auth NFT to the domain contract\n * - Output5: Domain NFT to the auction winner\n * - Output6: Pure BCH back to the bidder\n * - Output7: Platform fee\n *\n */\n function call(){\n require(tx.inputs.length == 5);\n require(tx.outputs.length == 8);\n\n // This contract can only be used at input1 and it should return to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the domainCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n // Strict value checks to ensure the platform and miner get fee.\n require(tx.inputs[this.activeInputIndex].value == tx.outputs[this.activeInputIndex].value);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.inputs[3].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n\n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n require(tx.outputs[3].tokenCategory == registryInputCategory);\n require(tx.outputs[4].tokenCategory == registryInputCategory);\n require(tx.outputs[5].tokenCategory == registryInputCategory);\n\n // DomainMintingNFT should be minting and of the 'domainCategory' i.e registryInputCategory\n bytes domainMintingCategory, bytes domainMintingCapability = tx.inputs[2].tokenCategory.split(32);\n require(domainMintingCategory == registryInputCategory);\n require(domainMintingCapability == 0x02); // Mutable\n // DomainMintingNFT should keep the same category and capability\n require(tx.inputs[2].tokenCategory == tx.outputs[2].tokenCategory);\n\n // AuctionNFT should be mutable and of the 'domainCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.inputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n require(auctionCapability == 0x01); // Mutable\n\n // Enforce strict restrictions on DomainMintingNFT\n require(tx.inputs[2].nftCommitment == tx.outputs[2].nftCommitment);\n // DomainMintingNFT has no nftCommitment\n require(tx.outputs[2].nftCommitment == 0x);\n // DomainMintingNFT has no tokenAmount\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount);\n require(tx.outputs[2].tokenAmount == 0);\n\n // Strict value check\n require(tx.outputs[2].value == tx.inputs[2].value);\n\n // Enforcing the relative timelock, the auctionNFT must be atleast `minWaitTime` old\n // to be considered ended.\n require(tx.inputs[3].sequenceNumber == minWaitTime);\n\n // Extract the PKH and name from the auctionNFT\n bytes20 bidderPKH, bytes name = tx.inputs[3].nftCommitment.split(20);\n \n // Get the name length to generate the complete bytecode of the domain contract\n int nameLength = name.length;\n // category + name + bytecode.\n // Note: `inactivityExpiryTime` in the domain is already added to the domainContractBytecode in the constructor.\n bytes domainBytecode = 0x20 + registryInputCategory + bytes(nameLength) + name + domainContractBytecode;\n bytes32 scriptHash = hash256(domainBytecode);\n bytes35 domainLockingBytecode = new LockingBytecodeP2SH32(scriptHash);\n \n // ExternalAuthNFT goes to the domain contract\n require(tx.outputs[3].lockingBytecode == domainLockingBytecode);\n // InternalAuthNFT goes to the domain contract\n require(tx.outputs[4].lockingBytecode == domainLockingBytecode);\n \n // ExternalAuthNFT does not have any commitment\n require(tx.outputs[3].nftCommitment == 0x);\n // Strict value check\n require(tx.outputs[3].value == 1000);\n\n // InternalAuthNFT has registrationID as the commitment so it can be used to authenticate\n // along with the ownershipNFT\n bytes8 registrationId = bytes8(tx.inputs[3].tokenAmount).reverse();\n require(tx.outputs[4].nftCommitment == registrationId);\n // Strict value check\n require(tx.outputs[4].value == 1000);\n\n // Send the domain ownership NFT to the bidder\n require(tx.outputs[5].nftCommitment == registrationId + name);\n require(tx.outputs[5].lockingBytecode == new LockingBytecodeP2PKH(bidderPKH));\n require(tx.outputs[5].value == 1000);\n\n // Ensure that the bidder receiving the domain ownership NFT is also receiving the pure BCH back\n require(tx.inputs[4].lockingBytecode == tx.outputs[5].lockingBytecode);\n require(tx.inputs[4].lockingBytecode == tx.outputs[6].lockingBytecode);\n // Ensure that the value of input from bidder is the same and goes back to the bidder\n require(tx.inputs[4].value == tx.outputs[6].value);\n\n // Ensure that input and output to the bidder does not have any tokenCategory\n require(tx.inputs[4].tokenCategory == 0x);\n require(tx.outputs[6].tokenCategory == 0x);\n\n // tokenAmount from the auctionNFT goes to the authorizedThreadNFT to be accumulated later\n // and merged back with the CounterNFT using the `Accumulator` Contract\n require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[3].tokenAmount);\n\n // Output can be added by anyone (Mainly platforms)\n require(tx.outputs[7].tokenCategory == 0x);\n // Enforce that the other piece of the fee goes to the miners.\n require(tx.outputs[7].value <= (tx.inputs[3].value / 100) * maxPlatformFeePercentage);\n }\n\n}", - 'debug': { - 'bytecode': 'c3559dc4589dc0519dc0c7c0cd88c0d10088c0c6c0cc9d00c752c7788853c7788852cd8800ce53d1788854d1788855d1788852ce01207f7c527988528852ce52d18853ce01207f7c527988518852cf52d28852d2008852d352d09d52d3009d52cc52c69d53cb537a9d53cf01147f7682770120547a7e7c7e787e537a7eaa02aa207c7e01877e53cd788854cd8853d2008853cc02e8039d53d05880bc54d2788854cc02e8039d55d27c7b7e8855cd0376a9147b7e0288ac7e8855cc02e8039d54c755cd8854c756cd8854c656cc9d54ce008856d1008800d300d053d0939d57d1008857cc53c60164967b95a1', - 'sourceMap': '44:12:44:28;:32::33;:4::35:1;45:12:45:29:0;:33::34;:4::36:1;48:12:48:33:0;:37::38;:4::40:1;49:22:49:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;51:23:51:44:0;:12::59:1;:63::65:0;:4::67:1;53:22:53:43:0;:12::50:1;:65::86:0;:54::93:1;:4::95;59:51:59:52:0;:41::69:1;60:22:60:23:0;:12::40:1;:44::72:0;:4::74:1;61:22:61:23:0;:12::40:1;:44::72:0;:4::74:1;62:23:62:24:0;:12::41:1;:4::75;65:44:65:45:0;:34::60:1;66:23:66:24:0;:12::39:1;:43::64:0;:4::66:1;67:23:67:24:0;:12::39:1;:43::64:0;:4::66:1;68:23:68:24:0;:12::39:1;:43::64:0;:4::66:1;71:75:71:76:0;:65::91:1;:98::100:0;:65::101:1;72:12:72:33:0;:37::58;;:4::60:1;73:39:73:43:0;:4::45:1;75:22:75:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;78:63:78:64:0;:53::79:1;:86::88:0;:53::89:1;79:12:79:27:0;:31::52;;:4::54:1;80:33:80:37:0;:4::39:1;83:22:83:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;85:23:85:24:0;:12::39:1;:43::45:0;:4::47:1;87:23:87:24:0;:12::37:1;:51::52:0;:41::65:1;:4::67;88:23:88:24:0;:12::37:1;:41::42:0;:4::44:1;91:23:91:24:0;:12::31:1;:45::46:0;:35::53:1;:4::55;95:22:95:23:0;:12::39:1;:43::54:0;;:4::56:1;98:46:98:47:0;:36::62:1;:69::71:0;:36::72:1;101:21:101:25:0;:::32:1;;104:27:104:31:0;:34::55;;:27:::1;:64::74:0;:27::75:1;:78::82:0;:27:::1;:85::107:0;;:27:::1;105:25:105:48;106:36:106:73:0;:62::72;:36::73:1;;;109:23:109:24:0;:12::41:1;:45::66:0;:4::68:1;111:23:111:24:0;:12::41:1;:4::68;114:23:114:24:0;:12::39:1;:43::45:0;:4::47:1;116:23:116:24:0;:12::31:1;:35::39:0;:4::41:1;120:45:120:46:0;:35::59:1;:28::60;;:::70;121:23:121:24:0;:12::39:1;:43::57:0;:4::59:1;123:23:123:24:0;:12::31:1;:35::39:0;:4::41:1;126:23:126:24:0;:12::39:1;:43::57:0;:60::64;:43:::1;:4::66;127:23:127:24:0;:12::41:1;:45::80:0;:70::79;:45::80:1;;;:4::82;128:23:128:24:0;:12::31:1;:35::39:0;:4::41:1;131:22:131:23:0;:12::40:1;:55::56:0;:44::73:1;:4::75;132:22:132:23:0;:12::40:1;:55::56:0;:44::73:1;:4::75;134:22:134:23:0;:12::30:1;:45::46:0;:34::53:1;:4::55;137:22:137:23:0;:12::38:1;:42::44:0;:4::46:1;138:23:138:24:0;:12::39:1;:43::45:0;:4::47:1;142:23:142:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;145:23:145:24:0;:12::39:1;:43::45:0;:4::47:1;147:23:147:24:0;:12::31:1;:46::47:0;:36::54:1;:57::60:0;:36:::1;:64::88:0;:35:::1;:4::90', - 'logs': [], - 'requires': [ - { - 'ip': 5, - 'line': 44, - }, - { - 'ip': 8, - 'line': 45, - }, - { - 'ip': 11, - 'line': 48, - }, - { - 'ip': 16, - 'line': 49, - }, - { - 'ip': 20, - 'line': 51, - }, - { - 'ip': 25, - 'line': 53, - }, - { - 'ip': 31, - 'line': 60, - }, - { - 'ip': 35, - 'line': 61, - }, - { - 'ip': 38, - 'line': 62, - }, - { - 'ip': 44, - 'line': 66, - }, - { - 'ip': 48, - 'line': 67, - }, - { - 'ip': 52, - 'line': 68, - }, - { - 'ip': 60, - 'line': 72, - }, - { - 'ip': 62, - 'line': 73, - }, - { - 'ip': 67, - 'line': 75, - }, - { - 'ip': 75, - 'line': 79, - }, - { - 'ip': 77, - 'line': 80, - }, - { - 'ip': 82, - 'line': 83, - }, - { - 'ip': 86, - 'line': 85, - }, - { - 'ip': 91, - 'line': 87, - }, - { - 'ip': 95, - 'line': 88, - }, - { - 'ip': 100, - 'line': 91, - }, - { - 'ip': 105, - 'line': 95, - }, - { - 'ip': 133, - 'line': 109, - }, - { - 'ip': 136, - 'line': 111, - }, - { - 'ip': 140, - 'line': 114, - }, - { - 'ip': 144, - 'line': 116, - }, - { - 'ip': 153, - 'line': 121, - }, - { - 'ip': 157, - 'line': 123, - }, - { - 'ip': 163, - 'line': 126, - }, - { - 'ip': 171, - 'line': 127, - }, - { - 'ip': 175, - 'line': 128, - }, - { - 'ip': 180, - 'line': 131, - }, - { - 'ip': 185, - 'line': 132, - }, - { - 'ip': 190, - 'line': 134, - }, - { - 'ip': 194, - 'line': 137, - }, - { - 'ip': 198, - 'line': 138, - }, - { - 'ip': 206, - 'line': 142, - }, - { - 'ip': 210, - 'line': 145, - }, - { - 'ip': 220, - 'line': 147, - }, - ], - }, - 'compiler': { - 'name': 'cashc', - 'version': '0.11.2', - }, - 'updatedAt': '2025-07-15T19:31:08.923Z', -}; diff --git a/lib/compiled/DomainOwnershipGuard.ts b/lib/compiled/DomainOwnershipGuard.ts deleted file mode 100644 index 6394a26..0000000 --- a/lib/compiled/DomainOwnershipGuard.ts +++ /dev/null @@ -1,89 +0,0 @@ -export default { - 'contractName': 'DomainOwnershipGuard', - 'constructorInputs': [ - { - 'name': 'domainContractBytecode', - 'type': 'bytes', - }, - ], - 'abi': [ - { - 'name': 'call', - 'inputs': [], - }, - ], - 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_3 OP_UTXOBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_3 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_2 OP_OUTPUTTOKENCOMMITMENT OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_3 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_NIP OP_DUP OP_SIZE OP_NIP 20 OP_3 OP_ROLL OP_CAT OP_SWAP OP_CAT OP_SWAP OP_CAT OP_SWAP OP_CAT OP_HASH256 aa20 OP_SWAP OP_CAT 87 OP_CAT OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_0 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT OP_ADD OP_NUMEQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUAL', - 'source': "pragma cashscript 0.11.2;\n\n/**\n * @param domainContractBytecode The the partial bytecode of the domain contract that has an Owner..\n */\ncontract DomainOwnershipGuard(bytes domainContractBytecode) {\n /**\n * If the domain being auctioned already has an `externalAuthNFT` with the same category, then the auction is invalid.\n * Because it means that an owner still exists. If it is known that the domain has been abandoned for > `inactivityExpiryTime`\n * then one must use the `burn` method of the domain.cash to burn the internalAuthNFT and externalAuthNFT making the \n * domain to be available for auction.\n *\n * Penalizes invalid domain registrations by allowing anyone to burn the auctionNFT and claim the funds as a reward.\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract\n * - Input2: External Auth NFT from the Domain Contract\n * - Input3: auctionNFT from Registry Contract\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change\n * - Output2: External Auth NFT back to the Domain Contract\n * - Output3: BCH change/reward to caller\n */\n function call(){\n require(tx.inputs.length == 4);\n require(tx.outputs.length == 4);\n \n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[3].lockingBytecode == registryInputLockingBytecode);\n\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n require(tx.inputs[2].tokenCategory == registryInputCategory);\n require(tx.outputs[2].tokenCategory == registryInputCategory);\n\n // AuctionNFT should be mutable and of the 'domainCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.inputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n require(auctionCapability == 0x01); // Mutable\n\n // nftCommiment of the externalAuthNFT must stay the same\n require(tx.inputs[2].nftCommitment == tx.outputs[2].nftCommitment);\n // Ensure that the externalAuth NFT is used and not the internalAuth NFT.\n require(tx.inputs[2].nftCommitment == 0x);\n\n // Get the name of the domain from the auctionNFT\n bytes name = tx.inputs[3].nftCommitment.split(20)[1];\n // Get the name length to generate the complete bytecode of the domain contract\n int nameLength = name.length;\n // category + name + bytecode.\n // Note: `inactivityExpiryTime` in the domain is already added to the domainContractBytecode in the constructor.\n bytes domainBytecode = 0x20 + registryInputCategory + bytes(nameLength) + name + domainContractBytecode;\n bytes32 scriptHash = hash256(domainBytecode);\n bytes35 domainLockingBytecode = new LockingBytecodeP2SH32(scriptHash);\n\n // Ensure that the externalAuthNFT is coming from the correct Domain Contract\n require(tx.inputs[2].lockingBytecode == domainLockingBytecode);\n require(tx.outputs[2].lockingBytecode == domainLockingBytecode);\n\n // tokenAmount from the auctionNFT goes to the authorizedThreadNFT to be accumulated later\n // and merged back with the CounterNFT using the `Accumulator` Contract\n require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[3].tokenAmount);\n\n // Reward Output\n require(tx.outputs[3].tokenCategory == 0x);\n }\n}", - 'debug': { - 'bytecode': 'c3549dc4549dc0519dc0c7c0cd8800c753c78800ce52ce788852d1788853ce01207f7c527988518852cf52d28852cf008853cf01147f777682770120537a7e7c7e7c7e7c7eaa02aa207c7e01877e52c7788852cd8800d300d053d0939d53d10087', - 'sourceMap': '28:12:28:28;:32::33;:4::35:1;29:12:29:29:0;:33::34;:4::36:1;32:12:32:33:0;:37::38;:4::40:1;33:22:33:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;39:51:39:52:0;:41::69:1;40:22:40:23:0;:12::40:1;:4::74;42:44:42:45:0;:34::60:1;43:22:43:23:0;:12::38:1;:42::63:0;:4::65:1;44:23:44:24:0;:12::39:1;:43::64:0;:4::66:1;47:63:47:64:0;:53::79:1;:86::88:0;:53::89:1;48:12:48:27:0;:31::52;;:4::54:1;49:33:49:37:0;:4::39:1;52:22:52:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;54:22:54:23:0;:12::38:1;:42::44:0;:4::46:1;57:27:57:28:0;:17::43:1;:50::52:0;:17::53:1;:::56;59:21:59:25:0;:::32:1;;62:27:62:31:0;:34::55;;:27:::1;:64::74:0;:27::75:1;:78::82:0;:27:::1;:85::107:0;:27:::1;63:25:63:48;64:36:64:73:0;:62::72;:36::73:1;;;67:22:67:23:0;:12::40:1;:44::65:0;:4::67:1;68:23:68:24:0;:12::41:1;:4::68;72:23:72:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;75:23:75:24:0;:12::39:1;:43::45:0;:4::47:1', - 'logs': [], - 'requires': [ - { - 'ip': 3, - 'line': 28, - }, - { - 'ip': 6, - 'line': 29, - }, - { - 'ip': 9, - 'line': 32, - }, - { - 'ip': 14, - 'line': 33, - }, - { - 'ip': 19, - 'line': 40, - }, - { - 'ip': 25, - 'line': 43, - }, - { - 'ip': 29, - 'line': 44, - }, - { - 'ip': 37, - 'line': 48, - }, - { - 'ip': 39, - 'line': 49, - }, - { - 'ip': 44, - 'line': 52, - }, - { - 'ip': 48, - 'line': 54, - }, - { - 'ip': 76, - 'line': 67, - }, - { - 'ip': 79, - 'line': 68, - }, - { - 'ip': 87, - 'line': 72, - }, - { - 'ip': 92, - 'line': 75, - }, - ], - }, - 'compiler': { - 'name': 'cashc', - 'version': '0.11.2', - }, - 'updatedAt': '2025-07-15T19:31:09.531Z', -}; diff --git a/lib/compiled/Factory.ts b/lib/compiled/Factory.ts new file mode 100644 index 0000000..4798508 --- /dev/null +++ b/lib/compiled/Factory.ts @@ -0,0 +1,185 @@ +export default { + 'contractName': 'Factory', + 'constructorInputs': [ + { + 'name': 'nameContractBytecode', + 'type': 'bytes', + }, + { + 'name': 'minWaitTime', + 'type': 'int', + }, + { + 'name': 'creatorIncentivePKH', + 'type': 'bytes20', + }, + { + 'name': 'tld', + 'type': 'bytes', + }, + ], + 'abi': [ + { + 'name': 'call', + 'inputs': [], + }, + ], + 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_7 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOVALUE OP_INPUTINDEX OP_OUTPUTVALUE OP_NUMEQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_3 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_5 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY OP_EQUALVERIFY OP_3 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_2 OP_OUTPUTTOKENCOMMITMENT OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_0 OP_NUMEQUALVERIFY OP_2 OP_OUTPUTVALUE OP_2 OP_UTXOVALUE OP_NUMEQUALVERIFY OP_3 OP_INPUTSEQUENCENUMBER OP_3 OP_ROLL OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_DUP OP_SIZE OP_NIP 20 OP_4 OP_ROLL OP_CAT OP_SWAP OP_CAT OP_OVER OP_CAT OP_5 OP_ROLL OP_CAT OP_3 OP_ROLL OP_CAT OP_HASH256 aa20 OP_SWAP OP_CAT 87 OP_CAT OP_3 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_3 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENAMOUNT OP_8 OP_NUM2BIN OP_REVERSEBYTES OP_4 OP_OUTPUTTOKENCOMMITMENT OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_5 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_ROT OP_CAT OP_EQUALVERIFY OP_5 OP_OUTPUTBYTECODE 76a914 OP_ROT OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_5 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_0 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT OP_ADD OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENAMOUNT OP_1 OP_MUL 1027 OP_DIV OP_3 OP_UTXOTOKENAMOUNT OP_1 OP_ROT OP_SUB OP_MUL OP_DUP 204e OP_GREATERTHAN OP_IF OP_6 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_6 OP_OUTPUTVALUE OP_OVER OP_NUMEQUALVERIFY OP_6 OP_OUTPUTBYTECODE 76a914 OP_3 OP_PICK OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_ENDIF OP_2DROP OP_1', + 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param nameContractBytecode - Partial bytecode of the name contract\n * @param minWaitTime - Minimum wait time to consider an auction ended\n * @param creatorIncentivePKH - PKH of the creator incentive\n * @param tld - TLD of the name\n */\ncontract Factory(\n bytes nameContractBytecode,\n int minWaitTime,\n bytes20 creatorIncentivePKH,\n bytes tld\n) {\n /**\n * This function finalizes a name registration auction by:\n * - Verifying the auction has ended and the winner's bid is valid\n * - Issuing an immutable externalAuthNFT to the Name Contract\n * - Issuing an immutable internalAuthNFT to the Name Contract\n * - Issuing an immutable name NFT to the auction winner\n * - Distributing auction fees between the platform and miners\n * - Burning the auctionNFT\n * - Pure BCH input from bidder is used to prevent miners from taking away the funds from any or all transactions in the future.\n * Out of many possible ways, this method will be suitable to easily implement by applications.\n *\n *\n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract\n * - Input2: NameMintingNFT from the Registry Contract\n * - Input3: auctionNFT from the Registry Contract\n *\n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change\n * - Output2: NameMintingNFT back to the Registry contract\n * - Output3: External Auth NFT to the name contract\n * - Output4: Internal Auth NFT to the name contract\n * - Output5: Name NFT to the auction winner\n * - Output6: Platform fee [Reduces and the not included]\n *\n */\n function call(){\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 7);\n\n // This contract can only be used at input1 and it should return to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the nameCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n // Strict value checks to ensure the platform and miner get fee.\n require(tx.inputs[this.activeInputIndex].value == tx.outputs[this.activeInputIndex].value);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.inputs[3].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n\n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n require(tx.outputs[3].tokenCategory == registryInputCategory);\n require(tx.outputs[4].tokenCategory == registryInputCategory);\n require(tx.outputs[5].tokenCategory == registryInputCategory);\n\n // NameMintingNFT should be minting and of the 'nameCategory' i.e registryInputCategory\n bytes nameMintingCategory, bytes nameMintingCapability = tx.inputs[2].tokenCategory.split(32);\n require(nameMintingCategory == registryInputCategory);\n // Minting\n require(nameMintingCapability == 0x02);\n // NameMintingNFT should keep the same category and capability\n require(tx.inputs[2].tokenCategory == tx.outputs[2].tokenCategory);\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.inputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n // Mutable\n require(auctionCapability == 0x01);\n\n // Enforce strict restrictions on NameMintingNFT\n require(tx.inputs[2].nftCommitment == tx.outputs[2].nftCommitment);\n // NameMintingNFT has no nftCommitment\n require(tx.outputs[2].nftCommitment == 0x);\n // NameMintingNFT has no tokenAmount\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount);\n require(tx.outputs[2].tokenAmount == 0);\n\n // Strict value check\n require(tx.outputs[2].value == tx.inputs[2].value);\n\n // Enforcing the relative timelock, the auctionNFT must be atleast `minWaitTime` old\n // to be considered ended.\n require(tx.inputs[3].sequenceNumber == minWaitTime);\n\n // Extract the PKH and name from the auctionNFT\n bytes20 bidderPKH, bytes name = tx.inputs[3].nftCommitment.split(20);\n \n // Get the name length to generate the complete bytecode of the name contract\n int nameLength = name.length;\n // category + name + bytecode.\n // Note: `inactivityExpiryTime` in the name is already added to the nameContractBytecode in the constructor.\n bytes nameBytecode = 0x20 + registryInputCategory + bytes(nameLength) + name + tld + nameContractBytecode;\n bytes32 scriptHash = hash256(nameBytecode);\n bytes35 nameLockingBytecode = new LockingBytecodeP2SH32(scriptHash);\n \n // ExternalAuthNFT goes to the name contract\n require(tx.outputs[3].lockingBytecode == nameLockingBytecode);\n // InternalAuthNFT goes to the name contract\n require(tx.outputs[4].lockingBytecode == nameLockingBytecode);\n \n // ExternalAuthNFT does not have any commitment\n require(tx.outputs[3].nftCommitment == 0x);\n // Strict value check\n require(tx.outputs[3].value == 1000);\n\n // InternalAuthNFT has registrationID as the commitment so it can be used to authenticate\n // along with the ownershipNFT\n bytes8 registrationId = bytes8(tx.inputs[3].tokenAmount).reverse();\n require(tx.outputs[4].nftCommitment == registrationId);\n // Strict value check\n require(tx.outputs[4].value == 1000);\n\n // Send the name ownership NFT to the bidder\n require(tx.outputs[5].nftCommitment == registrationId + name);\n require(tx.outputs[5].lockingBytecode == new LockingBytecodeP2PKH(bidderPKH));\n require(tx.outputs[5].value == 1000);\n\n // tokenAmount from the auctionNFT goes to the authorizedThreadNFT to be accumulated later\n // and merged back with the CounterNFT using the `Accumulator` Contract\n require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[3].tokenAmount);\n\n // Dual Decay mechanism, creator incentive decays linearly with the step.\n // 1. Decay percentage to the step 0.001%\n int decayPercentageToTheStep = tx.inputs[3].tokenAmount * 1 / 10000;\n // 2. Get creator incentive for current step with linear decay\n int creatorIncentive = tx.inputs[3].tokenAmount * (1 - decayPercentageToTheStep);\n\n if(creatorIncentive > 20000) {\n require(tx.outputs[6].tokenCategory == 0x);\n // Enforce that the other piece of the fee goes to the miners.\n require(tx.outputs[6].value == creatorIncentive);\n require(tx.outputs[6].lockingBytecode == new LockingBytecodeP2PKH(creatorIncentivePKH));\n }\n }\n\n}", + 'debug': { + 'bytecode': 'c3549dc457a169c0519dc0c7c0cd88c0d10088c0c6c0cc9d00c752c7788853c7788852cd8800ce53d1788854d1788855d1788852ce01207f7c527988528852ce52d18853ce01207f7c527988518852cf52d28852d2008852d352d09d52d3009d52cc52c69d53cb537a9d53cf01147f7682770120547a7e7c7e787e557a7e537a7eaa02aa207c7e01877e53cd788854cd8853d2008853cc02e8039d53d05880bc54d2788854cc02e8039d55d27c7b7e8855cd0376a9147b7e0288ac7e8855cc02e8039d00d300d053d0939d53d051950210279653d0517b94957602204ea06356d1008856cc789d56cd0376a91453797e0288ac7e88686d51', + 'sourceMap': '44:12:44:28;:32::33;:4::35:1;45:12:45:29:0;:33::34;:12:::1;:4::36;48:12:48:33:0;:37::38;:4::40:1;49:22:49:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;51:23:51:44:0;:12::59:1;:63::65:0;:4::67:1;53:22:53:43:0;:12::50:1;:65::86:0;:54::93:1;:4::95;59:51:59:52:0;:41::69:1;60:22:60:23:0;:12::40:1;:44::72:0;:4::74:1;61:22:61:23:0;:12::40:1;:44::72:0;:4::74:1;62:23:62:24:0;:12::41:1;:4::75;65:44:65:45:0;:34::60:1;66:23:66:24:0;:12::39:1;:43::64:0;:4::66:1;67:23:67:24:0;:12::39:1;:43::64:0;:4::66:1;68:23:68:24:0;:12::39:1;:43::64:0;:4::66:1;71:71:71:72:0;:61::87:1;:94::96:0;:61::97:1;72:12:72:31:0;:35::56;;:4::58:1;74:37:74:41:0;:4::43:1;76:22:76:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;79:63:79:64:0;:53::79:1;:86::88:0;:53::89:1;80:12:80:27:0;:31::52;;:4::54:1;82:33:82:37:0;:4::39:1;85:22:85:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;87:23:87:24:0;:12::39:1;:43::45:0;:4::47:1;89:23:89:24:0;:12::37:1;:51::52:0;:41::65:1;:4::67;90:23:90:24:0;:12::37:1;:41::42:0;:4::44:1;93:23:93:24:0;:12::31:1;:45::46:0;:35::53:1;:4::55;97:22:97:23:0;:12::39:1;:43::54:0;;:4::56:1;100:46:100:47:0;:36::62:1;:69::71:0;:36::72:1;103:21:103:25:0;:::32:1;;106:25:106:29:0;:32::53;;:25:::1;:62::72:0;:25::73:1;:76::80:0;:25:::1;:83::86:0;;:25:::1;:89::109:0;;:25:::1;107::107:46;108:34:108:71:0;:60::70;:34::71:1;;;111:23:111:24:0;:12::41:1;:45::64:0;:4::66:1;113:23:113:24:0;:12::41:1;:4::66;116:23:116:24:0;:12::39:1;:43::45:0;:4::47:1;118:23:118:24:0;:12::31:1;:35::39:0;:4::41:1;122:45:122:46:0;:35::59:1;:28::60;;:::70;123:23:123:24:0;:12::39:1;:43::57:0;:4::59:1;125:23:125:24:0;:12::31:1;:35::39:0;:4::41:1;128:23:128:24:0;:12::39:1;:43::57:0;:60::64;:43:::1;:4::66;129:23:129:24:0;:12::41:1;:45::80:0;:70::79;:45::80:1;;;:4::82;130:23:130:24:0;:12::31:1;:35::39:0;:4::41:1;134:23:134:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;138:45:138:46:0;:35::59:1;:62::63:0;:35:::1;:66::71:0;:35:::1;140:37:140:38:0;:27::51:1;:55::56:0;:59::83;:55:::1;:27::84;142:7:142:23:0;:26::31;:7:::1;:33:147:5:0;143:25:143:26;:14::41:1;:45::47:0;:6::49:1;145:25:145:26:0;:14::33:1;:37::53:0;:6::55:1;146:25:146:26:0;:14::43:1;:47::92:0;:72::91;;:47::92:1;;;:6::94;142:33:147:5;43:2:148:3;', + 'logs': [], + 'requires': [ + { + 'ip': 6, + 'line': 44, + }, + { + 'ip': 10, + 'line': 45, + }, + { + 'ip': 13, + 'line': 48, + }, + { + 'ip': 18, + 'line': 49, + }, + { + 'ip': 22, + 'line': 51, + }, + { + 'ip': 27, + 'line': 53, + }, + { + 'ip': 33, + 'line': 60, + }, + { + 'ip': 37, + 'line': 61, + }, + { + 'ip': 40, + 'line': 62, + }, + { + 'ip': 46, + 'line': 66, + }, + { + 'ip': 50, + 'line': 67, + }, + { + 'ip': 54, + 'line': 68, + }, + { + 'ip': 62, + 'line': 72, + }, + { + 'ip': 64, + 'line': 74, + }, + { + 'ip': 69, + 'line': 76, + }, + { + 'ip': 77, + 'line': 80, + }, + { + 'ip': 79, + 'line': 82, + }, + { + 'ip': 84, + 'line': 85, + }, + { + 'ip': 88, + 'line': 87, + }, + { + 'ip': 93, + 'line': 89, + }, + { + 'ip': 97, + 'line': 90, + }, + { + 'ip': 102, + 'line': 93, + }, + { + 'ip': 107, + 'line': 97, + }, + { + 'ip': 138, + 'line': 111, + }, + { + 'ip': 141, + 'line': 113, + }, + { + 'ip': 145, + 'line': 116, + }, + { + 'ip': 149, + 'line': 118, + }, + { + 'ip': 158, + 'line': 123, + }, + { + 'ip': 162, + 'line': 125, + }, + { + 'ip': 168, + 'line': 128, + }, + { + 'ip': 176, + 'line': 129, + }, + { + 'ip': 180, + 'line': 130, + }, + { + 'ip': 188, + 'line': 134, + }, + { + 'ip': 208, + 'line': 143, + }, + { + 'ip': 212, + 'line': 145, + }, + { + 'ip': 221, + 'line': 146, + }, + ], + }, + 'compiler': { + 'name': 'cashc', + 'version': '0.11.3', + }, + 'updatedAt': '2025-07-26T12:09:10.168Z', +}; diff --git a/lib/compiled/Name.ts b/lib/compiled/Name.ts new file mode 100644 index 0000000..6bfdaaa --- /dev/null +++ b/lib/compiled/Name.ts @@ -0,0 +1,234 @@ +export default { + 'contractName': 'Name', + 'constructorInputs': [ + { + 'name': 'inactivityExpiryTime', + 'type': 'int', + }, + { + 'name': 'fullName', + 'type': 'bytes', + }, + { + 'name': 'nameCategory', + 'type': 'bytes', + }, + ], + 'abi': [ + { + 'name': 'useAuth', + 'inputs': [ + { + 'name': 'authID', + 'type': 'int', + }, + ], + }, + { + 'name': 'penaliseInvalidName', + 'inputs': [], + }, + { + 'name': 'resolveOwnerConflict', + 'inputs': [], + }, + { + 'name': 'burn', + 'inputs': [], + }, + ], + 'bytecode': 'OP_3 OP_PICK OP_0 OP_NUMEQUAL OP_IF OP_TXVERSION OP_2 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOTOKENCOMMITMENT OP_INPUTINDEX OP_OUTPUTTOKENCOMMITMENT OP_EQUALVERIFY OP_4 OP_ROLL OP_1 OP_NUMEQUAL OP_IF OP_INPUTINDEX OP_1ADD OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_INPUTINDEX OP_1ADD OP_UTXOTOKENCOMMITMENT OP_8 OP_SPLIT OP_DUP OP_4 OP_PICK OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOTOKENCOMMITMENT OP_2 OP_PICK OP_EQUALVERIFY OP_2DROP OP_ELSE OP_INPUTINDEX OP_UTXOTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_ENDIF OP_2DROP OP_2DROP OP_1 OP_ELSE OP_3 OP_PICK OP_1 OP_NUMEQUAL OP_IF OP_TXVERSION OP_2 OP_NUMEQUALVERIFY OP_2DROP OP_2DROP OP_1 OP_ELSE OP_3 OP_PICK OP_2 OP_NUMEQUAL OP_IF OP_TXVERSION OP_2 OP_NUMEQUALVERIFY OP_TXINPUTCOUNT OP_5 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_3 OP_NUMEQUALVERIFY OP_4 OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_0 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_1 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_0 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_1 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENCOMMITMENT OP_1 OP_UTXOTOKENCOMMITMENT OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_1 OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_3 OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_1 OP_OUTPUTTOKENCATEGORY OP_3 OP_ROLL OP_EQUALVERIFY OP_1 OP_UTXOTOKENCOMMITMENT OP_REVERSEBYTES OP_BIN2NUM OP_3 OP_UTXOTOKENCOMMITMENT OP_REVERSEBYTES OP_BIN2NUM OP_LESSTHAN OP_NIP OP_NIP OP_NIP OP_ELSE OP_3 OP_ROLL OP_3 OP_NUMEQUALVERIFY OP_TXVERSION OP_2 OP_NUMEQUALVERIFY OP_TXINPUTCOUNT OP_3 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_1 OP_NUMEQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_0 OP_EQUAL OP_IF OP_1 OP_INPUTSEQUENCENUMBER OP_OVER OP_NUMEQUALVERIFY OP_ELSE OP_2 OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_8 OP_SPLIT OP_DROP OP_0 OP_UTXOTOKENCOMMITMENT OP_EQUALVERIFY OP_ENDIF OP_INPUTINDEX OP_UTXOBYTECODE OP_0 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_1 OP_UTXOBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_1 OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_3 OP_PICK OP_EQUALVERIFY OP_1 OP_UTXOTOKENCATEGORY OP_3 OP_ROLL OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUAL OP_NIP OP_NIP OP_ENDIF OP_ENDIF OP_ENDIF', + 'source': 'pragma cashscript 0.11.3;\n\n/**\n * @param inactivityExpiryTime The time period after which the name is considered inactive.\n * @param fullName The full name of the name including the TLD. .\n * @param nameCategory The category of the name.\n */\ncontract Name(\n int inactivityExpiryTime,\n bytes fullName,\n bytes nameCategory\n ) {\n \n /**\n * This function can be used to perform a variety of actions.\n *\n * For example:\n * - It can be used to prove the the ownership of the name by other contracts.\n * - This function allows the owner to perform any actions in conjunction with other contracts.\n * - This function can be used to add records and invalidate multiple records in a single transaction.\n *\n * Records are created using OP_RETURN outputs. To add a record, include the record data directly in the OP_RETURN output.\n * To invalidate a record, prefix "RMV" followed by the hash of the record content in the OP_RETURN output. This will signal\n * the library/indexers to exclude the record from the valid records.\n * \n * @inputs\n * - Inputx: Internal/External Auth NFT\n * - Inputx+1 (optional): Name ownership NFT from the owner\n * \n * @outputs\n * - Outputx: Internal/External Auth NFT returned to this contract\n * - Outputx+1 (optional): Name NFT returned\n * \n */\n function useAuth(int authID) {\n // Need transaction version 2 to prevent any vulnerabilities caused due to future versions.\n require(tx.version == 2);\n\n // The activeInputIndex can be anything as long as the utxo properties are preserved and comes back to the\n // contract without alteration.\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n require(tx.inputs[this.activeInputIndex].tokenCategory == nameCategory);\n require(tx.outputs[this.activeInputIndex].tokenCategory == nameCategory);\n require(tx.inputs[this.activeInputIndex].nftCommitment == tx.outputs[this.activeInputIndex].nftCommitment);\n\n if(authID == 1) {\n // The next input from the InternalAuthNFT must be the ownershipNFT.\n require(tx.inputs[this.activeInputIndex + 1].tokenCategory == nameCategory);\n bytes registrationId, bytes nameFromOwnerNFT = tx.inputs[this.activeInputIndex + 1].nftCommitment.split(8);\n require(nameFromOwnerNFT == fullName);\n require(tx.inputs[this.activeInputIndex].nftCommitment == registrationId);\n } else {\n // One known use of ExternalAuthNFT in the `NameOwnershipGuard` contract. ExternalAuthNFT is\n // used to prove that an owner exists.\n require(tx.inputs[this.activeInputIndex].nftCommitment == 0x);\n }\n }\n\n function penaliseInvalidName() {\n // Allow anyone to call only when the name registered is invalid, and the incentive system was not able to prevent it.\n \n require(tx.version == 2);\n }\n\n /**\n * If the incentive system fails, i.e `NameOwnershipGuard` or `AuctionConflictResolver` fails to prevent a\n * a owner conflict. When this happens there will be > 1 owner for this name.\n * The owner with the lowest registrationID must be the only owner for this name.\n * To help enforce this rule, this function will allow anyone to burn both the Auth NFTs of the NEW owner.\n *\n * @inputs\n * - Input0: Valid External Auth NFT from self\n * - Input1: Valid Internal Auth NFT from self\n * - Input2: Invalid External Auth NFT from self\n * - Input3: Invalid Internal Auth NFT from self\n * - Input4: BCH input from anyone\n * \n * @outputs \n * - Output0: Valid External Auth NFT back to self\n * - Output1: Valid Internal Auth NFT back to self\n * - Output3: BCH change output\n */\n function resolveOwnerConflict(){\n // Need transaction version 2 to prevent any vulnerabilities caused due to future versions.\n require(tx.version == 2);\n\n require(tx.inputs.length == 5);\n require(tx.outputs.length == 3);\n\n // Pure BCH input and output to fund the transaction\n require(tx.inputs[4].tokenCategory == 0x);\n require(tx.outputs[2].tokenCategory == 0x);\n\n bytes selfLockingBytecode = tx.inputs[this.activeInputIndex].lockingBytecode;\n require(tx.inputs[0].lockingBytecode == selfLockingBytecode);\n require(tx.inputs[1].lockingBytecode == selfLockingBytecode);\n require(tx.inputs[2].lockingBytecode == selfLockingBytecode);\n require(tx.inputs[3].lockingBytecode == selfLockingBytecode);\n\n require(tx.outputs[0].lockingBytecode == selfLockingBytecode);\n require(tx.outputs[1].lockingBytecode == selfLockingBytecode);\n\n // External Auth NFTs\n require(tx.inputs[0].nftCommitment == 0x);\n require(tx.inputs[2].nftCommitment == 0x);\n\n // Commitments of Valid Auth NFts back to self\n require(tx.outputs[0].nftCommitment == 0x);\n require(tx.outputs[1].nftCommitment == tx.inputs[1].nftCommitment);\n\n // Ensure that all the token inputs and outputs have nameCategory\n require(tx.inputs[0].tokenCategory == nameCategory);\n require(tx.inputs[1].tokenCategory == nameCategory);\n require(tx.inputs[2].tokenCategory == nameCategory);\n require(tx.inputs[3].tokenCategory == nameCategory);\n\n require(tx.outputs[0].tokenCategory == nameCategory);\n require(tx.outputs[1].tokenCategory == nameCategory);\n\n // Compare the registrationID\n require(int(tx.inputs[1].nftCommitment.reverse()) < int(tx.inputs[3].nftCommitment.reverse()));\n }\n\n /**\n * Allows the name owner or anyone to burn the InternalAuthNFT and externalAuthNFT making this name available\n * for auction.\n * \n * - Owner can burn the AuthNFTs anytime.\n * - External party can burn the AuthNFTs when the internalAuth NFT has not been used for more than `inactivityExpiryTime`.\n *\n * @inputs\n * - Input0: External Auth NFT\n * - Input1: Internal Auth NFT\n * - Input2: Pure BCH or Name ownership NFT from the owner\n *\n * @outputs \n * - Output0: BCH change\n *\n */\n function burn() {\n // Need transaction version 2 to prevent any vulnerabilities caused due to future versions.\n // Need version 2 enforcement for relative timelocks.\n require(tx.version == 2);\n\n require(tx.inputs.length == 3);\n require(tx.outputs.length == 1);\n\n // If an external party is attempting to burn the authNFTs\n if (tx.inputs[2].tokenCategory == 0x) {\n // If pure BCH input, then allow anyone to burn given the time limit has passed.\n require(tx.inputs[1].sequenceNumber == inactivityExpiryTime);\n } else {\n // If name ownership NFT input, then allow the owner to burn anytime.\n require(tx.inputs[2].tokenCategory == nameCategory);\n // Make sure that the registrationID in the nameOwnershipNFT and the internalAuthNFT are the same.\n require(tx.inputs[2].nftCommitment.split(8)[0] == tx.inputs[0].nftCommitment);\n }\n\n bytes selfLockingBytecode = tx.inputs[this.activeInputIndex].lockingBytecode;\n require(tx.inputs[0].lockingBytecode == selfLockingBytecode);\n require(tx.inputs[1].lockingBytecode == selfLockingBytecode);\n\n // ExternalAuthNFT\n require(tx.inputs[0].nftCommitment == 0x);\n // Both InternalAuthNFT and externalAuthNFT are immutable and have the same tokenCategory\n require(tx.inputs[0].tokenCategory == tx.inputs[1].tokenCategory);\n require(tx.inputs[0].tokenCategory == nameCategory);\n require(tx.inputs[1].tokenCategory == nameCategory);\n\n // Return the BCH as change.\n require(tx.outputs[0].tokenCategory == 0x);\n }\n}\n', + 'debug': { + 'bytecode': '5379009c63c2529dc0c7c0cd88c0ce537988c0d1537988c0cfc0d288547a519c63c08bce537988c08bcf587f76547988c0cf5279886d67c0cf0088686d6d51675379519c63c2529d6d6d51675379529c63c2529dc3559dc4539d54ce008852d10088c0c700c7788851c7788852c7788853c7788800cd788851cd8800cf008852cf008800d2008851d251cf8800ce53798851ce53798852ce53798853ce53798800d153798851d1537a8851cfbc8153cfbc819f77777767537a539dc2529dc3539dc4519d52ce00876351cb789d6752ce53798852cf587f7500cf8868c0c700c7788851c78800cf008800ce51ce8800ce53798851ce537a8800d100877777686868', + 'sourceMap': '35:2:57:3;;;;;37:12:37:22;:26::27;:4::29:1;41:22:41:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;42:22:42:43:0;:12::58:1;:62::74:0;;:4::76:1;43:23:43:44:0;:12::59:1;:63::75:0;;:4::77:1;44:22:44:43:0;:12::58:1;:73::94:0;:62::109:1;:4::111;46:7:46:13:0;;:17::18;:7:::1;:20:52:5:0;48:24:48:45;:::49:1;:14::64;:68::80:0;;:6::82:1;49:63:49:84:0;:::88:1;:53::103;:110::111:0;:53::112:1;50:14:50:30:0;:34::42;;:6::44:1;51:24:51:45:0;:14::60:1;:64::78:0;;:6::80:1;46:20:52:5;52:11:56::0;55:24:55:45;:14::60:1;:64::66:0;:6::68:1;52:11:56:5;35:2:57:3;;;;59::63::0;;;;;62:12:62:22;:26::27;:4::29:1;59:2:63:3;;;;83::122::0;;;;;85:12:85:22;:26::27;:4::29:1;87:12:87:28:0;:32::33;:4::35:1;88:12:88:29:0;:33::34;:4::36:1;91:22:91:23:0;:12::38:1;:42::44:0;:4::46:1;92:23:92:24:0;:12::39:1;:43::45:0;:4::47:1;94:42:94:63:0;:32::80:1;95:22:95:23:0;:12::40:1;:44::63:0;:4::65:1;96:22:96:23:0;:12::40:1;:44::63:0;:4::65:1;97:22:97:23:0;:12::40:1;:44::63:0;:4::65:1;98:22:98:23:0;:12::40:1;:44::63:0;:4::65:1;100:23:100:24:0;:12::41:1;:45::64:0;:4::66:1;101:23:101:24:0;:12::41:1;:4::66;104:22:104:23:0;:12::38:1;:42::44:0;:4::46:1;105:22:105:23:0;:12::38:1;:42::44:0;:4::46:1;108:23:108:24:0;:12::39:1;:43::45:0;:4::47:1;109:23:109:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;112:22:112:23:0;:12::38:1;:42::54:0;;:4::56:1;113:22:113:23:0;:12::38:1;:42::54:0;;:4::56:1;114:22:114:23:0;:12::38:1;:42::54:0;;:4::56:1;115:22:115:23:0;:12::38:1;:42::54:0;;:4::56:1;117:23:117:24:0;:12::39:1;:43::55:0;;:4::57:1;118:23:118:24:0;:12::39:1;:43::55:0;;:4::57:1;121:26:121:27:0;:16::42:1;:::52;:12::53;:70::71:0;:60::86:1;:::96;:56::97;:4::99;83:2:122:3;;;;140::172::0;;;;143:12:143:22;:26::27;:4::29:1;145:12:145:28:0;:32::33;:4::35:1;146:12:146:29:0;:33::34;:4::36:1;149:18:149:19:0;:8::34:1;:38::40:0;:8:::1;:42:152:5:0;151:24:151:25;:14::41:1;:45::65:0;:6::67:1;152:11:157:5:0;154:24:154:25;:14::40:1;:44::56:0;;:6::58:1;156:24:156:25:0;:14::40:1;:47::48:0;:14::49:1;:::52;:66::67:0;:56::82:1;:6::84;152:11:157:5;159:42:159:63:0;:32::80:1;160:22:160:23:0;:12::40:1;:44::63:0;:4::65:1;161:22:161:23:0;:12::40:1;:4::65;164:22:164:23:0;:12::38:1;:42::44:0;:4::46:1;166:22:166:23:0;:12::38:1;:52::53:0;:42::68:1;:4::70;167:22:167:23:0;:12::38:1;:42::54:0;;:4::56:1;168:22:168:23:0;:12::38:1;:42::54:0;;:4::56:1;171:23:171:24:0;:12::39:1;:43::45:0;:4::47:1;140:2:172:3;;8:0:173:1;;', + 'logs': [], + 'requires': [ + { + 'ip': 10, + 'line': 37, + }, + { + 'ip': 15, + 'line': 41, + }, + { + 'ip': 20, + 'line': 42, + }, + { + 'ip': 25, + 'line': 43, + }, + { + 'ip': 30, + 'line': 44, + }, + { + 'ip': 41, + 'line': 48, + }, + { + 'ip': 50, + 'line': 50, + }, + { + 'ip': 55, + 'line': 51, + }, + { + 'ip': 61, + 'line': 55, + }, + { + 'ip': 74, + 'line': 62, + }, + { + 'ip': 86, + 'line': 85, + }, + { + 'ip': 89, + 'line': 87, + }, + { + 'ip': 92, + 'line': 88, + }, + { + 'ip': 96, + 'line': 91, + }, + { + 'ip': 100, + 'line': 92, + }, + { + 'ip': 106, + 'line': 95, + }, + { + 'ip': 110, + 'line': 96, + }, + { + 'ip': 114, + 'line': 97, + }, + { + 'ip': 118, + 'line': 98, + }, + { + 'ip': 122, + 'line': 100, + }, + { + 'ip': 125, + 'line': 101, + }, + { + 'ip': 129, + 'line': 104, + }, + { + 'ip': 133, + 'line': 105, + }, + { + 'ip': 137, + 'line': 108, + }, + { + 'ip': 142, + 'line': 109, + }, + { + 'ip': 147, + 'line': 112, + }, + { + 'ip': 152, + 'line': 113, + }, + { + 'ip': 157, + 'line': 114, + }, + { + 'ip': 162, + 'line': 115, + }, + { + 'ip': 167, + 'line': 117, + }, + { + 'ip': 172, + 'line': 118, + }, + { + 'ip': 182, + 'line': 121, + }, + { + 'ip': 192, + 'line': 143, + }, + { + 'ip': 195, + 'line': 145, + }, + { + 'ip': 198, + 'line': 146, + }, + { + 'ip': 207, + 'line': 151, + }, + { + 'ip': 213, + 'line': 154, + }, + { + 'ip': 221, + 'line': 156, + }, + { + 'ip': 228, + 'line': 160, + }, + { + 'ip': 231, + 'line': 161, + }, + { + 'ip': 235, + 'line': 164, + }, + { + 'ip': 240, + 'line': 166, + }, + { + 'ip': 245, + 'line': 167, + }, + { + 'ip': 250, + 'line': 168, + }, + { + 'ip': 255, + 'line': 171, + }, + ], + }, + 'compiler': { + 'name': 'cashc', + 'version': '0.11.3', + }, + 'updatedAt': '2025-07-26T12:09:09.557Z', +}; diff --git a/lib/compiled/NameEnforcer.ts b/lib/compiled/NameEnforcer.ts new file mode 100644 index 0000000..1e6feb6 --- /dev/null +++ b/lib/compiled/NameEnforcer.ts @@ -0,0 +1,81 @@ +export default { + 'contractName': 'NameEnforcer', + 'constructorInputs': [], + 'abi': [ + { + 'name': 'call', + 'inputs': [ + { + 'name': 'characterNumber', + 'type': 'int', + }, + ], + }, + ], + 'bytecode': 'OP_TXINPUTCOUNT OP_3 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_3 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_NIP OP_OVER OP_SPLIT OP_DROP OP_OVER OP_1SUB OP_SPLIT OP_NIP OP_BIN2NUM OP_DUP 2d OP_NUMNOTEQUAL OP_VERIFY OP_DUP 61 7b OP_WITHIN OP_NOT OP_VERIFY OP_DUP 41 5b OP_WITHIN OP_NOT OP_VERIFY 30 3a OP_WITHIN OP_NOT OP_VERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_0 OP_UTXOTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_ADD OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUAL OP_NIP', + 'source': "pragma cashscript 0.11.3;\n\ncontract NameEnforcer() {\n /**\n * Proves that a name contains invalid characters, burns the auctionNFT, and takes away the funds as a reward.\n * During the entire auction, this can be called at any time by anyone.\n * \n * Rules:\n * 1. The name must consist of only these characters:\n * - Letters (a-z or A-Z)\n * - Numbers (0-9)\n * - Hyphens (-)\n *\n * @param characterNumber - Number of the character in the name that is invalid (starting from 1)\n *\n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: auctionNFT from Registry Contract.\n *\n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Reward to caller.\n *\n */\n function call(int characterNumber) {\n require(tx.inputs.length == 3);\n require(tx.outputs.length == 3);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n\n // Lock this contract to only be used with the registry type contract.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n\n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.inputs[2].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n require(auctionCapability == 0x01); // Mutable\n\n bytes name = tx.inputs[2].nftCommitment.split(20)[1];\n bytes characterSplitBytes = name.split(characterNumber)[0];\n characterNumber = characterNumber - 1;\n bytes character = characterSplitBytes.split(characterNumber)[1];\n int charVal = int(character);\n\n // Character is not a hyphen.\n require(charVal != 45); \n // Character is not from a-z.\n require(!within(charVal, 97, 123));\n // Character is not from A-Z.\n require(!within(charVal, 65, 91));\n // Character is not from 0-9.\n require(!within(charVal, 48, 58));\n\n // tokenAmount from the invalid auctionNFT goes to the authorizedThreadNFT to be accumulated later\n // and merged back with the CounterNFT using the `Accumulator` Contract\n require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[2].tokenAmount);\n\n // Pure BCH.\n require(tx.outputs[2].tokenCategory == 0x);\n }\n}", + 'debug': { + 'bytecode': 'c3539dc4539dc0519dc0c7c0cd8800c752c78800ce52ce01207f7c7b88518852cf01147f77787f75788c7f778176012d9e69760161017ba59169760141015ba591690130013aa5916900d300d052d0939d52d1008777', + 'sourceMap': '28:12:28:28;:32::33;:4::35:1;29:12:29:29:0;:33::34;:4::36:1;32:12:32:33:0;:37::38;:4::40:1;33:22:33:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;36:51:36:52:0;:41::69:1;37:22:37:23:0;:12::40:1;:4::74;40:44:40:45:0;:34::60:1;43:63:43:64:0;:53::79:1;:86::88:0;:53::89:1;44:12:44:27:0;:31::52;:4::54:1;45:33:45:37:0;:4::39:1;47:27:47:28:0;:17::43:1;:50::52:0;:17::53:1;:::56;48:43:48:58:0;:32::59:1;:::62;49:22:49:37:0;:::41:1;50::50:64;:::67;51:18:51:32;54:12:54:19:0;:23::25;:12:::1;:4::27;56:20:56::0;:29::31;:33::36;:13::37:1;:12;:4::39;58:20:58:27:0;:29::31;:33::35;:13::36:1;:12;:4::38;60:29:60:31:0;:33::35;:13::36:1;:12;:4::38;64:23:64:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;67:23:67:24:0;:12::39:1;:43::45:0;:4::47:1;27:2:68:3', + 'logs': [], + 'requires': [ + { + 'ip': 2, + 'line': 28, + }, + { + 'ip': 5, + 'line': 29, + }, + { + 'ip': 8, + 'line': 32, + }, + { + 'ip': 13, + 'line': 33, + }, + { + 'ip': 18, + 'line': 37, + }, + { + 'ip': 27, + 'line': 44, + }, + { + 'ip': 29, + 'line': 45, + }, + { + 'ip': 46, + 'line': 54, + }, + { + 'ip': 52, + 'line': 56, + }, + { + 'ip': 58, + 'line': 58, + }, + { + 'ip': 63, + 'line': 60, + }, + { + 'ip': 71, + 'line': 64, + }, + { + 'ip': 76, + 'line': 67, + }, + ], + }, + 'compiler': { + 'name': 'cashc', + 'version': '0.11.3', + }, + 'updatedAt': '2025-07-26T12:09:10.471Z', +}; diff --git a/lib/compiled/OwnershipGuard.ts b/lib/compiled/OwnershipGuard.ts new file mode 100644 index 0000000..4faf62f --- /dev/null +++ b/lib/compiled/OwnershipGuard.ts @@ -0,0 +1,93 @@ +export default { + 'contractName': 'OwnershipGuard', + 'constructorInputs': [ + { + 'name': 'nameContractBytecode', + 'type': 'bytes', + }, + { + 'name': 'tld', + 'type': 'bytes', + }, + ], + 'abi': [ + { + 'name': 'call', + 'inputs': [], + }, + ], + 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_3 OP_UTXOBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_3 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_2 OP_OUTPUTTOKENCOMMITMENT OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_3 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_NIP OP_DUP OP_SIZE OP_NIP 20 OP_3 OP_ROLL OP_CAT OP_SWAP OP_4 OP_PICK OP_SIZE OP_NIP OP_ADD OP_CAT OP_SWAP OP_CAT OP_ROT OP_CAT OP_SWAP OP_CAT OP_HASH256 aa20 OP_SWAP OP_CAT 87 OP_CAT OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_0 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT OP_ADD OP_NUMEQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUAL', + 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param nameContractBytecode The the partial bytecode of the name contract that has an Owner.\n * @param tld - TLD of the name\n */\ncontract OwnershipGuard(bytes nameContractBytecode, bytes tld) {\n /**\n * If the name being auctioned already has an `externalAuthNFT` with the same category, then the auction is invalid.\n * Because it means that an owner still exists. If it is known that the name has been abandoned for > `inactivityExpiryTime`\n * then one must use the `burn` method of the name.cash to burn the internalAuthNFT and externalAuthNFT making the \n * name to be available for auction.\n *\n * Penalizes invalid name registrations by allowing anyone to burn the auctionNFT and claim the funds as a reward.\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract\n * - Input2: External Auth NFT from the Name Contract\n * - Input3: auctionNFT from Registry Contract\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change\n * - Output2: External Auth NFT back to the Name Contract\n * - Output3: BCH change/reward to caller\n */\n function call(){\n require(tx.inputs.length == 4);\n require(tx.outputs.length == 4);\n \n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[3].lockingBytecode == registryInputLockingBytecode);\n\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n require(tx.inputs[2].tokenCategory == registryInputCategory);\n require(tx.outputs[2].tokenCategory == registryInputCategory);\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.inputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n // Mutable\n require(auctionCapability == 0x01);\n\n // nftCommiment of the externalAuthNFT must stay the same\n require(tx.inputs[2].nftCommitment == tx.outputs[2].nftCommitment);\n // Ensure that the externalAuth NFT is used and not the internalAuth NFT.\n require(tx.inputs[2].nftCommitment == 0x);\n\n // Get the name of the name from the auctionNFT\n bytes name = tx.inputs[3].nftCommitment.split(20)[1];\n // Get the name length to generate the complete bytecode of the name contract\n int nameLength = name.length;\n // category + name + bytecode.\n // Note: `inactivityExpiryTime` in the name is already added to the nameContractBytecode in the constructor.\n bytes nameBytecode = 0x20 + registryInputCategory + bytes(nameLength + tld.length) + name + tld + nameContractBytecode;\n bytes32 scriptHash = hash256(nameBytecode);\n bytes35 nameLockingBytecode = new LockingBytecodeP2SH32(scriptHash);\n\n // Ensure that the externalAuthNFT is coming from the correct Name Contract\n require(tx.inputs[2].lockingBytecode == nameLockingBytecode);\n require(tx.outputs[2].lockingBytecode == nameLockingBytecode);\n\n // tokenAmount from the auctionNFT goes to the authorizedThreadNFT to be accumulated later\n // and merged back with the CounterNFT using the `Accumulator` Contract\n require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[3].tokenAmount);\n\n // Reward Output\n require(tx.outputs[3].tokenCategory == 0x);\n }\n}", + 'debug': { + 'bytecode': 'c3549dc4549dc0519dc0c7c0cd8800c753c78800ce52ce788852d1788853ce01207f7c527988518852cf52d28852cf008853cf01147f777682770120537a7e7c54798277937e7c7e7b7e7c7eaa02aa207c7e01877e52c7788852cd8800d300d053d0939d53d10087', + 'sourceMap': '29:12:29:28;:32::33;:4::35:1;30:12:30:29:0;:33::34;:4::36:1;33:12:33:33:0;:37::38;:4::40:1;34:22:34:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;40:51:40:52:0;:41::69:1;41:22:41:23:0;:12::40:1;:4::74;43:44:43:45:0;:34::60:1;44:22:44:23:0;:12::38:1;:42::63:0;:4::65:1;45:23:45:24:0;:12::39:1;:43::64:0;:4::66:1;48:63:48:64:0;:53::79:1;:86::88:0;:53::89:1;49:12:49:27:0;:31::52;;:4::54:1;51:33:51:37:0;:4::39:1;54:22:54:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;56:22:56:23:0;:12::38:1;:42::44:0;:4::46:1;59:27:59:28:0;:17::43:1;:50::52:0;:17::53:1;:::56;61:21:61:25:0;:::32:1;;64:25:64:29:0;:32::53;;:25:::1;:62::72:0;:75::78;;:::85:1;;:62;:25::86;:89::93:0;:25:::1;:96::99:0;:25:::1;:102::122:0;:25:::1;65::65:46;66:34:66:71:0;:60::70;:34::71:1;;;69:22:69:23:0;:12::40:1;:44::63:0;:4::65:1;70:23:70:24:0;:12::41:1;:4::66;74:23:74:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;77:23:77:24:0;:12::39:1;:43::45:0;:4::47:1', + 'logs': [], + 'requires': [ + { + 'ip': 4, + 'line': 29, + }, + { + 'ip': 7, + 'line': 30, + }, + { + 'ip': 10, + 'line': 33, + }, + { + 'ip': 15, + 'line': 34, + }, + { + 'ip': 20, + 'line': 41, + }, + { + 'ip': 26, + 'line': 44, + }, + { + 'ip': 30, + 'line': 45, + }, + { + 'ip': 38, + 'line': 49, + }, + { + 'ip': 40, + 'line': 51, + }, + { + 'ip': 45, + 'line': 54, + }, + { + 'ip': 49, + 'line': 56, + }, + { + 'ip': 84, + 'line': 69, + }, + { + 'ip': 87, + 'line': 70, + }, + { + 'ip': 95, + 'line': 74, + }, + { + 'ip': 100, + 'line': 77, + }, + ], + }, + 'compiler': { + 'name': 'cashc', + 'version': '0.11.3', + }, + 'updatedAt': '2025-07-26T12:09:10.765Z', +}; diff --git a/lib/compiled/Registry.ts b/lib/compiled/Registry.ts index a5835fa..c7c2dbf 100644 --- a/lib/compiled/Registry.ts +++ b/lib/compiled/Registry.ts @@ -2,7 +2,7 @@ export default { 'contractName': 'Registry', 'constructorInputs': [ { - 'name': 'domainCategory', + 'name': 'nameCategory', 'type': 'bytes', }, ], @@ -13,10 +13,10 @@ export default { }, ], 'bytecode': 'OP_TXVERSION OP_2 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_0 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_0 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_0 OP_OUTPUTTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_OUTPUTVALUE OP_0 OP_UTXOVALUE OP_NUMEQUALVERIFY OP_0 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_UTXOTOKENCOMMITMENT OP_EQUALVERIFY OP_1 OP_UTXOBYTECODE OP_0 OP_UTXOTOKENCOMMITMENT OP_EQUAL', - 'source': 'pragma cashscript 0.11.2;\n\n/**\n * @param domainCategory - The category of the domain NFTs that are authorized to be registered. [In reverse order]\n *\n * The Registry has two minting NFTs:\n * 1. CounterMintingNFT, has tokenAmount and nftCommitment.\n * 2. DomainMintingNFT, does not have any tokenAmount or nftCommitment.\n */\ncontract Registry(bytes domainCategory) {\n /**\n * The Registry contract serves as both a source and storage for authorized NFTs.\n * It holds: RegistrationNFTs, AuctionNFTs, and AuthorizedThreadNFTs\n *\n * AuthorizedThreadNFTs are NFTs with immutable capability that share the same category as domainCategory.\n * These NFTs contain the lockingBytecode of authorized contracts.\n * Multiple copies of these NFTs enable parallel processing through multiple threads.\n * \n * The contract can only be called in conjunction with one of the authorized contracts.\n *\n * Imagine that the authorised contracts are just function composition,\n * those contracts are being used for the code in them and to reduce the transaction size.\n * This design reduces the transaction size to a minimum while using every OP_CODE required\n * for a given action. To use the code in these authorized contracts, a random UTXO is used and\n * sent back to itself to be used again in future.\n *\n * All the utxos, except for the DomainNFTs (InternalAuth, ExternalAuth and DomainOwnershipNFT),\n * stay with the Registry contract.\n * \n * @note Authorized contracts and their thread counts:\n * - Auction: [1 thread] (Single-threaded registration)\n * - Bid: [~x threads]\n * - DomainFactory: [~x threads]\n * - AuctionNameEnforcer: [~x threads]\n * - DomainOwnershipGuard: [~x threads]\n * - AuctionConflictResolver: [~x threads]\n * - Accumulator: [~x threads]\n * \n * @inputs\n * - Input0: AuthorizedThreadNFT from self\n * - Input1: Any UTXO from Authorized contract\n * \n * @outputs\n * - Output0: AuthorizedThreadNFT back to self\n * - Output1: Output back to Authorized contract to be reused again\n */\n function call() {\n // 1. Since the registry contract is static, version check is required to prevent from any vulnerabilities\n // caused due to future versions.\n // 2. BitCANN uses relative timelocks, need to enforce version 2.\n require(tx.version == 2);\n\n // Registry Contract\n\n bytes selfLockingBytecode = tx.inputs[this.activeInputIndex].lockingBytecode;\n // authorizedThreadNFT must stay with the Registry Contract.\n require(tx.inputs[0].lockingBytecode == selfLockingBytecode);\n require(tx.outputs[0].lockingBytecode == selfLockingBytecode);\n\n // Immutable NFTs of domainCategory in Registry Contract will always be authorizedThreadNFTs\n // Mutable NFTs of domainCategory in Registry Contract will always be auctionNFTs\n // Minting NFTs of domainCategory in Registry Contract will always be counterMintingNFT or DomainMintingNFT\n require(tx.inputs[0].tokenCategory == domainCategory);\n require(tx.outputs[0].tokenCategory == domainCategory);\n // Keeping the value same to not influence any satoshi movement in authorized contracts\n require(tx.outputs[0].value == tx.inputs[0].value);\n // The commitment that has the lockingbytecode of the authorized contract should never change.\n require(tx.outputs[0].nftCommitment == tx.inputs[0].nftCommitment);\n // Not checking the tokenAmount as it changes.\n\n // Authorized Contract\n\n // Expect the NFT commitment that contains the lockingBytecode of the authorized contract.\n require(tx.inputs[1].lockingBytecode == tx.inputs[0].nftCommitment);\n // With these prerequisites met, we just need to make sure that all the contracts that are deployed are written\n // and initialized properly, and they expect this structure and handle the inputs and outputs as expected.\n }\n}\n', + 'source': 'pragma cashscript 0.11.3;\n\n/**\n * @param nameCategory - The category of the name NFTs that are authorized to be registered. [In reverse order]\n *\n * The Registry has two minting NFTs:\n * 1. CounterMintingNFT, has tokenAmount and nftCommitment.\n * 2. NameMintingNFT, does not have any tokenAmount or nftCommitment.\n */\ncontract Registry(bytes nameCategory) {\n /**\n * The Registry contract serves as both a source and storage for authorized NFTs.\n * It holds: RegistrationNFTs, AuctionNFTs, and AuthorizedThreadNFTs\n *\n * AuthorizedThreadNFTs are NFTs with immutable capability that share the same category as nameCategory.\n * These NFTs contain the lockingBytecode of authorized contracts.\n * Multiple copies of these NFTs enable parallel processing through multiple threads.\n * \n * The contract can only be called in conjunction with one of the authorized contracts.\n *\n * Imagine that the authorised contracts are just function composition,\n * those contracts are being used for the code in them and to reduce the transaction size.\n * This design reduces the transaction size to a minimum while using every OP_CODE required\n * for a given action. To use the code in these authorized contracts, a random UTXO is used and\n * sent back to itself to be used again in future.\n *\n * All the utxos, except for the NameNFTs (InternalAuth, ExternalAuth and NameOwnershipNFT),\n * stay with the Registry contract.\n * \n * @note Authorized contracts and their thread counts:\n * - Auction: [1 thread] (Single-threaded registration)\n * - Bid: [~x threads]\n * - NameFactory: [~x threads]\n * - AuctionNameEnforcer: [~x threads]\n * - NameOwnershipGuard: [~x threads]\n * - AuctionConflictResolver: [~x threads]\n * - Accumulator: [~x threads]\n * \n * @inputs\n * - Input0: AuthorizedThreadNFT from self\n * - Input1: Any UTXO from Authorized contract\n * \n * @outputs\n * - Output0: AuthorizedThreadNFT back to self\n * - Output1: Output back to Authorized contract to be reused again\n */\n function call() {\n // 1. Since the registry contract is static, version check is required to prevent from any vulnerabilities\n // caused due to future versions.\n // 2. BitCANN uses relative timelocks, need to enforce version 2.\n require(tx.version == 2);\n\n // Registry Contract\n\n bytes selfLockingBytecode = tx.inputs[this.activeInputIndex].lockingBytecode;\n // authorizedThreadNFT must stay with the Registry Contract.\n require(tx.inputs[0].lockingBytecode == selfLockingBytecode);\n require(tx.outputs[0].lockingBytecode == selfLockingBytecode);\n\n // Immutable NFTs of nameCategory in Registry Contract will always be authorizedThreadNFTs\n // Mutable NFTs of nameCategory in Registry Contract will always be auctionNFTs\n // Minting NFTs of nameCategory in Registry Contract will always be counterMintingNFT or NameMintingNFT\n require(tx.inputs[0].tokenCategory == nameCategory);\n require(tx.outputs[0].tokenCategory == nameCategory);\n // Keeping the value same to not influence any satoshi movement in authorized contracts\n require(tx.outputs[0].value == tx.inputs[0].value);\n // The commitment that has the lockingbytecode of the authorized contract should never change.\n require(tx.outputs[0].nftCommitment == tx.inputs[0].nftCommitment);\n // Not checking the tokenAmount as it changes.\n\n // Authorized Contract\n\n // Expect the NFT commitment that contains the lockingBytecode of the authorized contract.\n require(tx.inputs[1].lockingBytecode == tx.inputs[0].nftCommitment);\n // With these prerequisites met, we just need to make sure that all the contracts that are deployed are written\n // and initialized properly, and they expect this structure and handle the inputs and outputs as expected.\n }\n}\n', 'debug': { 'bytecode': 'c2529dc0c700c7788800cd8800ce788800d18800cc00c69d00d200cf8851c700cf87', - 'sourceMap': '51:12:51:22;:26::27;:4::29:1;55:42:55:63:0;:32::80:1;57:22:57:23:0;:12::40:1;:44::63:0;:4::65:1;58:23:58:24:0;:12::41:1;:4::66;63:22:63:23:0;:12::38:1;:42::56:0;:4::58:1;64:23:64:24:0;:12::39:1;:4::59;66:23:66:24:0;:12::31:1;:45::46:0;:35::53:1;:4::55;68:23:68:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;74:22:74:23:0;:12::40:1;:54::55:0;:44::70:1;:4::72', + 'sourceMap': '51:12:51:22;:26::27;:4::29:1;55:42:55:63:0;:32::80:1;57:22:57:23:0;:12::40:1;:44::63:0;:4::65:1;58:23:58:24:0;:12::41:1;:4::66;63:22:63:23:0;:12::38:1;:42::54:0;:4::56:1;64:23:64:24:0;:12::39:1;:4::57;66:23:66:24:0;:12::31:1;:45::46:0;:35::53:1;:4::55;68:23:68:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;74:22:74:23:0;:12::40:1;:54::55:0;:44::70:1;:4::72', 'logs': [], 'requires': [ { @@ -55,7 +55,7 @@ export default { }, 'compiler': { 'name': 'cashc', - 'version': '0.11.2', + 'version': '0.11.3', }, - 'updatedAt': '2025-07-15T19:31:07.619Z', + 'updatedAt': '2025-07-26T12:09:08.936Z', }; diff --git a/lib/index.ts b/lib/index.ts index 5c6bf2c..3e099d7 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,21 +1,21 @@ import Registry from './compiled/Registry.js'; import Auction from './compiled/Auction.js'; import Bid from './compiled/Bid.js'; -import Domain from './compiled/Domain.js'; -import DomainFactory from './compiled/DomainFactory.js'; -import AuctionConflictResolver from './compiled/AuctionConflictResolver.js'; -import AuctionNameEnforcer from './compiled/AuctionNameEnforcer.js'; -import DomainOwnershipGuard from './compiled/DomainOwnershipGuard.js'; +import Name from './compiled/Name.js'; +import Factory from './compiled/Factory.js'; +import NameEnforcer from './compiled/NameEnforcer.js'; +import OwnershipGuard from './compiled/OwnershipGuard.js'; +import ConflictResolver from './compiled/ConflictResolver.js'; import Accumulator from './compiled/Accumulator.js'; export const BitCANNArtifacts = { Registry, Auction, Bid, - Domain, - DomainFactory, - AuctionConflictResolver, - AuctionNameEnforcer, - DomainOwnershipGuard, + Name, + Factory, + NameEnforcer, + OwnershipGuard, + ConflictResolver, Accumulator, }; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d8353f0..f5e9737 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,8 @@ "@types/node": "^22.14.1", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", - "cashc": "0.11.2", - "cashscript": "0.11.2", + "cashc": "0.11.3", + "cashscript": "0.11.3", "eslint": "^8.57.1", "eslint-config-airbnb-typescript": "^18.0.0", "eslint-plugin-import": "^2.31.0", @@ -562,9 +562,9 @@ } }, "node_modules/@cashscript/utils": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/@cashscript/utils/-/utils-0.11.2.tgz", - "integrity": "sha512-/H5Fvg8L6sRCs1v1XwyA1rYqgZXlojmK1Qr7Co5Wn3WDPv+Z6oKkvi9m283dOAO7U0D8HILJW1yF3fr1n4BsEw==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@cashscript/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-ThifGCvZdbJAikkg0Bgztvv0RG9CLZ7QwOmGOBo4O6MkJuhyjC3BuINChfVaRKH0MotWqAwPf7FJVjE/TmuDQw==", "dev": true, "license": "MIT", "dependencies": { @@ -606,9 +606,9 @@ } }, "node_modules/@electrum-cash/network": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@electrum-cash/network/-/network-4.1.1.tgz", - "integrity": "sha512-v5abF2qGRTnBoi9tcS/iz7j82D8HYsK9iY0NM5v8/Qu8SnlMGGNz8UDFl+YzRPFXb4SUL3K0uf3Oydy82DB3oA==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@electrum-cash/network/-/network-4.1.3.tgz", + "integrity": "sha512-amMvdcEfHhquoUkhN7x/H04KPYfqd5LilOGcg6O1OdUks1Mcrcah8WfHICHW/qyZ3Rgoos9o7Wx8gKz8qcSNzg==", "dev": true, "license": "MIT", "dependencies": { @@ -2345,14 +2345,14 @@ "license": "CC-BY-4.0" }, "node_modules/cashc": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/cashc/-/cashc-0.11.2.tgz", - "integrity": "sha512-5pUka/cI3l744J99Am7tQN6AbSnFOfDxcYqX8hXY9VsqEefJK7lrTyvE3FKL8RC7mixh6qwbxnY4tVYoWoh74w==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/cashc/-/cashc-0.11.3.tgz", + "integrity": "sha512-bmu0y/k+Nc+coIedb06oi8TmqPJWJaEWwxGTgONt2uFv8Qdu4ZSg98oTMZiF7DIonXj601ms0GPXBJiY7l1pYA==", "dev": true, "license": "MIT", "dependencies": { "@bitauth/libauth": "^3.1.0-next.2", - "@cashscript/utils": "^0.11.2", + "@cashscript/utils": "^0.11.3", "antlr4": "^4.13.2", "commander": "^13.1.0", "semver": "^7.6.3" @@ -2362,15 +2362,15 @@ } }, "node_modules/cashscript": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/cashscript/-/cashscript-0.11.2.tgz", - "integrity": "sha512-Ab+V8+PyktENKWXK4vRcf/wdmiEHSAg1wNJrilRFB2DYuJDfSyL3ZcstzfQZEg0+0P2ZmCT0RE4jUAjia2rAQw==", + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/cashscript/-/cashscript-0.11.3.tgz", + "integrity": "sha512-5fsHPIh8R1ihNVBlNtOAHmMiQ8zQVwbQo4P9rhybXxR46FqJ1VD8bubJmfEhxz8eiDcAxUaUJ5tLLi6XH3DT3Q==", "dev": true, "license": "MIT", "dependencies": { "@bitauth/libauth": "^3.1.0-next.2", - "@cashscript/utils": "^0.11.2", - "@electrum-cash/network": "^4.1.1", + "@cashscript/utils": "^0.11.3", + "@electrum-cash/network": "^4.1.3", "@mr-zwets/bchn-api-wrapper": "^1.0.1", "delay": "^6.0.0", "fast-deep-equal": "^3.1.3", @@ -5529,9 +5529,9 @@ "license": "MIT" }, "node_modules/lossless-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-4.0.2.tgz", - "integrity": "sha512-+z0EaLi2UcWi8MZRxA5iTb6m4Ys4E80uftGY+yG5KNFJb5EceQXOhdW/pWJZ8m97s26u7yZZAYMcKWNztSZssA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lossless-json/-/lossless-json-4.1.1.tgz", + "integrity": "sha512-HusN80C0ohtT9kOHQH7EuUaqzRQsnekpa+2ot8OzvW0iC08dq/YtM/7uKwwajldQsCrHyC8q9fz3t3L+TmDltA==", "dev": true, "license": "MIT" }, @@ -7496,9 +7496,9 @@ } }, "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 5840574..958033c 100644 --- a/package.json +++ b/package.json @@ -20,15 +20,15 @@ "scripts": { "build": "npm run compile && tsc", "compile:registry": "cashc ./contracts/Registry.cash -o ./contracts/Registry.json", - "compile:domain": "cashc ./contracts/Domain.cash -o ./contracts/Domain.json", + "compile:name": "cashc ./contracts/Name.cash -o ./contracts/Name.json", "compile:bid": "cashc ./contracts/Bid.cash -o ./contracts/Bid.json", "compile:auction": "cashc ./contracts/Auction.cash -o ./contracts/Auction.json", - "compile:domainfactory": "cashc ./contracts/DomainFactory.cash -o ./contracts/DomainFactory.json", - "compile:auctionnameenforcer": "cashc ./contracts/AuctionNameEnforcer.cash -o ./contracts/AuctionNameEnforcer.json", - "compile:domainownershipguard": "cashc ./contracts/DomainOwnershipGuard.cash -o ./contracts/DomainOwnershipGuard.json", - "compile:auctionconflictresolver": "cashc ./contracts/AuctionConflictResolver.cash -o ./contracts/AuctionConflictResolver.json", + "compile:factory": "cashc ./contracts/Factory.cash -o ./contracts/Factory.json", + "compile:nameenforcer": "cashc ./contracts/NameEnforcer.cash -o ./contracts/NameEnforcer.json", + "compile:ownershipguard": "cashc ./contracts/OwnershipGuard.cash -o ./contracts/OwnershipGuard.json", + "compile:conflictresolver": "cashc ./contracts/ConflictResolver.cash -o ./contracts/ConflictResolver.json", "compile:accumulator": "cashc ./contracts/Accumulator.cash -o ./contracts/Accumulator.json", - "compile": "npm run compile:registry && npm run compile:auction && npm run compile:domain && npm run compile:bid && npm run compile:domainfactory && npm run compile:auctionnameenforcer && npm run compile:domainownershipguard && npm run compile:auctionconflictresolver && npm run compile:accumulator", + "compile": "npm run compile:registry && npm run compile:auction && npm run compile:name && npm run compile:bid && npm run compile:factory && npm run compile:nameenforcer && npm run compile:ownershipguard && npm run compile:conflictresolver && npm run compile:accumulator", "postcompile": "node scripts/json-to-ts.js && npm run lint-fix", "prepublishOnly": "npm run build", "test": "NODE_OPTIONS='--experimental-vm-modules --no-warnings' jest", @@ -43,8 +43,8 @@ "@types/node": "^22.14.1", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", - "cashc": "0.11.2", - "cashscript": "0.11.2", + "cashc": "0.11.3", + "cashscript": "0.11.3", "eslint": "^8.57.1", "eslint-config-airbnb-typescript": "^18.0.0", "eslint-plugin-import": "^2.31.0", diff --git a/test/common.ts b/test/common.ts index f0a91bc..3aa69e8 100644 --- a/test/common.ts +++ b/test/common.ts @@ -10,17 +10,18 @@ import { } from '@bitauth/libauth'; import { SignatureTemplate } from 'cashscript'; -export const domainTokenCategory = '98570f00cad2991de0ab25f14ffae29a0c61da97ba6d466acbc8476e2e612ada'; -export const reversedDomainTokenCategory = binToHex(hexToBin(domainTokenCategory).reverse()); +export const nameTokenCategory = '98570f00cad2991de0ab25f14ffae29a0c61da97ba6d466acbc8476e2e612ada'; +export const reversedNameTokenCategory = binToHex(hexToBin(nameTokenCategory).reverse()); export const mockOptions = { - category: domainTokenCategory, + category: nameTokenCategory, minStartingBid: 10000, minBidIncreasePercentage: 5, inactivityExpiryTime: 1, minWaitTime: 1, maxPlatformFeePercentage: 50, + tld: '.bch', }; // @ts-ignore const seed = deriveSeedFromBip39Mnemonic('bitcann test seed'); diff --git a/test/e2e/auction.test.ts b/test/e2e/auction.test.ts index 18c0249..97d7335 100644 --- a/test/e2e/auction.test.ts +++ b/test/e2e/auction.test.ts @@ -1,13 +1,13 @@ import { MockNetworkProvider, randomUtxo, TransactionBuilder, Contract, type Utxo } from 'cashscript'; import { binToHex, cashAddressToLockingBytecode, hexToBin } from '@bitauth/libauth'; import { BitCANNArtifacts } from '../../lib/index.js'; -import { aliceAddress, alicePkh, aliceTemplate, domainTokenCategory, mockOptions, reversedDomainTokenCategory } from '../common.js'; +import { aliceAddress, alicePkh, aliceTemplate, nameTokenCategory, mockOptions, reversedNameTokenCategory } from '../common.js'; import { intToBytesToHex, getTxOutputs } from '../utils.js'; describe('Auction', () => { const provider = new MockNetworkProvider(); - const registryContract = new Contract(BitCANNArtifacts.Registry, [ reversedDomainTokenCategory ], { provider }); + const registryContract = new Contract(BitCANNArtifacts.Registry, [ reversedNameTokenCategory ], { provider }); const auctionContract = new Contract(BitCANNArtifacts.Auction, [ BigInt(mockOptions.minStartingBid) ], { provider }); const auctionLockingBytecode = cashAddressToLockingBytecode(auctionContract.address); // @ts-ignore @@ -43,7 +43,7 @@ describe('Auction', () => threadNFTUTXO = { token: { - category: domainTokenCategory, + category: nameTokenCategory, amount: BigInt(0), nft: { commitment: auctionLockingBytecodeHex, @@ -58,7 +58,7 @@ describe('Auction', () => registrationCounterUTXO = { token: { - category: domainTokenCategory, + category: nameTokenCategory, amount: BigInt('9223372036854775807'), nft: { commitment: intToBytesToHex({ value: 0, length: 8 }), @@ -75,7 +75,7 @@ describe('Auction', () => mintingNFTUTXO = { token: { amount: BigInt(0), - category: domainTokenCategory, + category: nameTokenCategory, nft: { commitment: '', capability: 'minting', diff --git a/test/unit/import.test.ts b/test/unit/import.test.ts index db7b189..11cef45 100644 --- a/test/unit/import.test.ts +++ b/test/unit/import.test.ts @@ -20,11 +20,11 @@ describe('imports', () => 'Registry', 'Auction', 'Bid', - 'Domain', - 'DomainFactory', - 'AuctionConflictResolver', - 'AuctionNameEnforcer', - 'DomainOwnershipGuard', + 'Name', + 'Factory', + 'NameEnforcer', + 'OwnershipGuard', + 'ConflictResolver', 'Accumulator', ] as const; diff --git a/test/utils.ts b/test/utils.ts index 39efc3d..202224f 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -24,6 +24,15 @@ export interface LibauthOutput token?: LibauthTokenDetails; } + +export const getAuctionPrice = (registrationId: number, minStartingBid: number): number => +{ + const decayPercentageToTheStep = registrationId * 3 / 1000000; + const currentAuctionPrice = minStartingBid * (1 - decayPercentageToTheStep); + + return currentAuctionPrice; +}; + export const intToBytesToHex = ({ value, length }: { value: number; length: number }): string => { const bin = numberToBinUint16BE(value); From 2060c531327fb5eed5288b2716db268bfecd83b6 Mon Sep 17 00:00:00 2001 From: kiok46 Date: Sun, 27 Jul 2025 20:10:04 +0530 Subject: [PATCH 02/35] remove reverse from auction and factory --- contracts/Auction.cash | 18 +- contracts/Factory.cash | 2 +- lib/compiled/Accumulator.ts | 2 +- lib/compiled/Auction.ts | 64 +++--- lib/compiled/Bid.ts | 2 +- lib/compiled/ConflictResolver.ts | 2 +- lib/compiled/Factory.ts | 28 +-- lib/compiled/Name.ts | 2 +- lib/compiled/NameEnforcer.ts | 2 +- lib/compiled/OwnershipGuard.ts | 2 +- lib/compiled/Registry.ts | 2 +- test/common.ts | 3 +- test/e2e/auction.test.ts | 336 ++++++++++++++++--------------- test/utils.ts | 2 +- 14 files changed, 240 insertions(+), 227 deletions(-) diff --git a/contracts/Auction.cash b/contracts/Auction.cash index 3dae309..3386a0c 100644 --- a/contracts/Auction.cash +++ b/contracts/Auction.cash @@ -51,8 +51,8 @@ contract Auction(int minStartingBid) { require(tx.outputs[3].lockingBytecode == registryInputLockingBytecode); // Registration ID increases by 1 with each transaction. - int prevRegistrationId = int(tx.inputs[2].nftCommitment.reverse()); - int nextRegistrationId = int(tx.outputs[2].nftCommitment.reverse()); + int prevRegistrationId = int(tx.inputs[2].nftCommitment); + int nextRegistrationId = int(tx.outputs[2].nftCommitment); require(nextRegistrationId == prevRegistrationId + 1); // Reduce the tokenAmount in the counterNFT as some amount is going to auctionNFT @@ -61,10 +61,16 @@ contract Auction(int minStartingBid) { require(tx.outputs[3].tokenAmount == nextRegistrationId); // Dual Decay mechanism, auction price decays linearly with the step. - // 1. Decay percentage to the step 0.0003% - int decayPercentageToTheStep = nextRegistrationId * 3 / 1000000; - // 2. Get auction price for current step with linear decay - int currentAuctionPrice = minStartingBid * (1 - decayPercentageToTheStep); + // To facilitate higher precisions and since decimals do not exist in VM, we multiply + // it by 1e6 (1000000) and name the values as points. + + // 1. Decay points (step 0.0003% per step) + int decayPoints = minStartingBid * nextRegistrationId * 3; + // 2. Get auction price points + int currentPricePoints = minStartingBid * 1e6; + // 3. Subtract price points by decay points to get the current auction price. + int currentAuctionPrice = (currentPricePoints - decayPoints) / 1e6; + // Set the minimum auction price to 20000 satoshis. currentAuctionPrice = max(currentAuctionPrice, 20000); diff --git a/contracts/Factory.cash b/contracts/Factory.cash index 1feeff9..b773989 100644 --- a/contracts/Factory.cash +++ b/contracts/Factory.cash @@ -119,7 +119,7 @@ contract Factory( // InternalAuthNFT has registrationID as the commitment so it can be used to authenticate // along with the ownershipNFT - bytes8 registrationId = bytes8(tx.inputs[3].tokenAmount).reverse(); + bytes8 registrationId = bytes8(tx.inputs[3].tokenAmount); require(tx.outputs[4].nftCommitment == registrationId); // Strict value check require(tx.outputs[4].value == 1000); diff --git a/lib/compiled/Accumulator.ts b/lib/compiled/Accumulator.ts index a9434ce..1a54dd4 100644 --- a/lib/compiled/Accumulator.ts +++ b/lib/compiled/Accumulator.ts @@ -100,5 +100,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-26T12:09:11.355Z', + 'updatedAt': '2025-07-27T14:39:12.891Z', }; diff --git a/lib/compiled/Auction.ts b/lib/compiled/Auction.ts index 5cd4e93..4af7c18 100644 --- a/lib/compiled/Auction.ts +++ b/lib/compiled/Auction.ts @@ -17,11 +17,11 @@ export default { ], }, ], - 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_REVERSEBYTES OP_BIN2NUM OP_2 OP_OUTPUTTOKENCOMMITMENT OP_REVERSEBYTES OP_BIN2NUM OP_DUP OP_ROT OP_1ADD OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_2 OP_PICK OP_SUB OP_NUMEQUALVERIFY OP_3 OP_OUTPUTTOKENAMOUNT OP_OVER OP_NUMEQUALVERIFY OP_3 OP_MUL 40420f OP_DIV OP_SWAP OP_1 OP_ROT OP_SUB OP_MUL OP_DUP 204e OP_MAX OP_3 OP_OUTPUTVALUE OP_LESSTHANOREQUAL OP_VERIFY OP_3 OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_SIZE OP_NIP 19 OP_NUMEQUALVERIFY OP_3 OP_UTXOBYTECODE OP_3 OP_SPLIT OP_NIP 14 OP_SPLIT OP_DROP OP_3 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_3 OP_PICK OP_CAT OP_EQUALVERIFY OP_OVER OP_SIZE OP_NIP OP_16 OP_LESSTHANOREQUAL OP_VERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_4 OP_OUTPUTBYTECODE 6a OP_3 OP_ROLL OP_SIZE OP_DUP 4b OP_GREATERTHAN OP_IF 4c OP_SWAP OP_CAT OP_ENDIF OP_SWAP OP_CAT OP_CAT OP_EQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_NUMEQUAL OP_IF OP_5 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_DROP OP_1', - 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param minStartingBid The minimum starting bid for the auction.\n */\ncontract Auction(int minStartingBid) {\n /**\n * Starts a new name registration auction.\n * @param name The name being registered.\n * \n * The function creates a new auction with:\n * - Starting bid >= `minStartingBid` BCH.\n * - A successful registration initiation results in an auctionNFT representing the auction state:\n * - capability: (Mutable)\n * - category: registryInputCategory\n * - tokenAmount: (Represents the registrationId)\n * - satoshiValue: (Represents the bid amount)\n * - commitment: bidder's PKH (20 bytes) + name (bytes)\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: Minting CounterNFT from Registry contract (Increases the registrationId by 1 in the output).\n * - Input3: Funding UTXO.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Minting CounterNFT going back to the Registry contract.\n * - Output3: auctionNFT to the Registry contract.\n * - Output4: OP_RETURN output containing the name.\n * - Output5: Optional change in BCH.\n */\n function call(bytes name) {\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 6);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the nameCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[3].lockingBytecode == registryInputLockingBytecode);\n\n // Registration ID increases by 1 with each transaction.\n int prevRegistrationId = int(tx.inputs[2].nftCommitment.reverse());\n int nextRegistrationId = int(tx.outputs[2].nftCommitment.reverse());\n require(nextRegistrationId == prevRegistrationId + 1);\n\n // Reduce the tokenAmount in the counterNFT as some amount is going to auctionNFT\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount - nextRegistrationId);\n // tokenAmount in the auctionNFT is the registrationId.\n require(tx.outputs[3].tokenAmount == nextRegistrationId);\n\n // Dual Decay mechanism, auction price decays linearly with the step.\n // 1. Decay percentage to the step 0.0003%\n int decayPercentageToTheStep = nextRegistrationId * 3 / 1000000;\n // 2. Get auction price for current step with linear decay\n int currentAuctionPrice = minStartingBid * (1 - decayPercentageToTheStep);\n // Set the minimum auction price to 20000 satoshis.\n currentAuctionPrice = max(currentAuctionPrice, 20000);\n\n // Every auction begins with a min base value of at least currentAuctionPrice satoshis.\n require(tx.outputs[3].value >= currentAuctionPrice);\n // Funding UTXO/ Bid UTXO\n require(tx.inputs[3].tokenCategory == 0x);\n\n // Ensure that the funding happens from a P2PKH UTXO.\n require(tx.inputs[3].lockingBytecode.length == 25);\n\n // Extract the PKH from the lockingBytecode of the Funding UTXO.\n // + name > 20 bytes\n bytes pkh = tx.inputs[3].lockingBytecode.split(3)[1].split(20)[0];\n require(tx.outputs[3].nftCommitment == pkh + name);\n\n // Ensure that the name is not too long, as of 2025 upgrade, the nftcommitment is 40 bytes.\n // 20 bytes pkh + 16 bytes name + 4 bytes TLD\n require(name.length <= 16);\n\n // CounterNFT should keep the same category and capability.\n require(tx.outputs[2].tokenCategory == tx.inputs[2].tokenCategory);\n \n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n \n // CounterNFT should be minting and of the 'nameCategory' i.e registryInputCategory\n bytes counterCategory, bytes counterCapability = tx.outputs[2].tokenCategory.split(32);\n require(counterCategory == registryInputCategory);\n // Minting\n require(counterCapability == 0x02);\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.outputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n // Mutable\n require(auctionCapability == 0x01);\n\n // Enforce an OP_RETURN output that contains the name.\n require(tx.outputs[4].lockingBytecode == new LockingBytecodeNullData([name]));\n\n if (tx.outputs.length == 6) {\n // If any change, then it must be pure BCH.\n require(tx.outputs[5].tokenCategory == 0x);\n }\n }\n}", + 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_BIN2NUM OP_2 OP_OUTPUTTOKENCOMMITMENT OP_BIN2NUM OP_DUP OP_ROT OP_1ADD OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_2 OP_PICK OP_SUB OP_NUMEQUALVERIFY OP_3 OP_OUTPUTTOKENAMOUNT OP_OVER OP_NUMEQUALVERIFY OP_OVER OP_SWAP OP_MUL OP_3 OP_MUL OP_SWAP 40420f OP_MUL OP_SWAP OP_SUB 40420f OP_DIV OP_DUP 204e OP_MAX OP_3 OP_OUTPUTVALUE OP_LESSTHANOREQUAL OP_VERIFY OP_3 OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_SIZE OP_NIP 19 OP_NUMEQUALVERIFY OP_3 OP_UTXOBYTECODE OP_3 OP_SPLIT OP_NIP 14 OP_SPLIT OP_DROP OP_3 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_3 OP_PICK OP_CAT OP_EQUALVERIFY OP_OVER OP_SIZE OP_NIP OP_16 OP_LESSTHANOREQUAL OP_VERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_4 OP_OUTPUTBYTECODE 6a OP_3 OP_ROLL OP_SIZE OP_DUP 4b OP_GREATERTHAN OP_IF 4c OP_SWAP OP_CAT OP_ENDIF OP_SWAP OP_CAT OP_CAT OP_EQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_NUMEQUAL OP_IF OP_5 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_DROP OP_1', + 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param minStartingBid The minimum starting bid for the auction.\n */\ncontract Auction(int minStartingBid) {\n /**\n * Starts a new name registration auction.\n * @param name The name being registered.\n * \n * The function creates a new auction with:\n * - Starting bid >= `minStartingBid` BCH.\n * - A successful registration initiation results in an auctionNFT representing the auction state:\n * - capability: (Mutable)\n * - category: registryInputCategory\n * - tokenAmount: (Represents the registrationId)\n * - satoshiValue: (Represents the bid amount)\n * - commitment: bidder's PKH (20 bytes) + name (bytes)\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: Minting CounterNFT from Registry contract (Increases the registrationId by 1 in the output).\n * - Input3: Funding UTXO.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Minting CounterNFT going back to the Registry contract.\n * - Output3: auctionNFT to the Registry contract.\n * - Output4: OP_RETURN output containing the name.\n * - Output5: Optional change in BCH.\n */\n function call(bytes name) {\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 6);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the nameCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[3].lockingBytecode == registryInputLockingBytecode);\n\n // Registration ID increases by 1 with each transaction.\n int prevRegistrationId = int(tx.inputs[2].nftCommitment);\n int nextRegistrationId = int(tx.outputs[2].nftCommitment);\n require(nextRegistrationId == prevRegistrationId + 1);\n\n // Reduce the tokenAmount in the counterNFT as some amount is going to auctionNFT\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount - nextRegistrationId);\n // tokenAmount in the auctionNFT is the registrationId.\n require(tx.outputs[3].tokenAmount == nextRegistrationId);\n\n // Dual Decay mechanism, auction price decays linearly with the step.\n // To facilitate higher precisions and since decimals do not exist in VM, we multiply\n // it by 1e6 (1000000) and name the values as points.\n\n // 1. Decay points (step 0.0003% per step)\n int decayPoints = minStartingBid * nextRegistrationId * 3;\n // 2. Get auction price points\n int currentPricePoints = minStartingBid * 1e6;\n // 3. Subtract price points by decay points to get the current auction price.\n int currentAuctionPrice = (currentPricePoints - decayPoints) / 1e6;\n\n // Set the minimum auction price to 20000 satoshis.\n currentAuctionPrice = max(currentAuctionPrice, 20000);\n\n // Every auction begins with a min base value of at least currentAuctionPrice satoshis.\n require(tx.outputs[3].value >= currentAuctionPrice);\n // Funding UTXO/ Bid UTXO\n require(tx.inputs[3].tokenCategory == 0x);\n\n // Ensure that the funding happens from a P2PKH UTXO.\n require(tx.inputs[3].lockingBytecode.length == 25);\n\n // Extract the PKH from the lockingBytecode of the Funding UTXO.\n // + name > 20 bytes\n bytes pkh = tx.inputs[3].lockingBytecode.split(3)[1].split(20)[0];\n require(tx.outputs[3].nftCommitment == pkh + name);\n\n // Ensure that the name is not too long, as of 2025 upgrade, the nftcommitment is 40 bytes.\n // 20 bytes pkh + 16 bytes name + 4 bytes TLD\n require(name.length <= 16);\n\n // CounterNFT should keep the same category and capability.\n require(tx.outputs[2].tokenCategory == tx.inputs[2].tokenCategory);\n \n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n \n // CounterNFT should be minting and of the 'nameCategory' i.e registryInputCategory\n bytes counterCategory, bytes counterCapability = tx.outputs[2].tokenCategory.split(32);\n require(counterCategory == registryInputCategory);\n // Minting\n require(counterCapability == 0x02);\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.outputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n // Mutable\n require(auctionCapability == 0x01);\n\n // Enforce an OP_RETURN output that contains the name.\n require(tx.outputs[4].lockingBytecode == new LockingBytecodeNullData([name]));\n\n if (tx.outputs.length == 6) {\n // If any change, then it must be pure BCH.\n require(tx.outputs[5].tokenCategory == 0x);\n }\n }\n}", 'debug': { - 'bytecode': 'c3549dc456a169c0519dc0c7c0cd88c0d1008800c752c7788852cd788853cd8852cfbc8152d2bc81767b8b9d52d352d05279949d53d3789d53950340420f967c517b94957602204ea453cca16953ce008853c7827701199d53c7537f7701147f7553d27c53797e8878827760a16952d152ce8800ce52d101207f7c527988528853d101207f7c7b88518854cd016a537a8276014ba063014c7c7e687c7e7e88c4569c6355d10088687551', - 'sourceMap': '35:12:35:28;:32::33;:4::35:1;36:12:36:29:0;:33::34;:12:::1;:4::36;39:12:39:33:0;:37::38;:4::40:1;40:22:40:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;42:23:42:44:0;:12::59:1;:63::65:0;:4::67:1;48:51:48:52:0;:41::69:1;49:22:49:23:0;:12::40:1;:44::72:0;:4::74:1;50:23:50:24:0;:12::41:1;:45::73:0;:4::75:1;51:23:51:24:0;:12::41:1;:4::75;54:43:54:44:0;:33::59:1;:::69;:29::70;55:44:55:45:0;:33::60:1;:::70;:29::71;56:12:56:30:0;:34::52;:::56:1;:4::58;59:23:59:24:0;:12::37:1;:51::52:0;:41::65:1;:68::86:0;;:41:::1;:4::88;61:23:61:24:0;:12::37:1;:41::59:0;:4::61:1;65:56:65:57:0;:35:::1;:60::67:0;:35:::1;67:30:67:44:0;:48::49;:52::76;:48:::1;:30::77;69::69:49:0;:51::56;:26::57:1;72:23:72:24:0;:12::31:1;:::54;:4::56;74:22:74:23:0;:12::38:1;:42::44:0;:4::46:1;77:22:77:23:0;:12::40:1;:::47;;:51::53:0;:4::55:1;81:26:81:27:0;:16::44:1;:51::52:0;:16::53:1;:::56;:63::65:0;:16::66:1;:::69;82:23:82:24:0;:12::39:1;:43::46:0;:49::53;;:43:::1;:4::55;86:12:86:16:0;:::23:1;;:27::29:0;:12:::1;:4::31;89:23:89:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;92:44:92:45:0;:34::60:1;95:64:95:65:0;:53::80:1;:87::89:0;:53::90:1;96:12:96:27:0;:31::52;;:4::54:1;98:33:98:37:0;:4::39:1;101:64:101:65:0;:53::80:1;:87::89:0;:53::90:1;102:12:102:27:0;:31::52;:4::54:1;104:33:104:37:0;:4::39:1;107:23:107:24:0;:12::41:1;:45::80:0;:74::78;;::::1;;;;;;;;;;;;:4::82;109:8:109:25:0;:29::30;:8:::1;:32:112:5:0;111:25:111:26;:14::41:1;:45::47:0;:6::49:1;109:32:112:5;34:2:113:3;', + 'bytecode': 'c3549dc456a169c0519dc0c7c0cd88c0d1008800c752c7788852cd788853cd8852cf8152d281767b8b9d52d352d05279949d53d3789d787c9553957c0340420f957c940340420f967602204ea453cca16953ce008853c7827701199d53c7537f7701147f7553d27c53797e8878827760a16952d152ce8800ce52d101207f7c527988528853d101207f7c7b88518854cd016a537a8276014ba063014c7c7e687c7e7e88c4569c6355d10088687551', + 'sourceMap': '35:12:35:28;:32::33;:4::35:1;36:12:36:29:0;:33::34;:12:::1;:4::36;39:12:39:33:0;:37::38;:4::40:1;40:22:40:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;42:23:42:44:0;:12::59:1;:63::65:0;:4::67:1;48:51:48:52:0;:41::69:1;49:22:49:23:0;:12::40:1;:44::72:0;:4::74:1;50:23:50:24:0;:12::41:1;:45::73:0;:4::75:1;51:23:51:24:0;:12::41:1;:4::75;54:43:54:44:0;:33::59:1;:29::60;55:44:55:45:0;:33::60:1;:29::61;56:12:56:30:0;:34::52;:::56:1;:4::58;59:23:59:24:0;:12::37:1;:51::52:0;:41::65:1;:68::86:0;;:41:::1;:4::88;61:23:61:24:0;:12::37:1;:41::59:0;:4::61:1;68:22:68:36:0;:39::57;:22:::1;:60::61:0;:22:::1;70:29:70:43:0;:46::49;:29:::1;72:52:72:63:0;:31:::1;:67::70:0;:30:::1;75::75:49:0;:51::56;:26::57:1;78:23:78:24:0;:12::31:1;:::54;:4::56;80:22:80:23:0;:12::38:1;:42::44:0;:4::46:1;83:22:83:23:0;:12::40:1;:::47;;:51::53:0;:4::55:1;87:26:87:27:0;:16::44:1;:51::52:0;:16::53:1;:::56;:63::65:0;:16::66:1;:::69;88:23:88:24:0;:12::39:1;:43::46:0;:49::53;;:43:::1;:4::55;92:12:92:16:0;:::23:1;;:27::29:0;:12:::1;:4::31;95:23:95:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;98:44:98:45:0;:34::60:1;101:64:101:65:0;:53::80:1;:87::89:0;:53::90:1;102:12:102:27:0;:31::52;;:4::54:1;104:33:104:37:0;:4::39:1;107:64:107:65:0;:53::80:1;:87::89:0;:53::90:1;108:12:108:27:0;:31::52;:4::54:1;110:33:110:37:0;:4::39:1;113:23:113:24:0;:12::41:1;:45::80:0;:74::78;;::::1;;;;;;;;;;;;:4::82;115:8:115:25:0;:29::30;:8:::1;:32:118:5:0;117:25:117:26;:14::41:1;:45::47:0;:6::49:1;115:32:118:5;34:2:119:3;', 'logs': [], 'requires': [ { @@ -57,64 +57,64 @@ export default { 'line': 51, }, { - 'ip': 44, + 'ip': 42, 'line': 56, }, { - 'ip': 52, + 'ip': 50, 'line': 59, }, { - 'ip': 56, + 'ip': 54, 'line': 61, }, { - 'ip': 72, - 'line': 72, + 'ip': 73, + 'line': 78, }, { - 'ip': 76, - 'line': 74, + 'ip': 77, + 'line': 80, }, { - 'ip': 82, - 'line': 77, + 'ip': 83, + 'line': 83, }, { - 'ip': 97, - 'line': 82, + 'ip': 98, + 'line': 88, }, { - 'ip': 103, - 'line': 86, + 'ip': 104, + 'line': 92, }, { - 'ip': 108, - 'line': 89, + 'ip': 109, + 'line': 95, }, { - 'ip': 118, - 'line': 96, + 'ip': 119, + 'line': 102, }, { - 'ip': 120, - 'line': 98, + 'ip': 121, + 'line': 104, }, { - 'ip': 127, - 'line': 102, + 'ip': 128, + 'line': 108, }, { - 'ip': 129, - 'line': 104, + 'ip': 130, + 'line': 110, }, { - 'ip': 147, - 'line': 107, + 'ip': 148, + 'line': 113, }, { - 'ip': 155, - 'line': 111, + 'ip': 156, + 'line': 117, }, ], }, @@ -122,5 +122,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-26T12:09:09.240Z', + 'updatedAt': '2025-07-27T14:39:10.737Z', }; diff --git a/lib/compiled/Bid.ts b/lib/compiled/Bid.ts index 5706a79..3fafbfa 100644 --- a/lib/compiled/Bid.ts +++ b/lib/compiled/Bid.ts @@ -89,5 +89,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-26T12:09:09.858Z', + 'updatedAt': '2025-07-27T14:39:11.369Z', }; diff --git a/lib/compiled/ConflictResolver.ts b/lib/compiled/ConflictResolver.ts index e715a96..9b1ac55 100644 --- a/lib/compiled/ConflictResolver.ts +++ b/lib/compiled/ConflictResolver.ts @@ -76,5 +76,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-26T12:09:11.059Z', + 'updatedAt': '2025-07-27T14:39:12.586Z', }; diff --git a/lib/compiled/Factory.ts b/lib/compiled/Factory.ts index 4798508..c6c17a0 100644 --- a/lib/compiled/Factory.ts +++ b/lib/compiled/Factory.ts @@ -24,11 +24,11 @@ export default { 'inputs': [], }, ], - 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_7 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOVALUE OP_INPUTINDEX OP_OUTPUTVALUE OP_NUMEQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_3 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_5 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY OP_EQUALVERIFY OP_3 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_2 OP_OUTPUTTOKENCOMMITMENT OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_0 OP_NUMEQUALVERIFY OP_2 OP_OUTPUTVALUE OP_2 OP_UTXOVALUE OP_NUMEQUALVERIFY OP_3 OP_INPUTSEQUENCENUMBER OP_3 OP_ROLL OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_DUP OP_SIZE OP_NIP 20 OP_4 OP_ROLL OP_CAT OP_SWAP OP_CAT OP_OVER OP_CAT OP_5 OP_ROLL OP_CAT OP_3 OP_ROLL OP_CAT OP_HASH256 aa20 OP_SWAP OP_CAT 87 OP_CAT OP_3 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_3 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENAMOUNT OP_8 OP_NUM2BIN OP_REVERSEBYTES OP_4 OP_OUTPUTTOKENCOMMITMENT OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_5 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_ROT OP_CAT OP_EQUALVERIFY OP_5 OP_OUTPUTBYTECODE 76a914 OP_ROT OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_5 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_0 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT OP_ADD OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENAMOUNT OP_1 OP_MUL 1027 OP_DIV OP_3 OP_UTXOTOKENAMOUNT OP_1 OP_ROT OP_SUB OP_MUL OP_DUP 204e OP_GREATERTHAN OP_IF OP_6 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_6 OP_OUTPUTVALUE OP_OVER OP_NUMEQUALVERIFY OP_6 OP_OUTPUTBYTECODE 76a914 OP_3 OP_PICK OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_ENDIF OP_2DROP OP_1', - 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param nameContractBytecode - Partial bytecode of the name contract\n * @param minWaitTime - Minimum wait time to consider an auction ended\n * @param creatorIncentivePKH - PKH of the creator incentive\n * @param tld - TLD of the name\n */\ncontract Factory(\n bytes nameContractBytecode,\n int minWaitTime,\n bytes20 creatorIncentivePKH,\n bytes tld\n) {\n /**\n * This function finalizes a name registration auction by:\n * - Verifying the auction has ended and the winner's bid is valid\n * - Issuing an immutable externalAuthNFT to the Name Contract\n * - Issuing an immutable internalAuthNFT to the Name Contract\n * - Issuing an immutable name NFT to the auction winner\n * - Distributing auction fees between the platform and miners\n * - Burning the auctionNFT\n * - Pure BCH input from bidder is used to prevent miners from taking away the funds from any or all transactions in the future.\n * Out of many possible ways, this method will be suitable to easily implement by applications.\n *\n *\n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract\n * - Input2: NameMintingNFT from the Registry Contract\n * - Input3: auctionNFT from the Registry Contract\n *\n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change\n * - Output2: NameMintingNFT back to the Registry contract\n * - Output3: External Auth NFT to the name contract\n * - Output4: Internal Auth NFT to the name contract\n * - Output5: Name NFT to the auction winner\n * - Output6: Platform fee [Reduces and the not included]\n *\n */\n function call(){\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 7);\n\n // This contract can only be used at input1 and it should return to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the nameCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n // Strict value checks to ensure the platform and miner get fee.\n require(tx.inputs[this.activeInputIndex].value == tx.outputs[this.activeInputIndex].value);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.inputs[3].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n\n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n require(tx.outputs[3].tokenCategory == registryInputCategory);\n require(tx.outputs[4].tokenCategory == registryInputCategory);\n require(tx.outputs[5].tokenCategory == registryInputCategory);\n\n // NameMintingNFT should be minting and of the 'nameCategory' i.e registryInputCategory\n bytes nameMintingCategory, bytes nameMintingCapability = tx.inputs[2].tokenCategory.split(32);\n require(nameMintingCategory == registryInputCategory);\n // Minting\n require(nameMintingCapability == 0x02);\n // NameMintingNFT should keep the same category and capability\n require(tx.inputs[2].tokenCategory == tx.outputs[2].tokenCategory);\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.inputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n // Mutable\n require(auctionCapability == 0x01);\n\n // Enforce strict restrictions on NameMintingNFT\n require(tx.inputs[2].nftCommitment == tx.outputs[2].nftCommitment);\n // NameMintingNFT has no nftCommitment\n require(tx.outputs[2].nftCommitment == 0x);\n // NameMintingNFT has no tokenAmount\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount);\n require(tx.outputs[2].tokenAmount == 0);\n\n // Strict value check\n require(tx.outputs[2].value == tx.inputs[2].value);\n\n // Enforcing the relative timelock, the auctionNFT must be atleast `minWaitTime` old\n // to be considered ended.\n require(tx.inputs[3].sequenceNumber == minWaitTime);\n\n // Extract the PKH and name from the auctionNFT\n bytes20 bidderPKH, bytes name = tx.inputs[3].nftCommitment.split(20);\n \n // Get the name length to generate the complete bytecode of the name contract\n int nameLength = name.length;\n // category + name + bytecode.\n // Note: `inactivityExpiryTime` in the name is already added to the nameContractBytecode in the constructor.\n bytes nameBytecode = 0x20 + registryInputCategory + bytes(nameLength) + name + tld + nameContractBytecode;\n bytes32 scriptHash = hash256(nameBytecode);\n bytes35 nameLockingBytecode = new LockingBytecodeP2SH32(scriptHash);\n \n // ExternalAuthNFT goes to the name contract\n require(tx.outputs[3].lockingBytecode == nameLockingBytecode);\n // InternalAuthNFT goes to the name contract\n require(tx.outputs[4].lockingBytecode == nameLockingBytecode);\n \n // ExternalAuthNFT does not have any commitment\n require(tx.outputs[3].nftCommitment == 0x);\n // Strict value check\n require(tx.outputs[3].value == 1000);\n\n // InternalAuthNFT has registrationID as the commitment so it can be used to authenticate\n // along with the ownershipNFT\n bytes8 registrationId = bytes8(tx.inputs[3].tokenAmount).reverse();\n require(tx.outputs[4].nftCommitment == registrationId);\n // Strict value check\n require(tx.outputs[4].value == 1000);\n\n // Send the name ownership NFT to the bidder\n require(tx.outputs[5].nftCommitment == registrationId + name);\n require(tx.outputs[5].lockingBytecode == new LockingBytecodeP2PKH(bidderPKH));\n require(tx.outputs[5].value == 1000);\n\n // tokenAmount from the auctionNFT goes to the authorizedThreadNFT to be accumulated later\n // and merged back with the CounterNFT using the `Accumulator` Contract\n require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[3].tokenAmount);\n\n // Dual Decay mechanism, creator incentive decays linearly with the step.\n // 1. Decay percentage to the step 0.001%\n int decayPercentageToTheStep = tx.inputs[3].tokenAmount * 1 / 10000;\n // 2. Get creator incentive for current step with linear decay\n int creatorIncentive = tx.inputs[3].tokenAmount * (1 - decayPercentageToTheStep);\n\n if(creatorIncentive > 20000) {\n require(tx.outputs[6].tokenCategory == 0x);\n // Enforce that the other piece of the fee goes to the miners.\n require(tx.outputs[6].value == creatorIncentive);\n require(tx.outputs[6].lockingBytecode == new LockingBytecodeP2PKH(creatorIncentivePKH));\n }\n }\n\n}", + 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_7 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOVALUE OP_INPUTINDEX OP_OUTPUTVALUE OP_NUMEQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_3 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_5 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY OP_EQUALVERIFY OP_3 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_2 OP_OUTPUTTOKENCOMMITMENT OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_0 OP_NUMEQUALVERIFY OP_2 OP_OUTPUTVALUE OP_2 OP_UTXOVALUE OP_NUMEQUALVERIFY OP_3 OP_INPUTSEQUENCENUMBER OP_3 OP_ROLL OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_DUP OP_SIZE OP_NIP 20 OP_4 OP_ROLL OP_CAT OP_SWAP OP_CAT OP_OVER OP_CAT OP_5 OP_ROLL OP_CAT OP_3 OP_ROLL OP_CAT OP_HASH256 aa20 OP_SWAP OP_CAT 87 OP_CAT OP_3 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_3 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENAMOUNT OP_8 OP_NUM2BIN OP_4 OP_OUTPUTTOKENCOMMITMENT OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_5 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_ROT OP_CAT OP_EQUALVERIFY OP_5 OP_OUTPUTBYTECODE 76a914 OP_ROT OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_5 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_0 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT OP_ADD OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENAMOUNT OP_1 OP_MUL 1027 OP_DIV OP_3 OP_UTXOTOKENAMOUNT OP_1 OP_ROT OP_SUB OP_MUL OP_DUP 204e OP_GREATERTHAN OP_IF OP_6 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_6 OP_OUTPUTVALUE OP_OVER OP_NUMEQUALVERIFY OP_6 OP_OUTPUTBYTECODE 76a914 OP_3 OP_PICK OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_ENDIF OP_2DROP OP_1', + 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param nameContractBytecode - Partial bytecode of the name contract\n * @param minWaitTime - Minimum wait time to consider an auction ended\n * @param creatorIncentivePKH - PKH of the creator incentive\n * @param tld - TLD of the name\n */\ncontract Factory(\n bytes nameContractBytecode,\n int minWaitTime,\n bytes20 creatorIncentivePKH,\n bytes tld\n) {\n /**\n * This function finalizes a name registration auction by:\n * - Verifying the auction has ended and the winner's bid is valid\n * - Issuing an immutable externalAuthNFT to the Name Contract\n * - Issuing an immutable internalAuthNFT to the Name Contract\n * - Issuing an immutable name NFT to the auction winner\n * - Distributing auction fees between the platform and miners\n * - Burning the auctionNFT\n * - Pure BCH input from bidder is used to prevent miners from taking away the funds from any or all transactions in the future.\n * Out of many possible ways, this method will be suitable to easily implement by applications.\n *\n *\n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract\n * - Input2: NameMintingNFT from the Registry Contract\n * - Input3: auctionNFT from the Registry Contract\n *\n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change\n * - Output2: NameMintingNFT back to the Registry contract\n * - Output3: External Auth NFT to the name contract\n * - Output4: Internal Auth NFT to the name contract\n * - Output5: Name NFT to the auction winner\n * - Output6: Platform fee [Reduces and the not included]\n *\n */\n function call(){\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 7);\n\n // This contract can only be used at input1 and it should return to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the nameCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n // Strict value checks to ensure the platform and miner get fee.\n require(tx.inputs[this.activeInputIndex].value == tx.outputs[this.activeInputIndex].value);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.inputs[3].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n\n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n require(tx.outputs[3].tokenCategory == registryInputCategory);\n require(tx.outputs[4].tokenCategory == registryInputCategory);\n require(tx.outputs[5].tokenCategory == registryInputCategory);\n\n // NameMintingNFT should be minting and of the 'nameCategory' i.e registryInputCategory\n bytes nameMintingCategory, bytes nameMintingCapability = tx.inputs[2].tokenCategory.split(32);\n require(nameMintingCategory == registryInputCategory);\n // Minting\n require(nameMintingCapability == 0x02);\n // NameMintingNFT should keep the same category and capability\n require(tx.inputs[2].tokenCategory == tx.outputs[2].tokenCategory);\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.inputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n // Mutable\n require(auctionCapability == 0x01);\n\n // Enforce strict restrictions on NameMintingNFT\n require(tx.inputs[2].nftCommitment == tx.outputs[2].nftCommitment);\n // NameMintingNFT has no nftCommitment\n require(tx.outputs[2].nftCommitment == 0x);\n // NameMintingNFT has no tokenAmount\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount);\n require(tx.outputs[2].tokenAmount == 0);\n\n // Strict value check\n require(tx.outputs[2].value == tx.inputs[2].value);\n\n // Enforcing the relative timelock, the auctionNFT must be atleast `minWaitTime` old\n // to be considered ended.\n require(tx.inputs[3].sequenceNumber == minWaitTime);\n\n // Extract the PKH and name from the auctionNFT\n bytes20 bidderPKH, bytes name = tx.inputs[3].nftCommitment.split(20);\n \n // Get the name length to generate the complete bytecode of the name contract\n int nameLength = name.length;\n // category + name + bytecode.\n // Note: `inactivityExpiryTime` in the name is already added to the nameContractBytecode in the constructor.\n bytes nameBytecode = 0x20 + registryInputCategory + bytes(nameLength) + name + tld + nameContractBytecode;\n bytes32 scriptHash = hash256(nameBytecode);\n bytes35 nameLockingBytecode = new LockingBytecodeP2SH32(scriptHash);\n \n // ExternalAuthNFT goes to the name contract\n require(tx.outputs[3].lockingBytecode == nameLockingBytecode);\n // InternalAuthNFT goes to the name contract\n require(tx.outputs[4].lockingBytecode == nameLockingBytecode);\n \n // ExternalAuthNFT does not have any commitment\n require(tx.outputs[3].nftCommitment == 0x);\n // Strict value check\n require(tx.outputs[3].value == 1000);\n\n // InternalAuthNFT has registrationID as the commitment so it can be used to authenticate\n // along with the ownershipNFT\n bytes8 registrationId = bytes8(tx.inputs[3].tokenAmount);\n require(tx.outputs[4].nftCommitment == registrationId);\n // Strict value check\n require(tx.outputs[4].value == 1000);\n\n // Send the name ownership NFT to the bidder\n require(tx.outputs[5].nftCommitment == registrationId + name);\n require(tx.outputs[5].lockingBytecode == new LockingBytecodeP2PKH(bidderPKH));\n require(tx.outputs[5].value == 1000);\n\n // tokenAmount from the auctionNFT goes to the authorizedThreadNFT to be accumulated later\n // and merged back with the CounterNFT using the `Accumulator` Contract\n require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[3].tokenAmount);\n\n // Dual Decay mechanism, creator incentive decays linearly with the step.\n // 1. Decay percentage to the step 0.001%\n int decayPercentageToTheStep = tx.inputs[3].tokenAmount * 1 / 10000;\n // 2. Get creator incentive for current step with linear decay\n int creatorIncentive = tx.inputs[3].tokenAmount * (1 - decayPercentageToTheStep);\n\n if(creatorIncentive > 20000) {\n require(tx.outputs[6].tokenCategory == 0x);\n // Enforce that the other piece of the fee goes to the miners.\n require(tx.outputs[6].value == creatorIncentive);\n require(tx.outputs[6].lockingBytecode == new LockingBytecodeP2PKH(creatorIncentivePKH));\n }\n }\n\n}", 'debug': { - 'bytecode': 'c3549dc457a169c0519dc0c7c0cd88c0d10088c0c6c0cc9d00c752c7788853c7788852cd8800ce53d1788854d1788855d1788852ce01207f7c527988528852ce52d18853ce01207f7c527988518852cf52d28852d2008852d352d09d52d3009d52cc52c69d53cb537a9d53cf01147f7682770120547a7e7c7e787e557a7e537a7eaa02aa207c7e01877e53cd788854cd8853d2008853cc02e8039d53d05880bc54d2788854cc02e8039d55d27c7b7e8855cd0376a9147b7e0288ac7e8855cc02e8039d00d300d053d0939d53d051950210279653d0517b94957602204ea06356d1008856cc789d56cd0376a91453797e0288ac7e88686d51', - 'sourceMap': '44:12:44:28;:32::33;:4::35:1;45:12:45:29:0;:33::34;:12:::1;:4::36;48:12:48:33:0;:37::38;:4::40:1;49:22:49:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;51:23:51:44:0;:12::59:1;:63::65:0;:4::67:1;53:22:53:43:0;:12::50:1;:65::86:0;:54::93:1;:4::95;59:51:59:52:0;:41::69:1;60:22:60:23:0;:12::40:1;:44::72:0;:4::74:1;61:22:61:23:0;:12::40:1;:44::72:0;:4::74:1;62:23:62:24:0;:12::41:1;:4::75;65:44:65:45:0;:34::60:1;66:23:66:24:0;:12::39:1;:43::64:0;:4::66:1;67:23:67:24:0;:12::39:1;:43::64:0;:4::66:1;68:23:68:24:0;:12::39:1;:43::64:0;:4::66:1;71:71:71:72:0;:61::87:1;:94::96:0;:61::97:1;72:12:72:31:0;:35::56;;:4::58:1;74:37:74:41:0;:4::43:1;76:22:76:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;79:63:79:64:0;:53::79:1;:86::88:0;:53::89:1;80:12:80:27:0;:31::52;;:4::54:1;82:33:82:37:0;:4::39:1;85:22:85:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;87:23:87:24:0;:12::39:1;:43::45:0;:4::47:1;89:23:89:24:0;:12::37:1;:51::52:0;:41::65:1;:4::67;90:23:90:24:0;:12::37:1;:41::42:0;:4::44:1;93:23:93:24:0;:12::31:1;:45::46:0;:35::53:1;:4::55;97:22:97:23:0;:12::39:1;:43::54:0;;:4::56:1;100:46:100:47:0;:36::62:1;:69::71:0;:36::72:1;103:21:103:25:0;:::32:1;;106:25:106:29:0;:32::53;;:25:::1;:62::72:0;:25::73:1;:76::80:0;:25:::1;:83::86:0;;:25:::1;:89::109:0;;:25:::1;107::107:46;108:34:108:71:0;:60::70;:34::71:1;;;111:23:111:24:0;:12::41:1;:45::64:0;:4::66:1;113:23:113:24:0;:12::41:1;:4::66;116:23:116:24:0;:12::39:1;:43::45:0;:4::47:1;118:23:118:24:0;:12::31:1;:35::39:0;:4::41:1;122:45:122:46:0;:35::59:1;:28::60;;:::70;123:23:123:24:0;:12::39:1;:43::57:0;:4::59:1;125:23:125:24:0;:12::31:1;:35::39:0;:4::41:1;128:23:128:24:0;:12::39:1;:43::57:0;:60::64;:43:::1;:4::66;129:23:129:24:0;:12::41:1;:45::80:0;:70::79;:45::80:1;;;:4::82;130:23:130:24:0;:12::31:1;:35::39:0;:4::41:1;134:23:134:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;138:45:138:46:0;:35::59:1;:62::63:0;:35:::1;:66::71:0;:35:::1;140:37:140:38:0;:27::51:1;:55::56:0;:59::83;:55:::1;:27::84;142:7:142:23:0;:26::31;:7:::1;:33:147:5:0;143:25:143:26;:14::41:1;:45::47:0;:6::49:1;145:25:145:26:0;:14::33:1;:37::53:0;:6::55:1;146:25:146:26:0;:14::43:1;:47::92:0;:72::91;;:47::92:1;;;:6::94;142:33:147:5;43:2:148:3;', + 'bytecode': 'c3549dc457a169c0519dc0c7c0cd88c0d10088c0c6c0cc9d00c752c7788853c7788852cd8800ce53d1788854d1788855d1788852ce01207f7c527988528852ce52d18853ce01207f7c527988518852cf52d28852d2008852d352d09d52d3009d52cc52c69d53cb537a9d53cf01147f7682770120547a7e7c7e787e557a7e537a7eaa02aa207c7e01877e53cd788854cd8853d2008853cc02e8039d53d0588054d2788854cc02e8039d55d27c7b7e8855cd0376a9147b7e0288ac7e8855cc02e8039d00d300d053d0939d53d051950210279653d0517b94957602204ea06356d1008856cc789d56cd0376a91453797e0288ac7e88686d51', + 'sourceMap': '44:12:44:28;:32::33;:4::35:1;45:12:45:29:0;:33::34;:12:::1;:4::36;48:12:48:33:0;:37::38;:4::40:1;49:22:49:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;51:23:51:44:0;:12::59:1;:63::65:0;:4::67:1;53:22:53:43:0;:12::50:1;:65::86:0;:54::93:1;:4::95;59:51:59:52:0;:41::69:1;60:22:60:23:0;:12::40:1;:44::72:0;:4::74:1;61:22:61:23:0;:12::40:1;:44::72:0;:4::74:1;62:23:62:24:0;:12::41:1;:4::75;65:44:65:45:0;:34::60:1;66:23:66:24:0;:12::39:1;:43::64:0;:4::66:1;67:23:67:24:0;:12::39:1;:43::64:0;:4::66:1;68:23:68:24:0;:12::39:1;:43::64:0;:4::66:1;71:71:71:72:0;:61::87:1;:94::96:0;:61::97:1;72:12:72:31:0;:35::56;;:4::58:1;74:37:74:41:0;:4::43:1;76:22:76:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;79:63:79:64:0;:53::79:1;:86::88:0;:53::89:1;80:12:80:27:0;:31::52;;:4::54:1;82:33:82:37:0;:4::39:1;85:22:85:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;87:23:87:24:0;:12::39:1;:43::45:0;:4::47:1;89:23:89:24:0;:12::37:1;:51::52:0;:41::65:1;:4::67;90:23:90:24:0;:12::37:1;:41::42:0;:4::44:1;93:23:93:24:0;:12::31:1;:45::46:0;:35::53:1;:4::55;97:22:97:23:0;:12::39:1;:43::54:0;;:4::56:1;100:46:100:47:0;:36::62:1;:69::71:0;:36::72:1;103:21:103:25:0;:::32:1;;106:25:106:29:0;:32::53;;:25:::1;:62::72:0;:25::73:1;:76::80:0;:25:::1;:83::86:0;;:25:::1;:89::109:0;;:25:::1;107::107:46;108:34:108:71:0;:60::70;:34::71:1;;;111:23:111:24:0;:12::41:1;:45::64:0;:4::66:1;113:23:113:24:0;:12::41:1;:4::66;116:23:116:24:0;:12::39:1;:43::45:0;:4::47:1;118:23:118:24:0;:12::31:1;:35::39:0;:4::41:1;122:45:122:46:0;:35::59:1;:28::60;;123:23:123:24:0;:12::39:1;:43::57:0;:4::59:1;125:23:125:24:0;:12::31:1;:35::39:0;:4::41:1;128:23:128:24:0;:12::39:1;:43::57:0;:60::64;:43:::1;:4::66;129:23:129:24:0;:12::41:1;:45::80:0;:70::79;:45::80:1;;;:4::82;130:23:130:24:0;:12::31:1;:35::39:0;:4::41:1;134:23:134:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;138:45:138:46:0;:35::59:1;:62::63:0;:35:::1;:66::71:0;:35:::1;140:37:140:38:0;:27::51:1;:55::56:0;:59::83;:55:::1;:27::84;142:7:142:23:0;:26::31;:7:::1;:33:147:5:0;143:25:143:26;:14::41:1;:45::47:0;:6::49:1;145:25:145:26:0;:14::33:1;:37::53:0;:6::55:1;146:25:146:26:0;:14::43:1;:47::92:0;:72::91;;:47::92:1;;;:6::94;142:33:147:5;43:2:148:3;', 'logs': [], 'requires': [ { @@ -140,39 +140,39 @@ export default { 'line': 118, }, { - 'ip': 158, + 'ip': 157, 'line': 123, }, { - 'ip': 162, + 'ip': 161, 'line': 125, }, { - 'ip': 168, + 'ip': 167, 'line': 128, }, { - 'ip': 176, + 'ip': 175, 'line': 129, }, { - 'ip': 180, + 'ip': 179, 'line': 130, }, { - 'ip': 188, + 'ip': 187, 'line': 134, }, { - 'ip': 208, + 'ip': 207, 'line': 143, }, { - 'ip': 212, + 'ip': 211, 'line': 145, }, { - 'ip': 221, + 'ip': 220, 'line': 146, }, ], @@ -181,5 +181,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-26T12:09:10.168Z', + 'updatedAt': '2025-07-27T14:39:11.681Z', }; diff --git a/lib/compiled/Name.ts b/lib/compiled/Name.ts index 6bfdaaa..8532f62 100644 --- a/lib/compiled/Name.ts +++ b/lib/compiled/Name.ts @@ -230,5 +230,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-26T12:09:09.557Z', + 'updatedAt': '2025-07-27T14:39:11.060Z', }; diff --git a/lib/compiled/NameEnforcer.ts b/lib/compiled/NameEnforcer.ts index 1e6feb6..7b07205 100644 --- a/lib/compiled/NameEnforcer.ts +++ b/lib/compiled/NameEnforcer.ts @@ -77,5 +77,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-26T12:09:10.471Z', + 'updatedAt': '2025-07-27T14:39:11.985Z', }; diff --git a/lib/compiled/OwnershipGuard.ts b/lib/compiled/OwnershipGuard.ts index 4faf62f..e167e03 100644 --- a/lib/compiled/OwnershipGuard.ts +++ b/lib/compiled/OwnershipGuard.ts @@ -89,5 +89,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-26T12:09:10.765Z', + 'updatedAt': '2025-07-27T14:39:12.288Z', }; diff --git a/lib/compiled/Registry.ts b/lib/compiled/Registry.ts index c7c2dbf..b8b42ea 100644 --- a/lib/compiled/Registry.ts +++ b/lib/compiled/Registry.ts @@ -57,5 +57,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-26T12:09:08.936Z', + 'updatedAt': '2025-07-27T14:39:10.429Z', }; diff --git a/test/common.ts b/test/common.ts index 3aa69e8..26d1f99 100644 --- a/test/common.ts +++ b/test/common.ts @@ -16,7 +16,8 @@ export const reversedNameTokenCategory = binToHex(hexToBin(nameTokenCategory).re export const mockOptions = { category: nameTokenCategory, - minStartingBid: 10000, + // minStartingBid: 10000, + minStartingBid: 1000000, minBidIncreasePercentage: 5, inactivityExpiryTime: 1, minWaitTime: 1, diff --git a/test/e2e/auction.test.ts b/test/e2e/auction.test.ts index 97d7335..0a71044 100644 --- a/test/e2e/auction.test.ts +++ b/test/e2e/auction.test.ts @@ -2,7 +2,7 @@ import { MockNetworkProvider, randomUtxo, TransactionBuilder, Contract, type Utx import { binToHex, cashAddressToLockingBytecode, hexToBin } from '@bitauth/libauth'; import { BitCANNArtifacts } from '../../lib/index.js'; import { aliceAddress, alicePkh, aliceTemplate, nameTokenCategory, mockOptions, reversedNameTokenCategory } from '../common.js'; -import { intToBytesToHex, getTxOutputs } from '../utils.js'; +import { getTxOutputs, getAuctionPrice } from '../utils.js'; describe('Auction', () => { @@ -30,7 +30,7 @@ describe('Auction', () => beforeAll(() => { userUTXO = { - ...randomUtxo(), + ...randomUtxo({ satoshis: BigInt(1000000000) }), }; provider.addUtxo(aliceAddress, userUTXO); @@ -61,7 +61,8 @@ describe('Auction', () => category: nameTokenCategory, amount: BigInt('9223372036854775807'), nft: { - commitment: intToBytesToHex({ value: 0, length: 8 }), + // commitment: intToBytesToHex({ value: 0, length: 8 }), + commitment: '00', capability: 'minting', }, }, @@ -88,9 +89,14 @@ describe('Auction', () => provider.addUtxo(registryContract.address, mintingNFTUTXO); newRegistrationId = parseInt(registrationCounterUTXO.token!.nft!.commitment, 16) + 1; - newRegistrationIdCommitment = newRegistrationId.toString(16).padStart(16, '0'); - - auctionAmount = BigInt(mockOptions.minStartingBid); + const regIdHex = newRegistrationId.toString(16).padStart(16, '0'); + const regIdBytes = []; + for(let i = 0; i < regIdHex.length; i += 2) + { + regIdBytes.push(regIdHex.slice(i, i + 2)); + } + newRegistrationIdCommitment = regIdBytes.reverse().join(''); + auctionAmount = BigInt(getAuctionPrice(newRegistrationId, mockOptions.minStartingBid)); }); it('should start an auction without fail', async () => @@ -157,168 +163,168 @@ describe('Auction', () => expect(txOutputs).toEqual(expect.arrayContaining([{ to: aliceAddress, amount: changeAmount, token: undefined }])); }); - it('should pass without change output', async () => - { - // Construct the transaction using the TransactionBuilder - transaction = new TransactionBuilder({ provider }) - .addInput(threadNFTUTXO, registryContract.unlock.call()) - .addInput(authorizedContractUTXO, auctionContract.unlock.call(nameBin)) - .addInput(registrationCounterUTXO, registryContract.unlock.call()) - .addInput(userUTXO, aliceTemplate.unlockP2PKH()) - .addOutput({ - to: registryContract.tokenAddress, - amount: threadNFTUTXO.satoshis, - token: { - category: threadNFTUTXO.token!.category, - amount: threadNFTUTXO.token!.amount, - nft: { - capability: threadNFTUTXO.token!.nft!.capability, - commitment: threadNFTUTXO.token!.nft!.commitment, - }, - }, - }) - .addOutput({ - to: auctionContract.tokenAddress, - amount: authorizedContractUTXO.satoshis, - }) - .addOutput({ - to: registryContract.tokenAddress, - amount: registrationCounterUTXO.satoshis, - token: { - category: registrationCounterUTXO.token!.category, - amount: registrationCounterUTXO.token!.amount - BigInt(newRegistrationId), - nft: { - capability: registrationCounterUTXO.token!.nft!.capability, - commitment: newRegistrationIdCommitment, - }, - }, - }) - .addOutput({ - to: registryContract.tokenAddress, - amount: BigInt(auctionAmount), - token: { - category: registrationCounterUTXO.token!.category, - amount: BigInt(newRegistrationId), - nft: { - capability: 'mutable', - commitment: binToHex(alicePkh) + binToHex(nameBin), - }, - }, - }) - .addOpReturnOutput([ name ]); + // it('should pass without change output', async () => + // { + // // Construct the transaction using the TransactionBuilder + // transaction = new TransactionBuilder({ provider }) + // .addInput(threadNFTUTXO, registryContract.unlock.call()) + // .addInput(authorizedContractUTXO, auctionContract.unlock.call(nameBin)) + // .addInput(registrationCounterUTXO, registryContract.unlock.call()) + // .addInput(userUTXO, aliceTemplate.unlockP2PKH()) + // .addOutput({ + // to: registryContract.tokenAddress, + // amount: threadNFTUTXO.satoshis, + // token: { + // category: threadNFTUTXO.token!.category, + // amount: threadNFTUTXO.token!.amount, + // nft: { + // capability: threadNFTUTXO.token!.nft!.capability, + // commitment: threadNFTUTXO.token!.nft!.commitment, + // }, + // }, + // }) + // .addOutput({ + // to: auctionContract.tokenAddress, + // amount: authorizedContractUTXO.satoshis, + // }) + // .addOutput({ + // to: registryContract.tokenAddress, + // amount: registrationCounterUTXO.satoshis, + // token: { + // category: registrationCounterUTXO.token!.category, + // amount: registrationCounterUTXO.token!.amount - BigInt(newRegistrationId), + // nft: { + // capability: registrationCounterUTXO.token!.nft!.capability, + // commitment: newRegistrationIdCommitment, + // }, + // }, + // }) + // .addOutput({ + // to: registryContract.tokenAddress, + // amount: BigInt(auctionAmount), + // token: { + // category: registrationCounterUTXO.token!.category, + // amount: BigInt(newRegistrationId), + // nft: { + // capability: 'mutable', + // commitment: binToHex(alicePkh) + binToHex(nameBin), + // }, + // }, + // }) + // .addOpReturnOutput([ name ]); - const txPromise = await transaction.send(); - // @ts-ignore - const txOutputs = getTxOutputs(txPromise); - expect(txOutputs.length).toBe(5); - }); + // const txPromise = await transaction.send(); + // // @ts-ignore + // const txOutputs = getTxOutputs(txPromise); + // expect(txOutputs.length).toBe(5); + // }); - it('should fail without op return output', async () => - { - // Construct the transaction using the TransactionBuilder - transaction = new TransactionBuilder({ provider }) - .addInput(threadNFTUTXO, registryContract.unlock.call()) - .addInput(authorizedContractUTXO, auctionContract.unlock.call(nameBin)) - .addInput(registrationCounterUTXO, registryContract.unlock.call()) - .addInput(userUTXO, aliceTemplate.unlockP2PKH()) - .addOutput({ - to: registryContract.tokenAddress, - amount: threadNFTUTXO.satoshis, - token: { - category: threadNFTUTXO.token!.category, - amount: threadNFTUTXO.token!.amount, - nft: { - capability: threadNFTUTXO.token!.nft!.capability, - commitment: threadNFTUTXO.token!.nft!.commitment, - }, - }, - }) - .addOutput({ - to: auctionContract.tokenAddress, - amount: authorizedContractUTXO.satoshis, - }) - .addOutput({ - to: registryContract.tokenAddress, - amount: registrationCounterUTXO.satoshis, - token: { - category: registrationCounterUTXO.token!.category, - amount: registrationCounterUTXO.token!.amount - BigInt(newRegistrationId), - nft: { - capability: registrationCounterUTXO.token!.nft!.capability, - commitment: newRegistrationIdCommitment, - }, - }, - }) - .addOutput({ - to: registryContract.tokenAddress, - amount: BigInt(auctionAmount), - token: { - category: registrationCounterUTXO.token!.category, - amount: BigInt(newRegistrationId), - nft: { - capability: 'mutable', - commitment: binToHex(alicePkh) + binToHex(nameBin), - }, - }, - }); + // it('should fail without op return output', async () => + // { + // // Construct the transaction using the TransactionBuilder + // transaction = new TransactionBuilder({ provider }) + // .addInput(threadNFTUTXO, registryContract.unlock.call()) + // .addInput(authorizedContractUTXO, auctionContract.unlock.call(nameBin)) + // .addInput(registrationCounterUTXO, registryContract.unlock.call()) + // .addInput(userUTXO, aliceTemplate.unlockP2PKH()) + // .addOutput({ + // to: registryContract.tokenAddress, + // amount: threadNFTUTXO.satoshis, + // token: { + // category: threadNFTUTXO.token!.category, + // amount: threadNFTUTXO.token!.amount, + // nft: { + // capability: threadNFTUTXO.token!.nft!.capability, + // commitment: threadNFTUTXO.token!.nft!.commitment, + // }, + // }, + // }) + // .addOutput({ + // to: auctionContract.tokenAddress, + // amount: authorizedContractUTXO.satoshis, + // }) + // .addOutput({ + // to: registryContract.tokenAddress, + // amount: registrationCounterUTXO.satoshis, + // token: { + // category: registrationCounterUTXO.token!.category, + // amount: registrationCounterUTXO.token!.amount - BigInt(newRegistrationId), + // nft: { + // capability: registrationCounterUTXO.token!.nft!.capability, + // commitment: newRegistrationIdCommitment, + // }, + // }, + // }) + // .addOutput({ + // to: registryContract.tokenAddress, + // amount: BigInt(auctionAmount), + // token: { + // category: registrationCounterUTXO.token!.category, + // amount: BigInt(newRegistrationId), + // nft: { + // capability: 'mutable', + // commitment: binToHex(alicePkh) + binToHex(nameBin), + // }, + // }, + // }); - const txPromise = transaction.send(); - await expect(txPromise).rejects.toThrow('Auction.cash:93 Error in transaction at input 1 in contract Auction.cash at line 93.'); - await expect(txPromise).rejects.toThrow('Failing statement: tx.outputs[4].lockingBytecode'); - }); + // const txPromise = transaction.send(); + // await expect(txPromise).rejects.toThrow('Auction.cash:113 Error in transaction at input 1 in contract Auction.cash at line 113.'); + // await expect(txPromise).rejects.toThrow('Failing statement: tx.outputs[4].lockingBytecode'); + // }); - it('should fail setting auction capability to none', async () => - { - // Construct the transaction using the TransactionBuilder - transaction = new TransactionBuilder({ provider }) - .addInput(threadNFTUTXO, registryContract.unlock.call()) - .addInput(authorizedContractUTXO, auctionContract.unlock.call(nameBin)) - .addInput(registrationCounterUTXO, registryContract.unlock.call()) - .addInput(userUTXO, aliceTemplate.unlockP2PKH()) - .addOutput({ - to: registryContract.tokenAddress, - amount: threadNFTUTXO.satoshis, - token: { - category: threadNFTUTXO.token!.category, - amount: threadNFTUTXO.token!.amount, - nft: { - capability: threadNFTUTXO.token!.nft!.capability, - commitment: threadNFTUTXO.token!.nft!.commitment, - }, - }, - }) - .addOutput({ - to: auctionContract.tokenAddress, - amount: authorizedContractUTXO.satoshis, - }) - .addOutput({ - to: registryContract.tokenAddress, - amount: registrationCounterUTXO.satoshis, - token: { - category: registrationCounterUTXO.token!.category, - amount: registrationCounterUTXO.token!.amount - BigInt(newRegistrationId), - nft: { - capability: registrationCounterUTXO.token!.nft!.capability, - commitment: newRegistrationIdCommitment, - }, - }, - }) - .addOutput({ - to: registryContract.tokenAddress, - amount: BigInt(auctionAmount), - token: { - category: registrationCounterUTXO.token!.category, - amount: BigInt(newRegistrationId), - nft: { - capability: 'none', - commitment: binToHex(alicePkh) + binToHex(nameBin), - }, - }, - }) - .addOpReturnOutput([ name ]); + // it('should fail setting auction capability to none', async () => + // { + // // Construct the transaction using the TransactionBuilder + // transaction = new TransactionBuilder({ provider }) + // .addInput(threadNFTUTXO, registryContract.unlock.call()) + // .addInput(authorizedContractUTXO, auctionContract.unlock.call(nameBin)) + // .addInput(registrationCounterUTXO, registryContract.unlock.call()) + // .addInput(userUTXO, aliceTemplate.unlockP2PKH()) + // .addOutput({ + // to: registryContract.tokenAddress, + // amount: threadNFTUTXO.satoshis, + // token: { + // category: threadNFTUTXO.token!.category, + // amount: threadNFTUTXO.token!.amount, + // nft: { + // capability: threadNFTUTXO.token!.nft!.capability, + // commitment: threadNFTUTXO.token!.nft!.commitment, + // }, + // }, + // }) + // .addOutput({ + // to: auctionContract.tokenAddress, + // amount: authorizedContractUTXO.satoshis, + // }) + // .addOutput({ + // to: registryContract.tokenAddress, + // amount: registrationCounterUTXO.satoshis, + // token: { + // category: registrationCounterUTXO.token!.category, + // amount: registrationCounterUTXO.token!.amount - BigInt(newRegistrationId), + // nft: { + // capability: registrationCounterUTXO.token!.nft!.capability, + // commitment: newRegistrationIdCommitment, + // }, + // }, + // }) + // .addOutput({ + // to: registryContract.tokenAddress, + // amount: BigInt(auctionAmount), + // token: { + // category: registrationCounterUTXO.token!.category, + // amount: BigInt(newRegistrationId), + // nft: { + // capability: 'none', + // commitment: binToHex(alicePkh) + binToHex(nameBin), + // }, + // }, + // }) + // .addOpReturnOutput([ name ]); - const txPromise = transaction.send(); - await expect(txPromise).rejects.toThrow('Auction.cash:90 Require statement failed at input 1 in contract Auction.cash at line 90.'); - await expect(txPromise).rejects.toThrow('Failing statement: require(auctionCapability == 0x01);'); - }); + // const txPromise = transaction.send(); + // await expect(txPromise).rejects.toThrow('Auction.cash:110 Require statement failed at input 1 in contract Auction.cash at line 110.'); + // await expect(txPromise).rejects.toThrow('Failing statement: require(auctionCapability == 0x01);'); + // }); }); \ No newline at end of file diff --git a/test/utils.ts b/test/utils.ts index 202224f..23b0a68 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -30,7 +30,7 @@ export const getAuctionPrice = (registrationId: number, minStartingBid: number): const decayPercentageToTheStep = registrationId * 3 / 1000000; const currentAuctionPrice = minStartingBid * (1 - decayPercentageToTheStep); - return currentAuctionPrice; + return Math.max(currentAuctionPrice, 20000); }; export const intToBytesToHex = ({ value, length }: { value: number; length: number }): string => From af6f3e4ae16a8a7c616d095886f7035d5e80858e Mon Sep 17 00:00:00 2001 From: kiok46 Date: Mon, 28 Jul 2025 00:33:38 +0530 Subject: [PATCH 03/35] Ensure that the lockingbytecode of the bidders are enforced to be p2pkh --- contracts/Auction.cash | 12 +- contracts/Bid.cash | 14 +- lib/compiled/Accumulator.ts | 2 +- lib/compiled/Auction.ts | 48 +++-- lib/compiled/Bid.ts | 42 ++-- lib/compiled/ConflictResolver.ts | 2 +- lib/compiled/Factory.ts | 2 +- lib/compiled/Name.ts | 2 +- lib/compiled/NameEnforcer.ts | 2 +- lib/compiled/OwnershipGuard.ts | 2 +- lib/compiled/Registry.ts | 2 +- test/e2e/auction.test.ts | 318 +++++++++++++++---------------- 12 files changed, 236 insertions(+), 212 deletions(-) diff --git a/contracts/Auction.cash b/contracts/Auction.cash index 3386a0c..407c3b5 100644 --- a/contracts/Auction.cash +++ b/contracts/Auction.cash @@ -79,12 +79,16 @@ contract Auction(int minStartingBid) { // Funding UTXO/ Bid UTXO require(tx.inputs[3].tokenCategory == 0x); - // Ensure that the funding happens from a P2PKH UTXO. + // Ensure that the funding happens from a P2PKH UTXO because there will be no way to know the locking bytecode as + // name can be of any length. require(tx.inputs[3].lockingBytecode.length == 25); - // Extract the PKH from the lockingBytecode of the Funding UTXO. - // + name > 20 bytes - bytes pkh = tx.inputs[3].lockingBytecode.split(3)[1].split(20)[0]; + bytes pkhLockingBytecodeHead, bytes pkhLockingBytecodeBody = tx.inputs[3].lockingBytecode.split(3); + // OP_DUP OP_HASH160 Push 20-byte + require(pkhLockingBytecodeHead == 0x76a914); + bytes pkh, bytes pkhLockingBytecodeTail = pkhLockingBytecodeBody.split(20); + // OP_EQUALVERIFY OP_CHECKSIG + require(pkhLockingBytecodeTail == 0x88ac); require(tx.outputs[3].nftCommitment == pkh + name); // Ensure that the name is not too long, as of 2025 upgrade, the nftcommitment is 40 bytes. diff --git a/contracts/Bid.cash b/contracts/Bid.cash index a0470f9..fc2382f 100644 --- a/contracts/Bid.cash +++ b/contracts/Bid.cash @@ -56,14 +56,18 @@ contract Bid(int minBidIncreasePercentage) { require(auctionCategory == registryInputCategory); require(auctionCapability == 0x01); // Mutable - // Ensure that the funding happens from a P2PKH UTXO. + // Ensure that the funding happens from a P2PKH UTXO because there will be no way to know the locking bytecode as + // name can be of any length. require(tx.inputs[3].lockingBytecode.length == 25); + bytes pkhLockingBytecodeHead, bytes pkhLockingBytecodeBody = tx.inputs[3].lockingBytecode.split(3); + // OP_DUP OP_HASH160 Push 20-byte + require(pkhLockingBytecodeHead == 0x76a914); + bytes pkh, bytes pkhLockingBytecodeTail = pkhLockingBytecodeBody.split(20); + // OP_EQUALVERIFY OP_CHECKSIG + require(pkhLockingBytecodeTail == 0x88ac); bytes20 previousPKH, bytes name = tx.inputs[2].nftCommitment.split(20); - // Extract the PKH from the lockingBytecode of the Funding UTXO. - // + name > 20 bytes - bytes pkh = tx.inputs[3].lockingBytecode.split(3)[1].split(20)[0]; - + // AuctionNFT should have updated PKH in it's commitment. require(tx.outputs[2].nftCommitment == pkh + name); diff --git a/lib/compiled/Accumulator.ts b/lib/compiled/Accumulator.ts index 1a54dd4..d328046 100644 --- a/lib/compiled/Accumulator.ts +++ b/lib/compiled/Accumulator.ts @@ -100,5 +100,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T14:39:12.891Z', + 'updatedAt': '2025-07-27T19:02:18.176Z', }; diff --git a/lib/compiled/Auction.ts b/lib/compiled/Auction.ts index 4af7c18..4d14d4a 100644 --- a/lib/compiled/Auction.ts +++ b/lib/compiled/Auction.ts @@ -17,11 +17,11 @@ export default { ], }, ], - 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_BIN2NUM OP_2 OP_OUTPUTTOKENCOMMITMENT OP_BIN2NUM OP_DUP OP_ROT OP_1ADD OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_2 OP_PICK OP_SUB OP_NUMEQUALVERIFY OP_3 OP_OUTPUTTOKENAMOUNT OP_OVER OP_NUMEQUALVERIFY OP_OVER OP_SWAP OP_MUL OP_3 OP_MUL OP_SWAP 40420f OP_MUL OP_SWAP OP_SUB 40420f OP_DIV OP_DUP 204e OP_MAX OP_3 OP_OUTPUTVALUE OP_LESSTHANOREQUAL OP_VERIFY OP_3 OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_SIZE OP_NIP 19 OP_NUMEQUALVERIFY OP_3 OP_UTXOBYTECODE OP_3 OP_SPLIT OP_NIP 14 OP_SPLIT OP_DROP OP_3 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_3 OP_PICK OP_CAT OP_EQUALVERIFY OP_OVER OP_SIZE OP_NIP OP_16 OP_LESSTHANOREQUAL OP_VERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_4 OP_OUTPUTBYTECODE 6a OP_3 OP_ROLL OP_SIZE OP_DUP 4b OP_GREATERTHAN OP_IF 4c OP_SWAP OP_CAT OP_ENDIF OP_SWAP OP_CAT OP_CAT OP_EQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_NUMEQUAL OP_IF OP_5 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_DROP OP_1', - 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param minStartingBid The minimum starting bid for the auction.\n */\ncontract Auction(int minStartingBid) {\n /**\n * Starts a new name registration auction.\n * @param name The name being registered.\n * \n * The function creates a new auction with:\n * - Starting bid >= `minStartingBid` BCH.\n * - A successful registration initiation results in an auctionNFT representing the auction state:\n * - capability: (Mutable)\n * - category: registryInputCategory\n * - tokenAmount: (Represents the registrationId)\n * - satoshiValue: (Represents the bid amount)\n * - commitment: bidder's PKH (20 bytes) + name (bytes)\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: Minting CounterNFT from Registry contract (Increases the registrationId by 1 in the output).\n * - Input3: Funding UTXO.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Minting CounterNFT going back to the Registry contract.\n * - Output3: auctionNFT to the Registry contract.\n * - Output4: OP_RETURN output containing the name.\n * - Output5: Optional change in BCH.\n */\n function call(bytes name) {\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 6);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the nameCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[3].lockingBytecode == registryInputLockingBytecode);\n\n // Registration ID increases by 1 with each transaction.\n int prevRegistrationId = int(tx.inputs[2].nftCommitment);\n int nextRegistrationId = int(tx.outputs[2].nftCommitment);\n require(nextRegistrationId == prevRegistrationId + 1);\n\n // Reduce the tokenAmount in the counterNFT as some amount is going to auctionNFT\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount - nextRegistrationId);\n // tokenAmount in the auctionNFT is the registrationId.\n require(tx.outputs[3].tokenAmount == nextRegistrationId);\n\n // Dual Decay mechanism, auction price decays linearly with the step.\n // To facilitate higher precisions and since decimals do not exist in VM, we multiply\n // it by 1e6 (1000000) and name the values as points.\n\n // 1. Decay points (step 0.0003% per step)\n int decayPoints = minStartingBid * nextRegistrationId * 3;\n // 2. Get auction price points\n int currentPricePoints = minStartingBid * 1e6;\n // 3. Subtract price points by decay points to get the current auction price.\n int currentAuctionPrice = (currentPricePoints - decayPoints) / 1e6;\n\n // Set the minimum auction price to 20000 satoshis.\n currentAuctionPrice = max(currentAuctionPrice, 20000);\n\n // Every auction begins with a min base value of at least currentAuctionPrice satoshis.\n require(tx.outputs[3].value >= currentAuctionPrice);\n // Funding UTXO/ Bid UTXO\n require(tx.inputs[3].tokenCategory == 0x);\n\n // Ensure that the funding happens from a P2PKH UTXO.\n require(tx.inputs[3].lockingBytecode.length == 25);\n\n // Extract the PKH from the lockingBytecode of the Funding UTXO.\n // + name > 20 bytes\n bytes pkh = tx.inputs[3].lockingBytecode.split(3)[1].split(20)[0];\n require(tx.outputs[3].nftCommitment == pkh + name);\n\n // Ensure that the name is not too long, as of 2025 upgrade, the nftcommitment is 40 bytes.\n // 20 bytes pkh + 16 bytes name + 4 bytes TLD\n require(name.length <= 16);\n\n // CounterNFT should keep the same category and capability.\n require(tx.outputs[2].tokenCategory == tx.inputs[2].tokenCategory);\n \n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n \n // CounterNFT should be minting and of the 'nameCategory' i.e registryInputCategory\n bytes counterCategory, bytes counterCapability = tx.outputs[2].tokenCategory.split(32);\n require(counterCategory == registryInputCategory);\n // Minting\n require(counterCapability == 0x02);\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.outputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n // Mutable\n require(auctionCapability == 0x01);\n\n // Enforce an OP_RETURN output that contains the name.\n require(tx.outputs[4].lockingBytecode == new LockingBytecodeNullData([name]));\n\n if (tx.outputs.length == 6) {\n // If any change, then it must be pure BCH.\n require(tx.outputs[5].tokenCategory == 0x);\n }\n }\n}", + 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_BIN2NUM OP_2 OP_OUTPUTTOKENCOMMITMENT OP_BIN2NUM OP_DUP OP_ROT OP_1ADD OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_2 OP_PICK OP_SUB OP_NUMEQUALVERIFY OP_3 OP_OUTPUTTOKENAMOUNT OP_OVER OP_NUMEQUALVERIFY OP_OVER OP_SWAP OP_MUL OP_3 OP_MUL OP_SWAP 40420f OP_MUL OP_SWAP OP_SUB 40420f OP_DIV OP_DUP 204e OP_MAX OP_3 OP_OUTPUTVALUE OP_LESSTHANOREQUAL OP_VERIFY OP_3 OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_SIZE OP_NIP 19 OP_NUMEQUALVERIFY OP_3 OP_UTXOBYTECODE OP_3 OP_SPLIT OP_SWAP 76a914 OP_EQUALVERIFY 14 OP_SPLIT 88ac OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_3 OP_PICK OP_CAT OP_EQUALVERIFY OP_OVER OP_SIZE OP_NIP OP_16 OP_LESSTHANOREQUAL OP_VERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_4 OP_OUTPUTBYTECODE 6a OP_3 OP_ROLL OP_SIZE OP_DUP 4b OP_GREATERTHAN OP_IF 4c OP_SWAP OP_CAT OP_ENDIF OP_SWAP OP_CAT OP_CAT OP_EQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_NUMEQUAL OP_IF OP_5 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_DROP OP_1', + 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param minStartingBid The minimum starting bid for the auction.\n */\ncontract Auction(int minStartingBid) {\n /**\n * Starts a new name registration auction.\n * @param name The name being registered.\n * \n * The function creates a new auction with:\n * - Starting bid >= `minStartingBid` BCH.\n * - A successful registration initiation results in an auctionNFT representing the auction state:\n * - capability: (Mutable)\n * - category: registryInputCategory\n * - tokenAmount: (Represents the registrationId)\n * - satoshiValue: (Represents the bid amount)\n * - commitment: bidder's PKH (20 bytes) + name (bytes)\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: Minting CounterNFT from Registry contract (Increases the registrationId by 1 in the output).\n * - Input3: Funding UTXO.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Minting CounterNFT going back to the Registry contract.\n * - Output3: auctionNFT to the Registry contract.\n * - Output4: OP_RETURN output containing the name.\n * - Output5: Optional change in BCH.\n */\n function call(bytes name) {\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 6);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the nameCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[3].lockingBytecode == registryInputLockingBytecode);\n\n // Registration ID increases by 1 with each transaction.\n int prevRegistrationId = int(tx.inputs[2].nftCommitment);\n int nextRegistrationId = int(tx.outputs[2].nftCommitment);\n require(nextRegistrationId == prevRegistrationId + 1);\n\n // Reduce the tokenAmount in the counterNFT as some amount is going to auctionNFT\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount - nextRegistrationId);\n // tokenAmount in the auctionNFT is the registrationId.\n require(tx.outputs[3].tokenAmount == nextRegistrationId);\n\n // Dual Decay mechanism, auction price decays linearly with the step.\n // To facilitate higher precisions and since decimals do not exist in VM, we multiply\n // it by 1e6 (1000000) and name the values as points.\n\n // 1. Decay points (step 0.0003% per step)\n int decayPoints = minStartingBid * nextRegistrationId * 3;\n // 2. Get auction price points\n int currentPricePoints = minStartingBid * 1e6;\n // 3. Subtract price points by decay points to get the current auction price.\n int currentAuctionPrice = (currentPricePoints - decayPoints) / 1e6;\n\n // Set the minimum auction price to 20000 satoshis.\n currentAuctionPrice = max(currentAuctionPrice, 20000);\n\n // Every auction begins with a min base value of at least currentAuctionPrice satoshis.\n require(tx.outputs[3].value >= currentAuctionPrice);\n // Funding UTXO/ Bid UTXO\n require(tx.inputs[3].tokenCategory == 0x);\n\n // Ensure that the funding happens from a P2PKH UTXO because there will be no way to know the locking bytecode as \n // name can be of any length.\n require(tx.inputs[3].lockingBytecode.length == 25);\n\n bytes pkhLockingBytecodeHead, bytes pkhLockingBytecodeBody = tx.inputs[3].lockingBytecode.split(3);\n // OP_DUP OP_HASH160 Push 20-byte\n require(pkhLockingBytecodeHead == 0x76a914);\n bytes pkh, bytes pkhLockingBytecodeTail = pkhLockingBytecodeBody.split(20);\n // OP_EQUALVERIFY OP_CHECKSIG\n require(pkhLockingBytecodeTail == 0x88ac);\n require(tx.outputs[3].nftCommitment == pkh + name);\n\n // Ensure that the name is not too long, as of 2025 upgrade, the nftcommitment is 40 bytes.\n // 20 bytes pkh + 16 bytes name + 4 bytes TLD\n require(name.length <= 16);\n\n // CounterNFT should keep the same category and capability.\n require(tx.outputs[2].tokenCategory == tx.inputs[2].tokenCategory);\n \n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n \n // CounterNFT should be minting and of the 'nameCategory' i.e registryInputCategory\n bytes counterCategory, bytes counterCapability = tx.outputs[2].tokenCategory.split(32);\n require(counterCategory == registryInputCategory);\n // Minting\n require(counterCapability == 0x02);\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.outputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n // Mutable\n require(auctionCapability == 0x01);\n\n // Enforce an OP_RETURN output that contains the name.\n require(tx.outputs[4].lockingBytecode == new LockingBytecodeNullData([name]));\n\n if (tx.outputs.length == 6) {\n // If any change, then it must be pure BCH.\n require(tx.outputs[5].tokenCategory == 0x);\n }\n }\n}", 'debug': { - 'bytecode': 'c3549dc456a169c0519dc0c7c0cd88c0d1008800c752c7788852cd788853cd8852cf8152d281767b8b9d52d352d05279949d53d3789d787c9553957c0340420f957c940340420f967602204ea453cca16953ce008853c7827701199d53c7537f7701147f7553d27c53797e8878827760a16952d152ce8800ce52d101207f7c527988528853d101207f7c7b88518854cd016a537a8276014ba063014c7c7e687c7e7e88c4569c6355d10088687551', - 'sourceMap': '35:12:35:28;:32::33;:4::35:1;36:12:36:29:0;:33::34;:12:::1;:4::36;39:12:39:33:0;:37::38;:4::40:1;40:22:40:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;42:23:42:44:0;:12::59:1;:63::65:0;:4::67:1;48:51:48:52:0;:41::69:1;49:22:49:23:0;:12::40:1;:44::72:0;:4::74:1;50:23:50:24:0;:12::41:1;:45::73:0;:4::75:1;51:23:51:24:0;:12::41:1;:4::75;54:43:54:44:0;:33::59:1;:29::60;55:44:55:45:0;:33::60:1;:29::61;56:12:56:30:0;:34::52;:::56:1;:4::58;59:23:59:24:0;:12::37:1;:51::52:0;:41::65:1;:68::86:0;;:41:::1;:4::88;61:23:61:24:0;:12::37:1;:41::59:0;:4::61:1;68:22:68:36:0;:39::57;:22:::1;:60::61:0;:22:::1;70:29:70:43:0;:46::49;:29:::1;72:52:72:63:0;:31:::1;:67::70:0;:30:::1;75::75:49:0;:51::56;:26::57:1;78:23:78:24:0;:12::31:1;:::54;:4::56;80:22:80:23:0;:12::38:1;:42::44:0;:4::46:1;83:22:83:23:0;:12::40:1;:::47;;:51::53:0;:4::55:1;87:26:87:27:0;:16::44:1;:51::52:0;:16::53:1;:::56;:63::65:0;:16::66:1;:::69;88:23:88:24:0;:12::39:1;:43::46:0;:49::53;;:43:::1;:4::55;92:12:92:16:0;:::23:1;;:27::29:0;:12:::1;:4::31;95:23:95:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;98:44:98:45:0;:34::60:1;101:64:101:65:0;:53::80:1;:87::89:0;:53::90:1;102:12:102:27:0;:31::52;;:4::54:1;104:33:104:37:0;:4::39:1;107:64:107:65:0;:53::80:1;:87::89:0;:53::90:1;108:12:108:27:0;:31::52;:4::54:1;110:33:110:37:0;:4::39:1;113:23:113:24:0;:12::41:1;:45::80:0;:74::78;;::::1;;;;;;;;;;;;:4::82;115:8:115:25:0;:29::30;:8:::1;:32:118:5:0;117:25:117:26;:14::41:1;:45::47:0;:6::49:1;115:32:118:5;34:2:119:3;', + 'bytecode': 'c3549dc456a169c0519dc0c7c0cd88c0d1008800c752c7788852cd788853cd8852cf8152d281767b8b9d52d352d05279949d53d3789d787c9553957c0340420f957c940340420f967602204ea453cca16953ce008853c7827701199d53c7537f7c0376a9148801147f0288ac8853d27c53797e8878827760a16952d152ce8800ce52d101207f7c527988528853d101207f7c7b88518854cd016a537a8276014ba063014c7c7e687c7e7e88c4569c6355d10088687551', + 'sourceMap': '35:12:35:28;:32::33;:4::35:1;36:12:36:29:0;:33::34;:12:::1;:4::36;39:12:39:33:0;:37::38;:4::40:1;40:22:40:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;42:23:42:44:0;:12::59:1;:63::65:0;:4::67:1;48:51:48:52:0;:41::69:1;49:22:49:23:0;:12::40:1;:44::72:0;:4::74:1;50:23:50:24:0;:12::41:1;:45::73:0;:4::75:1;51:23:51:24:0;:12::41:1;:4::75;54:43:54:44:0;:33::59:1;:29::60;55:44:55:45:0;:33::60:1;:29::61;56:12:56:30:0;:34::52;:::56:1;:4::58;59:23:59:24:0;:12::37:1;:51::52:0;:41::65:1;:68::86:0;;:41:::1;:4::88;61:23:61:24:0;:12::37:1;:41::59:0;:4::61:1;68:22:68:36:0;:39::57;:22:::1;:60::61:0;:22:::1;70:29:70:43:0;:46::49;:29:::1;72:52:72:63:0;:31:::1;:67::70:0;:30:::1;75::75:49:0;:51::56;:26::57:1;78:23:78:24:0;:12::31:1;:::54;:4::56;80:22:80:23:0;:12::38:1;:42::44:0;:4::46:1;84:22:84:23:0;:12::40:1;:::47;;:51::53:0;:4::55:1;86:75:86:76:0;:65::93:1;:100::101:0;:65::102:1;88:12:88:34:0;:38::46;:4::48:1;89:75:89:77:0;:46::78:1;91:38:91:44:0;:4::46:1;92:23:92:24:0;:12::39:1;:43::46:0;:49::53;;:43:::1;:4::55;96:12:96:16:0;:::23:1;;:27::29:0;:12:::1;:4::31;99:23:99:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;102:44:102:45:0;:34::60:1;105:64:105:65:0;:53::80:1;:87::89:0;:53::90:1;106:12:106:27:0;:31::52;;:4::54:1;108:33:108:37:0;:4::39:1;111:64:111:65:0;:53::80:1;:87::89:0;:53::90:1;112:12:112:27:0;:31::52;:4::54:1;114:33:114:37:0;:4::39:1;117:23:117:24:0;:12::41:1;:45::80:0;:74::78;;::::1;;;;;;;;;;;;:4::82;119:8:119:25:0;:29::30;:8:::1;:32:122:5:0;121:25:121:26;:14::41:1;:45::47:0;:6::49:1;119:32:122:5;34:2:123:3;', 'logs': [], 'requires': [ { @@ -78,49 +78,57 @@ export default { }, { 'ip': 83, - 'line': 83, + 'line': 84, }, { - 'ip': 98, + 'ip': 90, 'line': 88, }, { - 'ip': 104, + 'ip': 94, + 'line': 91, + }, + { + 'ip': 101, 'line': 92, }, { - 'ip': 109, - 'line': 95, + 'ip': 107, + 'line': 96, }, { - 'ip': 119, - 'line': 102, + 'ip': 112, + 'line': 99, }, { - 'ip': 121, - 'line': 104, + 'ip': 122, + 'line': 106, }, { - 'ip': 128, + 'ip': 124, 'line': 108, }, { - 'ip': 130, - 'line': 110, + 'ip': 131, + 'line': 112, }, { - 'ip': 148, - 'line': 113, + 'ip': 133, + 'line': 114, }, { - 'ip': 156, + 'ip': 151, 'line': 117, }, + { + 'ip': 159, + 'line': 121, + }, ], }, 'compiler': { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T14:39:10.737Z', + 'updatedAt': '2025-07-27T19:02:16.007Z', }; diff --git a/lib/compiled/Bid.ts b/lib/compiled/Bid.ts index 3fafbfa..e6c48fc 100644 --- a/lib/compiled/Bid.ts +++ b/lib/compiled/Bid.ts @@ -12,11 +12,11 @@ export default { 'inputs': [], }, ], - 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_5 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_SIZE OP_NIP 19 OP_NUMEQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_3 OP_UTXOBYTECODE OP_3 OP_SPLIT OP_NIP 14 OP_SPLIT OP_DROP OP_2 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_ROT OP_CAT OP_EQUALVERIFY OP_2 OP_UTXOTOKENAMOUNT OP_2 OP_OUTPUTTOKENAMOUNT OP_NUMEQUALVERIFY OP_2 OP_OUTPUTVALUE 64 OP_MUL OP_2 OP_UTXOVALUE 64 OP_4 OP_ROLL OP_ADD OP_MUL OP_GREATERTHANOREQUAL OP_VERIFY OP_3 OP_OUTPUTBYTECODE 76a914 OP_ROT OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_3 OP_OUTPUTVALUE OP_2 OP_UTXOVALUE OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_5 OP_NUMEQUAL OP_IF OP_4 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_1', - 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param minBidIncreasePercentage The minimum percentage increase required for a new bid over the previous bid.\n */\ncontract Bid(int minBidIncreasePercentage) {\n /**\n * Places a new bid on an active name registration auction.\n * \n * The function allows placing a new bid with:\n * - A minimum `minBidIncreasePercentage` increase over the previous bid.\n * - The previous bidder receives their bid amount back in the same transaction.\n * - A successful bid updates the auctionNFT by updating the PKH in the nftCommitment and satoshiValue.\n * capability: Mutable\n * category: registryInputCategory\n * tokenAmount: Represents the registrationId\n * satoshiValue: Represents the bid amount\n * commitment: new Bidder's PKH (20 bytes) + name (bytes)\n *\n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: auctionNFT from the Registry contract.\n * - Input3: Funding UTXO from the new bidder.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Updated auctionNFT back to the Registry contract.\n * - Output3: Previous bid amount to the previous bidder.\n * - Output4: Optional change in BCH to the new bidder.\n */\n function call() {\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 5);\n \n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode); \n\n // AuctionNFT should keep the same category and capability.\n require(tx.inputs[2].tokenCategory == tx.outputs[2].tokenCategory);\n\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n // The second part of the pair changes with each new bid, hence it's marked as mutable.\n // Enforcing the structure of the pair results in predictable behavior.\n bytes auctionCategory, bytes auctionCapability = tx.outputs[2].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n require(auctionCapability == 0x01); // Mutable\n\n // Ensure that the funding happens from a P2PKH UTXO.\n require(tx.inputs[3].lockingBytecode.length == 25);\n\n bytes20 previousPKH, bytes name = tx.inputs[2].nftCommitment.split(20);\n // Extract the PKH from the lockingBytecode of the Funding UTXO.\n // + name > 20 bytes\n bytes pkh = tx.inputs[3].lockingBytecode.split(3)[1].split(20)[0];\n \n // AuctionNFT should have updated PKH in it's commitment.\n require(tx.outputs[2].nftCommitment == pkh + name);\n\n // Since tokenAmount is registrationID, make sure that it's not changing.\n require(tx.inputs[2].tokenAmount == tx.outputs[2].tokenAmount);\n\n // Ensure that the bid amount is greater than or equal to the previous bid amount + minBidIncreasePercentage.\n require(tx.outputs[2].value * 100 >= tx.inputs[2].value * (100 + minBidIncreasePercentage));\n\n // Locking bytecode of the previous bidder.\n require(tx.outputs[3].lockingBytecode == new LockingBytecodeP2PKH(previousPKH));\n // The amount being sent back to the previous bidder.\n require(tx.outputs[3].value == tx.inputs[2].value);\n\n if (tx.outputs.length == 5) {\n // If any change, then it must be pure BCH.\n require(tx.outputs[4].tokenCategory == 0x);\n }\n }\n}", + 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_5 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_SIZE OP_NIP 19 OP_NUMEQUALVERIFY OP_3 OP_UTXOBYTECODE OP_3 OP_SPLIT OP_SWAP 76a914 OP_EQUALVERIFY 14 OP_SPLIT 88ac OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_2 OP_OUTPUTTOKENCOMMITMENT OP_3 OP_ROLL OP_ROT OP_CAT OP_EQUALVERIFY OP_2 OP_UTXOTOKENAMOUNT OP_2 OP_OUTPUTTOKENAMOUNT OP_NUMEQUALVERIFY OP_2 OP_OUTPUTVALUE 64 OP_MUL OP_2 OP_UTXOVALUE 64 OP_4 OP_ROLL OP_ADD OP_MUL OP_GREATERTHANOREQUAL OP_VERIFY OP_3 OP_OUTPUTBYTECODE 76a914 OP_ROT OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_3 OP_OUTPUTVALUE OP_2 OP_UTXOVALUE OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_5 OP_NUMEQUAL OP_IF OP_4 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_1', + 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param minBidIncreasePercentage The minimum percentage increase required for a new bid over the previous bid.\n */\ncontract Bid(int minBidIncreasePercentage) {\n /**\n * Places a new bid on an active name registration auction.\n * \n * The function allows placing a new bid with:\n * - A minimum `minBidIncreasePercentage` increase over the previous bid.\n * - The previous bidder receives their bid amount back in the same transaction.\n * - A successful bid updates the auctionNFT by updating the PKH in the nftCommitment and satoshiValue.\n * capability: Mutable\n * category: registryInputCategory\n * tokenAmount: Represents the registrationId\n * satoshiValue: Represents the bid amount\n * commitment: new Bidder's PKH (20 bytes) + name (bytes)\n *\n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: auctionNFT from the Registry contract.\n * - Input3: Funding UTXO from the new bidder.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Updated auctionNFT back to the Registry contract.\n * - Output3: Previous bid amount to the previous bidder.\n * - Output4: Optional change in BCH to the new bidder.\n */\n function call() {\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 5);\n \n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode); \n\n // AuctionNFT should keep the same category and capability.\n require(tx.inputs[2].tokenCategory == tx.outputs[2].tokenCategory);\n\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n // The second part of the pair changes with each new bid, hence it's marked as mutable.\n // Enforcing the structure of the pair results in predictable behavior.\n bytes auctionCategory, bytes auctionCapability = tx.outputs[2].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n require(auctionCapability == 0x01); // Mutable\n\n // Ensure that the funding happens from a P2PKH UTXO because there will be no way to know the locking bytecode as \n // name can be of any length.\n require(tx.inputs[3].lockingBytecode.length == 25);\n bytes pkhLockingBytecodeHead, bytes pkhLockingBytecodeBody = tx.inputs[3].lockingBytecode.split(3);\n // OP_DUP OP_HASH160 Push 20-byte\n require(pkhLockingBytecodeHead == 0x76a914);\n bytes pkh, bytes pkhLockingBytecodeTail = pkhLockingBytecodeBody.split(20);\n // OP_EQUALVERIFY OP_CHECKSIG\n require(pkhLockingBytecodeTail == 0x88ac);\n\n bytes20 previousPKH, bytes name = tx.inputs[2].nftCommitment.split(20);\n\n // AuctionNFT should have updated PKH in it's commitment.\n require(tx.outputs[2].nftCommitment == pkh + name);\n\n // Since tokenAmount is registrationID, make sure that it's not changing.\n require(tx.inputs[2].tokenAmount == tx.outputs[2].tokenAmount);\n\n // Ensure that the bid amount is greater than or equal to the previous bid amount + minBidIncreasePercentage.\n require(tx.outputs[2].value * 100 >= tx.inputs[2].value * (100 + minBidIncreasePercentage));\n\n // Locking bytecode of the previous bidder.\n require(tx.outputs[3].lockingBytecode == new LockingBytecodeP2PKH(previousPKH));\n // The amount being sent back to the previous bidder.\n require(tx.outputs[3].value == tx.inputs[2].value);\n\n if (tx.outputs.length == 5) {\n // If any change, then it must be pure BCH.\n require(tx.outputs[4].tokenCategory == 0x);\n }\n }\n}", 'debug': { - 'bytecode': 'c3549dc455a169c0519dc0c7c0cd8800c752c7788852cd8852ce52d18800ce52d101207f7c7b88518853c7827701199d52cf01147f53c7537f7701147f7552d27c7b7e8852d052d39d52cc01649552c60164547a9395a26953cd0376a9147b7e0288ac7e8853cc52c69dc4559c6354d100886851', - 'sourceMap': '34:12:34:28;:32::33;:4::35:1;35:12:35:29:0;:33::34;:12:::1;:4::36;38:12:38:33:0;:37::38;:4::40:1;39:22:39:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;45:51:45:52:0;:41::69:1;46:22:46:23:0;:12::40:1;:44::72:0;:4::74:1;47:23:47:24:0;:12::41:1;:4::75;50:22:50:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;52:44:52:45:0;:34::60:1;55:64:55:65:0;:53::80:1;:87::89:0;:53::90:1;56:12:56:27:0;:31::52;:4::54:1;57:33:57:37:0;:4::39:1;60:22:60:23:0;:12::40:1;:::47;;:51::53:0;:4::55:1;62:48:62:49:0;:38::64:1;:71::73:0;:38::74:1;65:26:65:27:0;:16::44:1;:51::52:0;:16::53:1;:::56;:63::65:0;:16::66:1;:::69;68:23:68:24:0;:12::39:1;:43::46:0;:49::53;:43:::1;:4::55;71:22:71:23:0;:12::36:1;:51::52:0;:40::65:1;:4::67;74:23:74:24:0;:12::31:1;:34::37:0;:12:::1;:51::52:0;:41::59:1;:63::66:0;:69::93;;:63:::1;:41::94;:12;:4::96;77:23:77:24:0;:12::41:1;:45::82:0;:70::81;:45::82:1;;;:4::84;79:23:79:24:0;:12::31:1;:45::46:0;:35::53:1;:4::55;81:8:81:25:0;:29::30;:8:::1;:32:84:5:0;83:25:83:26;:14::41:1;:45::47:0;:6::49:1;81:32:84:5;33:2:85:3', + 'bytecode': 'c3549dc455a169c0519dc0c7c0cd8800c752c7788852cd8852ce52d18800ce52d101207f7c7b88518853c7827701199d53c7537f7c0376a9148801147f0288ac8852cf01147f52d2537a7b7e8852d052d39d52cc01649552c60164547a9395a26953cd0376a9147b7e0288ac7e8853cc52c69dc4559c6354d100886851', + 'sourceMap': '34:12:34:28;:32::33;:4::35:1;35:12:35:29:0;:33::34;:12:::1;:4::36;38:12:38:33:0;:37::38;:4::40:1;39:22:39:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;45:51:45:52:0;:41::69:1;46:22:46:23:0;:12::40:1;:44::72:0;:4::74:1;47:23:47:24:0;:12::41:1;:4::75;50:22:50:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;52:44:52:45:0;:34::60:1;55:64:55:65:0;:53::80:1;:87::89:0;:53::90:1;56:12:56:27:0;:31::52;:4::54:1;57:33:57:37:0;:4::39:1;61:22:61:23:0;:12::40:1;:::47;;:51::53:0;:4::55:1;62:75:62:76:0;:65::93:1;:100::101:0;:65::102:1;64:12:64:34:0;:38::46;:4::48:1;65:75:65:77:0;:46::78:1;67:38:67:44:0;:4::46:1;69:48:69:49:0;:38::64:1;:71::73:0;:38::74:1;72:23:72:24:0;:12::39:1;:43::46:0;;:49::53;:43:::1;:4::55;75:22:75:23:0;:12::36:1;:51::52:0;:40::65:1;:4::67;78:23:78:24:0;:12::31:1;:34::37:0;:12:::1;:51::52:0;:41::59:1;:63::66:0;:69::93;;:63:::1;:41::94;:12;:4::96;81:23:81:24:0;:12::41:1;:45::82:0;:70::81;:45::82:1;;;:4::84;83:23:83:24:0;:12::31:1;:45::46:0;:35::53:1;:4::55;85:8:85:25:0;:29::30;:8:::1;:32:88:5:0;87:25:87:26;:14::41:1;:45::47:0;:6::49:1;85:32:88:5;33:2:89:3', 'logs': [], 'requires': [ { @@ -57,37 +57,45 @@ export default { }, { 'ip': 46, - 'line': 60, + 'line': 61, }, { - 'ip': 64, - 'line': 68, + 'ip': 53, + 'line': 64, }, { - 'ip': 69, - 'line': 71, + 'ip': 57, + 'line': 67, }, { - 'ip': 82, - 'line': 74, + 'ip': 68, + 'line': 72, }, { - 'ip': 90, - 'line': 77, + 'ip': 73, + 'line': 75, }, { - 'ip': 95, - 'line': 79, + 'ip': 86, + 'line': 78, }, { - 'ip': 103, + 'ip': 94, + 'line': 81, + }, + { + 'ip': 99, 'line': 83, }, + { + 'ip': 107, + 'line': 87, + }, ], }, 'compiler': { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T14:39:11.369Z', + 'updatedAt': '2025-07-27T19:02:16.632Z', }; diff --git a/lib/compiled/ConflictResolver.ts b/lib/compiled/ConflictResolver.ts index 9b1ac55..672c866 100644 --- a/lib/compiled/ConflictResolver.ts +++ b/lib/compiled/ConflictResolver.ts @@ -76,5 +76,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T14:39:12.586Z', + 'updatedAt': '2025-07-27T19:02:17.871Z', }; diff --git a/lib/compiled/Factory.ts b/lib/compiled/Factory.ts index c6c17a0..c29e440 100644 --- a/lib/compiled/Factory.ts +++ b/lib/compiled/Factory.ts @@ -181,5 +181,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T14:39:11.681Z', + 'updatedAt': '2025-07-27T19:02:16.968Z', }; diff --git a/lib/compiled/Name.ts b/lib/compiled/Name.ts index 8532f62..82366cd 100644 --- a/lib/compiled/Name.ts +++ b/lib/compiled/Name.ts @@ -230,5 +230,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T14:39:11.060Z', + 'updatedAt': '2025-07-27T19:02:16.330Z', }; diff --git a/lib/compiled/NameEnforcer.ts b/lib/compiled/NameEnforcer.ts index 7b07205..f6cb172 100644 --- a/lib/compiled/NameEnforcer.ts +++ b/lib/compiled/NameEnforcer.ts @@ -77,5 +77,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T14:39:11.985Z', + 'updatedAt': '2025-07-27T19:02:17.271Z', }; diff --git a/lib/compiled/OwnershipGuard.ts b/lib/compiled/OwnershipGuard.ts index e167e03..ce17c5d 100644 --- a/lib/compiled/OwnershipGuard.ts +++ b/lib/compiled/OwnershipGuard.ts @@ -89,5 +89,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T14:39:12.288Z', + 'updatedAt': '2025-07-27T19:02:17.568Z', }; diff --git a/lib/compiled/Registry.ts b/lib/compiled/Registry.ts index b8b42ea..d798ac7 100644 --- a/lib/compiled/Registry.ts +++ b/lib/compiled/Registry.ts @@ -57,5 +57,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T14:39:10.429Z', + 'updatedAt': '2025-07-27T19:02:15.696Z', }; diff --git a/test/e2e/auction.test.ts b/test/e2e/auction.test.ts index 0a71044..d533b82 100644 --- a/test/e2e/auction.test.ts +++ b/test/e2e/auction.test.ts @@ -163,168 +163,168 @@ describe('Auction', () => expect(txOutputs).toEqual(expect.arrayContaining([{ to: aliceAddress, amount: changeAmount, token: undefined }])); }); - // it('should pass without change output', async () => - // { - // // Construct the transaction using the TransactionBuilder - // transaction = new TransactionBuilder({ provider }) - // .addInput(threadNFTUTXO, registryContract.unlock.call()) - // .addInput(authorizedContractUTXO, auctionContract.unlock.call(nameBin)) - // .addInput(registrationCounterUTXO, registryContract.unlock.call()) - // .addInput(userUTXO, aliceTemplate.unlockP2PKH()) - // .addOutput({ - // to: registryContract.tokenAddress, - // amount: threadNFTUTXO.satoshis, - // token: { - // category: threadNFTUTXO.token!.category, - // amount: threadNFTUTXO.token!.amount, - // nft: { - // capability: threadNFTUTXO.token!.nft!.capability, - // commitment: threadNFTUTXO.token!.nft!.commitment, - // }, - // }, - // }) - // .addOutput({ - // to: auctionContract.tokenAddress, - // amount: authorizedContractUTXO.satoshis, - // }) - // .addOutput({ - // to: registryContract.tokenAddress, - // amount: registrationCounterUTXO.satoshis, - // token: { - // category: registrationCounterUTXO.token!.category, - // amount: registrationCounterUTXO.token!.amount - BigInt(newRegistrationId), - // nft: { - // capability: registrationCounterUTXO.token!.nft!.capability, - // commitment: newRegistrationIdCommitment, - // }, - // }, - // }) - // .addOutput({ - // to: registryContract.tokenAddress, - // amount: BigInt(auctionAmount), - // token: { - // category: registrationCounterUTXO.token!.category, - // amount: BigInt(newRegistrationId), - // nft: { - // capability: 'mutable', - // commitment: binToHex(alicePkh) + binToHex(nameBin), - // }, - // }, - // }) - // .addOpReturnOutput([ name ]); + it('should pass without change output', async () => + { + // Construct the transaction using the TransactionBuilder + transaction = new TransactionBuilder({ provider }) + .addInput(threadNFTUTXO, registryContract.unlock.call()) + .addInput(authorizedContractUTXO, auctionContract.unlock.call(nameBin)) + .addInput(registrationCounterUTXO, registryContract.unlock.call()) + .addInput(userUTXO, aliceTemplate.unlockP2PKH()) + .addOutput({ + to: registryContract.tokenAddress, + amount: threadNFTUTXO.satoshis, + token: { + category: threadNFTUTXO.token!.category, + amount: threadNFTUTXO.token!.amount, + nft: { + capability: threadNFTUTXO.token!.nft!.capability, + commitment: threadNFTUTXO.token!.nft!.commitment, + }, + }, + }) + .addOutput({ + to: auctionContract.tokenAddress, + amount: authorizedContractUTXO.satoshis, + }) + .addOutput({ + to: registryContract.tokenAddress, + amount: registrationCounterUTXO.satoshis, + token: { + category: registrationCounterUTXO.token!.category, + amount: registrationCounterUTXO.token!.amount - BigInt(newRegistrationId), + nft: { + capability: registrationCounterUTXO.token!.nft!.capability, + commitment: newRegistrationIdCommitment, + }, + }, + }) + .addOutput({ + to: registryContract.tokenAddress, + amount: BigInt(auctionAmount), + token: { + category: registrationCounterUTXO.token!.category, + amount: BigInt(newRegistrationId), + nft: { + capability: 'mutable', + commitment: binToHex(alicePkh) + binToHex(nameBin), + }, + }, + }) + .addOpReturnOutput([ name ]); - // const txPromise = await transaction.send(); - // // @ts-ignore - // const txOutputs = getTxOutputs(txPromise); - // expect(txOutputs.length).toBe(5); - // }); + const txPromise = await transaction.send(); + // @ts-ignore + const txOutputs = getTxOutputs(txPromise); + expect(txOutputs.length).toBe(5); + }); - // it('should fail without op return output', async () => - // { - // // Construct the transaction using the TransactionBuilder - // transaction = new TransactionBuilder({ provider }) - // .addInput(threadNFTUTXO, registryContract.unlock.call()) - // .addInput(authorizedContractUTXO, auctionContract.unlock.call(nameBin)) - // .addInput(registrationCounterUTXO, registryContract.unlock.call()) - // .addInput(userUTXO, aliceTemplate.unlockP2PKH()) - // .addOutput({ - // to: registryContract.tokenAddress, - // amount: threadNFTUTXO.satoshis, - // token: { - // category: threadNFTUTXO.token!.category, - // amount: threadNFTUTXO.token!.amount, - // nft: { - // capability: threadNFTUTXO.token!.nft!.capability, - // commitment: threadNFTUTXO.token!.nft!.commitment, - // }, - // }, - // }) - // .addOutput({ - // to: auctionContract.tokenAddress, - // amount: authorizedContractUTXO.satoshis, - // }) - // .addOutput({ - // to: registryContract.tokenAddress, - // amount: registrationCounterUTXO.satoshis, - // token: { - // category: registrationCounterUTXO.token!.category, - // amount: registrationCounterUTXO.token!.amount - BigInt(newRegistrationId), - // nft: { - // capability: registrationCounterUTXO.token!.nft!.capability, - // commitment: newRegistrationIdCommitment, - // }, - // }, - // }) - // .addOutput({ - // to: registryContract.tokenAddress, - // amount: BigInt(auctionAmount), - // token: { - // category: registrationCounterUTXO.token!.category, - // amount: BigInt(newRegistrationId), - // nft: { - // capability: 'mutable', - // commitment: binToHex(alicePkh) + binToHex(nameBin), - // }, - // }, - // }); + it('should fail without op return output', async () => + { + // Construct the transaction using the TransactionBuilder + transaction = new TransactionBuilder({ provider }) + .addInput(threadNFTUTXO, registryContract.unlock.call()) + .addInput(authorizedContractUTXO, auctionContract.unlock.call(nameBin)) + .addInput(registrationCounterUTXO, registryContract.unlock.call()) + .addInput(userUTXO, aliceTemplate.unlockP2PKH()) + .addOutput({ + to: registryContract.tokenAddress, + amount: threadNFTUTXO.satoshis, + token: { + category: threadNFTUTXO.token!.category, + amount: threadNFTUTXO.token!.amount, + nft: { + capability: threadNFTUTXO.token!.nft!.capability, + commitment: threadNFTUTXO.token!.nft!.commitment, + }, + }, + }) + .addOutput({ + to: auctionContract.tokenAddress, + amount: authorizedContractUTXO.satoshis, + }) + .addOutput({ + to: registryContract.tokenAddress, + amount: registrationCounterUTXO.satoshis, + token: { + category: registrationCounterUTXO.token!.category, + amount: registrationCounterUTXO.token!.amount - BigInt(newRegistrationId), + nft: { + capability: registrationCounterUTXO.token!.nft!.capability, + commitment: newRegistrationIdCommitment, + }, + }, + }) + .addOutput({ + to: registryContract.tokenAddress, + amount: BigInt(auctionAmount), + token: { + category: registrationCounterUTXO.token!.category, + amount: BigInt(newRegistrationId), + nft: { + capability: 'mutable', + commitment: binToHex(alicePkh) + binToHex(nameBin), + }, + }, + }); - // const txPromise = transaction.send(); - // await expect(txPromise).rejects.toThrow('Auction.cash:113 Error in transaction at input 1 in contract Auction.cash at line 113.'); - // await expect(txPromise).rejects.toThrow('Failing statement: tx.outputs[4].lockingBytecode'); - // }); + const txPromise = transaction.send(); + await expect(txPromise).rejects.toThrow('Auction.cash:117 Error in transaction at input 1 in contract Auction.cash at line 117.'); + await expect(txPromise).rejects.toThrow('Failing statement: tx.outputs[4].lockingBytecode'); + }); - // it('should fail setting auction capability to none', async () => - // { - // // Construct the transaction using the TransactionBuilder - // transaction = new TransactionBuilder({ provider }) - // .addInput(threadNFTUTXO, registryContract.unlock.call()) - // .addInput(authorizedContractUTXO, auctionContract.unlock.call(nameBin)) - // .addInput(registrationCounterUTXO, registryContract.unlock.call()) - // .addInput(userUTXO, aliceTemplate.unlockP2PKH()) - // .addOutput({ - // to: registryContract.tokenAddress, - // amount: threadNFTUTXO.satoshis, - // token: { - // category: threadNFTUTXO.token!.category, - // amount: threadNFTUTXO.token!.amount, - // nft: { - // capability: threadNFTUTXO.token!.nft!.capability, - // commitment: threadNFTUTXO.token!.nft!.commitment, - // }, - // }, - // }) - // .addOutput({ - // to: auctionContract.tokenAddress, - // amount: authorizedContractUTXO.satoshis, - // }) - // .addOutput({ - // to: registryContract.tokenAddress, - // amount: registrationCounterUTXO.satoshis, - // token: { - // category: registrationCounterUTXO.token!.category, - // amount: registrationCounterUTXO.token!.amount - BigInt(newRegistrationId), - // nft: { - // capability: registrationCounterUTXO.token!.nft!.capability, - // commitment: newRegistrationIdCommitment, - // }, - // }, - // }) - // .addOutput({ - // to: registryContract.tokenAddress, - // amount: BigInt(auctionAmount), - // token: { - // category: registrationCounterUTXO.token!.category, - // amount: BigInt(newRegistrationId), - // nft: { - // capability: 'none', - // commitment: binToHex(alicePkh) + binToHex(nameBin), - // }, - // }, - // }) - // .addOpReturnOutput([ name ]); + it('should fail setting auction capability to none', async () => + { + // Construct the transaction using the TransactionBuilder + transaction = new TransactionBuilder({ provider }) + .addInput(threadNFTUTXO, registryContract.unlock.call()) + .addInput(authorizedContractUTXO, auctionContract.unlock.call(nameBin)) + .addInput(registrationCounterUTXO, registryContract.unlock.call()) + .addInput(userUTXO, aliceTemplate.unlockP2PKH()) + .addOutput({ + to: registryContract.tokenAddress, + amount: threadNFTUTXO.satoshis, + token: { + category: threadNFTUTXO.token!.category, + amount: threadNFTUTXO.token!.amount, + nft: { + capability: threadNFTUTXO.token!.nft!.capability, + commitment: threadNFTUTXO.token!.nft!.commitment, + }, + }, + }) + .addOutput({ + to: auctionContract.tokenAddress, + amount: authorizedContractUTXO.satoshis, + }) + .addOutput({ + to: registryContract.tokenAddress, + amount: registrationCounterUTXO.satoshis, + token: { + category: registrationCounterUTXO.token!.category, + amount: registrationCounterUTXO.token!.amount - BigInt(newRegistrationId), + nft: { + capability: registrationCounterUTXO.token!.nft!.capability, + commitment: newRegistrationIdCommitment, + }, + }, + }) + .addOutput({ + to: registryContract.tokenAddress, + amount: BigInt(auctionAmount), + token: { + category: registrationCounterUTXO.token!.category, + amount: BigInt(newRegistrationId), + nft: { + capability: 'none', + commitment: binToHex(alicePkh) + binToHex(nameBin), + }, + }, + }) + .addOpReturnOutput([ name ]); - // const txPromise = transaction.send(); - // await expect(txPromise).rejects.toThrow('Auction.cash:110 Require statement failed at input 1 in contract Auction.cash at line 110.'); - // await expect(txPromise).rejects.toThrow('Failing statement: require(auctionCapability == 0x01);'); - // }); + const txPromise = transaction.send(); + await expect(txPromise).rejects.toThrow('Auction.cash:114 Require statement failed at input 1 in contract Auction.cash at line 114.'); + await expect(txPromise).rejects.toThrow('Failing statement: require(auctionCapability == 0x01);'); + }); }); \ No newline at end of file From add6e596deee351cee3b99c4297a4b8748b95dc4 Mon Sep 17 00:00:00 2001 From: kiok46 Date: Mon, 28 Jul 2025 00:59:59 +0530 Subject: [PATCH 04/35] complete implementation of dual decay --- contracts/Auction.cash | 2 +- contracts/Factory.cash | 13 ++++++++----- lib/compiled/Accumulator.ts | 2 +- lib/compiled/Auction.ts | 4 ++-- lib/compiled/Bid.ts | 2 +- lib/compiled/ConflictResolver.ts | 2 +- lib/compiled/Factory.ts | 22 +++++++++++----------- lib/compiled/Name.ts | 2 +- lib/compiled/NameEnforcer.ts | 2 +- lib/compiled/OwnershipGuard.ts | 2 +- lib/compiled/Registry.ts | 2 +- 11 files changed, 29 insertions(+), 26 deletions(-) diff --git a/contracts/Auction.cash b/contracts/Auction.cash index 407c3b5..6411ddc 100644 --- a/contracts/Auction.cash +++ b/contracts/Auction.cash @@ -64,7 +64,7 @@ contract Auction(int minStartingBid) { // To facilitate higher precisions and since decimals do not exist in VM, we multiply // it by 1e6 (1000000) and name the values as points. - // 1. Decay points (step 0.0003% per step) + // 1. Decay points (0.0003% per step) int decayPoints = minStartingBid * nextRegistrationId * 3; // 2. Get auction price points int currentPricePoints = minStartingBid * 1e6; diff --git a/contracts/Factory.cash b/contracts/Factory.cash index b773989..d16fe25 100644 --- a/contracts/Factory.cash +++ b/contracts/Factory.cash @@ -134,11 +134,14 @@ contract Factory( require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[3].tokenAmount); // Dual Decay mechanism, creator incentive decays linearly with the step. - // 1. Decay percentage to the step 0.001% - int decayPercentageToTheStep = tx.inputs[3].tokenAmount * 1 / 10000; - // 2. Get creator incentive for current step with linear decay - int creatorIncentive = tx.inputs[3].tokenAmount * (1 - decayPercentageToTheStep); - + // 1. Decay points (0.001% per step) + int decayPoints = tx.inputs[3].tokenAmount; + // 2. Get creator incentive points + int creatorIncentivePoints = tx.inputs[3].tokenAmount * 1e5; + // 3. Subtract creator incentive points by decay points to get the current creator incentive. + int creatorIncentive = (creatorIncentivePoints - decayPoints) / 1e5; + + // If incentive is > 20000 satoshis, then it goes to the creator, else it goes to the miners. if(creatorIncentive > 20000) { require(tx.outputs[6].tokenCategory == 0x); // Enforce that the other piece of the fee goes to the miners. diff --git a/lib/compiled/Accumulator.ts b/lib/compiled/Accumulator.ts index d328046..8bb7ab1 100644 --- a/lib/compiled/Accumulator.ts +++ b/lib/compiled/Accumulator.ts @@ -100,5 +100,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T19:02:18.176Z', + 'updatedAt': '2025-07-27T19:29:29.806Z', }; diff --git a/lib/compiled/Auction.ts b/lib/compiled/Auction.ts index 4d14d4a..9a507f9 100644 --- a/lib/compiled/Auction.ts +++ b/lib/compiled/Auction.ts @@ -18,7 +18,7 @@ export default { }, ], 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_BIN2NUM OP_2 OP_OUTPUTTOKENCOMMITMENT OP_BIN2NUM OP_DUP OP_ROT OP_1ADD OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_2 OP_PICK OP_SUB OP_NUMEQUALVERIFY OP_3 OP_OUTPUTTOKENAMOUNT OP_OVER OP_NUMEQUALVERIFY OP_OVER OP_SWAP OP_MUL OP_3 OP_MUL OP_SWAP 40420f OP_MUL OP_SWAP OP_SUB 40420f OP_DIV OP_DUP 204e OP_MAX OP_3 OP_OUTPUTVALUE OP_LESSTHANOREQUAL OP_VERIFY OP_3 OP_UTXOTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_SIZE OP_NIP 19 OP_NUMEQUALVERIFY OP_3 OP_UTXOBYTECODE OP_3 OP_SPLIT OP_SWAP 76a914 OP_EQUALVERIFY 14 OP_SPLIT 88ac OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_3 OP_PICK OP_CAT OP_EQUALVERIFY OP_OVER OP_SIZE OP_NIP OP_16 OP_LESSTHANOREQUAL OP_VERIFY OP_2 OP_OUTPUTTOKENCATEGORY OP_2 OP_UTXOTOKENCATEGORY OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_ROT OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_4 OP_OUTPUTBYTECODE 6a OP_3 OP_ROLL OP_SIZE OP_DUP 4b OP_GREATERTHAN OP_IF 4c OP_SWAP OP_CAT OP_ENDIF OP_SWAP OP_CAT OP_CAT OP_EQUALVERIFY OP_TXOUTPUTCOUNT OP_6 OP_NUMEQUAL OP_IF OP_5 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_ENDIF OP_DROP OP_1', - 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param minStartingBid The minimum starting bid for the auction.\n */\ncontract Auction(int minStartingBid) {\n /**\n * Starts a new name registration auction.\n * @param name The name being registered.\n * \n * The function creates a new auction with:\n * - Starting bid >= `minStartingBid` BCH.\n * - A successful registration initiation results in an auctionNFT representing the auction state:\n * - capability: (Mutable)\n * - category: registryInputCategory\n * - tokenAmount: (Represents the registrationId)\n * - satoshiValue: (Represents the bid amount)\n * - commitment: bidder's PKH (20 bytes) + name (bytes)\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: Minting CounterNFT from Registry contract (Increases the registrationId by 1 in the output).\n * - Input3: Funding UTXO.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Minting CounterNFT going back to the Registry contract.\n * - Output3: auctionNFT to the Registry contract.\n * - Output4: OP_RETURN output containing the name.\n * - Output5: Optional change in BCH.\n */\n function call(bytes name) {\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 6);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the nameCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[3].lockingBytecode == registryInputLockingBytecode);\n\n // Registration ID increases by 1 with each transaction.\n int prevRegistrationId = int(tx.inputs[2].nftCommitment);\n int nextRegistrationId = int(tx.outputs[2].nftCommitment);\n require(nextRegistrationId == prevRegistrationId + 1);\n\n // Reduce the tokenAmount in the counterNFT as some amount is going to auctionNFT\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount - nextRegistrationId);\n // tokenAmount in the auctionNFT is the registrationId.\n require(tx.outputs[3].tokenAmount == nextRegistrationId);\n\n // Dual Decay mechanism, auction price decays linearly with the step.\n // To facilitate higher precisions and since decimals do not exist in VM, we multiply\n // it by 1e6 (1000000) and name the values as points.\n\n // 1. Decay points (step 0.0003% per step)\n int decayPoints = minStartingBid * nextRegistrationId * 3;\n // 2. Get auction price points\n int currentPricePoints = minStartingBid * 1e6;\n // 3. Subtract price points by decay points to get the current auction price.\n int currentAuctionPrice = (currentPricePoints - decayPoints) / 1e6;\n\n // Set the minimum auction price to 20000 satoshis.\n currentAuctionPrice = max(currentAuctionPrice, 20000);\n\n // Every auction begins with a min base value of at least currentAuctionPrice satoshis.\n require(tx.outputs[3].value >= currentAuctionPrice);\n // Funding UTXO/ Bid UTXO\n require(tx.inputs[3].tokenCategory == 0x);\n\n // Ensure that the funding happens from a P2PKH UTXO because there will be no way to know the locking bytecode as \n // name can be of any length.\n require(tx.inputs[3].lockingBytecode.length == 25);\n\n bytes pkhLockingBytecodeHead, bytes pkhLockingBytecodeBody = tx.inputs[3].lockingBytecode.split(3);\n // OP_DUP OP_HASH160 Push 20-byte\n require(pkhLockingBytecodeHead == 0x76a914);\n bytes pkh, bytes pkhLockingBytecodeTail = pkhLockingBytecodeBody.split(20);\n // OP_EQUALVERIFY OP_CHECKSIG\n require(pkhLockingBytecodeTail == 0x88ac);\n require(tx.outputs[3].nftCommitment == pkh + name);\n\n // Ensure that the name is not too long, as of 2025 upgrade, the nftcommitment is 40 bytes.\n // 20 bytes pkh + 16 bytes name + 4 bytes TLD\n require(name.length <= 16);\n\n // CounterNFT should keep the same category and capability.\n require(tx.outputs[2].tokenCategory == tx.inputs[2].tokenCategory);\n \n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n \n // CounterNFT should be minting and of the 'nameCategory' i.e registryInputCategory\n bytes counterCategory, bytes counterCapability = tx.outputs[2].tokenCategory.split(32);\n require(counterCategory == registryInputCategory);\n // Minting\n require(counterCapability == 0x02);\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.outputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n // Mutable\n require(auctionCapability == 0x01);\n\n // Enforce an OP_RETURN output that contains the name.\n require(tx.outputs[4].lockingBytecode == new LockingBytecodeNullData([name]));\n\n if (tx.outputs.length == 6) {\n // If any change, then it must be pure BCH.\n require(tx.outputs[5].tokenCategory == 0x);\n }\n }\n}", + 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param minStartingBid The minimum starting bid for the auction.\n */\ncontract Auction(int minStartingBid) {\n /**\n * Starts a new name registration auction.\n * @param name The name being registered.\n * \n * The function creates a new auction with:\n * - Starting bid >= `minStartingBid` BCH.\n * - A successful registration initiation results in an auctionNFT representing the auction state:\n * - capability: (Mutable)\n * - category: registryInputCategory\n * - tokenAmount: (Represents the registrationId)\n * - satoshiValue: (Represents the bid amount)\n * - commitment: bidder's PKH (20 bytes) + name (bytes)\n * \n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract.\n * - Input2: Minting CounterNFT from Registry contract (Increases the registrationId by 1 in the output).\n * - Input3: Funding UTXO.\n * \n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change.\n * - Output2: Minting CounterNFT going back to the Registry contract.\n * - Output3: auctionNFT to the Registry contract.\n * - Output4: OP_RETURN output containing the name.\n * - Output5: Optional change in BCH.\n */\n function call(bytes name) {\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 6);\n\n // This contract can only be used at input1 and it should return the input1 back to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the nameCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[3].lockingBytecode == registryInputLockingBytecode);\n\n // Registration ID increases by 1 with each transaction.\n int prevRegistrationId = int(tx.inputs[2].nftCommitment);\n int nextRegistrationId = int(tx.outputs[2].nftCommitment);\n require(nextRegistrationId == prevRegistrationId + 1);\n\n // Reduce the tokenAmount in the counterNFT as some amount is going to auctionNFT\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount - nextRegistrationId);\n // tokenAmount in the auctionNFT is the registrationId.\n require(tx.outputs[3].tokenAmount == nextRegistrationId);\n\n // Dual Decay mechanism, auction price decays linearly with the step.\n // To facilitate higher precisions and since decimals do not exist in VM, we multiply\n // it by 1e6 (1000000) and name the values as points.\n\n // 1. Decay points (0.0003% per step)\n int decayPoints = minStartingBid * nextRegistrationId * 3;\n // 2. Get auction price points\n int currentPricePoints = minStartingBid * 1e6;\n // 3. Subtract price points by decay points to get the current auction price.\n int currentAuctionPrice = (currentPricePoints - decayPoints) / 1e6;\n\n // Set the minimum auction price to 20000 satoshis.\n currentAuctionPrice = max(currentAuctionPrice, 20000);\n\n // Every auction begins with a min base value of at least currentAuctionPrice satoshis.\n require(tx.outputs[3].value >= currentAuctionPrice);\n // Funding UTXO/ Bid UTXO\n require(tx.inputs[3].tokenCategory == 0x);\n\n // Ensure that the funding happens from a P2PKH UTXO because there will be no way to know the locking bytecode as \n // name can be of any length.\n require(tx.inputs[3].lockingBytecode.length == 25);\n\n bytes pkhLockingBytecodeHead, bytes pkhLockingBytecodeBody = tx.inputs[3].lockingBytecode.split(3);\n // OP_DUP OP_HASH160 Push 20-byte\n require(pkhLockingBytecodeHead == 0x76a914);\n bytes pkh, bytes pkhLockingBytecodeTail = pkhLockingBytecodeBody.split(20);\n // OP_EQUALVERIFY OP_CHECKSIG\n require(pkhLockingBytecodeTail == 0x88ac);\n require(tx.outputs[3].nftCommitment == pkh + name);\n\n // Ensure that the name is not too long, as of 2025 upgrade, the nftcommitment is 40 bytes.\n // 20 bytes pkh + 16 bytes name + 4 bytes TLD\n require(name.length <= 16);\n\n // CounterNFT should keep the same category and capability.\n require(tx.outputs[2].tokenCategory == tx.inputs[2].tokenCategory);\n \n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n \n // CounterNFT should be minting and of the 'nameCategory' i.e registryInputCategory\n bytes counterCategory, bytes counterCapability = tx.outputs[2].tokenCategory.split(32);\n require(counterCategory == registryInputCategory);\n // Minting\n require(counterCapability == 0x02);\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.outputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n // Mutable\n require(auctionCapability == 0x01);\n\n // Enforce an OP_RETURN output that contains the name.\n require(tx.outputs[4].lockingBytecode == new LockingBytecodeNullData([name]));\n\n if (tx.outputs.length == 6) {\n // If any change, then it must be pure BCH.\n require(tx.outputs[5].tokenCategory == 0x);\n }\n }\n}", 'debug': { 'bytecode': 'c3549dc456a169c0519dc0c7c0cd88c0d1008800c752c7788852cd788853cd8852cf8152d281767b8b9d52d352d05279949d53d3789d787c9553957c0340420f957c940340420f967602204ea453cca16953ce008853c7827701199d53c7537f7c0376a9148801147f0288ac8853d27c53797e8878827760a16952d152ce8800ce52d101207f7c527988528853d101207f7c7b88518854cd016a537a8276014ba063014c7c7e687c7e7e88c4569c6355d10088687551', 'sourceMap': '35:12:35:28;:32::33;:4::35:1;36:12:36:29:0;:33::34;:12:::1;:4::36;39:12:39:33:0;:37::38;:4::40:1;40:22:40:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;42:23:42:44:0;:12::59:1;:63::65:0;:4::67:1;48:51:48:52:0;:41::69:1;49:22:49:23:0;:12::40:1;:44::72:0;:4::74:1;50:23:50:24:0;:12::41:1;:45::73:0;:4::75:1;51:23:51:24:0;:12::41:1;:4::75;54:43:54:44:0;:33::59:1;:29::60;55:44:55:45:0;:33::60:1;:29::61;56:12:56:30:0;:34::52;:::56:1;:4::58;59:23:59:24:0;:12::37:1;:51::52:0;:41::65:1;:68::86:0;;:41:::1;:4::88;61:23:61:24:0;:12::37:1;:41::59:0;:4::61:1;68:22:68:36:0;:39::57;:22:::1;:60::61:0;:22:::1;70:29:70:43:0;:46::49;:29:::1;72:52:72:63:0;:31:::1;:67::70:0;:30:::1;75::75:49:0;:51::56;:26::57:1;78:23:78:24:0;:12::31:1;:::54;:4::56;80:22:80:23:0;:12::38:1;:42::44:0;:4::46:1;84:22:84:23:0;:12::40:1;:::47;;:51::53:0;:4::55:1;86:75:86:76:0;:65::93:1;:100::101:0;:65::102:1;88:12:88:34:0;:38::46;:4::48:1;89:75:89:77:0;:46::78:1;91:38:91:44:0;:4::46:1;92:23:92:24:0;:12::39:1;:43::46:0;:49::53;;:43:::1;:4::55;96:12:96:16:0;:::23:1;;:27::29:0;:12:::1;:4::31;99:23:99:24:0;:12::39:1;:53::54:0;:43::69:1;:4::71;102:44:102:45:0;:34::60:1;105:64:105:65:0;:53::80:1;:87::89:0;:53::90:1;106:12:106:27:0;:31::52;;:4::54:1;108:33:108:37:0;:4::39:1;111:64:111:65:0;:53::80:1;:87::89:0;:53::90:1;112:12:112:27:0;:31::52;:4::54:1;114:33:114:37:0;:4::39:1;117:23:117:24:0;:12::41:1;:45::80:0;:74::78;;::::1;;;;;;;;;;;;:4::82;119:8:119:25:0;:29::30;:8:::1;:32:122:5:0;121:25:121:26;:14::41:1;:45::47:0;:6::49:1;119:32:122:5;34:2:123:3;', @@ -130,5 +130,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T19:02:16.007Z', + 'updatedAt': '2025-07-27T19:29:27.466Z', }; diff --git a/lib/compiled/Bid.ts b/lib/compiled/Bid.ts index e6c48fc..f5fe348 100644 --- a/lib/compiled/Bid.ts +++ b/lib/compiled/Bid.ts @@ -97,5 +97,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T19:02:16.632Z', + 'updatedAt': '2025-07-27T19:29:28.289Z', }; diff --git a/lib/compiled/ConflictResolver.ts b/lib/compiled/ConflictResolver.ts index 672c866..86ef52e 100644 --- a/lib/compiled/ConflictResolver.ts +++ b/lib/compiled/ConflictResolver.ts @@ -76,5 +76,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T19:02:17.871Z', + 'updatedAt': '2025-07-27T19:29:29.511Z', }; diff --git a/lib/compiled/Factory.ts b/lib/compiled/Factory.ts index c29e440..ac29991 100644 --- a/lib/compiled/Factory.ts +++ b/lib/compiled/Factory.ts @@ -24,11 +24,11 @@ export default { 'inputs': [], }, ], - 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_7 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOVALUE OP_INPUTINDEX OP_OUTPUTVALUE OP_NUMEQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_3 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_5 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY OP_EQUALVERIFY OP_3 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_2 OP_OUTPUTTOKENCOMMITMENT OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_0 OP_NUMEQUALVERIFY OP_2 OP_OUTPUTVALUE OP_2 OP_UTXOVALUE OP_NUMEQUALVERIFY OP_3 OP_INPUTSEQUENCENUMBER OP_3 OP_ROLL OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_DUP OP_SIZE OP_NIP 20 OP_4 OP_ROLL OP_CAT OP_SWAP OP_CAT OP_OVER OP_CAT OP_5 OP_ROLL OP_CAT OP_3 OP_ROLL OP_CAT OP_HASH256 aa20 OP_SWAP OP_CAT 87 OP_CAT OP_3 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_3 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENAMOUNT OP_8 OP_NUM2BIN OP_4 OP_OUTPUTTOKENCOMMITMENT OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_5 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_ROT OP_CAT OP_EQUALVERIFY OP_5 OP_OUTPUTBYTECODE 76a914 OP_ROT OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_5 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_0 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT OP_ADD OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENAMOUNT OP_1 OP_MUL 1027 OP_DIV OP_3 OP_UTXOTOKENAMOUNT OP_1 OP_ROT OP_SUB OP_MUL OP_DUP 204e OP_GREATERTHAN OP_IF OP_6 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_6 OP_OUTPUTVALUE OP_OVER OP_NUMEQUALVERIFY OP_6 OP_OUTPUTBYTECODE 76a914 OP_3 OP_PICK OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_ENDIF OP_2DROP OP_1', - 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param nameContractBytecode - Partial bytecode of the name contract\n * @param minWaitTime - Minimum wait time to consider an auction ended\n * @param creatorIncentivePKH - PKH of the creator incentive\n * @param tld - TLD of the name\n */\ncontract Factory(\n bytes nameContractBytecode,\n int minWaitTime,\n bytes20 creatorIncentivePKH,\n bytes tld\n) {\n /**\n * This function finalizes a name registration auction by:\n * - Verifying the auction has ended and the winner's bid is valid\n * - Issuing an immutable externalAuthNFT to the Name Contract\n * - Issuing an immutable internalAuthNFT to the Name Contract\n * - Issuing an immutable name NFT to the auction winner\n * - Distributing auction fees between the platform and miners\n * - Burning the auctionNFT\n * - Pure BCH input from bidder is used to prevent miners from taking away the funds from any or all transactions in the future.\n * Out of many possible ways, this method will be suitable to easily implement by applications.\n *\n *\n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract\n * - Input2: NameMintingNFT from the Registry Contract\n * - Input3: auctionNFT from the Registry Contract\n *\n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change\n * - Output2: NameMintingNFT back to the Registry contract\n * - Output3: External Auth NFT to the name contract\n * - Output4: Internal Auth NFT to the name contract\n * - Output5: Name NFT to the auction winner\n * - Output6: Platform fee [Reduces and the not included]\n *\n */\n function call(){\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 7);\n\n // This contract can only be used at input1 and it should return to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the nameCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n // Strict value checks to ensure the platform and miner get fee.\n require(tx.inputs[this.activeInputIndex].value == tx.outputs[this.activeInputIndex].value);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.inputs[3].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n\n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n require(tx.outputs[3].tokenCategory == registryInputCategory);\n require(tx.outputs[4].tokenCategory == registryInputCategory);\n require(tx.outputs[5].tokenCategory == registryInputCategory);\n\n // NameMintingNFT should be minting and of the 'nameCategory' i.e registryInputCategory\n bytes nameMintingCategory, bytes nameMintingCapability = tx.inputs[2].tokenCategory.split(32);\n require(nameMintingCategory == registryInputCategory);\n // Minting\n require(nameMintingCapability == 0x02);\n // NameMintingNFT should keep the same category and capability\n require(tx.inputs[2].tokenCategory == tx.outputs[2].tokenCategory);\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.inputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n // Mutable\n require(auctionCapability == 0x01);\n\n // Enforce strict restrictions on NameMintingNFT\n require(tx.inputs[2].nftCommitment == tx.outputs[2].nftCommitment);\n // NameMintingNFT has no nftCommitment\n require(tx.outputs[2].nftCommitment == 0x);\n // NameMintingNFT has no tokenAmount\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount);\n require(tx.outputs[2].tokenAmount == 0);\n\n // Strict value check\n require(tx.outputs[2].value == tx.inputs[2].value);\n\n // Enforcing the relative timelock, the auctionNFT must be atleast `minWaitTime` old\n // to be considered ended.\n require(tx.inputs[3].sequenceNumber == minWaitTime);\n\n // Extract the PKH and name from the auctionNFT\n bytes20 bidderPKH, bytes name = tx.inputs[3].nftCommitment.split(20);\n \n // Get the name length to generate the complete bytecode of the name contract\n int nameLength = name.length;\n // category + name + bytecode.\n // Note: `inactivityExpiryTime` in the name is already added to the nameContractBytecode in the constructor.\n bytes nameBytecode = 0x20 + registryInputCategory + bytes(nameLength) + name + tld + nameContractBytecode;\n bytes32 scriptHash = hash256(nameBytecode);\n bytes35 nameLockingBytecode = new LockingBytecodeP2SH32(scriptHash);\n \n // ExternalAuthNFT goes to the name contract\n require(tx.outputs[3].lockingBytecode == nameLockingBytecode);\n // InternalAuthNFT goes to the name contract\n require(tx.outputs[4].lockingBytecode == nameLockingBytecode);\n \n // ExternalAuthNFT does not have any commitment\n require(tx.outputs[3].nftCommitment == 0x);\n // Strict value check\n require(tx.outputs[3].value == 1000);\n\n // InternalAuthNFT has registrationID as the commitment so it can be used to authenticate\n // along with the ownershipNFT\n bytes8 registrationId = bytes8(tx.inputs[3].tokenAmount);\n require(tx.outputs[4].nftCommitment == registrationId);\n // Strict value check\n require(tx.outputs[4].value == 1000);\n\n // Send the name ownership NFT to the bidder\n require(tx.outputs[5].nftCommitment == registrationId + name);\n require(tx.outputs[5].lockingBytecode == new LockingBytecodeP2PKH(bidderPKH));\n require(tx.outputs[5].value == 1000);\n\n // tokenAmount from the auctionNFT goes to the authorizedThreadNFT to be accumulated later\n // and merged back with the CounterNFT using the `Accumulator` Contract\n require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[3].tokenAmount);\n\n // Dual Decay mechanism, creator incentive decays linearly with the step.\n // 1. Decay percentage to the step 0.001%\n int decayPercentageToTheStep = tx.inputs[3].tokenAmount * 1 / 10000;\n // 2. Get creator incentive for current step with linear decay\n int creatorIncentive = tx.inputs[3].tokenAmount * (1 - decayPercentageToTheStep);\n\n if(creatorIncentive > 20000) {\n require(tx.outputs[6].tokenCategory == 0x);\n // Enforce that the other piece of the fee goes to the miners.\n require(tx.outputs[6].value == creatorIncentive);\n require(tx.outputs[6].lockingBytecode == new LockingBytecodeP2PKH(creatorIncentivePKH));\n }\n }\n\n}", + 'bytecode': 'OP_TXINPUTCOUNT OP_4 OP_NUMEQUALVERIFY OP_TXOUTPUTCOUNT OP_7 OP_LESSTHANOREQUAL OP_VERIFY OP_INPUTINDEX OP_1 OP_NUMEQUALVERIFY OP_INPUTINDEX OP_UTXOBYTECODE OP_INPUTINDEX OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_INPUTINDEX OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_INPUTINDEX OP_UTXOVALUE OP_INPUTINDEX OP_OUTPUTVALUE OP_NUMEQUALVERIFY OP_0 OP_UTXOBYTECODE OP_2 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_3 OP_UTXOBYTECODE OP_OVER OP_EQUALVERIFY OP_2 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_0 OP_UTXOTOKENCATEGORY OP_3 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_5 OP_OUTPUTTOKENCATEGORY OP_OVER OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_2 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCATEGORY OP_2 OP_OUTPUTTOKENCATEGORY OP_EQUALVERIFY OP_3 OP_UTXOTOKENCATEGORY 20 OP_SPLIT OP_SWAP OP_2 OP_PICK OP_EQUALVERIFY OP_1 OP_EQUALVERIFY OP_2 OP_UTXOTOKENCOMMITMENT OP_2 OP_OUTPUTTOKENCOMMITMENT OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_2 OP_UTXOTOKENAMOUNT OP_NUMEQUALVERIFY OP_2 OP_OUTPUTTOKENAMOUNT OP_0 OP_NUMEQUALVERIFY OP_2 OP_OUTPUTVALUE OP_2 OP_UTXOVALUE OP_NUMEQUALVERIFY OP_3 OP_INPUTSEQUENCENUMBER OP_3 OP_ROLL OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENCOMMITMENT 14 OP_SPLIT OP_DUP OP_SIZE OP_NIP 20 OP_4 OP_ROLL OP_CAT OP_SWAP OP_CAT OP_OVER OP_CAT OP_5 OP_ROLL OP_CAT OP_3 OP_ROLL OP_CAT OP_HASH256 aa20 OP_SWAP OP_CAT 87 OP_CAT OP_3 OP_OUTPUTBYTECODE OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTBYTECODE OP_EQUALVERIFY OP_3 OP_OUTPUTTOKENCOMMITMENT OP_0 OP_EQUALVERIFY OP_3 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENAMOUNT OP_8 OP_NUM2BIN OP_4 OP_OUTPUTTOKENCOMMITMENT OP_OVER OP_EQUALVERIFY OP_4 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_5 OP_OUTPUTTOKENCOMMITMENT OP_SWAP OP_ROT OP_CAT OP_EQUALVERIFY OP_5 OP_OUTPUTBYTECODE 76a914 OP_ROT OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_5 OP_OUTPUTVALUE e803 OP_NUMEQUALVERIFY OP_0 OP_OUTPUTTOKENAMOUNT OP_0 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT OP_ADD OP_NUMEQUALVERIFY OP_3 OP_UTXOTOKENAMOUNT OP_3 OP_UTXOTOKENAMOUNT a08601 OP_MUL OP_SWAP OP_SUB a08601 OP_DIV OP_DUP 204e OP_GREATERTHAN OP_IF OP_6 OP_OUTPUTTOKENCATEGORY OP_0 OP_EQUALVERIFY OP_6 OP_OUTPUTVALUE OP_OVER OP_NUMEQUALVERIFY OP_6 OP_OUTPUTBYTECODE 76a914 OP_3 OP_PICK OP_CAT 88ac OP_CAT OP_EQUALVERIFY OP_ENDIF OP_2DROP OP_1', + 'source': "pragma cashscript 0.11.3;\n\n/**\n * @param nameContractBytecode - Partial bytecode of the name contract\n * @param minWaitTime - Minimum wait time to consider an auction ended\n * @param creatorIncentivePKH - PKH of the creator incentive\n * @param tld - TLD of the name\n */\ncontract Factory(\n bytes nameContractBytecode,\n int minWaitTime,\n bytes20 creatorIncentivePKH,\n bytes tld\n) {\n /**\n * This function finalizes a name registration auction by:\n * - Verifying the auction has ended and the winner's bid is valid\n * - Issuing an immutable externalAuthNFT to the Name Contract\n * - Issuing an immutable internalAuthNFT to the Name Contract\n * - Issuing an immutable name NFT to the auction winner\n * - Distributing auction fees between the platform and miners\n * - Burning the auctionNFT\n * - Pure BCH input from bidder is used to prevent miners from taking away the funds from any or all transactions in the future.\n * Out of many possible ways, this method will be suitable to easily implement by applications.\n *\n *\n * @inputs\n * - Input0: Registry Contract's authorizedThreadNFT i.e immutable NFT with commitment that has the lockingBytecode of this contract\n * - Input1: Any input from this contract\n * - Input2: NameMintingNFT from the Registry Contract\n * - Input3: auctionNFT from the Registry Contract\n *\n * @outputs\n * - Output0: Registry Contract's authorizedThreadNFT back to the Registry contract.\n * - Output1: Input1 back to this contract without any change\n * - Output2: NameMintingNFT back to the Registry contract\n * - Output3: External Auth NFT to the name contract\n * - Output4: Internal Auth NFT to the name contract\n * - Output5: Name NFT to the auction winner\n * - Output6: Platform fee [Reduces and the not included]\n *\n */\n function call(){\n require(tx.inputs.length == 4);\n require(tx.outputs.length <= 7);\n\n // This contract can only be used at input1 and it should return to itself.\n require(this.activeInputIndex == 1);\n require(tx.inputs[this.activeInputIndex].lockingBytecode == tx.outputs[this.activeInputIndex].lockingBytecode);\n // Ensure that the nameCategory in not minted here.\n require(tx.outputs[this.activeInputIndex].tokenCategory == 0x);\n // Strict value checks to ensure the platform and miner get fee.\n require(tx.inputs[this.activeInputIndex].value == tx.outputs[this.activeInputIndex].value);\n\n // This contract can only be used with the 'lockingbytecode' used in the 0th input.\n // Note: This contract can be used with any contract that fulfills these conditions, and that is fine\n // because those contracts will not be manipulating the utxos of the Registry contract. Instead, they will\n // be manipulating their own utxos.\n bytes registryInputLockingBytecode = tx.inputs[0].lockingBytecode;\n require(tx.inputs[2].lockingBytecode == registryInputLockingBytecode);\n require(tx.inputs[3].lockingBytecode == registryInputLockingBytecode);\n require(tx.outputs[2].lockingBytecode == registryInputLockingBytecode);\n\n // All the token categories in the transaction should be the same.\n bytes registryInputCategory = tx.inputs[0].tokenCategory;\n require(tx.outputs[3].tokenCategory == registryInputCategory);\n require(tx.outputs[4].tokenCategory == registryInputCategory);\n require(tx.outputs[5].tokenCategory == registryInputCategory);\n\n // NameMintingNFT should be minting and of the 'nameCategory' i.e registryInputCategory\n bytes nameMintingCategory, bytes nameMintingCapability = tx.inputs[2].tokenCategory.split(32);\n require(nameMintingCategory == registryInputCategory);\n // Minting\n require(nameMintingCapability == 0x02);\n // NameMintingNFT should keep the same category and capability\n require(tx.inputs[2].tokenCategory == tx.outputs[2].tokenCategory);\n\n // AuctionNFT should be mutable and of the 'nameCategory' i.e registryInputCategory\n bytes auctionCategory, bytes auctionCapability = tx.inputs[3].tokenCategory.split(32);\n require(auctionCategory == registryInputCategory);\n // Mutable\n require(auctionCapability == 0x01);\n\n // Enforce strict restrictions on NameMintingNFT\n require(tx.inputs[2].nftCommitment == tx.outputs[2].nftCommitment);\n // NameMintingNFT has no nftCommitment\n require(tx.outputs[2].nftCommitment == 0x);\n // NameMintingNFT has no tokenAmount\n require(tx.outputs[2].tokenAmount == tx.inputs[2].tokenAmount);\n require(tx.outputs[2].tokenAmount == 0);\n\n // Strict value check\n require(tx.outputs[2].value == tx.inputs[2].value);\n\n // Enforcing the relative timelock, the auctionNFT must be atleast `minWaitTime` old\n // to be considered ended.\n require(tx.inputs[3].sequenceNumber == minWaitTime);\n\n // Extract the PKH and name from the auctionNFT\n bytes20 bidderPKH, bytes name = tx.inputs[3].nftCommitment.split(20);\n \n // Get the name length to generate the complete bytecode of the name contract\n int nameLength = name.length;\n // category + name + bytecode.\n // Note: `inactivityExpiryTime` in the name is already added to the nameContractBytecode in the constructor.\n bytes nameBytecode = 0x20 + registryInputCategory + bytes(nameLength) + name + tld + nameContractBytecode;\n bytes32 scriptHash = hash256(nameBytecode);\n bytes35 nameLockingBytecode = new LockingBytecodeP2SH32(scriptHash);\n \n // ExternalAuthNFT goes to the name contract\n require(tx.outputs[3].lockingBytecode == nameLockingBytecode);\n // InternalAuthNFT goes to the name contract\n require(tx.outputs[4].lockingBytecode == nameLockingBytecode);\n \n // ExternalAuthNFT does not have any commitment\n require(tx.outputs[3].nftCommitment == 0x);\n // Strict value check\n require(tx.outputs[3].value == 1000);\n\n // InternalAuthNFT has registrationID as the commitment so it can be used to authenticate\n // along with the ownershipNFT\n bytes8 registrationId = bytes8(tx.inputs[3].tokenAmount);\n require(tx.outputs[4].nftCommitment == registrationId);\n // Strict value check\n require(tx.outputs[4].value == 1000);\n\n // Send the name ownership NFT to the bidder\n require(tx.outputs[5].nftCommitment == registrationId + name);\n require(tx.outputs[5].lockingBytecode == new LockingBytecodeP2PKH(bidderPKH));\n require(tx.outputs[5].value == 1000);\n\n // tokenAmount from the auctionNFT goes to the authorizedThreadNFT to be accumulated later\n // and merged back with the CounterNFT using the `Accumulator` Contract\n require(tx.outputs[0].tokenAmount == tx.inputs[0].tokenAmount + tx.inputs[3].tokenAmount);\n\n // Dual Decay mechanism, creator incentive decays linearly with the step.\n // 1. Decay points (0.001% per step)\n int decayPoints = tx.inputs[3].tokenAmount;\n // 2. Get creator incentive points\n int creatorIncentivePoints = tx.inputs[3].tokenAmount * 1e5;\n // 3. Subtract creator incentive points by decay points to get the current creator incentive.\n int creatorIncentive = (creatorIncentivePoints - decayPoints) / 1e5;\n\n // If incentive is > 20000 satoshis, then it goes to the creator, else it goes to the miners.\n if(creatorIncentive > 20000) {\n require(tx.outputs[6].tokenCategory == 0x);\n // Enforce that the other piece of the fee goes to the miners.\n require(tx.outputs[6].value == creatorIncentive);\n require(tx.outputs[6].lockingBytecode == new LockingBytecodeP2PKH(creatorIncentivePKH));\n }\n }\n\n}", 'debug': { - 'bytecode': 'c3549dc457a169c0519dc0c7c0cd88c0d10088c0c6c0cc9d00c752c7788853c7788852cd8800ce53d1788854d1788855d1788852ce01207f7c527988528852ce52d18853ce01207f7c527988518852cf52d28852d2008852d352d09d52d3009d52cc52c69d53cb537a9d53cf01147f7682770120547a7e7c7e787e557a7e537a7eaa02aa207c7e01877e53cd788854cd8853d2008853cc02e8039d53d0588054d2788854cc02e8039d55d27c7b7e8855cd0376a9147b7e0288ac7e8855cc02e8039d00d300d053d0939d53d051950210279653d0517b94957602204ea06356d1008856cc789d56cd0376a91453797e0288ac7e88686d51', - 'sourceMap': '44:12:44:28;:32::33;:4::35:1;45:12:45:29:0;:33::34;:12:::1;:4::36;48:12:48:33:0;:37::38;:4::40:1;49:22:49:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;51:23:51:44:0;:12::59:1;:63::65:0;:4::67:1;53:22:53:43:0;:12::50:1;:65::86:0;:54::93:1;:4::95;59:51:59:52:0;:41::69:1;60:22:60:23:0;:12::40:1;:44::72:0;:4::74:1;61:22:61:23:0;:12::40:1;:44::72:0;:4::74:1;62:23:62:24:0;:12::41:1;:4::75;65:44:65:45:0;:34::60:1;66:23:66:24:0;:12::39:1;:43::64:0;:4::66:1;67:23:67:24:0;:12::39:1;:43::64:0;:4::66:1;68:23:68:24:0;:12::39:1;:43::64:0;:4::66:1;71:71:71:72:0;:61::87:1;:94::96:0;:61::97:1;72:12:72:31:0;:35::56;;:4::58:1;74:37:74:41:0;:4::43:1;76:22:76:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;79:63:79:64:0;:53::79:1;:86::88:0;:53::89:1;80:12:80:27:0;:31::52;;:4::54:1;82:33:82:37:0;:4::39:1;85:22:85:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;87:23:87:24:0;:12::39:1;:43::45:0;:4::47:1;89:23:89:24:0;:12::37:1;:51::52:0;:41::65:1;:4::67;90:23:90:24:0;:12::37:1;:41::42:0;:4::44:1;93:23:93:24:0;:12::31:1;:45::46:0;:35::53:1;:4::55;97:22:97:23:0;:12::39:1;:43::54:0;;:4::56:1;100:46:100:47:0;:36::62:1;:69::71:0;:36::72:1;103:21:103:25:0;:::32:1;;106:25:106:29:0;:32::53;;:25:::1;:62::72:0;:25::73:1;:76::80:0;:25:::1;:83::86:0;;:25:::1;:89::109:0;;:25:::1;107::107:46;108:34:108:71:0;:60::70;:34::71:1;;;111:23:111:24:0;:12::41:1;:45::64:0;:4::66:1;113:23:113:24:0;:12::41:1;:4::66;116:23:116:24:0;:12::39:1;:43::45:0;:4::47:1;118:23:118:24:0;:12::31:1;:35::39:0;:4::41:1;122:45:122:46:0;:35::59:1;:28::60;;123:23:123:24:0;:12::39:1;:43::57:0;:4::59:1;125:23:125:24:0;:12::31:1;:35::39:0;:4::41:1;128:23:128:24:0;:12::39:1;:43::57:0;:60::64;:43:::1;:4::66;129:23:129:24:0;:12::41:1;:45::80:0;:70::79;:45::80:1;;;:4::82;130:23:130:24:0;:12::31:1;:35::39:0;:4::41:1;134:23:134:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;138:45:138:46:0;:35::59:1;:62::63:0;:35:::1;:66::71:0;:35:::1;140:37:140:38:0;:27::51:1;:55::56:0;:59::83;:55:::1;:27::84;142:7:142:23:0;:26::31;:7:::1;:33:147:5:0;143:25:143:26;:14::41:1;:45::47:0;:6::49:1;145:25:145:26:0;:14::33:1;:37::53:0;:6::55:1;146:25:146:26:0;:14::43:1;:47::92:0;:72::91;;:47::92:1;;;:6::94;142:33:147:5;43:2:148:3;', + 'bytecode': 'c3549dc457a169c0519dc0c7c0cd88c0d10088c0c6c0cc9d00c752c7788853c7788852cd8800ce53d1788854d1788855d1788852ce01207f7c527988528852ce52d18853ce01207f7c527988518852cf52d28852d2008852d352d09d52d3009d52cc52c69d53cb537a9d53cf01147f7682770120547a7e7c7e787e557a7e537a7eaa02aa207c7e01877e53cd788854cd8853d2008853cc02e8039d53d0588054d2788854cc02e8039d55d27c7b7e8855cd0376a9147b7e0288ac7e8855cc02e8039d00d300d053d0939d53d053d003a08601957c9403a08601967602204ea06356d1008856cc789d56cd0376a91453797e0288ac7e88686d51', + 'sourceMap': '44:12:44:28;:32::33;:4::35:1;45:12:45:29:0;:33::34;:12:::1;:4::36;48:12:48:33:0;:37::38;:4::40:1;49:22:49:43:0;:12::60:1;:75::96:0;:64::113:1;:4::115;51:23:51:44:0;:12::59:1;:63::65:0;:4::67:1;53:22:53:43:0;:12::50:1;:65::86:0;:54::93:1;:4::95;59:51:59:52:0;:41::69:1;60:22:60:23:0;:12::40:1;:44::72:0;:4::74:1;61:22:61:23:0;:12::40:1;:44::72:0;:4::74:1;62:23:62:24:0;:12::41:1;:4::75;65:44:65:45:0;:34::60:1;66:23:66:24:0;:12::39:1;:43::64:0;:4::66:1;67:23:67:24:0;:12::39:1;:43::64:0;:4::66:1;68:23:68:24:0;:12::39:1;:43::64:0;:4::66:1;71:71:71:72:0;:61::87:1;:94::96:0;:61::97:1;72:12:72:31:0;:35::56;;:4::58:1;74:37:74:41:0;:4::43:1;76:22:76:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;79:63:79:64:0;:53::79:1;:86::88:0;:53::89:1;80:12:80:27:0;:31::52;;:4::54:1;82:33:82:37:0;:4::39:1;85:22:85:23:0;:12::38:1;:53::54:0;:42::69:1;:4::71;87:23:87:24:0;:12::39:1;:43::45:0;:4::47:1;89:23:89:24:0;:12::37:1;:51::52:0;:41::65:1;:4::67;90:23:90:24:0;:12::37:1;:41::42:0;:4::44:1;93:23:93:24:0;:12::31:1;:45::46:0;:35::53:1;:4::55;97:22:97:23:0;:12::39:1;:43::54:0;;:4::56:1;100:46:100:47:0;:36::62:1;:69::71:0;:36::72:1;103:21:103:25:0;:::32:1;;106:25:106:29:0;:32::53;;:25:::1;:62::72:0;:25::73:1;:76::80:0;:25:::1;:83::86:0;;:25:::1;:89::109:0;;:25:::1;107::107:46;108:34:108:71:0;:60::70;:34::71:1;;;111:23:111:24:0;:12::41:1;:45::64:0;:4::66:1;113:23:113:24:0;:12::41:1;:4::66;116:23:116:24:0;:12::39:1;:43::45:0;:4::47:1;118:23:118:24:0;:12::31:1;:35::39:0;:4::41:1;122:45:122:46:0;:35::59:1;:28::60;;123:23:123:24:0;:12::39:1;:43::57:0;:4::59:1;125:23:125:24:0;:12::31:1;:35::39:0;:4::41:1;128:23:128:24:0;:12::39:1;:43::57:0;:60::64;:43:::1;:4::66;129:23:129:24:0;:12::41:1;:45::80:0;:70::79;:45::80:1;;;:4::82;130:23:130:24:0;:12::31:1;:35::39:0;:4::41:1;134:23:134:24:0;:12::37:1;:51::52:0;:41::65:1;:78::79:0;:68::92:1;:41;:4::94;138:32:138:33:0;:22::46:1;140:43:140:44:0;:33::57:1;:60::63:0;:33:::1;142:53:142:64:0;:28:::1;:68::71:0;:27:::1;145:7:145:23:0;:26::31;:7:::1;:33:150:5:0;146:25:146:26;:14::41:1;:45::47:0;:6::49:1;148:25:148:26:0;:14::33:1;:37::53:0;:6::55:1;149:25:149:26:0;:14::43:1;:47::92:0;:72::91;;:47::92:1;;;:6::94;145:33:150:5;43:2:151:3;', 'logs': [], 'requires': [ { @@ -164,16 +164,16 @@ export default { 'line': 134, }, { - 'ip': 207, - 'line': 143, + 'ip': 205, + 'line': 146, }, { - 'ip': 211, - 'line': 145, + 'ip': 209, + 'line': 148, }, { - 'ip': 220, - 'line': 146, + 'ip': 218, + 'line': 149, }, ], }, @@ -181,5 +181,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T19:02:16.968Z', + 'updatedAt': '2025-07-27T19:29:28.612Z', }; diff --git a/lib/compiled/Name.ts b/lib/compiled/Name.ts index 82366cd..7c66da4 100644 --- a/lib/compiled/Name.ts +++ b/lib/compiled/Name.ts @@ -230,5 +230,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T19:02:16.330Z', + 'updatedAt': '2025-07-27T19:29:27.944Z', }; diff --git a/lib/compiled/NameEnforcer.ts b/lib/compiled/NameEnforcer.ts index f6cb172..7b2b01f 100644 --- a/lib/compiled/NameEnforcer.ts +++ b/lib/compiled/NameEnforcer.ts @@ -77,5 +77,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T19:02:17.271Z', + 'updatedAt': '2025-07-27T19:29:28.914Z', }; diff --git a/lib/compiled/OwnershipGuard.ts b/lib/compiled/OwnershipGuard.ts index ce17c5d..c16fc9a 100644 --- a/lib/compiled/OwnershipGuard.ts +++ b/lib/compiled/OwnershipGuard.ts @@ -89,5 +89,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T19:02:17.568Z', + 'updatedAt': '2025-07-27T19:29:29.212Z', }; diff --git a/lib/compiled/Registry.ts b/lib/compiled/Registry.ts index d798ac7..836c71e 100644 --- a/lib/compiled/Registry.ts +++ b/lib/compiled/Registry.ts @@ -57,5 +57,5 @@ export default { 'name': 'cashc', 'version': '0.11.3', }, - 'updatedAt': '2025-07-27T19:02:15.696Z', + 'updatedAt': '2025-07-27T19:29:27.144Z', }; From dbbf6ad1b7a29843bbfd064a9b1aef5e00add3bb Mon Sep 17 00:00:00 2001 From: kiok46 Date: Mon, 28 Jul 2025 20:43:29 +0530 Subject: [PATCH 05/35] Remove OP_RETURN outputs from auction contract, update readme to have the same variable names as the contracts, update documentation in factory contract --- README.md | 179 +++++++++++++++++++++-------------------- architecture.png | Bin 481049 -> 483338 bytes contracts/Auction.cash | 12 +-- contracts/Factory.cash | 3 - 4 files changed, 94 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index 69e276a..703003c 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Bitcoin Cash for Assigned Names and Numbers (BitCANN) Smart Contracts npm downloads

-> ⚠️ Important Notice: The contracts have not undergone extensive auditing by third parties. Users should be aware of potential risks, including the possibility of losing domain ownership or funds during auctions. Exercise caution and consider these risks before use.** +> ⚠️ Important Notice: The contracts have not undergone extensive auditing by third parties. Users should be aware of potential risks, including the possibility of losing name ownership or funds during auctions. Exercise caution and consider these risks before use.** ## Installation @@ -26,26 +26,26 @@ npm install @bitcann/contracts import { BitCANNArtifacts } from '@bitcann/contracts'; // Access contract artifacts -const { Registry, Auction, Domain } = BitCANNArtifacts; +const { Registry, Auction, Name } = BitCANNArtifacts; ``` --- # Documentation -BitCANN - **Bitcoin Cash for Assigned Names and Numbers** – is a decentralized domain name and identity system built on the Bitcoin Cash Blockchain. +BitCANN - **Bitcoin Cash for Assigned Names and Numbers** – is a decentralized name and identity system built on the Bitcoin Cash Blockchain. -- Decentralized Domain Names like `.sat` and `.bch` and more. -- Add Records, RPA Pay Codes, Add Currency Addresses, Text Records, Custom Records, Social, Email, and more. +- Decentralized Names like `.sat` and `.bch` and more. +- Add Records, RPA Pay Codes, Currency Addresses, Text Records, Custom Records, Social, Email, and more. - No Renewals or Expiry* -- NFT Domain ownership, enabling secondary market trading. +- NFT ownership, enabling secondary market trading - Easy lookups - Sign-In using your Identity - Plugin for other contract systems - Earn by protecting the system by: - Burning illegal registration attempts - Identifying and burning registration conflicts - - Proving domain violations + - Proving name violations ## Table of Contents 1. [Contracts](#contracts) @@ -53,18 +53,18 @@ BitCANN - **Bitcoin Cash for Assigned Names and Numbers** – is a decentralized - [Operational Contracts](#operational-contracts) - [Auction](#auction) - [Bid](#bid) - - [DomainFactory](#domainfactory) + - [Factory](#factory) - [Guard Contracts](#guard-contracts) - - [AuctionNameEnforcer](#auctionnameenforcer) - - [DomainOwnershipGuard](#domainownershipguard) - - [AuctionConflictResolver](#auctionconflictresolver) - - [Domain](#domain) + - [NameEnforcer](#nameenforcer) + - [OwnershipGuard](#ownershipguard) + - [ConflictResolver](#conflictresolver) + - [Name](#name) - [Accumulator](#accumulator) 2. [Cashtokens](#cashtokens) - [RegistrationNFTs](#registrationnfts) - [AuctionNFT](#auctionnft) - [AuthorizedThreadNFTs](#authorizedthreadnfts) - - [DomainNFTs](#domainnfts) + - [NameNFTs](#namenfts) 3. [TLDs](#tlds) 4. [Genesis](#genesis) 5. [Dual Decay Mechanism](#dual-decay-mechanism) @@ -73,13 +73,13 @@ BitCANN - **Bitcoin Cash for Assigned Names and Numbers** – is a decentralized - [Can a bid be cancelled?](#can-a-bid-be-cancelled) - [How is any TLD assigned?](#how-is-any-tld-assigned) - [Who earns from the auction sales?](#who-earns-from-the-auction-sales) - - [Can anyone renounce ownership of a domain?](#can-anyone-renounce-ownership-of-a-domain) + - [Can anyone renounce ownership of a name?](#can-anyone-renounce-ownership-of-a-name) - [What occurs during a ownership renouncement event?](#what-occurs-during-a-ownership-renouncement-event) - [How does ownership transfer work?](#how-does-ownership-transfer-work) - [How to records managed?](#how-to-records-managed) - [No Renewal or Expiry?](#no-renewal-or-expiry) - [Why use text-based ownership instead of hash-based ownership?](#why-use-text-based-ownership-instead-of-hash-based-ownership) - - [How do I know I or someone else owns a domain?](#how-do-i-know-i-or-someone-else-owns-a-domain) + - [How do I know I or someone else owns a name?](#how-do-i-know-i-or-someone-else-owns-a-name) - [What if the incentive system is not 100% effective?](#what-if-the-incentive-system-is-not-100-effective) - [What if an invalid name is registered?](#what-if-an-invalid-name-is-registered) @@ -91,11 +91,11 @@ The architecture is built around a series of smart contracts, categorized into t - **Registry Contract**: [Registry.cash](#registry) -- **Operational Contracts**: [Auction.cash](#auction), [Bid.cash](#bid), [DomainFactory.cash](#domainfactory) +- **Operational Contracts**: [Auction.cash](#auction), [Bid.cash](#bid), [Factory.cash](#factory) -- **Guard Contracts**: [AuctionNameEnforcer.cash](#auctionnameenforcer), [DomainOwnershipGuard.cash](#domainownershipguard), [AuctionConflictResolver.cash](#auctionconflictresolver) +- **Guard Contracts**: [NameEnforcer.cash](#nameenforcer), [OwnershipGuard.cash](#ownershipguard), [ConflictResolver.cash](#conflictresolver) -- **Domain Contract**: [Domain.cash](#domain) +- **Name Contract**: [Name.cash](#name) - **Accumulator Contract**: [Accumulator.cash](#accumulator) @@ -103,10 +103,10 @@ The architecture is built around a series of smart contracts, categorized into t ### Registry The Registry contract functions as the control and storage hub. Operational, Guard, and Accumulator contracts must execute their transactions in conjunction with the Registry contract. -This contract holds [RegistrationNFTs](#registrationnfts), [AuctionNFTs](#auctionnft), and [AuthorizedThreadNFTs](#authorizedthreadnfts). +This contract holds [RegistrationNFTs](#registrationnfts), [AuctionNFTs](#auctionnft), and [AuthorizedThreadNFTs](#authorizedthreadnfts) and [NameNFTs](#namenfts). Constructor: -- `nameCategory`: The category of the domain. All the NFTs in the system belong to this category. +- `nameCategory`: The category of the name. All the NFTs in the system belong to this category. Transaction Structure: | # | Inputs | Outputs | @@ -123,7 +123,7 @@ Transaction Structure: The Auction contract lets anyone start a new auction. Each auction requires: - A minimum starting bid of at least `minStartingBid` BCH. - - It runs for at least `minWaitTime`(check [DomainFactory](#domainfactory) contract). The timer resets with a new bid. + - It runs for at least `minWaitTime`(check [Factory](#factory) contract). The timer resets with a new bid. Constructor: - `minStartingBid`: The minimum starting bid of the auction. @@ -131,7 +131,7 @@ Constructor: Transaction Structure: Parameters: -- `name`: The name of the domain. This does not include the TLD. [TLDs](#tlds) +- `name`: The name. This does not include the TLD. [TLDs](#tlds) | # | Inputs | Outputs | |---|--------|---------| @@ -139,13 +139,12 @@ Parameters: | 1 | Any UTXO from self | Back to self | | 2 | [RegistrationNFTs](#registrationnfts) Counter NFT | [RegistrationNFTs](#registrationnfts) Counter NFT, with nftCommitment incremented by 1 and tokenAmount decreased by NewRegistrationID | | 3 | Funding UTXO from bidder | [AuctionNFT](#auctionnft) | -| 4 | | OP_RETURN revealing the name | -| 5 | | Optional change in BCH | +| 4 | | Optional change in BCH | #### Bid -The Bid contract allows anyone to bid on an active auction by allowing restricted manipulation of auctionNFT. It updates the `satoshisValue` and the `pkh` in the `nftCommitment`. The only condition is that the new Bid amount must be at least `minBidIncreasePercentage` higher. Even if the auction is passed the `minWaitTime` and the winning bid has not claimed the domain's ownership, it's still possible to continue bidding which will reset the timer to atleast `minWaitTime`. +The Bid contract allows anyone to bid on an active auction by allowing restricted manipulation of auctionNFT. It updates the `satoshisValue` and the `pkh` in the `nftCommitment`. The only condition is that the new Bid amount must be at least `minBidIncreasePercentage` higher. Even if the auction is passed the `minWaitTime` and the winning bid has not claimed the name's ownership, it's still possible to continue bidding which will reset the timer to atleast `minWaitTime`. Constructor: - `minBidIncreasePercentage`: The minimum percentage increase in the new bid amount. @@ -160,37 +159,38 @@ Transaction Structure: | 4 | | Optional change to new bidder | -#### DomainFactory +#### Factory -The DomainFactory burns the auctionNFT and issues 3 new NFTs [DomainNFTs](#domainnfts). It verifies that the actionNFT input is at least `minWaitTime` old. It also attaches the tokenAmount from auctionNFT to the authorized contract's thread. +The Factory burns the auctionNFT and issues 3 new NFTs [NameNFTs](#namenfts). It verifies that the actionNFT input is at least `minWaitTime` old. It also attaches the tokenAmount from auctionNFT to the authorized contract's thread. Constructor: -- `domainContractBytecode`: The partial bytecode of the domain contract. -- `minWaitTime`: The minimum wait time after which the domain can be claimed by the bidder. +- `nameContractBytecode`: The partial bytecode of the name contract. +- `minWaitTime`: The minimum wait time after which the name can be claimed by the bidder. - `maxPlatformFeePercentage`: The maximum fee percentage that can be charged by the platform. +- `tld`: The TLD of the name. Transaction Structure: | # | Inputs | Outputs | |---|--------|---------| | 0 | [AuthorizedThreadNFT](#authorizedthreadnfts) NFT with authorized contract's locking bytecode as commitment from [Registry Contract](#registry) | [AuthorizedThreadNFT](#authorizedthreadnfts) back to [Registry Contract](#registry) + tokenAmount from auctionNFT input| | 1 | Any UTXO from self | Back to self | -| 2 | [RegistrationNFT](#registrationnfts) Domain Minting NFT | [RegistrationNFT](#registrationnfts) Domain Minting NFT back to registry contract | -| 3 | [AuctionNFT](#auctionnft) | [DomainNFT](#domainnfts) External Auth NFT | -| 4 | Pure BCH from bidder | [DomainNFT](#domainnfts) Internal Auth NFT | -| 5 | | [DomainNFT](#domainnfts) Ownership NFT | +| 2 | [RegistrationNFT](#registrationnfts) Name Minting NFT | [RegistrationNFT](#registrationnfts) Name Minting NFT back to registry contract | +| 3 | [AuctionNFT](#auctionnft) | [NameNFT](#namenfts) External Auth NFT | +| 4 | Pure BCH from bidder | [NameNFT](#namenfts) Internal Auth NFT | +| 5 | | [NameNFT](#namenfts) Ownership NFT | | 6 | | Pure BCH back to Bidder | | 7 | | Platform fee and rest to miners | ### Guard Contracts -These contracts serve the purpose of incentivizing the enforcement of the rules. For example, if someone were to start an auction for a domain that is already owned then the [DomainOwnershipGuard](#domainownershipguard) contract will allow anyone to provide proof of ownership of the domain using [External Auth DomainNFT](#domainnfts) and penalize the illegal auction by burning the auctionNFT and giving the funds to the proof provider. +These contracts serve the purpose of incentivizing the enforcement of the rules. For example, if someone were to start an auction for a name that is already owned then the [OwnershipGuard](#ownershipguard) contract will allow anyone to provide proof of ownership of the name using [External Auth NameNFT](#namenfts) and penalize the illegal auction by burning the auctionNFT and giving the funds to the proof provider. Similarly, other contracts also provide a way to penalize anyone who attempts to break the rules of the system. -#### AuctionNameEnforcer +#### NameEnforcer -The AuctionNameEnforcer contract allows anyone to prove that the running auction has an invalid domain name. By providing proof (index of the invalid character) they burn the auctionNFT, taking away the entire amount as a reward. +The NameEnforcer contract allows anyone to prove that the running auction has an invalid name. By providing proof (index of the invalid character) they burn the auctionNFT, taking away the entire amount as a reward. > **INFO:** The nature of this architecture is that it allows for more types of restrictions. These rules can be modified to allow for more or fewer restrictions. @@ -211,28 +211,29 @@ Parameters: | 1 | Any UTXO from self | Back to self | | 2 | [AuctionNFT](#auctionnft) | Reward output | -> **Important**: Applications must verify that domain name follows the rules before starting an auction. Failing to do so will result in the user losing their bid amount. +> **Important**: Applications must verify that name follows the rules before starting an auction. Failing to do so will result in the user losing their bid amount. -#### DomainOwnershipGuard +#### OwnershipGuard -This prevents registrations for domains that have already been registered and have owners. Anyone can provide proof of valid ownership([External Auth DomainNFT](#domainnfts)) and burn the auctionNFT and claim the funds as a reward. +This prevents registrations for names that have already been registered and have owners. Anyone can provide proof of valid ownership([External Auth NameNFT](#namenfts)) and burn the auctionNFT and claim the funds as a reward. Constructor: -- `domainContractBytecode`: The partial bytecode of the domain contract. +- `nameContractBytecode`: The partial bytecode of the name contract. +- `tld`: The TLD of the name. Transaction Structure: | # | Inputs | Outputs | |---|--------|---------| | 0 | [AuthorizedThreadNFT](#authorizedthreadnfts) NFT with authorized contract's locking bytecode as commitment from [Registry Contract](#registry) | [AuthorizedThreadNFT](#authorizedthreadnfts) back to [Registry Contract](#registry) + tokenAmount from auctionNFT input| | 1 | Any UTXO from self | Back to self | -| 2 | [DomainNFT](#domainnfts) External Auth NFT | [DomainNFT](#domainnfts) External Auth NFT back to the Domain Contract | +| 2 | [NameNFT](#namenfts) External Auth NFT | [NameNFT](#namenfts) External Auth NFT back to the Name Contract | | 3 | [AuctionNFT](#auctionnft) | Reward output | -> **Important**: Applications must verify the presence of External Auth NFT in the Domain Contract before creating a new auction. Failing to do so will result in the user losing their bid amount. +> **Important**: Applications must verify the presence of External Auth NFT in the Name Contract before creating a new auction. Failing to do so will result in the user losing their bid amount. -#### AuctionConflictResolver +#### ConflictResolver -If two registration auctions exist for the same domain name, the one with the higher registrationID i.e the tokenAmount is invalid. (Since registration is a single-threaded operation such scenarios are unlikely to occur willingly.) +If two registration auctions exist for the same name, the one with the higher registrationID i.e the tokenAmount is invalid. (Since registration is a single-threaded operation such scenarios are unlikely to occur willingly.) This contract allows anyone to prove that an auction is invalid and burn the invalid auctionNFT in the process and taking away the funds as a reward for keeping the system in check. @@ -246,20 +247,20 @@ Transaction Structure: > **Important**: Applications must verify that an auctionNFT with the same name doesn't already exist in the registry contract before creating a new auction. Failing to do so will result in the user losing their bid amount. BCH's UTXO-based system has no concept of 'Contract Storage' to confirm the existence of an ongoing auction. -### Domain +### Name -The Domain contract allows the owner to perform a few operations after [DomainNFTs](#domainnfts) are issued from [DomainFactory](#domainfactory). There exists a unique domain contract for each unique domain name. +The Name contract allows the owner to perform a few operations after [NameNFTs](#namenfts) are issued from [Factory](#factory). There exists a unique name contract for each unique name. Constructor: -- `inactivityExpiryTime`: The time after which the domain is considered abandoned. -- `name`: The name of the domain. -- `nameCategory`: The category of the domain. +- `inactivityExpiryTime`: The time after which the name is considered abandoned. +- `name`: The name. +- `nameCategory`: The category of the name. -There are 3 functions in each Domain Contract: +There are 3 functions in each Name Contract: - **useAuth**: This can be used to perform a variety of actions. For example: - - Prove the the ownership of the domain by other contracts. + - Prove the the ownership of the name by other contracts. - Perform any actions in conjunction with other contracts. (A Lease Contract) - Add records and invalidate multiple records in a single transaction. @@ -267,28 +268,28 @@ For example: Transaction Structure: | # | Inputs | Outputs | |---|--------|---------| -| x | [DomainNFTs](#domainnfts) Internal/External Auth NFT from self | Back to self | -| x+1 (optional) | [OwnershipNFT](#domainnfts) from owner | [OwnershipNFT](#domainnfts) as output | +| x | [NameNFTs](#namenfts) Internal/External Auth NFT from self | Back to self | +| x+1 (optional) | [OwnershipNFT](#namenfts) from owner | [OwnershipNFT](#namenfts) as output | | x+2 | | OP_RETURN containing record data or removal hash | -- **burn**: This allows the owner of the domain to renounce ownership OR if the domain has been inactive for > `inactivityExpiryTime` then anyone can burn the domain allowing for a new auction. +- **burn**: This allows the owner of the name to renounce ownership OR if the name has been inactive for > `inactivityExpiryTime` then anyone can burn the name allowing for a new auction. Transaction Structure: | # | Inputs | Outputs | |---|--------|---------| -| 0 | [DomainNFTs](#domainnfts) Internal Auth NFT | BCH change output | -| 1 | [DomainNFTs](#domainnfts) External Auth NFT | | -| 2 | Pure BCH or [DomainNFTs](#domainnfts) Domain ownership NFT from owner | | +| 0 | [NameNFTs](#namenfts) Internal Auth NFT | BCH change output | +| 1 | [NameNFTs](#namenfts) External Auth NFT | | +| 2 | Pure BCH or [NameNFTs](#namenfts) Name ownership NFT from owner | | -- **resolveOwnerConflict**: Ideally, this function will never be triggered as no one would want to keep the free money on the table by not triggering the transaction that earns them money. Having said that, it's important to have a safeguard for such an unforceable future where these incentive system are unable to catch a registration conflict or burn two competing auctionNFTs for the same name at the same time period resulting in more than 1 owner for a domain. The owner with the lowest registrationID must be the only owner for a domain. To help enforce this rule, this function will allow anyone to burn both the Auth NFTs of the NEW invalid owner. +- **resolveOwnerConflict**: Ideally, this function will never be triggered as no one would want to keep the free money on the table by not triggering the transaction that earns them money. Having said that, it's important to have a safeguard for such an unforceable future where these incentive system are unable to catch a registration conflict or burn two competing auctionNFTs for the same name at the same time period resulting in more than 1 owner for a name. The owner with the lowest registrationID must be the only owner for a name. To help enforce this rule, this function will allow anyone to burn both the Auth NFTs of the NEW invalid owner. Transaction Structure: | # | Inputs | Outputs | |---|--------|---------| -| 0 | Valid External Auth [DomainNFT](#domainnfts) | Valid External Auth [DomainNFT](#domainnfts) back to self | -| 1 | Valid Internal Auth [DomainNFT](#domainnfts) | Valid Internal Auth [DomainNFT](#domainnfts) back to self | -| 2 | Invalid External Auth [DomainNFT](#domainnfts) | BCH change output | -| 3 | Invalid Internal Auth [DomainNFT](#domainnfts) | | +| 0 | Valid External Auth [NameNFT](#namenfts) | Valid External Auth [NameNFT](#namenfts) back to self | +| 1 | Valid Internal Auth [NameNFT](#namenfts) | Valid Internal Auth [NameNFT](#namenfts) back to self | +| 2 | Invalid External Auth [NameNFT](#namenfts) | BCH change output | +| 3 | Invalid Internal Auth [NameNFT](#namenfts) | | | 4 | BCH input from anyone | | @@ -313,7 +314,7 @@ The contracts talk to each other through cashtokens. There are 4 types in this s - [RegistrationNFTs](#registrationnfts) - [AuctionNFT](#auctionnft) - [AuthorizedThreadNFTs](#authorizedthreadnfts) -- [DomainNFTs](#domainnfts) +- [NameNFTs](#namenfts) #### RegistrationNFTs A pair of minting NFTs that exist as UTXOs within the [Registry.cash](#registry) contract, consisting of: @@ -321,7 +322,7 @@ A pair of minting NFTs that exist as UTXOs within the [Registry.cash](#registry) - `category`: nameCategory - `commitment`: registrationID < 8 bytes > - `tokenAmount`: Keeps reducing with each new registration. - - **DomainMintingNFT**: A minting NFT that works with [DomainFactory.cash](#domainfactory) to issue new Domain NFTs. This has no nftCommitment or tokenAmount. + - **NameMintingNFT**: A minting NFT that works with [Factory.cash](#factory) to issue new Name NFTs. This has no nftCommitment or tokenAmount. - `category`: nameCategory #### AuctionNFT @@ -334,7 +335,7 @@ A mutable hybrid NFT created for each new auction that remains within [Registry. A new bid simply updates the `pkh` in the `nftCommitment` and updates the `satoshisValue` to the new amount. #### AuthorizedThreadNFTs -Each authorized contract's lockingbytecode(Excluding [Domain.cash](#domain)) is added to an immutable NFT commitment and sent to the [Registry.cash](#registry) at the time of genesis. These immutable NFTs stay with `Registry.cash` forever. Any interaction with the registry must include one of these thread NFTs to create a transaction. +Each authorized contract's lockingbytecode(Excluding [Name.cash](#name)) is added to an immutable NFT commitment and sent to the [Registry.cash](#registry) at the time of genesis. These immutable NFTs stay with `Registry.cash` forever. Any interaction with the registry must include one of these thread NFTs to create a transaction. Structure: - `category`: nameCategory @@ -346,26 +347,26 @@ x = number of threads [The exact value can be anything. It must be decided at th - Auction: ~x threads - Bid: ~x threads -- DomainFactory: ~x threads -- AuctionNameEnforcer: ~x threads -- DomainOwnershipGuard: ~x threads -- AuctionConflictResolver: ~x threads +- Factory: ~x threads +- NameEnforcer: ~x threads +- OwnershipGuard: ~x threads +- ConflictResolver: ~x threads - Accumulator: ~x threads -#### DomainNFTs +#### NameNFTs A set of 3 immutable NFTs minted when an auction ends: - - **OwnershipNFT**: This NFT proves ownership of a specific domain. + - **OwnershipNFT**: This NFT proves ownership of a specific name. - `category`: nameCategory - `commitment`: registrationID < 8 bytes > + name < bytes > - - **InternalAuthNFT**: A specialized authorization NFT that resides within the Domain contract and must be used together with the OwnershipNFT to enable the owner's interaction with [Domain.cash](#domain). + - **InternalAuthNFT**: A specialized authorization NFT that resides within the Name contract and must be used together with the OwnershipNFT to enable the owner's interaction with [Name.cash](#name). - `category`: nameCategory - `commitment`: registrationID < 8 bytes > - - **ExternalAuthNFT**: A specialized authorization NFT that resides within the Domain Contract but can be attached to any transaction, particularly utilized by [DomainOwnershipGuard.cash](#domainownershipguard) to prove existing domain ownership and enforce penalties on illegal auction attempts. + - **ExternalAuthNFT**: A specialized authorization NFT that resides within the Name Contract but can be attached to any transaction, particularly utilized by [OwnershipGuard.cash](#ownershipguard) to prove existing name ownership and enforce penalties on illegal auction attempts. - `category`: nameCategory -If the domain has been inactive for > `inactivityExpiryTime` then the domain is considered abandoned and anyone can prove the inactivity and burn the Internal and External Auth NFTs to make the domain available for auction. +If the name has been inactive for > `inactivityExpiryTime` then the name is considered abandoned and anyone can prove the inactivity and burn the Internal and External Auth NFTs to make the name available for auction. ## TLDs @@ -381,17 +382,17 @@ To ensure the system operates as expected, the following steps must be followed - Mint a new hybrid token with an NFT commitment set to 0 (8 bytes) and the maximum possible token amount of `9223372036854775807`, the tokenCategory of this NFT will be `nameCategory`. - Using the `tokenCategory` i.e nameCategory, create the locking bytecode for `Registry.cash`. -- Mint a mintingNFT i.e `DomainMintingNFT` and send it to the `Registry.cash` +- Mint a mintingNFT i.e `NameMintingNFT` and send it to the `Registry.cash` - Determine the following parameters and generate the locking bytecode of all the other authorized contracts: - `inactivityExpiryTime` - `minWaitTime` - `maxPlatformFeePercentage` - `minBidIncreasePercentage` - `minStartingBid` - - `domainContractBytecode` + - `nameContractBytecode` - Create multiple threadNFTs for each authorized contract, commitment of each threadNFT must be the lockingbytecode of the authorized contract and the capability must be immutable. - Send the threadNFTs to the `Registry.cash` -- Remove the authhead after adding information(Name and Symbol) about the domain in the authchain. +- Remove the authhead after adding information(Name and Symbol) about the name in the authchain. ## Dual Decay Mechanism @@ -476,24 +477,24 @@ This implies that while anyone can claim any TLD, the community will naturally g #### Who earns from the auction sales? -Since this is an open protocol, the platform facilitating the interaction can attach their own address to get a percentage of the fee. The percentage of the fee is set in the contract parameters of the [DomainFactory](#domainfactory) contract. They can choose to get any percentage less than `maxPlatformFeePercentage`. Remaining funds are sent to the miners. +Since this is an open protocol, the platform facilitating the interaction can attach their own address to get a percentage of the fee. The percentage of the fee is set in the contract parameters of the [Factory](#factory) contract. They can choose to get any percentage less than `maxPlatformFeePercentage`. Remaining funds are sent to the miners. -#### Can anyone renounce ownership of a domain? -Yes, The owner must call the `burn` function of their respective Domain contract. The function will burn the Internal Auth NFT and the External Auth NFT allowing anyone to initiate a new auction for the domain. This action will allow anyone to initiate a new auction for the domain and claim for themselves. +#### Can anyone renounce ownership of a name? +Yes, The owner must call the `burn` function of their respective Name contract. The function will burn the Internal Auth NFT and the External Auth NFT allowing anyone to initiate a new auction for the name. This action will allow anyone to initiate a new auction for the name and claim for themselves. #### What occurs during a ownership renouncement event? -Each domain contract has an inbuilt function that allows the owner to renounce ownership by burning the internalAuthNFT and externalAuthNFT along with the ownershipNFT. +Each name contract has an inbuilt function that allows the owner to renounce ownership by burning the internalAuthNFT and externalAuthNFT along with the ownershipNFT. -This action will allow anyone to initiate a new auction for the domain and claim for themselves. +This action will allow anyone to initiate a new auction for the name and claim for themselves. #### How does ownership transfer work? -Ownership transfer is simply transferring the ownershipDomainNFT to the new owner. +Ownership transfer is simply transferring the ownershipNameNFT to the new owner. #### How to records managed? Record management is done by following [SORTS](https://github.com/BitCANN/sorts) standard #### No Renewal or Expiry? -The protocol uses an activity-based maintenance system to ensure domain upkeep: +The protocol uses an activity-based maintenance system to ensure name upkeep: Owners are required to engage in at least one activity within a specified timeframe, known as `inactivityExpiryTime`, which is determined during the genesis process. @@ -510,7 +511,7 @@ Marketplaces would need to depend on indexers to display names accurately. Creating a registry of hashes is relatively easy, which undermines the perceived privacy of a hash-based system. -#### How do I know I or someone else owns a domain? +#### How do I know I or someone else owns a name? Upon the conclusion of the auction and the successful claiming of the name, three distinct NFTs are generated, each serving a unique purpose. Let us explore their roles in more detail. **Ownership NFT:** @@ -519,16 +520,16 @@ The Ownership NFT, along with the Internal Auth NFT, serves as definitive proof **Internal Auth NFT:** -The Internal Auth NFT, along with the Ownership NFT, is used to prove ownership to the domain contract. The internalAuthNFT is minted to the domain contract that is responsible to control the name and contains the same registrationID as the ownershipNFT. -To interact with the domain contract, one must provide ownershipNFT and use the internalAuthNFT that have the same registrationID. +The Internal Auth NFT, along with the Ownership NFT, is used to prove ownership to the name contract. The internalAuthNFT is minted to the name contract that is responsible to control the name and contains the same registrationID as the ownershipNFT. +To interact with the name contract, one must provide ownershipNFT and use the internalAuthNFT that have the same registrationID. **External Auth NFT:** -The External Auth NFT is used to prove that a domain contract is already owner by someone. It is minted to the domain contract that is responsible to control the name and does not contain anything in its commitment. +The External Auth NFT is used to prove that a name contract is already owner by someone. It is minted to the name contract that is responsible to control the name and does not contain anything in its commitment. #### What if the incentive system is not 100% effective? -In rare instances, a name may be claimed by two different bidders in separate auctions. The legitimate owner will be the bidder with the lower registrationID. Each domain contract includes a built-in function that allows anyone to present two competing pairs of internal and external auth NFTs and burn the one with the higher registrationID. +In rare instances, a name may be claimed by two different bidders in separate auctions. The legitimate owner will be the bidder with the lower registrationID. Each name contract includes a built-in function that allows anyone to present two competing pairs of internal and external auth NFTs and burn the one with the higher registrationID. As mentioned earlier, it will become impossible for the party with the mismatched registrationID in their ownershipNFT and internalAuthNFT to use the name, rendering the ownershipNFT issued to the party with the higher registrationID ineffective. diff --git a/architecture.png b/architecture.png index e90e4b3df905c1c5de881c6fdfb880c73ebcc698..0e5080245c74ea22004db18b9a8f1673b615abdd 100644 GIT binary patch literal 483338 zcmeEP2|$c%_b0iOy|P?OvP8HovrkHa#oT+dFIlscvL;KG5Rv*^ zgrpE52@xvUDwOIw&&>Nyv$klt4SfIq_cial^FGh}Jm;L>IlptB=UHiCX3|Nqw_>YS ztvZc29W$j>tBwk-TD7xp{~KJ{Iqpn0{7-AoDJDj(Vs7<))2cPAfIW65+r@VQ)7hbw zI?0&#mpW0$iRH;wCyh}j5^dbvwCtHSjvh8Ho?5OBY`6s9+c-M7VmC}?`7@oJZPbb5 zC|Wx3mBwTnJEkj}<>{nOGJ? z;b1$F2~QtKAH|K;pyc z!D7Ml1ZGX}Yy2h`UtM`Bl z!F~J;O)?dmdT$$NFKp0xW97;A!)4fm<>hMcfaVBf)yT(*$#$@Gv#~?B`+$hSH7B-< zGkhYzpP=y0#w=$RPuNN0DCj@vI?I(UaGk{c2iHB>9;`(U0^d-%|KJ-i4E9WTb^JS5 zmMb*LZNL)ioF~f}jWieoq>yel)gy%s9_T3B0h4_0rJ853dLB(2zzVQE{NSLxxlwaj%RD1>YdJf?p&jl2|Lujhz1z77y{s9F?EaxmLN(8bh3 zU^0kAf|f4$H%Pz))qp_3?rlM8fZOM4*fI37V}m=TWoHBa9g*q`XlrEa0e?B7ztky4 zHePHemIrtv_EVia9Bk~(#!XeHAmoSF{a&=Hqx#^6zcJuMaKd0-34j+=8i7h7638SB zjD;qTMAV`Y2{ZzcLZTCiR1#*iThLr$k}05BKg(QVt^rkx1hIt<4SX6Zl}x4!BNl<6 zMIhCx_gw&Mk?uGnmu3M<-$qg}3I7B46!I*+S zhkuXjzad}w;Vz#3!yjM*Jn<&LJ>s>384Ovo3%HjG>aIg06UbC5jYOpDV(N~^YD6s} zjZUP~$W#K2MxxR%bN|E15(!h1hR70CiY!fWaAZQ*;U@$&wTTae5lCJIA{`rNrXz~} z?I1=$vAq#O2PTB6hS(P_OnZCe1xxhds=YiER3XX5gBorWg+B8FZ@?Z&0Zu)glJ9rG z`Q8I2g8V2*K;{6P#N7@m=5e(gq#|RzY=}@vGf>75BqK548m0fzCXuOlSl0-yK5mqB zM8hyLHn{>qK*XbALHC71(xPErP0Nu3qOQ36iBDOBk!lRf#fIq$axfXVN~Wvhk17#5 z;MM3<|HDQU4^ME9Sx7#}S~QFyZSf)kuABsCMkLf|vGF2-tfhkiC^jMBo@!CCITD-` zh(PEB8ihusP{|a!4p~z8NYvFL=@QA{o#_&3R08Jiw;XjQ$yNYTEn1CWpatfVpi9<* zSQ2K6phF<&(j`Wd1RX7!E*U&aT^f<3OV-7tyai3BxZbtoXtGf!lOIm%=uk*RDv?B> z>JoG)7=B`!tgA)TrPIj}Y3dN@BpnGn&<`RA7*sTcvxZ4j3y2__LVgHDEl7d_qeP~X zfHNWxMED507DbmvrGpXB0Vx7G<00J-XG#e4Mi3!sK_u=k+JX>OvyTF_1x9edeOv<5 z2!cTe50ud5QJWBN!cTFof`6?>3Io!WZYb;K<>G8;$3`hOe#|+^#@4~vg5}9X94g#m z%VM)xE&?~;K`EOhlCWcWv7MQ&kb%RKwUXma++%J8%))~TUC5aWizZcq+bAijXnCO} zk%qlVwJZ=F>>+K9Whz-7KwdktTy31kR$ny|CIPDNnZ!b~#LcHJbYQdnFe(5Ta28Cd z00$wGZMCO3(Cx`^i&$2bPP7GG=YKN;OIVWN3eXoGelxM=xF2T02P6gD4`a}DRewS# zRHKA=vb;RFxnZy>5=jao1#>dlHXe>p_QB2GOT3eW=`R{c-fdhd{0=U|!@=2x&GZ(0 z3Y3~iNX6FW*+@uX2q4XB!GahMK420}#-m~!szn$O-V0+tLa_uGfm-SUcx~Y=8hUux zpp-P;b2rpJFrbnuKm>m&{L+aMf<4=kwFOSpS>& z@i>oPzuuFe=x7{o9_u1&+}oj)2d@^W`8U|z-0XjCch;>)fY=xqsw0JnHTPn>d9lIa zuN(X!Gr+|;FiiqBuY#yI9QB$}v$E6DTb_~8X#u4{Mo zc(>X!y+xhEIunw0?%q@n8&^*oZnj-O_3GM1m~x?BA{-NNdTOaxvGH`{l>{ze`l6CD z2^!boqAh;3CNSj9;AirEM7|?93;BV_f3Mk1 z6md`&(Cr-T?BF+I4yrAkLZ+an^S%*uP{oZhCL*=Fni!c>tEb8LNn5C^*`$siKck2H zO}qiGt8Eu_E+x2?qE9E_S-AQ=U)KC4Y9r(trTIO=|e#*gq(W*)6eLxvQSY|PF!4^rW^c39i^qBEIBDjHS<)x2Sn2#S0k z9q-FzbJ=*nHZGLG=ULofOBYxmfHwGC&B>z=Xx_MS8GI1Mwd%kP7RX?@$a@<9WA(#O zBB1(d=#%K<8W4)%3{0&M#|Og}8jFeX+9Z6L5t;}XA;`@0#tGM;7S8(jbLLxE zUAVXz=N_8Jdm>1zs*q)0if>Yz*q0K>_}5zU3Z{!6L)7kXAwwrX+Wc50ubMccMgFAmA-FmX}7* z5%vR{ya6g+6IH)Akg-Y)5}rhC2xLlg17)_tm2cgf!4E_gu0*`n9|J}#-fLn%P^Miy zeAmWi#$OyGAw5MyAPb*)I0>7QV4tfqN(eR4jN*tYnonvW;MqcH1+h$%Cal7i!Uf=> z+_G~`nviP}MlnqSzB_0AP)0fsi#O5ugb7(VWA?q2jLbnL=9V@cR01W#4;E#ls*!{n z;^u-%A|`xI435~MHceb%aSJ1hIVKWWlH`hT4N|uAz<=5Fg+Lof#fiWj>xS5DX(>!eJ97Yx*J!w9Vk5Oil^V>D(xD`sq+9(D%RXQM4*_TR z1Y}G`wpAnHdy)!?Slx_kJ$1J6i!X80iN~FocPcOo#2;dhBz!j@fjN?-y)9+O-D;U? zT(kXy?2Ut$?EZ|iiJ4O>5D>q1N#gs60+N7-P<1Zem$s*+p)1;KV%pT1=AQU7YeV;H zmkv=rwJZj%s|&}+mqxG!nDqSGBZ+qa2nlC0gyg4g*o}MmqO;QYAT1IYY4YoP1z{S` zx3uuch(wlDb|Nz3)oRp~krBoti18LbM`T387a|LZem(1dq(w$hX8Pl8FI{acL{YitxQug~Url&!tdKQlcVh`r7yg_h*TU@Uzea=BPzSMSPiR1O*hp&L1QyB8x9% z{b^?NS5zc$tR0DrRdx#KH6FqIFi}xs+JawEkI|=y4_81eaNfAwaw(!ylUA({e3-BY&#;U9kYk@yRjnsnl zIjCO)vQ}4tA6?rJxT?7X5wu43>MEKq-a!j_X(C4~La2tKwI;5z!0nh!R#oU=yOuOF zOyV0>L*1w@9pNInFbkC#YQeEBH7|LTSokkehWK6UAirzzfH(t=!CS3c83(T_>>{&td7Y3Y|XoxQxg+&nMPEK zBq^>NX^j6;lp1XOsn&$^Y=x`1;UqX7lnIcE6nw2?OHat*exH!#5E=7kNyzahUvvuV z64;{s5+#=^%FaaK4!>Z{Xd@-$#=(Z?tRL~rS5jk24bB5n;1psq9gaDm!`cZdUGzk2 z3AKtu5={&4)TL4=1R5MchZ$o*-N%208|eSON}2B()uj6M%F#)kjK;UZGzx4!g=PmI zbDjmp{7U2>A{!=Pkl_$VEhrU-W97(js4ty_Z&58cM}!uC*ds-JW9=55mrNkg33RZWWRYD- zYCrGsM-c`5-0B9Pe@m}qY)oT-i4PH=8n)3PlBgsanM~&&`-B{h7TN!_HVo#c?6id86N6I8&?$aCnMn4q4p814P)ovT&7Tr$MUUcb zbgUI^pz)EAhPwxtfoS>`$T(^AgJW$Y6o=M~wKAA00!cwIU4=bnr}4{cuWp z;7q_%)qJRJ>J~JCx#x*y{oX;=;##~Wx#{9sNOH+uT#MQ6=D}KB{tjh$sEH+Kn;2|m z8D2>^rP&a{m9{t-mstL8=w>!94v?!gcD7-<;9r7W;D0Z0O?Yo5p5*^JorcKYZmbbX zXRySICJirY!qkSp+Cea%Nj z@F)(kIH!l#n#>X`HIZ0%^8FA?qG07=GJ~;n5mJeN&IFLgpi7HBG2a^4$(-D*AyN8CU1a0zx8_qnS;dM3 z4^U1?g2PO?X-GC`G9S`ljdG*H!rpf*4B`4gV+pshBm0i zJtrK`nm6X-*)H6tl}!NqPfAloVzuuA4nE(>4c4l+yXMi0T!NEdZzDu^HV5L6FkiQY zB92(VCRTt68%_&Lcgs#7YU<)NCT*K-^+Oct4G=|+hG_Bzz!CZh zFp-E;&`rU`%53n;H@MA&i6k(Kf(c8_=4E3=8pi@b*+xuk1pgz%6p2JBbS~`G5Q;Y0 z$xA%Bamf>buB&$7!y&ST#<)$IAr#%;u8*JEfk)_f+?Da>}!&6 zCmk36mQBGDNH_#y!5|TTp+V^VXT@JMxF)>EqzG)G;7S-}Y@C^nuAsYiz`w!1ek3F0r7-PmoDH!rT$uLu2ILy+diV!ER<@Ufg%+~nj%ZRv))(>@!sCL}V0WZRd1HS564Mtrbd)!Fb#*#K9R<;h zEUtgp^NHwwWUU(M`C{YWmGm?O^F(BuV?hnd%+(jiHH1bEmM zBpkZvv~eB&KiuoU-2lvk7LXKvW=l{~C;%$h%(Nt7!P&3|oN*vcSjHo6jx;( zK~b~;f)0MNXk)cN*AdQ9H+>8I3g-H~g3O^V&Y3nKw5u6)Vd7*Db#eZ!c~F-|1AZG( z+!*R&pPCqSaUYr2v`j-V33F2<_>P`#Hm)$T)!;0R&638K;^NCM=rHzhuwg^p1W{+a zD=N)kdczr`d=cm0CUA|{2dX2g@8FGgb$5tA+L?aw)LVZTQvj9RjBx5bp zrL_2RMa;FQ|0Vm_tlY#?I zXn`Y+3x-e-B{JgG+4&}{|A9?}xjwfT+%>TNM`k9X5jYuX(X6=-)MI%%G2KkOY&`5` zs1B&kzEUF!4_@~}plVUcP?$*5h2_a4_>0#WoIt_lNeBJOZ3L%Cm!_pdCX)yhBAmZX)1hN;(~@QkFO{rDNki}mnJiZ!)wcBjCyPfvQP5H# zCzc1S1+t&&1XPwiD$as6LZUQBwW&v`pjryCc)u1T1SAUtU&2I*)rOgNBAp0(&yxvM ziY^pDz&~Dp1m}`Y($WDQimXc`>cY0Jn54I)sl+m`0?PN(OeJP$=~`4>8i}f_L!^Mo zq~SpoW@o8dx->AtL>id_rkI4}6&Wi*mHrXLKsuG(;KEEpS3elAX;WvKdkO?yHL@Tr z-U{FtP17Rjl0js2C^}>Ug-pXt0FKclEnNU(Dh0O00s#Us`!R(Fdust3!h27F;8+TP z<36XTVQze+rsFg}HaD^zG)W>Xky#QL@oF7v!k`I19P6hInxr#ub+((*f+k~@>jGz{ z9eav{C(9WISje{mE~-Wg+7L2IlGjx&O?6p5!BhV_IA*xz5Y_+haLjO%{L_R@c$8D^ zgxA&TO7W7VO-ADz|I|#FwzfHS9k^rimg>mHjkR`730q4w>+9LXfuNmE!#{AyZ>o z0UV=$g-j4K;W?b|0PyAunMBL;WIAM`;^QW$t|Bf|vxH3isc&3lm6?zUuN@H*{d(F> zX(5xLot>A9mor*l_5&uF8dGv`Yy7iBO#JhkYEb`rMNCpB<5$GgI1v+7oa1WIZZ==S z8rQD>2oV!ioF)5drt(+B#E+Qpy3&Rqe@jm?HKrB7G5S};1Q8QnuPRU&_8+b!uWg$k zkrM^%DGqddwDMc{;0QQtfKId}fnMO>%wXEXj-RA4B)B48dxV8BwVx=Uiy7S7_q)T3 z@_))z;`|+_@iJE({J6jREfbf%wGW+)u|&<*0u4Xis(!B^a~tn8Lcv1K8}ESo$zNDo zV})W1TIr4(NwF%{#t%x+d3-c{x6EH_+ohfmMx^0Z8{6{)%=&)_T(IXpwz+HdHg!VV zxBj=tRrjN1N%(r(pAoqT{G-TmTxn$B5^I2r({?{JtPt^?#2ON!)C?0V!`g7 zVu>T}cW}HjoQ>r)nPrbQlM}dy`>j3G#?iyZ1^WW~0U!~asfBJ;cv=wIs+C%+@na09 z`dSxex7)1hb#C-~$A@YO*Sa%*Ypq~4p=t+c9`q@2NvC#w-58ei_FdH&OIx*;ldt<9oJ$?Z`$Cd_cMSXU%Q$jn-^^EJ)qvd zJk+7R!qf_d^wISiWOVc9aSYQ)^4^U0_5S5lEBIx;`O-=SDbvg8twT9RrR6mbh};eeFX zd>)213~j|P`#IK^y%q^u0r4N=S6(w1rb&AZB>*MEMc#hVqFmQ$(jUc{0tm3#|H6(z z_~Ysdxt!q&fE~<7{_6Gq<*znt8RW(rTmN|oRQ#9xKB5Lx6#j~12JQq~?)tELi!vj! zy_}KU*linoOM`alu*(=K|Bw7g&I=XkvMTMz7V@A$11F7N`yCxth7N}_JQSrpTe@s( z{IxWM(e1##arN(Tx&n0AOieeqe!ea0qlzIoFzqd;461*JU;hIg9vTv6B*9YD(veka zYJ)BQ85X^vmir|5>1-qDu(GLH-F(x?qYTS2WQM@akwqXE`L2T%2TGp`Y1)*n+*RrI zuAPS^bW2#|#Rij+4aO}a+f-#t{Rb<4sM_H4QM4J|q;*TGm~!Hw!w1iA&{**u<7^J& z9N-#LhhdsJ5!r-8_fEPBs;R{oq(x?vQ>AuKbd(}Po#SKWccZ>&h^(L3nl3C#*S0^)Va(2*#yWW)vb3zQ!!y6D8fd=g?|fOt&@DUSMdF< zPB#jPCSyE^{{*6`i_q1&wLYMATHS6G8hMSCU?#@@7TrncYMo?RZSd3I>vW^=$g6kj zs-$sU?X(0JX8r=sffY`zjQR5>;SWYI4AF41^HC+Swx`aBjE)|rb>aP`voE*Kd4AiZ z|Ju(_0+zc|Y$JZ93D{diR!BJXHSA>oDQ*%7|MUVRZ1_*MsZu>ya0c zHCm*4NWenjT}scD^LgxU(%1cRH^Tg+r4JSzOL$%|ugd?A_}6;~Uilfm@Bh~>_r{20 zYZR1+Tz(T3p_=6!J9+)Fcgt#&-WOm^LLyFYksvxRwA@lDnU~)```7N~HUh!-{nO5w ziEqzj+2#a(UTbNltG=vrza0VLJq8O8w{UZjj_^kQS8rQMXdojzY2Wxwwz+F*>)NUM zs!<=i{rm7fE$G{q2lksLKi|Dvbrt3QOuI9$^f>tV;A0`!BBu;lwZ!cQg{|7~Vd*N} zI&CSOdsn`F@vfxfHvif_P2#wJdA8igf5Yv)Cdp^rb8QPBoCv>ob)|5ZMH_6MFLL?$GU{iT5NJ zwBl7s_h!uN=aUoYGuJbfVs)`9eYde)dZgirWYqxn*V#R5)K-f*V74BW%?TG7wS(Ic z_+>bIbx?lo!a);wz&t{AXgGUHXz0k&2X=c;oFFXg(*McJMOK32EA|o$m$v`NFDzhr zNzmW~FktB;j<`+`dR5_(i!6fQ=Ld~Zlpr|iUYx_nE{-g^SYC8CZ^xqe#UC2u%|US) z$J&FMb~tf;$j-|4PhkqztW+L))zW-a z_q0);=k7~PEPb@-!?7h5BMiR1|96`~p#Se#_mu%lot&N5e0>!$y2l*n6Ei)otJ^(v z2wfXgQFbN&@G;ej^3Q3NMPe2MCQi-BG|5uJ07veV`*&HV)tol%u0C7d|H+%qChJxE zL_N~Kb%nPJcR(?)Q-aO3m`O2ZY zR*bItnnuW3{OQVqoi1D7t{{V+J2^SMuGn)#QYSm3xpUK)Ai)nvSC-o}Wb&%Dv?uhe z?(_fcL<@VXnY_M##9M>+>Be2I^j>s?ko74X9?ChXu&-*gZ^^wG$zGXOJqwN{29<7f z_kT#Qm~igzR-Ce)rso6qxtS||)&F>H(24S*^C0zwx1+06r&eW0hlofun%h^34W8*a(CzMI+Lg9F z=XO1oSm|FFn`W@V+xAUX+`F*HcTUT<&zSjif2j2h?{V=kx^^qox2|Z@MPJAJ5i_dy z3)c&Jrzpy2bJyuTUomLiiq>s@yKEisw)?!QvP`?%V|%WRwv4*^ zcNc?RGpzj_CT-IhU^cp<_yJ7K!s$`LU@Ai5lB|5PyC;6RqWv7YxVnGEYZHR~wU7%F zkE^zp(``RD;O%MmyzP!x!EkgRUHM_X-QlBWZrDtlD8XbL3EY{IaTd2N%~?|A3-kCqGqKXE_-z&~=gQG>Vxz){3`Ov& zaNk7&ie$vX^o}w&aRC6_mt?noR1aid-hs(*&-l}%3LV`)ULX4E$-ji;zRH8ft+^Ix)d3l- z!*a?)Caquv7R+%6O>xjuKuMSM4U9+QpI#fZehsL;!EYUVwOS=XsdvL{D2_6?qAYGi zU+-EPE+79YZ@8TNszD*%T^|IMzs(Kvymj4M$qnRuzPB86D95<7?qJu1lIePiH#?_% z78eCCO^}4l+&}w@0bDE26L1+{_nYCP&WJRSxAuRt|4SY%@%QBZOD^=2&$kTJFi)SVW(N(r}QdRLozY> z4rOE^1St*?$1B@UoiiVh{-KtfNk6Ztrw6djmonny$F1$XPSquTAklBx;A;=)s<|Y9IG$Z5|Wq}oxp)ycym^$gq9uws`;w5@2asnX)-NaZJugzn;EbtiZGG>U^>?5nho1f-#Mm|}+7zx*QoXsry6}I@ z$r2uD1@rt`DRh$rQQK?`qSn_lqb=u9hke;?g1-FQ|LTkDn+jGLnMTH3-My{1!e-6E zA-!hIa@a!u{Md8Q+u={eUq;O$4b-7Tzu#$b{hT`cX?*gnwHH^%^y;Hkm8$9kU2&g6 z8_bC8+4c_WBkh!Dm2sC*^2CG;ZZOK=Ihc4N{w zp_QM%_E^135pd7<0?cQ8($Tmly?a`*|9tG3Hp(k=RR|+{?HEJKwR5&J@LGVb8g`~? z1lMyfw|+=?7pQ$EC#byl!Py%nsMjmdiIJ~f{2^h&DczhBb}|a&^2mRHdTer^4F+0O zg>c;mW8#sEW%*w1AoK4t=ueAI z^3a%Xesq*AA_;~@EI#?$p{BfSi*I>FFpIEVEZ!ia1q zZ@u`#l?RJIrRG8i`Id9O#@>oG(0irO%pG;_5Jb z01Vek!XoZ)uNv-F7FcDKap#_ZQ^r^;KeA7cZJR=pR@1V(YCryx)!WNymfV%Dy?h@& zuvM9zKAaW*=D=A9oO1JavSLy?KkA^cxa>{ThwyicqqGX9sc@cj^@UE@eSC5KL}gh) z8oNBW$7~JchCG=`4GjS2aUJq%A26fBJ-9d}=Dx-che&f8;T6|C;PeF<^tgORg^#+N zjS7WNs+QmOx!pbM?ZU@r-A|O6MB&plZh(BFAW>w>FYnzXx>~o& zLdVf5gH6`-x}BHSJ@4K~j;eC6&*4=8HddyQ$uqMOZo_=4?loDz4mrg>J@fOjgXQBD zUY(rKe)E9A@8AF1#nEs%L~O^DNOuSGCjy+p%${NO|PXTGyY6rfV_yI6#nrl$+A>05_(1vR)1EWI7 z+c$wbSqI_6*mlQUswL~F{DO`OY)aqs#fN9T?UnwdbDL!_f|($`@vir?M@*s5e{_m@ z`@w?;*>j5Xy^Q1%ZWQFT?Fntq2RSCB>3^l0Y2m{B=&X`2fuK)e z8J#8$wpbRz=xtJ#_O)IA#TI+6JBjC_A}7HE9CjSAMlNS08$Rss6TPh3|74F>3zolg z;@QWX!aH6^&5LemwT^w{5}DQZYzS9$KK3~i!U(!QePH@s`0Xgo{2;*r#o&1X7~=O+ zxyFMt5`{3i&otKZO(~CAd9FV`Jd#cQmm4vGpF zsw&If?@gQWa=D|r9Hj^HM4!hO#H=pX9_FZTQ28a74ngUnxi+u1#%*G}3JV)s?I2?< zS@c1S+}Sx>xS!b*Krt;RnF-)!WE)OVVaN0>;q8)?)zYDeNG);bo=F2Z`|d}XM|>&r zN!-~#a!dORi>D~$SNUz5&l5k#L(hSeT7)2!awO){#10DVCpWf>55d>!SNJ~3UPEH*hifgQG4jmOH?WAh4bu>wI#yS_#VaZY=bA=tvztOedOvjrOCj~;tQNxHMn=ne+tkdDJB6gn|%K>QW;Mg|Mm zv+YSh>kdqKy5v>3&($POoAXvC=GjqPnQ0Aof9$h^Co>_TAotdH?u+y62T|U&`+*}S zNDDNMzy}+XC;=N!QGR{BUn_rGlfQNt&fLc61ReTl+*9dV+M@~bAJLFad^A#gOs1g_ z?9%2EQzRR~!2D_yzE-J@r6Y&Pz1d&MUek-PC;9%nwE?9w-Cf5--a^mrv!bCrKG&f* z?^xyM7e(^t{+$`ZSO?nj)uw~O3r81qIX`5*)Lr^jt31|s&Tw$_r`p``M?MMXk|JQ3 zH(Rk9!$J=8M*12)(gBO#KDoGLeb2&W@(TyUWcI(%;C?H<_%N|5;hd#m%*-Bx(pN*j z6IST`do;L#LUL`gTEc~hb=?MyD=ypN7jv&5b>>orHoR`J+`@KoDh-@UwVs^W-{P03 zp6~JEFUM|j34kx@9S-(;JjGOP^r8{nu7UEYX)_B1{22?D+~jVzEm&itmm~Y2s0|?W zrQ1TT6}f>t*St6R)ix_BtOFFFf@sGs`8bkmy(09zwXPi=TF~|NFfcQ_HilJz73T6- zW5I@LJi44QQPd)7Y+`&JI0wwHzd;E%U%wySnQclD&PS_*#k|Hk3-U5+&Ps+gOn~3` zIowWGn`vac72%s{+fJ$?c#S*`AA{`a zLlqI;^k5_y!k`A*IWz{{f#bc#dfj4#3MdoEgBfHM0mL~5+yY8cH6o~t>1}OnZ2U(; z@txR&s|rB>$DslE)U6@bq4*HP(tm&aj2Uj8a{Uw@d!%TheBR9-PXBA$a}8udXyxGi zm##dv_~i(aRV;je1!bx}`YCqWS1>Zb`TW~6?!eW>g@Mbl-Xt-oVq}2d%{L|C)=r(x z7Z)6jd%nR$^?Icr5MiXf(`Hyv?n7Rom%gvtO-klm&$EA}01E~0kT&a9kr{Yy${$B@ zo`dyQVJ#Rg?vEav2l8ANzV-%Jgc%UIx33oFj8HE318j1^lt16>FGabF6WQKbr_Z^X z8OzOaJ?DTZp_jXW3S6^ghV{9-r&D>|G+PXewk@Pt)_%@el9+w|NMdD~`z_s9mr-u0 z-QlyF89cJ=Wnt0a!8&>aa+Ec8L6<))IAALdP-Q58n)C4B8l*Icl@cg_$k@*CF_KHw zy!mBf6}+oz5qL>G|NgDh!q_aMfEoL`UPXE54_A5~i;WtqJB=%l`Jw{J`PQ~@#kRzA z-GvNUbzw*T%p@~8Fa5sz`E1YBDf6#}FkoVAA+%+k_)>h=JQt-Ci#vQ7rjlMphtxrC z)aVLkib>D8ZiW}2RN!GwpWeNnAOE-WYJLmkem#q?Hs-?18n4w5)PGYwPIfh?K#VSUI=J^%J8jrG+Pl8tL~ zzU9$0msGtPUG;m!;*aA?KdUIc&WdB5|Ga&q*Fyadmz5u`{&4un$qB{paz^JxEUA25 zII+^xBM_)1lyfEceSF~ybV_XROvqBsgdiz)hok-zAX>jba^v2|4^t*;c83Y_nY5j_ zU!9EN{)c9idlzmJQ%;42%E@)nf6q8_;p2@B3*(=!8u2{x@6HQ7BftZWnmAzf!yeD% z<+8!Kdi~pMnszs(*F(Zm+Gm^qq3PGfgem;~$oK>!oO^stg`4u4rgVH0W6Ob~Gw^}p z3~bL$XBpev4<`2T+K>laYN+}C^8 zyDF2NDvM60Xr&nRA04zfYoAk9`8l_;{9Oc-9DR$gC|_NRU*C3r z?4Epjv3I7GkHh~~tk#OxD0fD*)aZ&g=CS=s_O3VRz0>*R_!K_~r&%Zu<$QOJ;vf4g zj6QAfb~V-0qqolhf?REM#q)pUcT7<^(ssz(UL==Crywou{P(@vufDFFy^`@3rhMV( zHr=Zy8ViitZ#DsN)sM|kKw`2PFg5ZY<9kZD%rJJh9oa|8RQpSpzI2@-lXqm?VLh5P z|HZWpnoA<4nLdTitwJe4RsYw!%>qt$RscIRY;lP9@NL;#eTIBYrpKc-12~WjK0CQf zcqfI&aA|`#)5##H@>4`I)OI{N5Y~zMAad=ygwIdG2m6vx# zLE#hytnItsPfQVH4TPmS;~30z#jSA;+?iGbc3i%C&hsaNIwlcQ{!pIda(dd%fmNk( ziKoj8${`w_gvc+=SGR@_4hizBG8tX=XXp-AKc#J(3Lx8h*Hrtd`7Zr)sb$R1OYbK> z&iVEdbg{=ymou|UGeJp6)bxiTNyTsO4r<$P$NIFuB2NNv!g1e5z4bT_X}nu6E?!oe z$i9B_y1f5v7}ltHy>{NoG+BB&)B1esn|{Ial@;%nIBF*)e$Csd z`6_Br8icSjoljgDSaG9LVQ84n-45lc130<~l^Z8&F+Uxy7}oy7;dvFqsoCtiTv`N> z#~DG8=IQ!*oLgn54OqY{ocMH0FHXU>xFAzo zb;-96MUQV$qlQqJ@(8;MX494(Z109IsMZ{ zS1m^Dv}S)Rzn5FwpK{ur(T&m_tP_>_4xrP;s1;-uX7^>M8)_~Z1qrMZw?-D6npFDRtUtSdSL#kX@ikJcR;;i{v4Eake12!nCtexnM*24ve-MVw?*jteVil*N%|RZ zwXJ3Zye*1${pMiP{OLQqK)CS+@pid-nf3^aYrk88=C3`lP^! zOI?N~rTZ@rX5VSuu5ZZEtUr%O?pwd-Og_w_TH^n}3vNg-s1cn6stzRAsH z2UXlzQm}W#0FL?S3Nln%sSX>QUS`T!eaiTMkmb7ja<&yCk_JZG-!krPLIY!f%~K9Ypd;P4gZe}9(a6~MQgygR4L4G*18 z*t>i8fQz3K-d%wLlr4c3fA{~UrUk~98s&QNYo7bF;GS0j?x)>}nlxuDH@fr}^}BEg z!YzF*(dC5U>03gQ9y&(!gCZ4npOx;pSI1UWR>Y=_R8-)WNd52cc5&y5zz=hd9r3?T zw8vdhHhQP$4r4h_{|cbl2bMfs%=N{u^q6yMRzYbNYdsL|rx%|=2?Owfa-u^Rj{Vny zE3R7#Y#TMIKiEL`7aL5*+1c4SvU2t1&Y>_e84`r3j`DkBKc1~9D=Fv+qcw4->pZX# zzEGg`{6lho)~Bxs#z17)rK`)|yw(`30|lQm!6fEZe9mE=GpO*d^m`Q^THzn`Xd0}X zjqp}>3M4O=&nqrN>W>l?Tj!v9w+%)LkjF5CRInZw4a`Qq*+*TJ2ycoiz)GA<=l|V_ zx?=pMA*YXQ@+LW1Wv*HO0AxhxZjqvTV8_$bP$l)(P9_bD;u+)HA~Jh_>WG-LdnQLl zf?C8o2AuDD)AcCV+Et-!_0>MCz94k2vG)Gs6TRGae@!V)sYJy?rJ1OB(>=LgrbqTH z$MEju?-HvDCq%p{S7Dp(UKP_9;E*6SQ_I1iWVkGxwYy}8d@|O>}dN0fp-{<(#IQJ3k8od206m3z*8N*?+S1l- zY>y?Bt^zO&Jg^7`{KAK;eKuJ8yBKgO>$2B-bCHAZ=~6bf3Yl1>o@Zb0Sv~vWMD5bg z1)0Z^W0rvaKmSMBYy}h+p3DwP(|r~80BTbA?tWHfL}b2N3Jlt*u!vg^S&0=33Mjb> z2#n8o&seAOv=`aQVuqHx7Du%wN_5}p%+hF>b}WTN%%t#6%FpS|eHgb_zo zz~YBrtO~QP0;T2FGoE|3YxUTkR^;hcUiW6&1b0WE6CU%wmGXz6E;S@)Ujq#)@}gTj zsp50$4)~_I5G`K6So!#sK~Jb4p7D0|!fA$wH19otd_rNw!;%g8=7Fb*4###%9rgL~ z?Tic+F6A-4EC6iU@=$Xi{Phz3QcKMydFg{+1!!$<|0?FpqlG6v-WZV#<|FIjNkyMs zP|`H+zNRM^JAQ42E;_A*%P`*wBQ zEieAQ01K!6Z&@GLUZ{|)|F@^@+>6e=@}8LmrK`P0f4y#Q8{B>Rip0v2W2gWv86Y`p zzxAC_7t8J)y9&6o(JBsXT{4*c^rBI!elIpW01J2Woj24lo`pazE9^6*@|uue9az$TXuO%J__l@yX!OcQsztab-cwd9CxMP+z=mxAF2b=h6W3R2CgQJ^hSv zl2HfE&D%ARulx#SJ3pkdf=DPKxi#%RAaUX2<*NP%Qxm{JQ-%pO56wUFc~h`_exOml z_{vO;jSy9BIIw>@n%RShG{F8W zI}|epbX3(po2s+~BE8&2^U7zlw6gXG_|OKwhq~sgV6Sr)9EFTK@eH7DVJr%ePk;v; z_caL7V5p!Px#cqoFeHRa$vOze4ZnH*%qb_uK)mc6-PHYwmk)h7bF% z9-USFp78b>XW?p48dpu78pnYnqF4B7onFxjh#;xuLCO7m)<8Zw`el4x*DLT2{SY|p4A_?z z1N;W)S#EMN*zHGFsCHwm*Zo;TNmb8#CiVj8w&gnV;pN~Bm_!%^+QyvAJG68A&6ut* z>cV%ZxTlA~O!n=uxsWN{8J&0PK2&mS4GaR1I#cek_|w?aiVFKmx{Q1si5&UZDKYW+ zhaNbDTAiH#zq{t6!xJ8i2GZyY6dmQ<$@!Evx_?DkNlN7|?=GnN%2D?i!S3p*LN#v9 zA&{I~!bgKU-hqd{c2$tEXX-E$5*kNGkkIfzT+yCRBsRe1oTndH~%G{ z#)4*Fztjfk$s3mxVn;6gUC9?{-q=TouW)#_^3Lr;eg#M#!}tHZx>h zu}2lSgrt-w2B7RH?tczE%A$w=dj{1ZYtR~k%PW;*7u}72Q=VssNM#kRgRj8n*cG*x zDmDh3J%|T(vCT(ItvM6m!_LtU<}=lLOn)%dsCpRc@oZu3 zL#nlF4(Nm()EByFF*_^l>oQ|DbARF35+uk_!G8h;;;}s+`T(U^__C__V}QS5B@jd_ zA|P0Fzoanq)Z+rEd|v@oq&npR1@qG0neN#-fHUCzgLllmnY zma#p{K^?QrI{Cz=Qdgr=_Y)PRS;?AlkClM$o?Hk}@bL7ov&gnM>b08-0r)J_0sX)w zn*GO#%Sz$#OEk@tlnw^RQ-t_kZkFZ-)D~IXzn;%6x|)Ehp;i$V{)Go5+*^io9g0Zg zP`D;c@n5M|VH7lS7dY%Q0d8YQ_lKog?svuyby%_sDAGSpUK^+xN?9&{r^9>g3-65< zgJlG273j2CUDbly9hkIzV_*=5f_L&_i@%~U1(a`*_bp&`lsmw0{^+)_pOWbnAl+yDZsQTH5fvG%kNpXbz z*1)o2=D9RfmCN*Ij)SEuW21`Zt=lx?ZZD_cMbhIs7?u(A;)>5IRmRedwx7v5`uK3bY_7RWI> zU~Sh!%ok_*Z$F~F>6!9NkUY(W0pN13Xc0r~FM9$!P1Pz^RY2kV-2?9dScakkUS=oYT9obF`i^#94&3>0^(T~5SpEtHryn4eKlQTb*DSpgdILDf zPDA2xV|D zX!DVj?3^({`xOKYrQx%`!1B2jVE3tdaX{<&7r!>GnsTV*_MfI|Z>NV?hnZ{NGv7{B z-{I5O)aEa+>W3{8*X4YhSdIuh085{lKA-$heHRM!ojzXx0YMpYM4NjbJN37bzhC0R zBDBipu9@D^)RAx0tI(RA^1`=qIqM+Wciwsn@f|%fOidIAcI+Id9-82M_GX*g37aUuA(3pJfV-1+U}cIp5vY65}RfY?AVtN>z9O8oP)VjA16I`19p;&`av&3zd<4>^Zd z!Gp*yN;t2izV&o1aCyg~7~Q*XhUtV9?Yz_vEuSi_fCN?cSy)V<_i{#K z*^s*vkc*6L^us-&R^GD~!$`TtEd@{?83a^TTw$vavwSmCHLEy4SqJUAQK8RJza0xE z!)hri`1fX%C1@oVn9i{9oz|OkT+hE<5MKmvuo~7QI7Z9^VLkyRU2y^LV*ZgY1={i~ ztmuj@*MFbXhT!%zsW6S%?w{fjl>pS%JycDfEe8=j>yUt=e2^PeX52quPzO(kQgH7@ zeo)go=e%uSMuOQm zD{(Oz3u#D==NTE?)YKLbn1eSDQiRu0s)KM{W35196t(3RL~f|^EOu;^S$OZ&f1ETu z-*#VU?%jzW-ynrXOL?+8D*z!o1ft`_tzOdsjJFXTCPeiy*S>x1?DJK#k_IIBMp!4Y zf^4dwt}I2a-LQ<1TNAViv z{4k^9?z&d64%s{9D=^aG-j9amL4_@Z7Y1Jk7bR9z-Xz@%Tc<+i`tqZ@{7xJk1E5w| z$;=FfbmytZBZB*DoqMs-nf7;zcH)J*%lsmMC5Ss)A{a!0K><}0DuWR!km27u92Fhq z97lj`Hsa{%`?EJ#KYfC^BmJ~05kt@u>~LXu|Ee#^?ofE`tvC36VpVzK3Gi4?AK6m0 zrRd?7-0hCLu6wIG1w|VKw?=W(kW-IzcRN%Kn;Wt&ubN~d!hF3n8y~7)pp`bN}b1wBdYCaM% z`jFh+@g`nnooOaonwb*hXAQF&b>=jGn)!d+&;qxtRH0x!k`G0+M(Op3VZw3H@F7Sf zl8{Kmyqo|fi@sst;%~5CW1e_&^iY$sX%)b9UV(KUXu;1->#_(ygS;186a3%8y}ckj zzn&is)YwU@Hi(u5vMF}%XOQoeC`0=JB8_DIPdAl8bQj-WRG?;btcO86T2G~IZS+G0 z7j|*w7nUkPVN=o|9xCL|gqV5V^1Wmsw_)2+MnPzvW57w}_M1(g-iR8S+}(T>$v~e!GC|pNpslaJR_i-M=B% zp?B}z@k=ac^gx@gbhVozp~Bk`dZ9?vaNrhbQsAkFbbYL<3pAlV><{3O9k%Z?;1%$n zsM4znG48x_UggJ7&Hj)P?}rNYeX5ql{ILeyD4TKnErdo1d1M?p@HkcGn@-@?Cgy{O zI@PCiq|p2PE)UB~f3v2Z>8P1{a`~Z>IVl`1SPl(L*VGiH1?RlW?v=RgCy4K43p*mCrKUjVEoYyGuYBKJUGsL&Iv0x)A$lRiW zJG=|w!<6+iJOvHBMgrrW^^Rk+6HhN*8q9N{5@5wxx?W6?uz}}3VKO{n;Vr97?uV}M z;jfMpgbRDqWBAW2{PjEmSL_EIzfT<%<{DHM&n!xuzHajL-jCmGg_5B?(b3EUpUPp0 z%H0E#S6{t)m73#K>=)IOJ&>b>Vzl`yW{Za|2T>Jz{O`d=TpOBn6ZCMGqL6ZFoaD6x1>`3wzF?1oFJoL5sdR10Zo1e_hl=>&`MhzB%swCs?CZBXzw5 ztr5>U^H)IxcSX#m(bPkxu(BAv@QXLmXd4Z$flm|9-oLK4nP zg0Wn5=I=hrPT5s!Y@mm;4f@^JIs~Tc&!JJSLZizyUH)i=dvf%_mE1A=21PhYdwD(% zK2T$O9DFqK)3k!Pt!WE1jpX{h9g_ZNx9)e1en#frbbr;7Y1 z&CBMO=<(_K7qHYPvYG&!a`2ZLH|5t3ByjF+I*Mv1Y1^4_q$_WsOFsTMOL{3qbP=$7A-fr_lRuq% zIp@`e7!T9=I)1&Cdac=!_CjmEsoG~{wIyOt<`CO}=lJCjrT3sF}l9_}#a7xA7dI4>W-LDegr_n<9iZtkFrxC!j2RS*r8E`0eka_!s6 zkqhCG(^hA3g91UD$B4WRL$+hC)j4De7+D>hCYT@oCx65AQDX$^{TXF)=P0SKwz9&9 z>bxAlf&7sk@RnMmKEguwPS3#gNAvsagwIKM)<(^>NkT1ft;QpZq!31*9+R)3cM4e# z!*pulQB~$WV={k=y+q*-8qaxYcr$G^D?#W#KB=!1;B znB_AI2^wQ(do;urH4Je&EFdc@9ipQT{aBWK4|Z-Bn0Kx*^8;PI7F%=@IRhMOH>8QX zYtY2V0i2N~Zzs?9mG21eeR!IHTq0WzBk)lLhSxFMf$O`t42&E$>Znn)$?TG@f>!ZO zhZpzB5pynj?P3_qdF5yAOnTqGmr^g)8HdF~s~5vSW76VNcA%#}8UywHvxBG9pv}wV z^E2XToM$l2I{Gowg>o$18Mr zR6Lvri2?!8FVN!d_v@q#7J=D)!l*lkmOBXyx9L1wZRO8=3Q2+rElRs=h-~{pfF7;` zX|DU%7qa9|!*m?nl9MMsQJs`^2B#nYYXUc2wnH6_tN$VCr1ty!q82!C!7+c-FPw{rqgs!$Gcj9@9?{#ysHArU!SdapspP7cIWPT_jU~ftVm824Kjb5FTJXr1Re6}cz36_FC;Wr^ zv6GIfcxNvn^s1=1GAo`$8a=1?Mt1zNkFS>1i0P-g3y&6_oR;dcIp4Ka{~Qi?;>(b0 z+r@uycE!xVpewjC^N2WsMfc8}KJl~H?`DICU;5y%dQ-e17LqZ|Hkglgp`}SU_AB#h zn)1U-*BxB-J6M0P>W|m`{44Ll6@RYHl}hr%-;o=WqWI@d0>=Zk$D00ES9R_3sawqf7XQEn{_}%~e&rsM9jkPWS}e(NtNw%g zyylB2kHV3w{#gPGR}L3aH7Tf>w6w11*Kv{m^Kg#`3-?uGKiRkK|U?tX0? zLejvI-%Hp|TD>w|6qhOB{=0ZKYqVrl7ETBd@A?dpT>05~av=(HK zGytuEA2yq1IpPbgr8}+y^vE?A%q=T}`ofl1+;ZC90!hKILMSJ3dt0kzj$k5S=VMGB z=dZCdmA!1c^Hb`W5dlyBhX0`#ipzL*p*1_F^^Z-SiYEws(*g>{UF&;jEJMv-o?J;F z^=Rp!uN0q%QTe?T1jQw0LJN>DbPGA*uDvd*Li}37*VCR~7>AJ%Ef9+9>7SUeJ>$iX zo)j|p+jtMB1qCV71RJXR$jaL=qZGcR;qMpIqR}F5Jiv#2HnO5jglgA?1M(EhwkNxV zz!|C^q5ajpUGpKi@B!9hB-@WGBvnO%Rs*aTYWm+tTe^V;l6U|EVJ8eLJKes z*;WQUSK@l-IIP}XLqcnd5&q&-GvVd8Sm04!P<|2n`60cTAdcQG#=TmPZGh59@`tSa zoEHk7F>MMw4EW}I8Ne7AxSc2fn^)@%rAWCg7RyNPov_$RtbRa-_oZGdN^I4Vr)c=< z$j#&g3blpMP<>E=3`j%jNJTPxX$eVT5r!0GHJ3&Kil-IMU$QBk2)k%_BL!GGHL2CW zLSsX+-$-G*$MM>Ot|;Psl)Q#K1vE-_7G{{$2M}E$*(d^nx_VzaFa|4GK&M%cE?D~e zMWnGQuYC#l0W?5Gx`H_Jg9}+hiQj~Ys-uT&`pSmse~B2tLPT)p---zAAA(BJxtjYzF%elS2xU&?4co zz%-Ws_|EQg5Zc5)me)Nc?R(0UoZ=Q!?Y+-0Y)<-;;n~-VUTE;QGN)jnTs=B0oKJK! zClBjJczF6@gg_@8%Q1FH51nhr?3KrV03i`oV*L#(m2aPa7$uDf(3Cot0%gs#^ZZ> zPa`y4BsrxY8RYlyCPKqBx;`EKgCtX8CFkFS#4Kf|Zk)E8cfZ=F2W<<5U^P@}x7p?C z0_Qjddcilz4qGVljB_MEJYeE5KMF*7rdXATFHxV{)Y7~LoBw@3gg2%#J$dE4w2{0Q z&K)kmaC+te{9eo|BC)snjFqbsx&n(KI8TQTAX*k8H3Jbi`i^ZfnY?(^MZ-(aVwYDf z(R(umUXQhk0ByJ~NR&3LbIwtbCvUwzP+~^EwE!(hfYY{eUx^mIip+f9Bs4#y_s*qm zknJV&Jd0z`h`=!FG-{U+N$vWDhIK^wu|#UG<24-b zI9oCz92&qervZk|SHdzhw72fNSz8}cfCxi{hRA3`ConDQCJS>%6z^@i7hc14NSJ*? zO3O*N;O=_2MW?$7{Nl*tyf_V6yZd*CRnXX-+Vtea-4~xYK0i~n3};k?=wJ2Q4kjLQ z3aI#Iy;P>&af-rw(<;ES(OKZ+{DZ+M)y<2q%k+IQsBsL-(JK4xcCg*(K3FT%x6$U* zdy8w%!IS1aFlRLq8|GUV7Z34a!%LkK1kf|1|# z10tu)Vc_?51D$(kG9S$W0v>63_r;@$*D#C z?AWjKpw-OC_XJ=tBJVXxP8ft5qxa;}mV9eG6Z}o!)zN4wibr=IojE266^dWH23~G@ z$+Ll?Xw5y+cYgfpYE;wf&Jk*_0G|tz4LpdN$yHIhVTJH&;ebi?<4YAl%-@{|l)I9e zK`}mT{#RL#UeA$ic=ICgzHLUi&34@36IvI*>>#7?^hmz_@*+_0I6I5E!xwwC$TXV( zWohjXpYMbYfu3;U1URQjXpis%I&@rF)?&k|E^85zjkrYBQ&0d66dHY*Rbs81tac2! z*>f`?k^jQx)Jr*S=|JbRp9->)@wYl=eU86N@{Oid&d>NS?I~E2f_H7JEQY-(&fDV{ z6&|k;b<~n4Bor)6V!0sn0{NcB**1vuXg@FM>|4HdL6=xmpQ?o_1^aCj!Lm#L0M~|{^sWpCLbxB z-%NV_7job~S<06?<)s&_`c+_ng6YZfC*?LE%uG#Qb?)5mR&TJdtDvC!u8I&`==XJ7 zm4nsOnUmrs~(R=MXj^lw~;j_0Sn zk=1}Ob5}puw;dF6c7PU7J{6k(klogq9cL@#i+OptUB0!#cC>cMjf6w}#sKI$K>>$0 zIk|!4Y70W3dd6@gd4?fD;p=l&zS>4O3}3R{e%XcN;e{8bVv*mC@jbG-(OkaU>nC2rOkIqxn+Uh4y@WzAI-0 z$4Do5he?q5dEGk=Js5iXoUqBr^>hQ@?<+xvlMD?_D)eXE9uTz*j60gCDzb=V=xEF+ zy9C~0gm7){iBF^qn;+&r3SS`wx{g$%2v^$%QMf@Pm~m!m5L8SOH$NxkBdF0QnQJSw zmh{vBpy+m)U#r0Os?le$U+G-_I0HWD3`Ebb4i|oL63)Q6Ps^L9U;BIw)e+!2qclt^sEv`4nvhq(~g#x4DG5#iqv@iYZG66}^dKxmW>dPbb4 z27x0hj3Q?p8IDd$Y^MlzBTe3P#Dtx*{QDPYpA|y01~v(3+)rEb?Qa3gyA3Wv3HPPy zDZLcsN22i+*(mnEcqKk&z^RC&kAQk-?Au&n$jEinJFz55F9a>suL>r$f+ zAqzU64sv=Bx=VB+i#!+0XZejY{OA_U?*Qxn$!p0z;KdV^S5g^0paE`-(hefZsaVvf zfp`PCJH>!9F9HPKpiYL%id~>_jqU0&3^d7lG!bOjISDm2Kpl}!TGWXC?)6Uz>Rw2B!7gH8tFhR2z&i`thFJ?+hf6287dE z;P;YZEeM9%SyEsiw=!d`cEi1s<=(iOAD(Tn19>*25b;3F;)}NU&8lyp(O`)9`vF#C z!Bs1$%OI>q8#vqs@X+Ig7U1i$?NoW(+#da4D?pTxL~1Ao&~-BO7Ns?A z{^lR}^HTMpQ*Q)r{%$9b&&W<(hG}I!!fxF(1|S12JzxV;BG+MDT6D!kr02?f&w=V{Ogh^px3upqa+TbvtC@tAqamBf>sL=Xdw@B{SeIE z*?$1B*^%&0P9o#0-i22qD39eP6l{EXXp_TUNI;}0e{;S`vC`i!YvbA>P8v?~TBZV$ zV1c#hg{mNBEoJgM8}VX)Ve3RjmX3Vp?#S&GCzOhBC>|i7@Xi1?&IFL)zBm9JIm&v!RGG`SjnF=#4BLaRTKQ3R`;J&3F7*fb0@^0fGYhY=g7ZT zrT^zSIbs6=Isrw9e+!-XO}dx$;_8bZT!7zA{r-19{{Q473!_k^F;T++I2^AI&_V?) zB)$NZe~=!P$fXZAq3i5t4sS>{xW>=?d@?AnD$_*3N&kgaLW?~hJ}-V$U4TsWH&qv5 z(5>kP9Ui)C7Ym>j4*zm}k97exe!l=j$s*|4p91jNH8?j2TEN-<$Axi*GKBw^8qc9MTpt06|pB+RqXq_S1*r zhSBXv4k4h=^(#<|y0;igbC(Y0+Jt@)T9^F{dbJ_Z)5YF04NohjUO6 zByK^CTp}>XOR6XZl)E2Nb3$KfHzc(*rJS~>VktiT%+FTTfh2sGg^9G!mjzLL@NJdj z0Pz9PdVa(Qz+(HG_#hbuV@#$;EhiFoTCu@~y=3*b+CLA22sogd^TKT7_n%3}Jhj3A zb1oa&Fq1a;g#sK@>G9mLH5$+`uma9Ft{;WjNoqg_6OFm^ji)yK2S*{x6ZVf(mq&i) z#~Y%_c?RY8_87#3o-bF&efilp#3RTvSFVQyc_t#r^PC4ds-edYAcBWcPRom1@M#%m zQ+-bU3c{dWt4wlzBj3_P0p%7;r1(}OHIjIsIMfZz8dzp?f{|iHkiQlIiqT1A$h!NN zbnP$ygDZh;+q)r?i}c2u{eet?H>D%y4(~$*jan7>?w5E?!yP|xb08h~3auVn`eh$l zi|eSCvs974q=Y7tNM&3nB@64YgpRp9wyjQGcaOlZ5*EO&&YdI=kRd`nRMbxRG* zNkY4b$Yy9|YW+xL2LH8MyB6&IYFIHQP@APt9D+M^L3I?fuy(2zWyp(MOQF-M2+FA( zh&;nEar`_2dUoMaGTRLu=VVxugF`w8cT5+I+FXtJjurDsa6XmV|QWY%;%l=4B=^=LL#A?!{-2Isw zn)D5zYR-Tvz0}?Qcqre~Jhmy{(+y3iG)OV7=s+k;>-(D%hRcETWjaS45QX-fef0*d zQV9Ox4T`lMM;dQ_pq(Ir#?$Tb*$&iTcA*rINxF^_E(2)QyW$l4>E~-?>Ik=JO!vqX ziL&{Obfk6v1?#I`4N?nM+<7pM#19oo<9+z(;UjmWTP$$uk?0;Iw?J2^vdTkV+sD;P zuQH+vW(1UzsDkVys-O*eNyUSYK(mprq4rAf2p|`U(!TrG!6lB3h2Fn1#LAg%u=+NA zvOZG$r7Tzf!~+t8_P(d#hRhy_>$p%w*NDY6~1mf{8!rp92^q z3U4m|^f8y*0~1Alf(U6h9w6RSMztRt>R|O}W;XThXR5**GfWiqU+gLzgamprAGZ6Y zES0i{uGyFAe#8T?oqwEI?EJ-W}_LUhm7l zRaYl%f3zU77{?xBPM}rFg3{&bgl~x1qIxU#qq3v?K`YJrNM8JtNJ_}eyb6ejg1N#c zI`14~R>RQ{74*%qlEf6Rklp94H+3O@;hY4BKoo&w#$m!SH#ET~mlBocy6>r}P$3xc z_asm(c){@rmh(Z^3oJH*6f{GT)4zR1v^{(rhUhg;f_8%#(s1pD7G7#p%0WU(g-8|A z7hu4H=yJu!?tk&N1JbuiXcqT@L`|>~sM$J=rldDG!o`T-uL4dAZy z(?CvLJQzyQwt@WVIueT}42(JCAhnPbNTGZ|%qyd=hGXn}SY%HX=|$BJJp&0L&x`J} z4Mm`FqDG^97()r{eMqpmd(umTJ!YPXRm%NK+#tzX0clfpAFeg3ejKyl>Sx zz_%}E^snJZq7BW^#9z1u3P|~%^PSj^rm7+Iw+OZPBX!20TOiBKm^b^%>N+YRAWg24 z8YFqbR-+B*vC;M@ao^Kf*`cdvaacu_=->isJ%ylbTQ^yux3V8e&nz#7>XS*~G!$I}1j`59D2}&zRHp$xDy1%>~_( zfDSeq7;i=mwRL9#Xxi$DP9PMmICD>N6bI90{|r~SM21ZdprXb zB%5NVA`eSKO)TcwysWWn?U9Wv>-?uc_9LPYaWo-q%Im3_YS~UtD>~+UY4V)P;N8_q zp46@};P{AIyW_^@uk zMugdiF5T5KFVN@NwuhFA6H0>J&`mCscY^?if);8!)+Ci)kX%bJ#C1W%j0y>5aWrMj zohHtl=FQtzNRpe7XC5+Fj9#?HnTrF5)6uFF$`P-+px@u!-^wr>fcwHL39rtRk)k)a zl>!@>xuID3LE4Houl%dqep0W7^5Ap(TYPyp_;lg41gp}W)rJ#`FMw=R@2E4x1Q|y% zZ_2NHjrIefgwe8>t~;h&yZ0o+tM z`H2iv!no)x(eWlAT`Nndp^-|X1hExi&-n@+8CMipLn+~zsjn*tQ3QTiLET+cgvF+Hufdc+B65lhH=ffAs4*tK}W5==!Gasv?!N*H~*TP(On z0+qH2NPG`QQ@*mE?+;_E*#xcR#?4UE2`~w2%w2ZR(=dK$6i`#bTTZyj7-fO3xizF} z*AedA9l)PNNu1RqvOrFeY)^)u1THJJ04wu=xtvd6#t@OKUdlq}8E9ZwAnf|$l5`cy zy71H}&?vDzyEM4>_m1rt5MQIY5_TGsO#N|W>SN9$Q*R@h>U>xiAUP^9DE->E#GS1yyd)o_%=2b!s&IbOR4kFD`z{{CmSjS5U?qbr%_8h<*jZY?vEN*sHBxnQ539;D@l< zV@_!FIFiaAWCylEq^4WC(Gn}MbLD&HvgK2AI&M45K0V+G1?Fl(!7=&LI?zWRV?&bq zSig0MThtTTEW#YV02S3*&74IKl%EDz&9`4ouR`ebeubp9BA@2%)>4`8sp=JCFW$m( z22}YW58;Ocqas^GPAF$=9{-k52qyas?T!ybSsBn-Gwg;jf=eTW`%ff9Qcx+^iaCIY zy#@nJOQH=&wmds%w|OI7Z&w$PCUoB!@QC$);HTdb#3623P$LeAh+tk;=0WQg=Sgc+ zn}DItLllgi0@%)CJ5T`gp_|B$#bW7#aHvVBNqucQ^z(^-oA2ye0p9D+x} z*nBr$nciZP+re&q+pA6^oa%2ghg&S*z?wguB+Z9-#Dg5z-upVi)gv*X@ut4pQo1Q} z97I1rO6#hVr;#6LFARlM5sxJTQX!f|0QB1%mQc=($bTh21^c4lPFA-Pb*(H+|`oaqXyUy1r}NRVeE%1_D341M@}C z&!4(8s>CfaQ(C1u@_aGb48&Zx4qUh9f3PqPb--!fh*Z{7K#SGk!DG8mC%PY83fjYN z3;*N`xooE$Fn{3E6~f4jvx(eMCfRyO6?aOx+mF*mvAO%YWarb*jU`Nn@i*6(Z)Wm$ zLwbzZRBH4eh$ATjDhLA`2z?n`yY?B1sv_cpxF>ejh(ABp`V#6)j zL9pkIHAsg0ZX6&Qy)@!b*Y?G1C>fB4hj<`g5Do$OCU-yq#kshkzOO@}1yDc{T;D)( zOiAX>l1!G8L4OcDd9``NsN3rfQPF1JvT?X2;RU52ehCb~Enp}1ZM+Ce;JH-lxoKTU zl*{*;2JbJo$?yh@yUi7OHn0(8m^%5aLGFar%ElJJbK?R!ox{$Ol(Z zkY67;fYBinB*Ex#u`jJJw>)VjpnmK|s1AcX1q(}3k3}BAYL^c{UP;ZfA9qF3!oZAj z?d>2xwyqF_b?fUgJi$%)AyyHZJz`LmUUuCG4y0lAb)*R?uXv~yb@{35yz*x4MBnNqwUtLh)uOJ5Q%yg?rDzaWE z$qxf`KyUYkh@Q?th)S@gRHKMt1UvxhDP;=4Cbn%}J*dA)F>e8*tk!@3Ns?mUqrGPw z)LhgeUX~|}y4=gvJxBljVNL;{XKi)b-1$bb9+y0=nh+4>Co}T|OPq$qr`KEf6gKeX zXw#LqQ6QMq3~Lu?1k8p>s8VdZ3Nee$8(F~o0zN$OB+hvXKO%GGU~+ zGd8dP5jyN>Qy){7F0HX(b$I#lY>R zsii9L8JrR;X8Zi~s|y5qNLes0)t9n>yw^Ej?N|ul@vM+5H}rvbgPs*NL>ZR;o%iNKligwnY~54Do&wxR zK<5&gfx+ha7!XD9@J#msPVC78c0hK8_;`tD%hvn|2`My)rW>eQS3bXo!+{ubIPdj6 zBK3z)3^uif;P{6+eAmBI)}J&Iv{mc^{XDz3FmoXrQ}0rLX2jVzx<*_&SNiKS?q$=u z&#o{@!WWJG;9AUu4=y>N{to4Cjw^Ltte6{2n zN^QC=`Cd*qLd2Le2{0IOO(U?c@tz0rXTIcJnR|8-i!z0Ubt zpnNxpLzKF<~KM3%Ex6T=&$UioDDVE_(wLpxM&qT_(9E7dv4ZAG5q@ruPWjGlzfg? z3w&STR0WKOs~L624PXXINv0iAjWmFr08$KrGZm_tn?^ni#t_Cjr{;Ic)MxmL^lH%t z$t(=@WbKsc{7c4n?x1_zookl!_s4W&kUS|{9!OrX62rg38_=MVddt^~!DM*MYDpb{ zR#qzpASp4k&ovfdK3@O1WOL>&)CC`PS*#Y77Pd#F`6c+PW%6EF3Cfx;Fi0=s(#r?mKkL6%L#EBab9P&q2+gH3tmBXXJdGzIsi+aLGz9m~5r_hU9jCBOQKDw5spE_aoM+#C z<%XvekhN%LX-*e}_R0kX}3blZ9)x5=z4 zHo?Db!bMqWS{bn;#yj*22M*o`($cfdNBxU7!q())kU*Jzo3|uO_@}^x7SVC%OV%tZ zBWmZw+~Y!=VQVaO{W_C9W)phCi#s8P?Wv-e@+{2)b01kiqF`2$Pelb6i{C)x*eWH} zZ$^6m?B&>3B!8S3VFjHhChpLJ`pSb(ZWoHxTA)JV68i>qoBX zP5w%{hvz-2XR~UWJ_)jbzJ zi#8(V=ejaBVR?%Al{vWq6x)QODiKKA8{oyaK4R^O_A=L}m~!ra8Yo2FTN*6PKG7Ra z*3;l(TZhyIk#M0vV<(V$z)u*b+VLZLZMM!B2k(zo>EB1FJ4ly?1 znM345`$>B07W~mS4!`;osuP^nq@={(X)47)m9fb%{R>zeb+?3Z#I-fN+YWX~^1^e1 z;QiBwr;egYGN`1%ij2JO1aU-<$c|{X2=?!3b#Vk+)1zf?g-}7rj9EGp7a^AElL=ZN5mh$gPGY2_ttH;)_M zx26P@B9g&p2_b3@Z=RVx$P!+enf`zqxzn?3FXrCVeH&Z^M_kt&ko;xPB=2sxTl_1I zRWNho4DGej{7kq6ih5ljw=IyDNMhD?R3f=RKcGU8Oyk#$!g9Y80quY?@ovU?RnkR? zzDlm#XB0?pk()Mh_PdLGMRi;S)9zKL))k7S za&BrTITwPw8;Bsp%Sq}52-!oAMNA&|n87)iG{%F?Z|g!0(@rp5A^=~Al;DwWF>l_D zAympir7u#%)g39(#~gL=caIU0akx~Il&|@N3((L{lFhULYkc^X>R*pkxoI}XOH@Cn z{ys6?!B;E0%Mpe12t0JB3MDS?kGuf7CVFA9Ne@)(9a%nrB+47Jat}0V1+BCE(*17` zcZn9u=EG$<_RNO!Ou(%QsH^zu@zD1bn>>_=X71GA+-aEcf!%zQ)#7HDqcvrMFo>Vh zQC_f5)TF=t**@Kj&3Q7sg=zCkeWHw&Vzj5qI7EjyJrrs&?Iq3`9ZrVr%okGD+1DRC z(Jcb9Gih}5p@LVWf9G@HlgrKlT#^(TCXSvX|#v zz}FHy`sOcf9~VmUUhiNGI@NtR$bHw&oL#%10v>>?6HPTbOvx+yaMwN?4f=%69M-R1Q1fSn8dA__14WAv^Zx!-ff8PH zn)k1rW+hRmdqDAS{@{IHa*762e*VxRgc*iv;MYTtFv(o_)fW7=Rt*LGy}$-&Dz6}^ z9(|F6uwBz=Z_ZYuPEsWCm&x#bupoST+sH`*qt)nEzx4Q1Z}opS z@V^%5|2YdJVol;~Z&=wE#S`)XY-cg`HqKQytAULD;FJG3O4!l2b^T+L0 z@cVBZ^Z#}rKBT1YKxq(}XZg+br_2&B21$3V?=j~6D8K3>CEA){tF@bdF*U)jP?<7M z`~bgyV2rC_Zw9KWI=sAp5rug?he!ecyZ?%HSNgpcW8sA)&O_EoVhQ*6${iyFrB(CmD2pwI8%-8@RlS7dPgb zl3d=AT?5`lB$2)vkyjQin;>O>erEkXs2X7K?`f%)X1o0g0Ul*+JT%)l8y_Y&edn}P;!>M z3Z+qlg_*DKLvUXib5Sc%x5>g2yt+;Nl&7ql;w7Fu?d5v1COIBH0-3V$&(;(46r^2DS zXGMTXu>tkL$_K{RZXzdj)BeQP^5d|4%bN%fYygvPXn;L#U7CxC6ohfdd=4?t8=mYJ z_RLbEKrP6rrHFcZAJ|+ywt$4YqpDi-<-5v=p}`0h&38Qq0tMe(oSLo%V%d`@ziw^b zwPhcj{8I`b(E#6d_^#7rbPg=2HLnL06u=-xl{H3G565DT-qai#*EH1&VwDFtU51_@ zf+{5d7FVazZ*Ny{5S;=zySf|N@dP}!ZlpvF$baQ}rA5tGniL|EE6k~5Ih0B=;b*fT@|tivP_k64dn098*9wa#=Sj&k7|87a@6M;D=+o5d}) z^ZhVr0%?yeA|-X00_{dqb|lidZ5moJZ}8%6f{IgK-B~MvS^qgDFsvUYGJhnvJ3b+a z@|t47xmyJeVR|e=U`%?#m&MmCiD7SJI0k0Hr&~jJu^na_Xso{LSuaKtreV?&>wRsT zk3qb62?>%u0-{Ivu7HZo9wgFX0BQiVO}efYG4d4Rqi<{v`a5h%!O>fh*Y(MCU8?I} zx8yr8_}dw^e=Ryi?*K(ijn$NZiU!r0B>qmFsk6f{J*DH5(z*19I#QSg&}Kj^Xy|I} zICgbSPl^GNdbxpRdB_*@7YsOS4yJ|a>~s4;u!4g4cWS7H(aY&kWHC@x((_ibLcH;en@=~1cdL>99h#6?%+}D93 zldOaTT>_7{&{jOKnMndLpCXWjY3}d%D_=zn$X;2X&G1)#@eVW-sl>!(LBlZucoX6< zuTqu@1UslVa@n0JP0l?Looy>x8vz75b%FdvTW+SZ&UZsc zik>ieYVi(|KM#HIGG@b`l(KDswBPPl5u1Tg)$*2)j!HD?Iny>=^iI44-{f3AF}Hd7 zYeMpQD?NH@QT=8rtN#}oAxhwepPsr!REtv)ymJV6vW38hW<_=g8$NkR z53D06kXI4ec-&?JW&>)zJEjlZy>4hzcsvq<8=+fo1Rpq+^-OYC^872||7+KSf2k3tdKx~Q<>5;t!+_4Cn0=A%e%|Wu6mXQa|&tEisay>v6 ztc;tq`+hER<#G9h_4!Zc=S6>dw$%vJ6d}W`tEc8~kl~vjLU0rI;(Q^4p1UqX$sNB! zIU~wfyP$1VMA5Ocmb)dZQ*8h&&7Qm)JbAZybV25c#CC)3XiW*AfEEET$B9~}Qh+SN zYQ;(-=D~d2K-XR^N)?#o*OLr<2qdf13`~BPQ#7m$wV@V;FL0L=o^>!0gM zxc@7CBL=GCSR#-Q`2|piC(>R3N&y2J`}P({weoEvpmv}DZPhYZ=z`b+kC98#uaN~7 zL-(6j%Mr!rASr%Ufr|yP|Ip=6fgfY@Y+v5bCz5fXtG;M^^hbBoq(5ey$`7D+gwdAt zq7%C!HlY@AU0%cO?Z6v8Ct61LeF28?ZACpPq?S!!>sX~!%q-e2w^)HXLpNZ<3^3(N zZ_48RE711Hf7lMdG#=X44)E2=eLXDDWkDf-XfNEW9}s*88MYIHO>Puli~=B;SFGDH zuMx;^J;YLwDk(Am>nG5`WDR_0UqIk@k*KQ^nin{4-YlX35ki;J8W`vul!A0?yeVFd z0IPq`%Kju4KmJUJXnSfvJte9TJZXEXv0L7-$Lq*Lz?Miv?Fga^S(i*3zJJ}j9vzeL zKYUEsnFweGIT{^EVE`0kT2NNdzX-g>0N=O3_ZBA!CS;C$;1?S#v1;e4WC`E<*QN~- zCEB3_?qh8EbWWg7AkZkvC&HsC4(6K291m(?8PS^P)`$?E)i2X2dTYcV|-May)BOk8c^F zljNbH)zGi%Z=!+DB->K)E(hZI*2WDa?K|+ZTM?ZCWf*9(pYMCo;n{`^**>3a!Pght)`oA1#a5=g^q)dUMk$4!hpW!4D&kZL6uETY^~H5mC^pK0)LrE~&>p z4xqhZyPOC#UvE<%^FF!f>^<7srlR7CyYJW?^ADhn*?&xE+vUT= z(!s@i*Ut2sx78Zvo=soO^Ba4waZ3)mH7&U1*Gx#g9`Wcfx4&Hy$W~GNL_Wr2dBFx6 zH%cBKx?F~x2SQXoC}4!P=QvhOb{81|C)oH@#eKnkn54(HY%Ob8OsSFQmNhfpt~F2{ z!4HFMQ_Z@P&n4VPiXiZh?+)3Q-r_JB_>${7XPUzMt%#mW%3rJ*c0$WoH|nJ$+Yc^) zMnk;_?iTyO{fP&_EPf2m{$Lh9V?iB%E&I)M#c+o$j~5vQWuJ0^vLcTai=@(eN-Aq^ z9xONcqqg>}p26zibVeK*IR^WCU;HVtOkKLG-{C2s@WK4@S`vNXKJr*O*;l?Fj*XaF zxVTlMsA(-vt62whwC=ywbCeld8(je1uT>lyzPvWy1!SIJ$vI#;--%a?do*I%nWqB` zH|FVSFr5veGnuA!a-cCMWK?lKv?^TO!bpYks-Q{=H3s~nEe(~nn;xm+5=wMCAimrZ)2 zCbl?rxKNn&r0 z3u1iqm?fs@kL?kMJ;s+LIR4JA;>hs+WW40#*Pmk*S#A@R0z%UiF<8oQGG2VjnCYlX zbLZsP1SHRUZgGf<(bVr69jpiwZaoHU_wY||EVSaav(gfEavJLaLm#Lzf~~Auh>wpq zwzM6r4uw&?<#2vYfP(hHSlZp(C)kUV&R0qSMeL4-qkn2rieuw1s|#8x#T^uQgoVusY%4`v`g4)Lm3 z^b7G)!l@|0Em+~lDSBy!gHgvj=TFf`)=eL(oW^~M_0$h!f83IElbZYBTcrlrp?B%_ z`B$1J;20hwx`B5qg9#=A@N2R&tU6_+&PN4tJJZO)H2Cwv%M?nXdw0OS!P1?23>pA+ z+DQCKMcB9Q_LDA}nGjv&|(NbM)&u*wxW{{)M=)C-Y?_p`!?&rBLmO~Q){|=@5 z_o6%&P6TY)bLXo-l;?`GGXyt$J9ZprOLu@+pGkc*2$)53!%fLn zd3<~!v&zR6dGzzWPHiN|Fr5dZQ)tP>tN?-+7*iWZN3E`Z%Q}nR-sog|_yPOk2)SW_ z&=X!f9IX?u6_p$mkaANzl(K)R{!)F9aV!$b%Ge`2#<4MvxZ)}aI+@A^`tX1|anTB3 z#V*%I9~r25cpZ~rT=(=?RgG4LDKA98VQEGc<=^@%VvgQm8dx-MOuT)i;pIcwWbLd^ zmCEbZv2&U>CO!np^2PxZKL+mmzH}VrPmcvwHo-6Y)TordKIUZT9necEdwDLQQgd4= ze%(fjH(KLy@`?qyycdv*QAc;2oPyHl1Ui8^H=+*7Rv-U+o(adZ4Q{F{+*EK}cru#W zqg%VGru46jCU5j7&++la%se<~e!mUKemi$ZD^xu?s9f|;tX%kFN`YwI$Ugl7GDr#) z`1JDJ21cGK1fNVbEO|=q4U&Z)@9a=|O1CJr6KZS@V_(wjzN*%L_JqC-g-PCYr6O9>Q(wZFT4$$*;}^N?@*?YDff|g za5BVxSaI>%oT7A8jza1ccugg69!BGBSzC*&bXL4O;hk~8&;tDL!$9jXo z#3;gf_7d}s=kf9~N5KZ;zZY!$_Bh{$w#SE$#7dJj(mx!I$hZ6YN%h%rx;>g1rf&|& zf?r*N+$PO-P$m9i>eG9>&u*)j0y7sk+L5bu^1i?x&HQ75toz*;?B?O0#lYS?ty<8^ zvGDEgoRDl8La+kFvU4Az<#ToBD+ z#`ph$5Benp*T^9UHb$Jk{s`P93O<}74=((@Iu=*(8odbRlBT<7k_A{DNg_&bCh&ZOuGg9?HK9EE)ApEHA#`>Uw6 zuOc*g6!V{K3J-Xg<4h6sy!hyG+G#mhtR*&xS%_Oj)?u)dD##|?|Lsl16DXetlo3k? zKWVf4Ce-prq14+t8v_jK*0zCl8t)J(yP)ss{q9gD9scop5Es6DLb-wH>c0;CWWg*R!Q?>&;dDbh$E0}QaI2ry7+Uf>}w9S}1x}<=C><_OK;=MP_t zJ|;@SZZJy3;luthEB#cNv#gpvkQ7 zk$KWJI}5HE=zcmuTv>kB3yI|}E-5=}H<@*e)_Z@LIUYt7c4R932}guiXc<&Xl)iq7 zvCS=nhFv+(jAfLBHlwG5*>qGx2O-;?8kz+$SwWp_yN|PN66E;DU}^gJifiBu+$KGT z)}Qhm5ltapcb>3hC+rK%QVR-TO4D{%tQbzF$Utuee7l+%+#70hUA|YrZv)yKcap*)Fn9e z31SV}rwiOPqR}Gy1ztlnYsZEvRYqxA306<<+vj6scqd9&Jr$Ne>;mA)?(E%0B*uSz z0~yDglDp3$A;Z0L<>kz1i<&P3_txw2P@ECaNaI_uR{_ms_l2_PBi@`vOib3NN(!{$ zrOI|}E`rlrOR6_){ZsC^3O7@2OsuC1u{z>VfBk9MQ1`{}C5oUpbwcy?6_#aP2VOj6 zzbByjW3%IgA_y_iR$m7@r>(zl6F7Tel*0-Z{?Yj1s1V*O7)+O2F~ha55VNS8HeQCT}GM zrJitJ(3lq8eJ2^=TOnRG95`oi=jzC3J^phHjD}*@%Y>u=jj2d!*UMn>wrncDFUSw- zdV!Cv3#kT7G_GSjmS-=-pLrrzIp3OT6$UBB2e)MVk;eVNR~VY3r1Bwrn07XUh3+6L z-|poVhYnEhA1*!oW{qZGEZDtGEs&=8vc$#4g5ICnQI7fgtxz<$`{51arkl|UX$1*wLXL!jYtn)pV4F_@^Cv=gktK`6JTj#Mfr2wVPfcgCiAzmv2%N&&G zZU9yr3Wu*83K53Y4OqD^@O!$fqI@EFc=0~%QagjlC>|nfKK`cQkJ_$cG98rrgdc%) zM|`gu)jOOqFL~VEJdmU810e7QeT@)b%H@wtSuiFjLifwf?R+8pLK_Cq~tj?kpg-lz*iUwhNk`3 zr%DGTG&b;rczcD16hz4;-Bzo46>yCnl~9ghQ+A=~+x!oEX4={v+o~J^35~nC<-mu^ zQNVsfp_uS+X9u|Dk3g6*k8I1eY?qpz2DnWP$+{eVCC9OMmH@GE3=U7>oPFFCtnDM>4$Ay-d-#2N4;f0GvS$flC}n* zbi9xPe>2#5@6>txJ0FZF=j6ytyp3kV71i+%tRGJfI<+rI9`klz_`vAiTLY}ZenJ}$ z?O~Uh7tWV%ckr|DpT9Zps(!J^|A@5-y)$H)(pHwY)BkX=xepgsJCL)UhP4c+QU=Bc zq2Bowz#iW%E3-{{pR(=EA42`jS(5;Ylskk^&(X;#Mgu;u-(mFP^z*43%)1_CZZKtz z+OTfn`XaYlPKJR9e*%HogYGZ`_t z1xiGUISylefTe|j71@7=pIh1`!wtY~8xZ6x2Z0&ph*pT1s#;ZNY}LNVdp{`hV+b=W z3AiqHbMp&2!~IzRlSp6C-m;KPR1HMhFYoNdW<%`^+B-$g!B2iL!TG>&@|N{YMy zwS;x#x>mGh+4lDa%ifPs3Oo5q^K~DNwI))!PQSp9+r#v5p%Y~5y-8_1A@q?ifI!h*0yn6U=d9GR zt;@RTm#s{1L3jO_?13SU8<0QhpMP!MBowcenUtuTtEsB0+6Q_i_h(0%!o{P8&ZZkH zYouMi!6{`|_JpDco3b6pEe$=Bz_;F7~Ma=~$cvU>Y^v2Sd< z&lG$P@WIT~u#*jaj=#$(>!B^mS(|AE^Vxuqw)1G0C?7V zRmzAL#m*utegn`ajn?mvg3$w??!Ut-mcQ{mFcFujciKYfDCja^VYUwTl2W5VhZZE|c znuYJS7eqf+3+YU6dnjV?3)o$;o{_urvqSAUnTJ8CL>1kgJU8bJEX(s?EzI+h$&b=) ziFLnpJMXMw_~NYM;(XCpjyte$d=QBT#wRqjmr&!opAGk*fnsej+ipv zHI})f#q!$4y@@}#0BLQ}G>I+)@k5*DUC)g>@(X*!MJ^JHzHt8i3NO#3EQAMbbgTaY zLEl~xRVPNS#%()SxZC>N6n*SN6lD&y7MJPc-psI774~u4-vxN}vl&X{ijKNll+ox0_u{n(dtXiAdBmzwb#IS&t3o5{y)1(JLwlv)awvH*y9 zTIKu$#f@Eo@ccQX@F)bVlQn0~7h6O#Fkzo_dr zb<9=;0>?g3fe!sY?EPt6&1?GxjH?|=BBeq!ON6L24@(&em1HQQ&>+pF5*ilCSenqN zkVvDVS!FKG14*f9)F7Hm!*g6~vG=z3zVChiU;LlX^Wyhn`|NGm`mXDHUgvO}$9bG+ zH*QSQ)bKP~1Q3}pxsC>{6zqL0SM9|l_et1KPa_KHNgt?=8!lNk>7H>v(f$6>RwR~- z*jSF(HG$0?Ahhw~hF5bJ%WWqBoyCt1o;@oI{Ke+?Y1ZQC8T$chDBNS@wlwmQ1sA_` z;>Brx1DTF-c_r4B9Q;AKs|i`H!+YCu+DpYQLC6+*}Sl53U?U=L_* z>4!>NaOfR)HVtjXAduF}m6pn(PJ5fMucUrJgO^ee-vF}OQZ`U_{PJ0@qbqr2++BZDcDk)T~sgj@*Mprf6W zO%Pkp8j8O`;&hGeymHUBPm^D&pgwteki#^g;wNUTiUuUJ()h5bgMfpBZqPtQQsEA&`r z_42mU>yB3Y5ol`+JVp137K}AqUKkf?UWzq<{mK*Ohiw}Df@=2#3Qd3MyTno@yW?hL z#6Zu+MO!ZCjEcoaN`?2X7Cs}IoczJMVtGZ*jqe}qJKK*LelgEq_@6zY7cM(RZpeQZ z?Y4UXBXUsW?Yf?7uWkV-YB14Qj zNT=>z;sF65)6CBYts7uIr}En4Va2%kDkAK$0sHkSzcDVF-()8WM?h(mi}o3|xPs6= zR@(vzqYDeO-6u|PE~+kD3rwnPkT(GE8KIs@f|#bawb47;mXq-R4j(-?=;)mqVY-uj z`jG=J>EhZG;l7Tty#K8=Z%^nXUmDgr(!7!gd(?&cQqk|N-Fx;c-Y?v^XvbqHGe`I@fIMJDWJBP*eOVCUk5X8gF2(a z5VQkvEFB|CH%D^E+bWbFesPh@%;to&Mdfp^g=S?pRuK_I09bK$OHmhD6y?p!C)3dC1v?CNkRtNie_J8;EcV@z|wRcySeU@`c z{rF${{2Y5=exJT9ks?GSb`3@}TYIchj7r_#Jh~lvYL}28!~RiJ{C6S+srw|&2QvZZDZ6{XY^lpmyH~rxy$JqK?b%FB0uN= zwekXppIfVR0p~8sY1Oc79b(fQ8~%;$H-Z_2%G$2R-qg!S{4^T9q9YxJZoU|Z!_|o+ zLwlv{3(k5j#W6;x^UFItAI~lr8|*U028llWjhb<)Q#@ih){|=en`GD|y7HFYQC7+P z{IcVE&9;wH5sprqggia>FfNNY7U0T$#(lLy&%>(doOIp0C)KQEL*ub0A0sFzbz3ppaq+-cq}5U-YyDxKE^iNJ?|}A_ z|DdHlrBO6$s=43*MfGmZ4;EwpjA?G1o_ttVN7$Q%pv$aA|LIERd2il53450k*u}!G zDxHat+DV);cyQ?Ks6op;^nh4c36E;*aea=U1@iPZHcfxaqtCtqbO>xt8v^zy_#H>Fz!umMVM zbykN^?EdVu(UrTSHRjF#M?&xUY@Bumryg-G$Q5%yO1R;YT_66L+y%$O&-B{?lBJ{x zk^6PXmWnsrhPIq2sAESTUY2Tu zagq%~f^277S)>FoF3FMyWWVe<#O@@uQkR9>sTK1nSWmhrthSS7=2V+TjplrpVtQ#M zq9^)sLYUCIy3#PC+-o{P3z4Wks*X31`n*kl4RVjaX7-*-Na7S8dzvIBo|GF*M}*2a z_a*A|5wpvj@rh|O;{oYL`4vGW0u3TfyVtn1cj;;A>FGJTaNJhrA{uTjNW;%(wxQXx zA*`Z`Z>+itG`E+hba?kFx_s&Wd@5k#ko;Mu0hW&zjpIZMRMW5FL_qiV_VzIxoKYhV z>oe4uBOj&C@?r{?U;Fw7CFL9wBAAKV88`ty()z?`&@$|_^e2lI+pL8Rdd^q1dE9O8 z?oql*Ov0QIwKJz%SE% zvI;3fqohYj`8tL#0S6!LZ@&)6>Gee}@#3<9;X#nJWIam0=;ygVCI}Q7(aWUT3H=b% zUF#c^ShlwYOEM!I-*+umIi>$FQDwPd`+aY&6+74H%+@)1M1NBxcfoUa#ylh9vESU^6^P_(E0uBjr=v@X z6&34Kg+$n=CI&w_&Vv47FDKViLBDuGDk1xWx4sk;q`oXn9qo*4Yukrv|N@1D*VUYpeEM^qx6z9J>QbIF}3t zOPTvxR0HPN9CIlL(|@Lb!`9laTmWqJSkL=)3X>R2zMARS{u+No%J4QQaD7mQHaQYq z^|cI3Ojg?FeA^V`^vJ^fYtxfTG$CfImKB8y^SA=|Dmfvg2w2KAUV6jLS3sASVvE_) zYhkKDBR`klJmdQ8%;Y+Ufu6cOGv+K_;Mf4{m4>_7F4j<_JewD|Oy?R_LW;C0NyJa& zoeXrD;l=bRZ}`9|Z9yEe+buOYrhrue#%INd)lU2BaVi5{XlD2tqJ7WX*^=^v(!&RqX-6e~cF{c5IH%>VYdUkqhy1V@M zT{XTdcCn+NvgKfI<7;0h5>+yUB1y`&`TSzy$ObJPH)#{jC~0QzfJ2p%c5BYcbrt9t ztTNJ>n(l^E2X8BAYWdBCX4>2u}Mu~q4B}65)P7y|!HaWut~M;#rE@ zcjx}MTyC$exBDi0&pY#}NXbFMZzV$ij2tKh?1iCY?i$z>}iKodP&}%jT};% zXRd*DOM!n|Zz_19*8$0x9KRjbBd-hk5%C_s{37rTXC%^aW9?z`+f)(a6-v#1r0P4@ z3ykBTPG`P?t2apt)zF9nLw4b!K`B^%)NWW491A2VLks?0_gj#z9NW z<#jyVvCk&N=}5CNYW(Wzot$#|50B^;uRB(P!0=jH?khO%8USufj;Fu%w%{SaC)jmIlGYU$;y^qjHJ9%XC!yj^?L;t2hGjIdZ9Z=ac>-=MUv90Li@Gkv{LdX zC%(4=A(8fIe3Oj5K1%EpE&@e*Hy8htMKOZqp^>k3_%$uC6be%~Gb0}V$XH@eQ$|qt zFgafNfPLfFR$1OKGeQ{5#5HUk`PS?JO8X|BI9t|WNz;oKm#bb5VcOLIF!$n#OqpUb zPS@5|yr^c-=qcQkeMOrKo8Xb#o8LEM4Nki2Nl!i!*Id;pE$5Xojq)1kg{WX#2@`@& z5QriB-zM%4yDQJH=)CQin?<8xl+Smpy!0oSE0aJXrWu1rSMN(J4!`{hOPfW|O!JvM z1q)x>50slM$DGam-s?C9u!FlMFoWO#*t`g-?&8M;Js_p}4gHz@Hrh7~=5c7;cx-lu zS>y~wU!8GGe#04$6CLIe*!3d1y6*GC(ZND&sUL38;@@5_= zj~PRAw^X`p^BlKZi7KtSSHOLM#|oJ-&Z#XYsE|vib60lMU=})&g|9;wjP&5U{axdP zpO03$KUOI3A}I*!;VU=dY!{aIwmX%+_cQ`IJlIF;`$PAMCs=1=*06|O{s4LlnJ58E zuEQM@Y8A^q406>;KZ#aGiFW;?#S75I1I9=Dv(x6ZeF#@VEb&u1S+ZrG#tdrmS*FSH zejV3=ip{NH8v8T+HbVn{`cswvdJdljH~RjvUvEPvID6*m;%-dY!jqv28xC(+ij`Q2Z+M|^mNvj zZmbb9Q;JJ?hFNHt>(}+hNd7DD>hQtb%L#N4=as8oOmln! zu{8fb@n`=pNHzZQ0Da<$+nW#H=;Jr|hzdx{u^vMXj)uqJe=vilO%|*NTOR9e*iTkz zOaM8^2Y;sp0t=-N+DP?!ddAfiyDs{c6on%z6OT}I@jaj(`DF@(s8)SdNz$eVcpjY2 z5Uq^4fWr+J{U*!P#a8ZpC^ptZ3fxc`>(Gx{jG2#|b2MhW-00~?G1Si#V`Lr?VfXgS zN7LcXo}>dQhhEHDsBr9_wAH)v&=^`W7_Sws221dq%Q!Y8jTHDzPtLf;dCr{-q5*`z zI{MSAVICcd&&O#2AYd;kg6n$eMevU4)UT{)57Hu`UqFBm(z24|d+2j%5|jm5-a_{n zoqknRcpD<~`Q<@1$N^~rcwkv@mkAF?Idv@hl=XPI*+{o9a^u^BeePi8lW7FR5>-;R zYoZjrS$>X|=l}W(y9sK5^$knfyvgtT4UoDsbNaZ$P+a1YUH1jp-SP30`ol@;jPI@1 zLa@H$(7FX+9!|3;kD`K;^G?aas8Nz#K4%WghQw^sBJL1mT`VFwh%o4M<<92C%=Zz1 zQ)IMOdoAY*oBm+{|=~0CwK?db?VEHNdr^9J5 zfemD*gWGyAHyO5l)B8tB`_i>b<=YqIsm7g-DT%8Q-YC8=i-2E&AQgdO7xpo%?Js*n z<|%os@0LtyzZy+pzDRe%1C9Ow(6wuG-LfwOOa}w(ugbq3}1C~i@jCZ4_-_*xua$wP!Sl&Qa&-@hhdFZ#isBBZJ&cJuAs9Ymd<`e>M(}uZmWLM!YNG@ zAKmT6#9^5iyo!<8M8fYLs8O?1afvEC()Iu;Nw;=>Ki+#R+?MqwDH5B`i1+@f`jJif%(yP-$BBhfH(K??$sHc_nT7*=rkvJ&{7kNX6gp<#EY z+MG&B>nKRV=m7MM<~=q8=25JG@iZYa{fDaYd9ejfY6s?E`yV6T{Upu;BF0Q%Ke}ZE zMUqync|dehiXV}lc*VM-%*OE1ZDSVg=#|J;EaS_5G^Z^h+>k^m!|x?W=P_qfZk=TU z?MwjH8mOl&zp>#|zZT(dpZXv~x38=P7s*_t3UfzZ`Uh;|LQa~b5hi1ODwtvEKj9Cj zh2Vn36rU*3kCluWIw+=}r58C{xR{<-A)<;6P#c4cI=2o^4zAG4L(Gfr{56E z2mV~-KLwtV~aq7|%~E3TJ3C%Z|G1f7_rpUg_%AM?Nnw)}fTYq9E=Qw9Di z52G4YArWHQQGSzTHDM1d)Mo%xQWzqf29!by_Y=;fS=9=e-*Amt*!%Xqz3#7G*iQqt zWB!>0{u?sN$G;j!K0j1MMMTjJhJ~Yh1dFL;7QyeC%Lr7Guhqhi@xv>A(N$bj*yA2q?Epuwl@@6MM&*pMe z;%f&7?4;T;=oK>9xhO?3G2_!LGT=8~eD_KPD}?JxfD*IsBJZ=TM4Mum3(`j!~c6OcKBrju)14~R~CN`YaC?pQ!s-Q4k^`ApEdD) z_&TnaUSE3||B}EO`L5}{e99}ZU%`tkGzfWHy%*Duff%!WK0cmL{q5G2zaYo%^W$qc zID+NuZ$Z#uNrmIFhTmq)KA-s#<-#5FvKt^Wz1wC8B35|2KzQg!a1Fw> zMECv(+d;UTTejR}tSZ(36(pg}5KwPNhTX?)?H`>>7hNnDeTu1t0x&d+v&b5Qm0cS} z?J-v^rK6mOZXto3HT;D(I+)%$$dG`UzoYV}Mv`~N>sQ3DbDJizhsV^;m{dW|mca|4 zIfw7*x6c|!0bF@cSNL;dULIPs zqCu7>x_|c|(-MZl>*93o3+3YOphRw*CmYCm%V_HIu@@7(S>J)(< z$^FZV>K4eLe}6e1y^lNTKc56LXtC_! zUa2o&`Z|c^KmL5wF!>R5Z$q24PfwNqcLM;ZR1yWQKt@N^?X76F&U~_wix$8#gJ<0( z3DQk2vK-w6qqBWb>p1J|vl4#rZx%>KlS#6O|A7kmf%?*+5qdlG2S)NV?^QgK)NWv` z$<5m^|ClVy9_^QpNEMA|nNZbgT^ds;?3?e)uDuljh5eEo-v*6$zs@3^z25PnBJ zdlNS;CH>WmFwwL3|H1au7*XLPoyO4STqR@kZ+tuLX<5%qy-y@= zQN*Sry;_D`^XBXYYbZ8YnY=yGf8#$m^g>cT)b>t$dxe|<`w<#GdF9_43um@%7to4r&*w?tWM0%3-m`-NHm zAvF%zg$GFaqSQs3N7rDc9YEl}9{@888qXV`kRkjb%PBSW$XxpED}5GgznuR1k?Q6W z)Rdfld7}T>e>!~({0S^{iC9N_{UVoX-~RQ#eR~G%ltbYeB=6W(fSogk%%AY>p}+oD zRm}uL!0RVN7JwroG4>@H)OGkD+zus$Brb0w%s1~U`5@S}fVdL~F8Vf-wkWa=Wn831K&2ae*U2HPiQ7TJPop-)^bR86e>!?C za`ZiNU6@)xS6qyC^wqh<5x%|9<>xjFvTtSD|r1#vFuD-=?R&lcV22 z!^JTLBuS>0&+6F6M5HB9!_4hjWPp%Kcd&nW0KaYU`#}B;#K~urr=;XCFZ#76``CDR zH|jxWc<6WbF|)|K_$n?^j@%wSNyli9sYz$-FEF#K*2Nh?VT0M71Mwdb5k)rWwbiytC}L~Wk-4he&L#B$On64PRPfOW+8~yUZ@8l=Rf#? zrz8e-D;AH@-VX;o2)6&XcR&&_&l10Ukf;POV-|oeW1T_xS^70dU1O@JHf8V<1hpre zAhEDDM01#IiagbKL20bxb{`oH`&pBX|`rltz z(QGnc8?i~=FaLTT37CrUm|Tv|%T)+SX@C1`XTmN^6Y4Jy5LCn_UXBNHPZBg2v(^U!hR=I$Yk-1hCW-{fy12cC-v-bJgo_lG4KM8qcRO#gj+5aSEOs z3-ZznKt}KS|HFgWVVT<&WC4WV|KA>j%@qZeDIraP%Wb>50BQa&sS52s;{g*TZ_*M( zh{uNx5|BswQqt$(O|DAGrFre}9=$Wdfqwj(GhC z#w+V@8~jY0T8}Rb^>-9GG>!4?^pVE{-Z{XAZ%s|y)`?VUF!YQ22dBZT59JbLpHMZr zy|zHCp|3uywRJY}pJ6=0rR%3rFO$kboII;7BFOP(@}&e8Fv_SgLJKLD$QtDW@HJlf z{@ITQc@}GWa(Sq~wWi)ohIU39cZodxVgKs5`fAq)>{r5phLU`8An6}T=NE_~#e<9V?4KhgJif9`#HWLWi$q{@QpjL-`l8qGET6 zh+lc#s}|a2hbu%Dw&d6L*gs@p0u6#xBo8UpigVB2h4K#35z`G4 z>1Elzs%0X^TSFC?<#-CZhb6P^JP(AVGn;%NC_kBQN59A2K9}M0KeSlbzwU=?>)n@6 zzx3<{vl=sCGxj9v^<6QlS}}`-75 zA+yLiSWC-0^WF5^EsAC>a{r69+=Gx4)VpK}(Ls?l#qg)q>S*L&lXhaaLpqS3$t6s? z5Pw{jfPJ{g4!x>zbJOvI?Rq8R=#&~eTCNiHuer*%*({6Y>?kBF9;->_qh-vgts8#+ zOuwPwzDrL0@+WRnCSI3>^Jt{_15v+2I7Hx{1)GSQXZ~WN6uIfS??*I`9rR*)n)xbz z76Ztr`EK;&{(r_Wo=b@&BSW_MkoBm;=oy_7Q+kc_ciS+_yDoory0pmlBnQ3rjR8km z2t5hQqp3OQziU1`dX08df3GOK_ISEq{rY88CiK& z2a%i-{n_r7FVFqRs2+KlFtc>NOIzgaxk!eg}u-6~G2rqXc!0VW* z@+|I^!99B?C%@ZPoV<_8*a~~tb0F$fImz;giv9@SD~1Enf!YD++&|Iv*{~q!L$9gx zLF%N|=A}>j;>+wO$4AMySP?L#_?j4jUp<58Fylbnaof&;i?Kp|9@;ad<}h4`pD<)} zP@w<8Z}XefU6q`sT&(}y5*Km=tGH!#Oko+oA@j8=KOmHZrCxv*ow{~gH0C}oj!=}< zE5+W_oqQ)c1S>pLKNal7;&`Y#m`G6~pC`tIEWun{0AT%Ke)Lns7d<^bIoj@%8h9JX zaoZyFZyY6+6xx8$vVSN`UaajdA;k)+FSWSW6Y`G-Nbk!4I+FS}^WM_tDSm&IB9Ep` ztk8Tsu~Ov%0=GzoSt`NQrcs}BHmPSuDEI&CUB4m7V7+n@6nc`;ICD3v|L{C}PY06N z@GZqxMSdJhwf!(`6;c!v9bI60eZSdc4mxSIP#+p~L|4-UL>}s#S!7^{sBv5s-*XM1 z_ld%mj=ku!bdG>!K=K*M@GvMjK-M)7aEs#oH<))Cg@)^LVDm20Dv}zJ98;h=w04FI z%q>Nb93!Orf{au$1c_0aB6Q}@XTVxTZ(Td=4>aLY+dLTdQp}90w4+ z53Kzcm(d~bZ9(%)x-ksRYZi`Z<$-Xtx$9ys?0cLueqhs6#vCny<}`FgYQ`r?1!-R= zwfgd}!O&R8)1B+&+ns9pI!H_0JN4+&uhhj%Bin7-x&PhiV`VUqr~?nze*b(oGA(rh z{|}_@Ek|D^vMU?cgenNwE|j$im){?`_y8mwwn;ca{yW&)QCa%=Ev@d)SJ164LSSm1 zj3Ki z(3>_^Y`a2xpyy9sj7henAgwJpOo2>tlg37PANozN(OSCR`kBaHUXsYwC%`FO?E2!m z84uSQHJ*9^oydMiHg-cu5QugVRp3oYnP`q=qP0tO5(&0!dhZS!cv$f@3gD#`ml3X| zyj_#0>$4|-{aXk#6O5~RrO!DSIK7o8fe`}P!-KIC3NsUKzk_;5v;t?I7Xw=AP=E9k7M(G=+HZwWh^p3`;n=k(1jYR6U-*DVI^ zCfQSEmC+fNMaRpV)ZgDm15va(>f8*@fv(~?k9~8O-^83_+bd4dZGLD;X#%h%QaCAx zU(M;C!0H^0;V&O(XE-nwlno8;JCNQr>xMXo;1!jzBLhlmjG4J_Qj`R(@~$@&woGgR zg~jGo$gkNzIa9|TmiNAE_%WkJ(GXBc{HYtLgM0dg<*+Xg?WW3~UISxZj3UzR`Evny zbb-wbp*{|z>|+-3L(FywYo&olE~)nJ&hJ9~F>+(oAQ1HmJf>DJPHj71|HRKQ%5R`E zYCv|R^%cKIP?$|tp%QZX*4J$oW7B8Nx8vd{zhH4@3f3$}s98WJG!cVKP8qqja#buF zw#=tA9xad2;^kaa?s{Z@L=|X>2i2GqVi2Ubny6(Id^+5 zjwA1qZ=M35ajj$go}?SDUHRmW5oKRhD21W5s?~#FvGEm@)Fvb`nN_uB3LKUq#PtOe zwV(yw_L(v|_3f=ddm9sAGBPo%oRu@&vswDJB1=6a1mee98({ zNF5z2o7?4CzGf;f6Lg0(&Yw;N#n{A(3#ogG=Q9u(p}xa@HT`3I1VGnt>eNZs|DTUV zjS+(L*9i5Es0JHkI^4m@>Q%!vP4G&P8uY|^*eEJ6ntTuMkvjgJT+BViV-U3 zi)(9fE)m~pllUY6(8?g@D>#*pwOe;T}*TF+9s8q%)$kuZ3&^KpSReyJlGQE)HN!{K-(tBE-%WwOabX8p{B$LLv9$gV>1`pnP;UVR71(f#IZ)<=#D zPlT~A)}e@qdL~=dO7;(VtyyTYqU0wRfdAn4$FrXv_?D^6oSnazejYy|5LtsY@?Cp{HhsGiKVChg)sShlUvvI1IQVX2RF@#Y%?ksrdC=J2an zJ)L>NWJr|3?e$dNov^RD{Q?C)eR!iUQ)&b#V&&DqT3siBy06f08@p$fdF%P5(mw_J|{e!ai7>91?i3HBinD7 zXBkFx5E$9<>@#DCpg^8-nk?ZoHTg^{+0-D{Hnbu{xmG^ChPiVD)G{Wgqs8JZ^odpZ?Tm?=GLJvraE0vx)2vb&bNNs`$r->xjNK<^-nrPY(Y!_2$&n)Rf>-!G!pj z4d*!`N2^{wujCijz4v@vvv1k*mpYJ7U9(f{(mIPst`|eAE(D8gnJkWsNif^EQ)*S* z&58{>dK+864Y*FY=9PV)_!3^zl~da9dLyYW)6m)Au~ECylwo9$8jvb3nT>k@kjFo zspwJ%5A8|+JO$c55r{T;STc6a3m#i}NS5VD9%=D_`zte1q>7=FsU`7Ww()yNT;g0l zdGu}13*H?+b{G8c-Axy#ecxrckmWskbRa`)i|0mH-BULu(ps(J8p_v% zFVM_y_i^?YxG!H@4EOZ)&n_`FEm+fGLd)VBv~zGL|1k`_rsH*Cx59 z8M*&8ko{hayAl~4@aRHHCW-tV@z~?1kZ=-Z#}LrW9$MAzAZ>jttqME^R^1K=Sv!^z zwLHk{+!-ab&K8Uj$Xb`*4))8(Or|(yW@X*T==6r(_5o~&N+GjqZDnNMf3e#@MOzsc z7Z=$L@ES8?C5?kUv*73@G&Lwi9{A1Z{n@WA7pXCR^q3i^SoNy0=+lOad0D|uC%-+? z`}JTwDdCLWdoJh&rm)*#oZ2({uzC^Q+p%-E7H(tQSDMzTwdv9L(QmG|_dmG(xoZVN;D=Fw(`XT?joVd(SE@j3T< zyz9ScsWlSk_Tln4uC4}T3s#PTZJ0-_@d z3_t{fQ}?7L{JaO5NGSc{@vvgo^I7RO2r^MAJ9}XD$82)7g$MzKrLvcY{ zt~t!JBRZ@5n%7&>5A&W#{w<{tmV-sLyB`umqRL8sptn=5 zv23av6Kp{!(`u9^RHw?9jjE!oxOLNWyqOGHkTIynZBdb*RhvBqe1BsyQG| zRFI+)_WI6XI(B&w>(N4&r=!+NK`m7t*b<*wDmhj97|YzWJnk!!AP8l?d+6mnJr#fO zQtzpD#kMk+Mz?f}+qu;B)~xcASlSiZH&{OHB^FugYOIaTeA(W(b>A6w(+@UrKGhHf z|KT=hewtm|6WL9;94qx9!92b*SGv>ko}}seLCd6a(!#CZgk3&5`ep~_+IR(zJ@d3N zDc53cocQ9>S|EHS2!~_{eyA{lWcnTrQbZDpqPvAr|6C*4&UqYPkIt$BIVV|+-7Ho2 zjh&}V`mQ9EoQ+ZE?W-PbZL=tN#dk8E44`cc;-0!QS7jus!)()s;1kg3G!rkstAGUe zMeQ=(u9;aM$--J09rHxwfOwzhjY2rK3Q{zp7$AYtvG`Ot@1|VDPKcZ9a@-oAI=wcah0jomP7<%Z~E$QabZ|Sx3XUEa4Df?Sp1_ zA?Ap{wqG5S3Qn-U?-Iq(k_hmb^2WcOQw_qHq3+uxBU=?BfH#|0rnv6WW_#h%H<~CP z#k3W=+oqD+xoSec?nzxJpTlJ?^oJ*vE2g z=LThXC&GHLS)Ve-Z#FmrzI8P2)by+KT5olBhG7a0UBdkNju?X~f^y?hDf7}|?l>Kh zx63&3a6%;nNvDjm>`bq{muBZV>l-)qQ@iFObw)A>cNyehLMEt?Wg`ybmQHMLTc~Am zS=c&D(J^OXqZ+;!R}_|t(9Es%4|6LqYx%l6`~qbM64;03uD9h^jKhj0+33YJ zr9mOTc+{9Ck7Mb8BUOVjQR21gc-eRVvF6UgDxRqobq}TL=4!@=AfCjZG2N|O$9?WK zk^X=ih-oLAhlp$!!`Z3H;XDEdktTQXrZiN$TSbl8&laD>91K5T8d0cbZPTZyvl6e4 zsUQv7susk@oNQ-{pu{_E^=F0@whffbqc>Auu(a69ygwi(pjUNM_^A6~9s7jkk#S+^ zQ&c5P5LV`ScyM-B*+ud#I#j)EWG=r-eY_sg8gzx%-_ST88Wpmsf_Y#hw$+37x88j? zyRlHmV@!`=LZ&Y-FNCg$A@rRS2qVpV_QC+=VHB{kyrYWXkRk_7WJ<8zRH2g$ya(YnS-QCwuZw96jj&HR-Z#qJ*_TW^l>*$WtN zHU1otSFn4@Ft5&l520R3+4l7fGG~vfcDriIKUgTg+@f>6Kn{%bW5UxLkSdIy-%cmR zu?Koiwx6?&ty1;;aj6JF0-{YAKNw;qTJ3lgMo0V9B<(K_TyeO~8fdQI-rs7h-K# z`>}(n7+WGDj)pEDFmBwSgq7Up#by)2SClHP(z*a;r)g$AFseI2ba>9Nh0Jw&mZw4; z_TS96w&#AL%7^V4@Ru6+n_H0t1#UaCzNowllO9U_&Qx%owQNo*EVw52K|^Fu-|5E3 zOZ)jp^&Gf%aFT30)^k$Pq-UQ;z+AHu6GxZRhwH6h#vfH@%<^4;wL&D-h!?-lH#T}t z_jd6Wv%le{o6l%g&42H%{L6f-GuCu<*XRv+s zVQ{0oAcO5)z9r%m<0CcpDQJL(iaVXzcYXE#qpMn>18^PF3!y|1@~KTYP=SbWESkLf zS_QPW;+NxuxQPzX3hU}3ReLDKn+ZqqjZI}e(20BBjC2% zd}gdmh{H^-_}C08C@cFgJh;58Ch2yuCh*^AF{8tP<;<{)*`Ot!7RFreEX~n@kH2*LOCefwojjnc0iHmxhZ{&EWj8UlG3Z=hg#7 zl6aggo2Yx)zoK;ZoI1R1E-i%aSlKjotA23k?2;4vCX#pA>hqLd5+zG#O(KJ{(Itct zYbQ+K7AB*GKGrf&zUO@~<{#2x47uC&mYbvFP26>*$&qy4RfG5L-CJW#7Akh=O(tb^ z-p3I8q8T7g*r%03LEx>DtNs-NF+ST?SUc9n{HT?N;fQGqaB>s`afq&r;7&+8(r{zCdQ9{!Vje@cpa+Al?#tz0cB(DzV(J}^xjO`9H9sN}-_Cy}q zS7%SQ*kSR*VVPbswDrK7NpxGV0BL;O>D=tI$h1M!4gw=Z81UpdsnH~xH|pd1{wtJn zO;K~j*gURXpy<3(ka|S#0@-OvP2s$^h@)UyH8*#;C#7DeBaJQTLFy6?tLON#W4+F9 z;_!u7RD2=P^*$t|p^KNl_13b*D$uh2mJ5tRzJIj6!l>G@Tdr^}{F& z^fn|dQ<&u84#{$)mS7eZ5y{qjOw^sMU%ju4oJ)#vIyt`X&fVwNzek2y<8cNq^RA+i zmwy6o^M>Nqu(FHI1?tqgwse3tyDV^rWElHxjouwhe=~RAptsYsfTg!KhJ$f_2@3Qz@n|gOtf4?)voQ?!9mk*O=i8hHk0_LNf zMDpX3P3z81KORQ0SI>UzM|rdE^xNHf8pb1EKayH~_tA1x5#|j-<^)+iqq8bj#Y-;J;g_0W(gl=lEt+JynDxucj`CTr**B3W2u+^WGlc%v{JDHuG(r~r;G&ABzf0QD#_9RxI@TpoQx+aopmUJ0XO^cm zF2C^44+VBd=8F9D9T!B<1Lv z6&3xy5GkjfnjdA$jEiQVAonxYv9g#bY+tvsskx3RU~#;hI^*4Nhl@uv)=Z5|^m<4j z+@^)Ys}abr01D4GPcMe#nU{XZaZT+8U%oBr(OISg=)ru3SUTptw?iL(%A{t0h{M3D zEhMjWz5N6)F+yailb~NP&SCL7@Udc+a0&YDC0)6>``;1!%iagQIG0!6*Zw?EK2*N4MDFC|fhvgPO1d}b5JpVrCN_0+Y3=@=9MGMxc5w_V7@aDNbLt%Ub# zq=86OAZuHOfM{Q^k-3s(pP1&CkM89v3?nA&bTMnLha^3jkG}>@lpQax@Y_z_wdE>U zOn@Xz02huwO7ruy*}e$GD_6xQNVa~PDE49k>b*-IDeUcoF)dbVjLR2_g@Q6v%DUl$ zRS-Wn7E_;{Ya0%>!daV7#LTtM3}~rggAu#iSYB?XMr0q+0W89k};bEQyT6d&ZbwCDz}KV%EwOJ&QH%79EslGeQo-0ky4~d4fz``pUNjKRuYi!Eg?! zL(2^4KtJQW8Y9>Q5*2l1rch^=@+5c09-Qj%OE2zUPE`(NqnG2Sl}wd{que8`@qM11 zc9hx%XS>8*(#+I{vodnUTpqg}eDz_ss?{6bw`corv2<)w{E(A|z!@sbwD?5FO-U>W zl5g_-Z3R~}Z>wZjUw@+I!V|Ad2b)dE=Hiuo6*VohsHBW-f+4 zL0cIL!?o5`%X*I5{fLHE{k32hSU1d$;{85&4@%aty|lfj;^l1ad)qLtYn~W;@17r7 z831ap@noXTSOUnT(#1jHsEd~VwowX`T&`3#Sze1D>S^a!v%8?5&qRpq-QaQb0nDQ* zHjSw_Dn&cFfvbgbFtA(UGl)6YA#pF2{iU3*oXXs^D#qJz{Y{=zYW0}!?nbyDL1=@M)rNuzJMmzLe{-> z^{yYdNCnfoB#x3xsq-}A@TwmAF7BZL{bH;?-Ga!2KRjyIsMZxS5@TQbfYw&jn>ZdR6A9h)8_ z3n;=00TZAGG3U`-Dnx(ZbnHtr26A8N-qFDbjk;&aCYkCNR`rnpmquMOav3O%Oi`QL z3MV0@#625G8p|E%Bxg ztF+ttJ3C3)K&Hb9l(MVy(lsf_GZ3mSK@J*hs)a?^dtAI|2}3B;J1Y}&o0g>gv#i(S z(LTO{ptP>aNYif>>B2pz;0Ow3^2~3v)wV88i`0+=R(}X`-uJfKq3@HyCrx#)NM|?k zg+*^5E0GA|9Swb52jF9`q0-<#QD5VE=w|bCr%7VwK1&4|JlfVNua6>Y?>=2O{lF)^ z6oObvw>K~$L7>i>S9?<_Yl{T^2<>(Q`752n#-=W?8)y5#VaJbop>IDu%;sl*c$l5( z722}*RvG2iD@7Q|CV#GSfqc86g;AbQWPi|pF87!h@EVfk)_Rb`bA z1gswg;>+#Ym!#sqr!E8eYySr04qK_O-DLi7@9xAKE$Z0wrc-p-@cfYC z(^?Wtv>2^qQlw{`{``US zD5N8W2?7kHZVTo_yN^YO?sX0Hlu}=>euyy6e}gUdx7?CrQi7T{`&3kl&D>BZIeXmD z75dZrVipnh>vq2iTNMoP)nz619ayej^e4|hNHyVUtq?RsX3JOmM|vhF^L}I8+T%m$ z`xDL46wr}0Fd=(!)#|h^S{p$iZMoWdW`XT(G<#w&)XxsO-wtcq(z-G8GnvQY73WcB zME_{-2~o~f8>413DWLPt#g)ZE6<*Eowr9O2db%a6Uz1g#KfMz*8X083L%Bgg`jE8C zQF>l35cJ~!XBlkx;{>rQtml>{Q`ry=rafdnqlOG#HO@+|Js(1lcrQ#=_&GW(7fHtU zl8W>!d-R4+G7`82>a$Gak*)*m_;6&m zr_)J0QFe}|^k29Tl`HbyuOcKKsp3}p$pz@yBoK-~dE~&4h3k}CKTD4%DgWCK_QKEY zB|o>G)A$45Voo*2FC}_=z5aZOKa!wadLflvgUF6Pyduy;%jCcA{TT)1e;zbIc!GuG z&n9o`(>&u;T_xWj1o%nPa`#y)$S~Ees0@^Lm zQ|&oFi*og8)c3e#Yt#wia~$4&KCQy{oT6RCZ5sJgquQwG^Oa#hPu2>GG&LyQ+4zTN z!idg)Y`*-cZUcY!!0ePLXL#viBDg8Q#~{P|qFG}(fIXt)UcZ2oX0|L z4Z&4b$70@}uay@kBB|W6QZ<1dJYJ>B>4*Hpl=Cfr<~1$J|M$B7?{)p(?fSpl^@r!B z{6jJN{{d%si?utYXmy3eia>GHf-U=XSXhqBMju0hwvr{ewtz$F4c0hP8M%Z~)TuSx zE(-;z;rxQsPi-7~NZdTjoNP1q{yYvb6H$A%KfA~bfqZu^o=sF4f$d}}_l|7L40iUN(cW=_LFF%8}K0YLKjJrIv5eU&Agay zl?f;QDFvIxAd9&6>7`uxET%21(9EuW8bs5?@TAQ95k&R>K!AY;M43scF;=v5?je!C zRVd%=w!=b;F(4Z+P(&55aBzq@%h)U9E3xlK7$^rVW5D(K50M#qve4uWs!;ucnR}^} zScI;iy}^C-q)<%lM=0HD`#Wkg9Y7|LrZm7R1v359hvrS8OWybW@_hxutzhI_Cu?g_ zm@Ii5FyHV2N&u`Q%6xeA%@znnNmLyI@UlQIlqRLeVVvr}^!R-&)~!aeS-^%@QmtjQ z2!jWFMQo2mOH`SnWh0rUlr+l>-%1``3a$3t@;$=%erMgi4v6{6UG?dX1o2MQ^^sCuv@~ zm?o@>!FAaeoE*$?#jd3I7=AjxGv6sWDg*vSz4I5!Pd0YFhXGj7x0OC>z0CmX7Q zciZo3GX)@v^2?E5_oFEInQ5_F|6v}|BFT0i)kyK5IYO4IZk2c}y}cY{3L_%aUs5$h z8t8F*lCEvb7ub?;P^M?|a@&*bHc7clw_Ym%rSZlpXi^Ss7+p*f5z6#;R?n4)0X!uZ zB0rCVS=L;R6(60~gE4Op^rM7JT~7I#e3So0Kobuu605bJ`5hTcF1ry*x8-+b)m0)@^HS|+?>15uOT$ptUoRly{U{;=(bz7845s*g zY%S6!J@1y7Fq;7I-Oj5#@BrzhEu5JDgI9dwSs9U7(6#bFjy}O|2>FUY!35!672kDr%~HD>1)4Fx3z8XBYW3CwVGiI z4|A68VYU(v)0$jm0S`kY(dPJ&e&cyszfov9(pd%;mih9r8MNd4S*OGk3c?~~E@8iW z$P_nZ#?tkOs7HP}80(+etoSx#aP-#kl%kWHg~q$3AW@Uq7NPKliAh>4innW!?KdKQ zhLs;XvxY89mHI)cGSs0aVc#-<(9UGf>2IIL%kny6J8QhI7A8`bJ9FHFwm#@IZGkG(?)L7Z0Xr(EGF&fC zVKPyTAdhFpea5jrLe|L?Egy5XX@qy2VqGUwVdrV{|FHMw@l?0%+IUv0BAH1cmJ&*+ zC{(f{Lq(KiDl|zcV=A+CXDCgYL!lC-3`Iny=2D`FBttT*%=7df*OKAh`+0u*dH3_~ zKYpM0{bS#sp3U-I-|IT(^Ei+5Tk#Kop%lgcN)Tu0mVEvkU86eG^P~3QRJ08h?B&pA z*QH4~lonhu6AAug>TUA`PB{CpJXBG$j|6!3jKnzHMmsySn?8FLL;f96_sLaG|IJ%vslUI(kCpQyyJXhQ7Pf3*6D z0my`fc#GGV6K>oR=|tU3_y#h+?<^Bdl91r87`Q2cUmw*qZk??Wi!>;!_+(4gX&u~1 zHYDtvWkVhL@(uhgHBn4s6#Aj$@|msJtXuaf-PO6^3;M_JGW&m<+dfBAW3y|^(`oY) z_jv<9Ly$fp(WOS7LN#FLUkFOni|@nLDps-Ra}AzNbR)xm1S;EJ_XoK^XvduJI$L9g zVU;cbg1kO_wddMlkCy_*Z!rHP&n)(ytqs4uE8BX%Hj4GtMTds*An4yGFdwFP%~ z@H2KIrr?*k`Xmg>cs_{k(SGv$gyW&pOo=i!=^bSKf})PqhA_7ggWW>Uz7jf>7^3)T zhIu7&g!bS`szrEVd}y`p3gQeg2zRA^xM^xX28E0IBpLABq+0;340Ej|)b;MS8<)*+ z`0gjj1XO~8{O%G7Qb8?Pu8Pjwz|XyE3ic9T%9kAYvN#p>=#(R7jjN?3g3`oqJw+CM z2d+|N&a4pN2|h666E<;{x+p0Q^=wl}U@Hd)yybi}ZfXVR|E*W^5FM~}MWJ806CfAa z1nEUeIkZZKkGv8GDJxoVMTI49|AIN!-7k*wEZtuyO{%0|$a?rns=fl7Ic|p5flE_b z%(LTMy?;AKtG=zO8HPAskP16Gw=@s9<##Ad9~crvf1jW{0jo(m{~3QzvhU(BDO-bV zei4C5yH;fl=~u7@saUu*%x}Q{&R*Mt=y3fI8}50XeY8H0*%AX8O|JP=v>BC2`!Vsy zR-5Ozu!XN(pnmi{@GnaUB_P9r1UnlsWu80x0|?ZX*l#RUTH0tXtP)5nG#&FK48C2f z+CNe96QmG2yLX`)dmf)XUl=7f^3zp(qf*~gqIf7~r4VlqX__HiS!U0w+>x(Acz2z$ zS*C5-Uk3>dFS{5%{E7*2$s8VR4)uA8hOa$^>PC1QumpG$ z`;GBl`*Pig8vUz_pI&q?b14p1np5A3Iq8xly|bWj3SIo1z#V|*Fs&H~qPuh&DT%e%JT%#rQ}`oFSTjVnK!Sh^f*eP=nenYjl*Pj{Zysm0MPk}P|pWjP31^*%eY$v*D63i{=1yDyWV9w#IAUS(nwEDo`dqQRqZ?hl0;htT=2QUKw4pvDs@8ri4OG3jozy#;Yum78d?Is#_ zU+Iy!6`3WFb!{A}y=PBmv9uFn7K24~RrfwY&Rlx~mDit1w|B4td2Qe8;QYAcKy~fo z{DsVFDO!=tYN;BkrE#PcX}|m5lWK}MBP75-$S1MjvOn<^MTqWpd$QV)mtBkFw2NQ` zB<9m6T|_Z7gb*}DbY^IS*n4f_9f=f1LLmtm)Y_SlVRIbBfwzW#Fngk`Jx&EyBnj)Z zYE&tmA~?bfNLX=K_y3#+c%j3qz#wMVh3H%ze0BA=pnMGeI&2}2w_;^eUa@Z9ObRFS zZ4LgtxBUy@xUnP3%`28Bkz6f7KHGX;|E{19Gq)fDZ8)$ZRx5%IBcP(JPMNZCuAD~1 z3|NvEO7$p+T*m!|T3-BHq5ti4bn6IKJo_@bb>?k<#ua>~jTuhWt8Rx_Sfr?j5s=)y zlg|gv(6$AmC7n?F}-Nm!!M(L4FvMQ~!NQO2eQYuHo9OsX_p# zp=MF?=GdA@c2E`__c;k)lG}HJro8nn`^x5pyP`Ha-#1d62L5a2tBUPo; z@+b|b%Qf|!9K7{hd>!5$XSS@{CeEU@k(Ena}fVG z;zZHy^lUb$%ElR!Pcxk*6!$6Z69|`d)@FAG(Nkt81|Ggznqr)M>T3?WyTwP2nA<)C zaYLw%L3_L)E#wbGDyz&%k=F8FA@2>BSIe-?#lSTt;ky-BBMWcB9SSD)V? z_tuL{AX$7^(X^<&`%ezB*1JyY)T~Y1xBY3zsw4@YFv3nHb1jiQf`t)6ayeXV=BRmf73AwOrgZ^S#evqR)5NYu*9tZc z1gH(HaqFlTmo-nmh+(+JrT)q}XZYmQ0NdFZw#xL7q37pa?&1uL)4s(TGb7sq#cEO! z0Aj@Xc00(DC6vB79P16v{C=bHW8^x%yMT{8Ekm_(@%Bpf{~?1!$|Q+c6oXw6W*CKe zw7;$Z1&y^z$@bvBIHb`9l%U0r^a5v4eazZt(J1LYc*My?`(SuYc-9|q@#&hi`wYIU zP}s9okoPg!IEBEOYfC>cdchIuL+8YJSxY~B{c2*ULpxBi?z%BX!`iI@qyB^%!g{?g z7CMzTPs;InuGA3MjyjOBRqw}YSPQ8fB>Nb^$n4czH!<1AXiUEmvu%9%^LAa8g;f2L-5NW5@WXY6Fa<&{8(ct%Cb?#Zv^AC>s4#V}EwcHnE$x}LUQE*%YQ_HOHz;fE0T(kt zZW=bPle#WJsC&Xx-E)x3hpKu4=Gm4t+@FO#uu5fzF7)PTUe)Gz&WE)S!nqy>ZHevpLhtN*~;-~+j zSV{C6rlmds%jfL*>L-~JVc}36CfmpCryB?l4j%X%b4kTm>9*~iT_}FnHe>hYU$fcg zWHshw!lV)Y*0-6mRadeG_H@ZkGfN%{SjBOCiz;E3?vAx7{dxZtHnw_TtDPh7T?rq>0_Y_V_GvK&js;~Ojc2NhpwUZd@;o`1xlhkw->M?;RF5&LI zVIotP$Q1`=6R-ym8%w#<*suW$nE(!-iG?+#mEDO4-;)SfV!dyl?h00#N~&91@0;-S z)O^(YEhO?rUPJ$rQw7;VQXg^b{WARPbjhUn&>DsCI?Dp!kDa}&+dZsrrCPM3=pJml zRIMPno^R-0(cL<-!PNHreK6RLSz^+BP65;EEjA-S6%}VK zMjH6_LJ)g@(!yLdQ#`&>yLH6zL2m@-40g`~(oGZ3#k)I)E zmblLk+`A}gAad+KFV48Kb(QSvM-4nzH}y$)dw%V^xWJGS?%DD-MZeDek#?wo!X~AU zXT{VkI@YzNJvQ{XDK@ruirI{c4lI9F^EDq zH0o!e~=kI#xK1?*H^BPido6Zc0fPxdLDx1*M9~1tZctGw$g6j^WOb#%EJL>m^H%uuVia?Oi^&H>^5N|6Or(t+DD-Osm8FK%%&>cm-q2mw=h^T= zD89BSJ>AA$e!?SLB@bGS?vi+Qz&R$N`K zg)psPp!4oful6ftHTotU9h#cKxdoOQlY|!KbH72@N!W@@lok@4z2xyA?*R-tTtOHT z|8>cIPWg80>9N|VZq=8vq9VBct!m=Atf(NLsUl15WA4O>awOVF-%dLgL_b~dHI)mw zR{%g>&IK2z{;hDUgVXo*tx}|JCLsHo$vW*Lr=PH3Eb6GecEgV9fn2h(z~&QHbR{f6 z@)g=w2v&oA%nUPD0%TN@EM-0M+Fm9!HHjcG&NtTtm(wEZwAt8pFMni9qaR}-gYhM& z2pQoz)Z!_IW)+9X^0$wA-^esh?<(w2%pFQ=P;;(U(;Omwi8)8UE$KIP!C0U{xSbk0 zk9_oerI}k)pJlF#NR#TYN4(OJx#29tkHm4Kj0{k@)dH5`^;tkZUTstK$%n&x17)^w zTekv#<8J5*&Xlslp|0(JO_lGEmcW!xgZ3E_)4M-mBFob|X$oGwo(9b%@mXQ{Iu_Av z?Own1b@cdtl$NR^j&Uw+U;kROG{W90|H1tQ5)lk`4hw!K1Mi9P3?KGy~66f#-5HasX^yY@s^ zFvl=Tdl$k7s-+mN{aE;IHkC{CnkR2YSWv1~g-FYDv7yE8dR~yv=|L*``WR$b?A~=i z1-l3z7B?0A=34o4F=61eDQU0E;~JD?AfO1me8rnznD?rRa};4@`b3B(F_$QwHhYjz zDyzN=xVqC zChBa%-G8h6hzyu2hSCk79UlhG#e1hd04^5GF1`JfJT)SR?cWm;i;k;cr2xMh11I65^(^wm@?(i+o zuj-VqWW)A$(*NR<+DJHZw>pst4UsmUytNbnkTQ;0J&u0=ELNBF|ug>Mo5&BRbk zaCi~{`HofOZvq7{KSXfvydVPg-#16)>j*asJj!T8V$RhSiYzShP`aZ_P8~x3R)iav zoyo`Od^?e1akJ0!i`1q4&h6Xr4z-6**Zio|q|?DlRE&;WgrS#tAW_M_{J?Uzrh;z> z3N6~5J!ASw299uMv9~vvpT~@&z9L3bZZ7dhHPG8+uKaX!| z@h$X&B=3_QFmI0Y%v(5k0K*%#CkA^1K1}m$|Lbx)mN0~#UD|#dOpRbxp^ywqv*ITy zljxikReg9xP?$IWFU3<}Js3U^j2_ldbf9@Bbn(nIah8-?>Da$Pzgy7wW3zu5%o<6S z3595%O&&pHTbYh^GHdo}bg~`=Gh=q>jmUWz!ZC$yyFAUOaTlMJr~L#H<+yy*sm0oSOn$RE^eI;7>PDs?6(8|92@GG7Xsj}W)cb%SnaJCL@VSk16d zO4vI@_GLO1=qZ`9v5_$;gtt$2XGCqhz7Rcqi>~I|)7b`KI?rQ*Ce#fH?yho5D+1nC zmzfKwbSpJHJCzZQX=(`f9l1U)SZeq+4{v_UQ+=;CzF;-)%)2M?O}#b@4qXEf*#lF) zs&x%>M(h|%oGy(_#VVyqgiXxqHV2P z9vQ~w`}oW}jI`_>`bROc#!8-IM=KfDd_(iEY@3^QH>2?Z8hfdLrIGE^j65{g=OK61 zTRtm7>L>Db>v_F|=DM%zulF~Ehln3&H|Aj3{fydo#4~3QbaNy=Cp?u`oq>_qp4i}t zgt}yDQbyYBA*3*i^*>v_ewNd(Y^q^c~@_uQ%t2ky+WU;A!rgIiEIcU|SX$f8!*|pnKH% zFwH27`Rxm+VOndjAg;TNcF=}1tJ-v&%RwbL7mMZYeq5AgiCLpaLKX8ltRMC&IO3S- z3d=*~yU!4kGL%Sm)z*Sr$HaE~GS41J$=7s8b*?$DAD?BHdz}3~U_fc^i|yK}7hl5; zeL@<#r;Fer$IQ?GUuFM+;a3+$#SN|_s;!;!M=tmmjk&H$mO5;`yMqPPK5S$Z{KY(= z9FK}Dc$qn=$-RG9r8_&K01*OzLv>%rpapCJF+;CyiK=hfnC-n|Q*S|K> z_fjFb2a+yQG6Ct=;iwPqtvxw}Y}^TNR;<~hg_@louH41CKDrh{--nC+x4to`7gCD(1BSpLlGu+6)~~Xj_C-V z!pljj6GUdU??HcRjC-gLI}2lTo?6ZkZFRQEKss93TaaN92>5Kc2@@HrvKAVOtqs@xD;V&L5&%CwLk5Es% zmXI6dlYFzW6z<`oTbH3%W_HW>=X=2P@F`)3#%ydD#Y*e4^kr5AfkM(WLn-~RMQJKw z8mBZszCm_IIq)w)J%|->pEv7X){J^+`YbY)CjS1!HP9k%qT&PtXV%RPvVfOrn6uJs zgrC&MWvr=JB*n==&*sCA!)8d7L9m+eWgo7lD$*obin^Dq*{pjSMI$FgYzgYM=cCcG zDQo01nD|!g}zMy+gV&l4YwCY4e4b0+k z`xXDZE!bW1U$vHRv9l_Jttk7%jtFo}Rx;4vT{UZMnibWgLV<@MjcHOp)pe`}PFcRG zFdp9iinCwuZa^52z(CWfQ#31$QVE*w9|L&)&m0Q6mRzbGL5obg(HGdP?~$V&p+g=` zsohRMVgI;J{C`H0WyBfaC3%Co#(LEbg16R06;hfKpL6@4_9R<84^MzIyh|&G%-*;^ zrG6FcSLMkutdC`_*dTqvm|^8f9K6Tub$w6!!c_i917PK02QQxWQG+~G32*qfZHb~p z3z}UjA7_%jB%+ZBfa2Ov0**{&PnyFrQ4asZlhrBzq#OFLS+OPxDb5i-OShGzw~cei zM#EttT9DnhS9aXeonlgPr6F7D3;M7s7{O=TxGF}SR>OR2`oHigFt?a2Se^edX3Hb|`hSHSNMqu-M*idDOu65DU6{97Xf5XCBC%g7 zwEPe&<2=#RvzLSD|Bj-JLS|6dR)GQgz;WUT2i9f_Z&s(2>)EgHrN4#6eW491q<=>z z#$Toqbwu|%{})fD@}SGHl~TG-avmzVcM@*0?pW_!{eV zvY#~Q1#n9T!(a8-u(479F*K>N|L1>&su1|-M-=>cQ!4*>62)kQL@OwaSWjKdY9yX+ z<7oj>mF}?%=IZ~y*Hd^qC>#By^iW$2(1cS(k5;mP$KoV9Q5Dg4rVZ@k4BAR@78%2H z>qkmtBiBru%95FPhUMH`ZXE^=oPP$d-U#b;6z{dI(N1+O^};$HQph zI68?-EOx9~8?5Snc!eZa>{<}AY+U(E5wrAG!7}_6o-h&`U{lSLSwAjV_}5B`NBF_l z*Uflz$d;{7RfSA?X&)c$u}oGq?j=S-fxmpYFN}oEb|knfKh~33cY|a2 z=Cm2&EMD8kei|nUHaz=WlTPg=~-0 zGWT8`AYDme;FgBo+qwbgL`ICcMPv}v1ZO2p5GoexLZm~>6)220G`0;yf9U=h&X+zh z>Q&0m>rS0Mf$#^%1k2h>)rshb*rSxrd(~kJvHe@fGp_OaQhS#DEmcjL4))c^V!;ZhBw@@(U{uT1kqpWZ z%UR7fin!XtJ8|xsJ^;V)L+SR59Q?kMC=UJ?c|9^X!>tHx@xZD`t-l?sHlFaqF_NFU z4Zl*^$NF*g$d5(Fx^PV`7jN#H_@%buCiVx&E#tu76JDrJxf`6U2>Drs0yMXXt~y0j z&mnCBeF;674ObD&piN7jP*TL}q3J7%-(2?2OeI{Eu5*ymLz{%6Q& zk&TZ?5^I+o5km@ylCHTSm=yNGNDBwBb%puY{#T(d#H92a8_gAPYjygzc|pY zn*mMt04hz@cpqvHJ!I9XavbK&uQhc{*h$N9;)XYHnzg6zxDTs1i#5xi3;$2)==l5J z+2qrydf`?fx$$QtqA!&uW7MoD#j=KAUS4UU;X!LBySs#0Z9}^UlY8mPBlKl}hK)gC zm!9~WS@l8`&>FgVCn2g);Zmmz940dO7Srpje=iD;ND0#8?h@u0e;j_r!PD1HD^Arm8a!glfYckIMthqi`x0$ zxu{7LPWDmj;&OETN5!!>XQ6JE$?jYINaIR~Klu1K1TZnFYc3S%Q%rfO>NVd}5r%S? zmJZFc{p!q#RtZGe9^tvV^oUg?SAVTsuwmp+uAe(rW0APPP@iyM($0{CJ*!!G>sJFg zgnW!DDPlxk#t46b8ch{veD&7Lk=1#wrZ}c)0Hs}+RIxXDUy_Xs#Sfw<{q2m*$lsK9 zA9E{v)ErsHb%!27g~8n5v;N#A{NZY4R<@RRN4zFH z2anOE%kJd4={fRa<*|BE@%IIq1rxtj?4+jtLeA~`h4A;3w!{Tc3F9M*bsP5A&g9U{ z7GMAu{T75(b?cpNv<0AKPZzAw@v-EaaPHh-e!{D{RNE*~KH{4)yR;+Ecr!N6@J`~7 z7iG#SqX#zhwf*`Nmq-a`)hBLVAmHR9(MQwW1?#XN0dsHfKud%djl6QY0LS_n>@`?oH@uMPv7a2bNurJ@l=9&HJm?e%`yzAEu6h|ibMxM)$lBj-bmfA$7pp5tUed&rg3iO3-S-zAMg@c!Q={SRrE&~S9%nax zzIPkw?aiRLqnBaR#TD#alPs59cz+QjQ+XcDrul@B4`>7dXfu?9%6%_qa0$fTo6J#z1lPy z6*D~{SXrABH{9J&!upsrx|mF@%pXK<9L<#M8zqG}CfG{a6gUCH#X1Y@F@FASQk1Gh zK00m}^Rznsmxu##fk-H`wtO>uBr#(-WZj2-zWP|v7X+wh~Wv)6-W*@n` zf83gd(I-h6B5UySkOXApTTB}Z5mzjBA&{T3V<=}!$n3V$x!%N$6AVJwXXo~bMqLlX z6|p<%9J{0hw*+YDT!kD*16t-MmFFTWG zXZp-L+~?*@&kSMz6F6LlRDLSi+?Il`=cK?u-YLL}0xg(FUxkpElss5%AKQeJK!Oh0 zC)`tXGkD%erq5=+73Uc4&BdxQA83d%fk;RDwYe;4MX@-{zBp!szI8;dVZEq}ulv?! zo}jJ{O$T9Bn_?AZ!iTsW){BYEEBH45C)4`2g$ zCma3=ZdZ?mqn)lBKPgQ(0xUDgOqp-Fx)ZC@PCv32XQ&N)=V~J?AbGo^qFL8+n_#&c zVL^NdxX0AEub3q#8y$Go|0vuEnW2J3k6+=|i51Hn}MWbkKX(%|37W!CtwuU#p`ut*9*G44#CAXTXe7Y<=E3va z6WKNtb;>PQBgaQ5s@@Eu3tX=e9sdYxBsU&0-DMeDp*+fcS*d}{pV5RF+eC#n=gu=K zJ0O(5cI*kPiaOatnQKm-Yd{#>$1WogwC_UbOa>3tm#UQ=Q@>f9u|@~GV?y2?9RCO? z{<7;z9$k>pp`Cz5<2|g&M!qmzl#;qiF)~CuCe8IwxasISuyWg6_8Ig{ESmTFBP&~n zF_tL4=2ViR++t&8J5_7+^0PYSSfDz^=+I^N@sH4g)bWS~g`e>Tc*J1sLSCj69H2V6 z+UP7hyTv7OADvGef-!P``81aP{;9HBQ5rnF_44hV1Zr)ie_ik?RqfQma zJnDYAl<|)cvb*rT#T(;j3<(_Qx-SCrnSaiewYn`oXus&)#@dgb)@w$eK#wq`bam&N zofXlZCy{Sygfw>Lw`;7YBxw?hLyQHC+1X|+oX)o8qv`MQkND81;}OvY#uw;=ctmID zJsswsd&u*q=7#v^RhvKOMzQGmj!8NF81-kfaMG+`PEDPNtORvR@bc7sJhRq-wVrxE zP)>k%mM=f=o&Q@D!%RM5m2ZMs)7~8vsp08~3J2H8KMoq<$Ez>*zt;@An&Z* z|Ce2i)osc<4c2=P#ObuuG0)icYRRN=9h(h}+57koM<@^^-e}GW61yg*fA(`s0w3$B;S~O}|N-HlfRUCgptN`5muTPJx zE8%-@^YYs0l?q9t7=;?UO?1YCplljKi$TvjBT>SL27G4iS@lFn`-ph{A?R*Yd{8S> zH5gObM}5lj$G2yk9@ApF?UbL*=1&gA&VG#7xa&(bD@dNYo)(e~G(*aewdv!XB~!jf z^lGmawmd^LbQ{y{lwBM5#fLOt&&o{pCQ8sGBBqV}{}~U)Ou%ak2m1;7Y-PsM@1DzJ zjx0`okp%aX(r@AP_9?H}*gBrEyv%A|Y@e54RW6zvZgWgaT4M3zS?Uyy{RSEne5REI z48qwLH9IM>0^Gbdzdp@YX6n6;{OBo$W;&l5vpGI=FBqNP*nI`o5|C{koQX?xEB75s ztBXO|)edy~$mWtCg?-b?+7JO8W0#i5Z;M*)F@6VgX2OsL2v!uXoDcJJw`RF1^JfOJ zV^Z;Gd!^mDv1aARv+O-#gUWoVubSpJ z&mFZ|G&(KACoi!ep25R?M+1k$xo6pkF^w7vX@Zk)?HjjqIt#75!!Ks>y@XSEw@4!Y z#AT&*6ou*U`FXF(iR3@IfhWbM!U6rqC%rMTa%J7|=v3X6kA(;}Kkf{f&X^5ff)*3= z%@!w*zS-|D?7vS>cr#u2p?H;@a9SOHe$<^uLi_n_GP85kDfgyN<4vw}BHjb7-$4=2X?y>{tBc~-0L%VQO7q_O^=z(7F117fI zmWiJpo?2hEMPus~dJ|PLfa=dStwYiTsFqiJ%D+8d`|Ucj+B#inZ(rQBvnzM)w01tT zQo6u2WagMdWTCjQy$4&G%cbe7^WsjMiBr}=Y0tOGY-Ebo*Rg1z^mm+s+qk^s@V{hc#Aoa@B@GQ@pKaU97 zUERsLzetuDag=F(9`V;WsrtZdo|T@F$#?r!Jz2irk5vLJfO z1`XMM{k-0)7cTlG!$ukz(H~BdMUM`^{|789GTF7hWP@C)#DS!bT05FzQ^MNn)cQ;E zo(0H1S%tyD$PYVWG$TG!GJ>)(;coHaN6sEWPJx+Uc9e(3h8@XyD%PF(i{qKG{$+7xK#6gPCC_g zdh0^3(w@gVSj1V>QmIw!x`>jD!sHeiP^p0tKE?Hrg=5x8?z+z%neY`B8__=3<=kyF z^QKT6A%Ay@&6hDHBIweX{B6~5@Ler_7O7d>^;jFt(Q0HSmo>KSfyLE$&D)%wU2Ulwed!y;;(!(gLs(L zjPjTfmv(a7p;BuoEs6K-Fw)1d%mmRHs11uR4*M`qxk3*>kMJu zdXA$`CfO-$r&3Pgo$b^qKRVAJ8Mm*rd_kSgRHvAEG3eB!g7Lr_qT)UO)=G|plR)u)w%8Udbd%jto z3*ZWJk>@tgYaR<=X>_X?dQ9FW!_SX~sH}xDFq!cw`$tTs%$Ui%ACkX&++-@Qg|>^c zpKD!=6#As_N{Pi~DQvoS@xjk*F!b@H*-(W<@ zOY`?EUPLLu!*@SPk{n4Ms>XtsSIsj7#$Wr8KCO0WD8yWj!??m}UAIB*<5o)E;J`1w zT?4wK8n(t&IlWQ(^pXPYK^eO`f<#!mB>9CU)Cc92A z+j&L!F_tw|hy^}>XB(Gl{p*qOB9ku_n85u0oy+^E2?o%OSA@N_Rs8A0UVJk=&|PpD z`M<9N3*DmXuVV|A>iw*tzIpnZI##bUZ*kmqi9t_gN9{QPU+ zugNrnM5tYEWzLA&T^mz7=f*Y86V#5MakU>LAyM}qI^LHKVxGXgMxWf}R|NxuvMpEJ zz1uC$I4Pg0oZfL;UN#^N)lB~lawmpmC0;)LUgh%ZQL%sBsydZ>J7cg&6VvJbdsUlE zYg9q(r*?VT2gA&bs-1-fzH2yFwPZVhGBWOvwDEQqBNK3?={*02UiWh_JM#uC#4$0>Ro0bGy_%CVQ{DJj0}!YTFiQoMdRHFrp(iKdN=vR=WwVtevt% z%{Yur$*Amuyz(T;kkQE*%f^mvY^W(o4z0k+9oS_wPRCs$yLU0rT0}(%UY@&%i3q4==uurP_WNvuxGK+Y`lZ+4D@$ zA7mJ%HG|!l^Ek1=(4Puo8!=xDXs3y6Fb7I-$M78Ni;Y}X0&4juID5SMuE3X9q@Wp?GnRR+iUEvRal1}d%aNe0>1hy8kUCTqC<+$Cl{RDz0aW?Lz%WL999 znSDm5FIDmeP{Y4#pZ4S3LvT$M0V6fKk-O5P)z;-GnBI;FE$s{D`WK2V5$cSZ;pXF? z_Y0Kg#lpxePNcsd%n0)NErZCR&y#zlghS0k_U8icIDcwUx!CUqz<*bNT zj>CSXB?nS#F7LSRQp83$v19yITVQ$SUC8a@fGhJ=rzG0RD2+Rmd{@TRaG5;c&=|Wq zK@n`Tfj8Sf)%XlE39LpS^rwDp$Q8mq?#-fDUwo@zXH(pBso;!;PDHa9)2p!FUnr6< zyaRr_nS0p1ygjVtTit3UvfHdCC@_f5GxO>|-=`~?{Hxo8^L%^T>#k&_dbKqrm)fy= zw<~!YiayK2h|ebj_pf?e_)xt~oEhcMwJR9%37} zpu!nzg8i_POBo~}G>>`>vFZ9ppYcT%N~^QH+|eWN@twUUg|FjxKd7=+5QfMdv5$&E z9uHw`%gy!d+@1vVY5Y^tYh-IoR(Iaj_rp#;SrNwDBXZKertGY{~JhNp0wd+B2{Xtq*;6 zZhkV!a=p5iNDCN4E&pF7t6#01{zA2 zYF+O?i%ri?zhADXfbrS^(n~IRw~r^E5j)Tp%2@=Sr3LLnnC0f)h((nK_jm2+2RVuw zS$A0D9@d>+$HsQ5aQ;4dl&&vV${4urpBOhfHSR1ODPTc%>T4!LF1nS*{C~*aI#zm@3U#Z|T|j zdB-u1<g{vC`X8BN>2PZsORKBeZY znO_N7*rTp`j(J%_O^Y|H*eW>W= z(aip`W`7KAAHH;0^XiVmRKf4ZqaispA}5Oje@GYIVKwW>)V%rFE^DyMyuqydzQ={^ z`@?lNl7*2dZDwaHEb^K@9!0*{%DjmiutLAoLuN7j5!Wal+A-rK^CptkjNJrB#-3B- zH}O>xCK$E2WV_=E7Kq<%nL#YitYt*^pk}SY)h8>Ghr#!|k}TP-F&OHyA5I=f_Nk(E z$9r|&J8`V^WyNV;UT}fAx=N?_=(WDIKPT^X3;?ZWG11;P+Lr^`;Hs-KG0=Y0RVPci z($beJuqn{EHQc@8K>oe$1HHuffE$@7P^e;{R_Qq?-_0zB`--zxdw_~SZrB;$u3dKM zY%Dy|jcZMW&jE0-ZkT)n5$o^)Y-bF}Io6*+%Z67r_2&#sdzmFz@mIpjndTQC1?E46 zPc!>)t4D9}WfSO4@v8R3w3@sHibSmeGN5vY|K|^iEJb)X_$Z-sYtaQ%9-<Ns0W|X9RfbJx^voMohrk#L0$mW^&DqfhwA}BX>WHkF&}}EDq2WNh60XN;q`b zUTgyU<^2a*y_r?!lZl=<6q)Yzo6Mz?R-_}q`(SmwO%6++cokgE%@EJ#4H)AQ!orVz4_?tm6GLc*gI`SlSp_d_O7Zf z`i^D#P;dBf>I?(^c<>;2SD8}+t+KHcU`133rnq?Qf zcX_wwz1-1THR$4Tf$o@)Im^Debar50vwKBU-AFI&gzXQs<;ydBSJ zZH_a&J6&U@3NveB=a!Lhc(qK*e@^YM*$7wB;TM@V02>cxhiC|68m-l)80W-cFGWAI%3tnqvI% z!9TUGyE|r8g$f3G3slfHr3X6DTV(|_rEm0f1(Yij)3Fze88-CpCCrl~S%`M%EJ7}@ zeE6VYvpdq!3^c5Mtr^}f~Ly57Q<8kmqxY_bDZOX`XosyJMF@Lff@ zTJIwSom|Kr+Daz{7Qjy`3m+Nhzy)d4aN=~onws@)jkgb zis#AUzWCvgo?0KHWtCY2@3MT2Ga8L*_9*yK!TTJ8H0iIlv?`^S@ef`J)p(arjK$j$ zJerL}MC2@9-e@H$n4$jzME|?Q`c0(0LWjYMFyN58ta!$Z)E?Q+vmVv+G#r(O6T=4& z94%|IP~zeJ@+bLHsZqtl0wdNesjZ^9JH8o)`dgTI4|YbUXR&hg2Yfzh%(Lz4??>RK zyplCL>qtUoPi*f?@g4!ojAN}2ves3CVsrY-;ipQy%djtd&*57iGVUOr)Ar-?tk>Y% z!=4zeHrP<(HnSqa;3~r13{jb%Uf3SPX;OBmK(7X}yS4K|={2Nbv$kQJtF|Nh+yrgE zM}MDpY46f?Mk%pPyT4JtqPLY_bVdcLMPbjsHBs2+}CP=ajhu;;yl z>Pq7`;sF$h50r`C_CB#^x!=w??(sybzk}BNUPxZ7y`=qLypdWZX8)ahrjzL1ee znK8Yn;J}E{jU6+(kCvayhNw}3ewwfLTB5<^Ur@Oi8GCB~`ci23d~ zH+R0zp@?c$?z(k)`9fK@3JC+MLRkN|y35{MsZ%X_bBs4W@Px@08fpk1TBj4CIm~y( zQ^)F5JNG{*eUbc35rsG5v>E=IG;}jhP^Q@4TTg1s3e>A+DqrCUSk;@fk`70GI zMIxD&*C!jR7G@!XkN3r@hm`aI{7>m)lVn66iY=39fj3j+AFeB|?%mj3F&8AEhDY&b zEQUJ4Is5&@BzkKeGrq)7v_x0$Fx<`zCvnD-{B%F2=VRId#sX#y<-`i3 z?eFw`=ozGj=yq?!`90(vMN{O)u<4Cs#poK;oxhv?s6r3loCpv{+Y#VHHPiGTbDCMP z@M5R5SE9S)QNo>CaJXT4=>bF0_ujP%;imrVNRh(Y;G2dm(gP>=XZ5_4e@47ijpzmJ ztEm{U*k&M7OQL>rQGPL07Sg-Hjyauu<|64P{%OBSDFDGFmoJQvG^vQy+-RM9s--Y` z^`mrabUR5(-8Oj7O<-DKR%fjJr7Ls57ICAew^4;u6TTx}sfrp-h>Ue@e&r1ywl9XoO@1ChsB18wi5a*{FC+W3v^?()}DDoeVyYsWS!OQ1b>&)4G2 z<*j=))dZgU%sabkX?mkk{B+}Oj+!OrFYnbCT*>UXO?o9VSqCG!k)0T&EU&{cO)XZ} ziBK8bCoyg)zAj`29lD!8A8cANTZxg3yeY}0=2YlN21_5yn^T*_8@C-; z+8}y3*0TH7yt@*hy}IpOjrcHV|6&Gy4Jl-7T+tADqT+nkfs%)9p;O2bjsVmJVpmMX%P{4^QlpBguJ~Uh-@2#>5p-b4~uKS4}7FJk_ESL9sGF)xk zYTg*$d4KOE?=7b~376LPt(|T|?crkB?-SDY@L5~+1$|CrXg%4bgv_%olv{Edvb2-- zGWDLe^kzMI_mX$dz~Q`2G|bX5^PZ4?Yq#p_XWn~uEr=^2o?>#B=3q+3uY-nm?%4Qs zk#VwWv6th~tfRTRl|FnsS^0uxn#f`vxdp#BQB^mBQuLw4gD6e;bhsrh?=_D`+(eM(MrL1u!pgR#_|!{AH$suR8$GhEXFY%Jde=gi=TCUJ z@TEN#iXF6q6_?985z$@R`Fi$8k`}grAy4Al(I2O4o(@$h^$1S;u-7@y@zqCneaGX2 zSfIz6^37?((f7MV4MnZIJ8p@o{*nc0>xVXBuHRVTwRF*@qe-31a=taAP~8%b*y_9Y zP@Q*yFf*?!Tkn=P%}k4?`Z1{^fr5SKWP(Q6liZFtv&~lYUqPJe@A}aSz33$ z+QN^j`cY$kEjcpQ(xv5VGQ+;=;21l>;xE_OBKgtNy&J!LB)A(#oV+Xj&@$fAQ{2pq zMk3B_u;`CKNwQj<@+c+zFRG9fdPKwLQGW`hxnh6bk&kf8*v|r$-%+*0slIxX5^g? znr~Urf*$Yh$F15L!Q0)xxl6{Cc0K+P;HH0FFaxHS^U5}-b)@AIb#d^^>(w*Ex{{*( zP?mGa3Ae<5cYHM68-P^3P(}9DU-dT!*{{g}l6v_-eAFel2O6gP(qv!z} zXwx@+w%EvJCyZ6$kD2G>Gd=!DZ3vYkgq{(P4sL45#`5#40~{=usvcEZ*L^$U-Sb{k zFvTj;UngQV@kjCCqW(YE(;Dkzt)C_*VX$ED4?C1C9?xGA;m^Ev#BBc~w|=x(opRRg z)#ZY|GRGfhC5P;ynig*N;juf6y=P?|0^%nLX$F&Y2(EYrD7D{oK!8RjXF5sxpVy=);moRQ@72 zj8&AFb%rL|+K!uv|NYX+&8)NMbu*3vGj{?6%{D3R0~5e13VstZ`}ks3zomG;=geEB zHLJ>TZ~W?jKSW>3uQN0L5~uE6^>S$WWH1k;{)yIg-D5}zdPYrEmxoj6AP=XKs%t*w zAJ~@s-I|U&SDT7+VPUhW0_%NvmVK_O1Yd}y|Mj*sk40SgovoQaC4RpO`jbue;2&os zQForF<4o-GhHlM#FcZ&Y>VoWNKU7&*{Ic7%tYSF&AWT*O=3?B~n8u5HLbdHhUhMIu zQxoIJE&xPI1-=bF_(e3F^3efMqw6 z_hoYJbr^!Fy1G1#e0tyS+Cxt0hu|ut4DviL7KR`qkw?js0;lc_e?yz*u4~*tUGlKu z3=6BB|BS-i1Yg;z+}4Kq`$Yvr55yMmf}r9cg&5U)ZznI51|#=qIk>G^C|>I>JE#uY zjuKr10H5IwE@8F>7I{$FEr(@KaXPNB<aXpOO8IuFNemwjvIy4N4@&uX zA7(@X@qZUd{|j6@=$OUhg0V3vZDV06hd9%L_Pqr&AP*%k?IcMe$GmVKel`SPndv=D z)CBg157Cje^nZp--pCEeYs8qV6F3JD`Xh^q$S;O(p%%0YUc* z$6?7wh5lNlzHv5}vpgub1uUHZRzFP$??)*RD~knT&)D|AU)b|suoG0i6-b0fIwU7#6wY36^m$-1B7)d?Sph@R0z9M;Pa-*f# z1xn9|+wzR@=__1>1tWAHCGcx^tf2CqlU~ae_6V>**PSsduR}tOG~&&Fr>bS>8yjVC+uK5i2soY>dk#~4KM*Yy~{K#WiDe> z5{1B?PNc%JxO>2QCYt-S#iw6+K{ycmC`LhIZg4QnyRZk+swFKB?T{lRu^}GYhz>37x z>QhUBMcRO_%00&voi!v>`&J4S{^F>{8kI0a_#m zn59}X^NP~T{L16Xx8FWN6-22wmyF!=Gvff6*$apB$MI`FDo!u<9+g@mut~5;L_50w z-YarQ7hs z08XG+?2D;Y(Q8tXK6{9+hHwz@TXA}CXRIgq9&5A9B5Zt-108ww(*cx2+onb$X z%hO!5zY)>KxWiE0Mj@Y#HCT{S&f`-8K0MvL>2X$0gw=9Bi*J(lAP5(a-cN67s#t&#IK@L~CMSdqnCx4>czA}TN4p%b6Be+x0 zqgzk80LChOr+hH030|SK6q513)Gfo8*v)aMMBZRFX~`5v`eWfB9v02aQr_utl|?T7 zSCEJOm6zW3s*J%bA~nCmerU!oM1TO$5ay$q{}1NlPOp@$A}b357&|WiwSY0N7I4cW zg6zKC?aQOlb!BKji~!ofmx;!nCs&{_umoWhywwRQ#8B zEGosNrIQ~c{F3bQjX!OKCF36n;V%?wT@S>(@M`Zegrd*ZQa|wn71`CrX0O+Bs+lFm zU+uV7YM2%_?^{*$_Irf*5$lC}_f0)rzt^KEh9@uSac~N8W#u{(8AgOS`d_2JV?#xC z&?v;|V-Dc&5}b9QK#>JO6NuwQUp}3za(JP35gONPos(XMtp3WZHtCakhJ)p((o*O8 zO-p(>OxX!x?HqE6{Qi5$2_FiYk_|7@TYbXmx=VcX!Z_+G+9*tZy)gg`Ln7|4B(C1lpQ2~=M-of z%-Zb72nCgXwL_~VN$etup_WyEGcEb$hA+VO_ujOKA*HJa`?nQ zF7bJ9REgK%is7{AgmdQp#;2lrvvqtGrVX9uq0{}=H{4%A+Gx@-Fa{Ewgs&)+GV<-G z$$)+Nq2fQU+?G;0uJXI@F}M_)_=ERXWJrT$4CI)57Lzu}8;kduMEIAVv4b(?wcQkR{rk2yie!38 z4ZU@nzF*NK@(Rho3#G&HJ;KOMYEuh-s#mKSWLgg4_O> z;DfkGjjHxt)+Z@0_`Xr(@3(SfH{qoO8&=#q40FT9wUnWEw*%X=1aBhVUrj?bd|ZM9h(rsyRN4|wf9%V?=z>9?g|p5WJ?z5p46596;)a9 zmK|K}eXdCLd=c}G>*z`e>)Ow)1S*PdgJzd00YCUoCL0fS2WM4Awd^Fk5Z@%~J z9|n?w{qT((WDh;2(RZL5IRE)Rn&Y3J(^l;Iv9QBuIgh{MXAg}D^zfgzbZ4M_dI@n( zFyi~SN3_kl3#Sxy|6SYfC%(lXlN}_l>gx8)GHs&UGpWrnR|EmKh=+Xs7tq`2p@JQ++ShNM#iCv{R=|SI+ufHhri?8HoaQJelA_! z#9oXkS3aySnR7S@|9neZa6_bo*Tk$B+s#)fGGIC1n^F%5#YXS65jTN_yF+@l>)f{^ zLGLCbPyPNe1&Vi`sc>VRtmNgO3^cGE?gkeb0{2Qm3;CzX{`rwRxRL(RZu=(%cj4Lg zQbc!G$0mkZ^1uhHd>j)|gcEbOx^j;2n32M$eCyYa&!nyPRbzj0W`{}F@BxRN`>t&x znvGT((QFtcQK0Eo-@b1VDN2LX$&S#$RI2^EgXxYbH`YJ~Y@O{DZ;>1fpE*k4h4`%F z4(8s?X9+rDAo?3)Qb`itifG$wSDTWA7i~5#;imm)e|MXhZ&;y;XVQKw5cfObuTf$h zcJvFz-r9Mz>Jbq9=JKGQ1?kX`@77%!qvQj?01ee}1VQ073SaD3b{9e7i#Pv7;!&#i z@>y7HI8%RjuL>1m6TPgVTkE~bHIt5E?Ua{=*_v=p^+{$EgmYqjgB1uH(HVY_$NY%+ z#*qKqXhA%naNCqSwNMOMjRfTJtYCh#{KG&q!yzAug3}9JuTUIUthaMFkX;8)J`7oPNgKJqWR!v8tg zzvy5iCt+nOsLf=RL4!503sJ5ufxJJp3q!RB$bAEucyd87-*<9^Xiwh5Q% zr~FU9j3&EDwHfY5eun$Ty5O=|s&dQv%wrJqZ&A(sIEj9Ekt|Dw^@+Tbzz_3F<9m32 z2b2l)6>)Xws_hUkl86074ejk92KTAa0S)-paUT#f`2i~~sssHtM)S8ssYhNTp+5JcdjOg)sn}3P1Ara61rKtq~|8(jG?% z@opKN-{8Z*4X3i&sp9PaBw?X0wLVwtw5t`;v>K+~&`J2N*egUd#NlxApyDsgg7ils zE>x^fi#7xr;bEe>Zv+Mf&Ub7YJokS-68oQn{r~IriF7A0bi#HTn<#|r3l9Mivo^fk z>#PRC?Za%iONG_*=vkPp74IYew|V5Bpp-x{{CjcmzxE?f+(PTkK73;CghHR+-3h|` zau`e^{;5z$JQcw_Iz(jGbJDq3aM-&j^+bG&@iVmKPc6Vs9Q2hIGSUiPgv6|s6{!HX zDM?{a4#dRZe$t8czP}rvqsfe+9|sZ>){0wujb!09Wc9+9G4UmXr_f})M|X%HUBEvB zg-(HFkht`nk9SxA3bK<}bTcZvugW{@?U_vCPK&cZgJ2wF8q3I8l^r6TP&C7gOx~v^ z;&M9Aj^VI6&AwrzxM3%M?=~S-%7RIhgwP`YdC#vzbPG6XVrc0UGLnNDWzb`7(7C(J z&?C<)Fi6$Tx`_(=ojB+b{Oxd1fLkca7uH!rEfp(_T8M*rco#j0K3w_EKa&wiqKu9O z=fWcyJVxy@+=+@sN*z;&v=6TIKNy2*Z#d-+HF4HM`)015V!^cackUMaikPeK zU^KnQ1G#z=46`z4wS#vF*NCZnADRebG;@^Za;tlTq*;nk;WUuSwq6Z`Iyp7mG>%UN z&MBybg*Q-JyL&Q0_B)g9?uH|0UD+9q|B;rF`q^sx7-l|j0ZM}P$49`_hy zsBRxL@YzS*kzz$hxaGNOy%#DA{@&F1znb1LXcpL4qYc@h&_xpe^lc;r9?kVJ|L*lk z;31l}r(xp&Oo(Ooa$WSro+DXpxt(XLn?y|ZvOg*Cwr4NLURBF=hc{ENP0K+Pc%)Dh z0h3nC@olodunLUJBJfyyMLz6NrlA>$g8uI&Y(ETpz-f{nzbQ=ilrV84)B4qjT@Ym3 z;%=m3zekp_lXpg(f9E`|Mf5o-bgq}*q}GduW){?g_crf;zK5EC6rr(QcL&P5q8{r?>ug`Z_S)$OAWYD%|;|7AOn%4&qkRzZ%0H7kpssM4q z#9cpIJ%LMG*#6!eLuH-{q295hs^&Z`kp@2&K9)KKG&*n-lMs54QBVjC{$o-Vx^RHy1SSL2c5KM3mVIH^IMQ7wz67Vk^cMK5;wd2b>m(Cm zfsIFDRd0}}*Hl%907L?plqXFztc^Ml0*6!YKe*3DWDU&4+I0JXHQ@fHV1u+ptdj4O zAjBaV=tw~*Pflyn7^0n;5!zM8Cnn@yY{AtM&2vtR4LP8#K*-agyJ6UPV~Ma6nU#G# zk&hqMWptG2pUY>|FN7%Eovn8sqOc>tkFY|K^CHiQVK{13#nl;T-|!bFvyrBNNj~#6 zBJB&JQUdLpPWR|HTa3)oLCQ(tc4;*5r#ET&EPeQQFRKyoO3z~m;+mF1k$;m{GDpUf zueLX`CBGHENZ^lRU2Px;w|ukpoCMfq-r=#Ap9ED;mo8y5)xD?7oQS&uW0m{b0B|~A zooaP~HcgS$f9W=nX%)kn#!u zr`2Nu{*a&U2Dlm9(6`i$ppVq~BU&&-9wvu&_hui?Xd;%ZnC0UNk|5<7`_h{*33g+n zf95lhS;uVFLT;u+9wS@?_~WC3!}EmN&N2mVx5bJ;47LCzhZ~H!_;p0ie~Sox>eyTrluCT(~bmUbxSiVHnL8h z4qcT~CW-)F{tu4xzlZ>!x?5>}ZGcP2J)gy4@%)h4z)IRoV(GQfNA_5U`o8y)8gnj? z>w2FmI>3RubQaa(x7C9a`i+GB!Sg>Rl1sXxmHEnG%P*)4t<=Fi78J}5%ll&601T-H z6zKC3fq3EY3(Jx*nh}8NBcwzxYhaD&yRSa0?xYy~bKdj^Ekd=oEcpp{01Vffd-4Y6 z9nzJ`8>rrQ>mg-(;Q+eYoqT@26c{d81+~7ki7#~blWZ&#y<2Rh4?BlSHs81fXZ6XbgEc&{}X1P z4UKmbxtLQ61GyNe{`6sG25BRf&gH>;zN(Quc5ICbIOww!MrcGt$j$V$?0Sd9=~L;$ z#gi|dUNYmE>1`MgKb$r#z%rB@W4es4#up(!(%)uOL1knIAn%y#(V48DPM2(#*YZQH=VbLe_{o&n_g95)q<&0Ibu9Z5!*?@Ix2m4x7sP zVwu3oj;G2OCk12Y`DlOqm>82jxq8yYrX-WW(O(B}YnO}x~D9D&h$h(TDOM%akupL47MsOhBwc&tmj9NqjYKATEhBOZlRI({;Ol4 zOR^6ST@QU$6DqX+{5E1&4-)Yl)$f8Q|RGVxCC<u`nVAYYMhD6UN#1JI32a-Q$&#kYBTjc zLvirJFP@2@jztQE=9!WNfNKL?G#n9AJ?OhzKR|$jRbtgI%6QRgJ1}zs`l=d(upHnf z`=LSMYd9uK!+N!^iGJJVdatZDM4ayF*}1*}MY;Wv6c{Q*t%JRRZRa>sN$>g4^JP3}i{9u5s|`roh00tfDoh5g3`Vn56;D8wwglZVsK$HB-Iq z^rzb#k`6+6m~pi4_#=r0c{8`qPV=?}qp^hclNT_!Wa#C!DW2(>j3>p12u&3*=hI=i z0laLQ%!h`|B$mmE>CWusk9r@Nufof&F*#6wgP6EcZ6~iA zxN0=GY88&)&l_0`G)U&ThcF`UrboS%9_=nrW6#_(I~8>bizf_9n8t5RS7eyHuPc>6 zI#@`y&y@~Y?(Qp3cSHJZ4PllP)7KOu-NFnHsY#ohF0M5g@w5{UlnW3wJ;7(F_7zKt+cy(>*Q&_CTY z=^>FEo}UHcVFOvzK?^^qbRQeI+VUw9k2Eg|6wfu!2Cs>he7|q;#bOfd}*x+b-|KK=YpK`+ddg-y0fWgi|>T5vKM25jJph`+m2`=T#Pi zx|hS%FQ|&P(K!9L4l~%V3=tWEZ##ULfem-)aT_N)`8{Mn{3wiX zrjR;bZRLshw)<({<6MdLr2lFe;~QB^19v_-Bg1MLibtf5USW1>JcA5_J+u{7`HlSB z%WLoqTR|GX13IU^#TG5nrMXERhlwL~s~aE%*@X<_id$Q^();mz7K;~OouGwgH71dM zx6g#Za;?Z5?EU?-k8>aS=&h}C!}v=3mC-o=8faed(eQ`L~3(iwq5P~l@{pQ}S>$-)`D3>|*Ob0^AEuwzV z%;;+|nrsf?SGWH3wJXRR%Tw#4ih?*eHo_2Xe1@x_foSM5zPgm-z^%KO18mg~9_%e` zYJGY&QEAr+1!;dLk8Sem)QN{u+J|8y5_wz4+7$cGDVvuDb7G%b@T1MwB~{Qv2|7@zqN$*==blG&gNZa$byf z_`u50HFetaH;KF34bVFIw_y+JQ{_M$J8}PL0_+i0JYlf|*GTbn%@dYo&c!|1#>#0w zdy33M2HQtsx3=`%fm?))WaA}=QWpH-RkPax0qI9nv$Hh@)JmUuIKYO?HCVyxdz6F( z)}QNQPw%x}@i`ykZBQ1tRAHeT?K~b~x8v$W=bOBf;|#|_pB*);gQ$GSdAvQeK1}r5 zJ}C1ggdk(WyC?6^a?f=l>o1I|?md85e)4m$uSfTp>HgvaKKc(7RG$vPPd?JjG_CcI z!HXKS$=T&~trdsXPG`1+6?a9Ii|A)Q&)Uxkdmzsw(i=&^GF4bFX@=4{x0=klV>|LY z!heEak}-s*Ez{EUY6Dq9$X8%#l9WT<)_bt{u;J6I)_}O1c-`{s&>$V+@0gKSp=&X1aH8#BM z#EBCQ>y#|wIui1c`la4STqZj2okb!ki#OGnnf=BevnUiDs^ATr> zkLcS1k}#U%?)zca=QJ8MM9a!)P&#}QO=D$X^`<1ndosfB-WC#3WPR-=X;WA{-$h4u zQD!h_K;jU%pwz0qH%9)g@t2}xMqpO?+13yYN5Fzv!%Kc-1M|z%*X|Zv^qFXXb#}XE zx&iKxUXeyC^GaK6>i4#V7Db5*372E$B2Kza9m>zofB57HH!^@S=mSUTwIv2|(eWY}du69Je|v6Q+InU%HS6Fl zxb>oAHHWCMtc9pIY`C$~M84Ak#p;6g683{wm+e>ba+_l=N;7mM6;32o;{9w^7pI>< z)3{m>5k^IG^&gr`;@v!|XsxAz&E_*>#hxUm|{{-9B>@(Jh~kFqIRU+8JhvQVC!0ewwfki^E*l{*#GQsht9 z#Cw9~48zh`tjeBFz;faA&kAY8V>r%MCY42NVoXE)Ei2It;(Tixq;H~69Sq(o57wf2 zP7<~2MWY-EZa1<&natF{72H8Q(H)doNQIEyMAmE{eE%_;8e6s%C#{#y(OoytE$=jY z0maLi?mDfPa4G6m$&C#u23<~Qr+v_!ZB>m4WE6!-dlCzGwoaGgOO9E0DhVbJ6gVgC zqKdbf8GL&LV#NBl>yoI%29Vi|FK!ynQ