# Listing period

| Previous state | State  | Next state(s) |
| -------------- | ------ | ------------- |
| /              | LISTED | COMMITTED     |

#### Period actions

| Seller                              | Buyer                               | Dispute resolver |
| ----------------------------------- | ----------------------------------- | ---------------- |
| Create an offer                     | Create an offer                     | /                |
| \*Premint vouchers                  | Commit to an offer                  |                  |
| \*Create a group                    | \*Void an unlisted offer            |                  |
| \*Create a twin                     | \*Commit to a price discovery offer |                  |
| \*Create a bundle                   | \*Create and commit to an offer     |                  |
| Void an offer                       |                                     |                  |
| Void an unlisted offer              |                                     |                  |
| Extend an offer                     |                                     |                  |
| \*Update offer royalty recipients   |                                     |                  |
| \*Commit to a price discovery offer |                                     |                  |
| \*Create and commit to an offer     |                                     |                  |

Actions marked with `*` are [#advanced-use-cases](#advanced-use-cases "mention") and are described at the bottom.

***

### Basic offer parameters

Boson dACP supports a variety of different offers. The basic offer has the following parameters:

* Price: the amount the buyer needs to pay in order to receive rNFT. The price can be fixed or unknown at offer creation. If the price is unknown, the seller can put the rNFT in any ERC721 compatible external price discovery mechanism (NFS marketplaces, AMM) and the price is observed only at the commitment time.
* Seller deposit: the amount that is added to the price and given to the buyer if the seller revokes the rNFT before it is redeemed.&#x20;
* Buyer cancellation penalty: the amount that is deducted from the price before it is returned to the buyer  when the buyer cancels the rNFT before it is redeemed. The cancellation penalty is given to the seller.
* Available quantity: the maximal number of rNFTs that can be issued for the offer. After the offer is depleted, new commits are automatically prevented. It is possible to make unlimited offers.
* Exchange token: the currency for all financial properties (price, seller deposit, buyer cancelation penalty, dispute resolver fee). It can be any ERC20 address or native token.
* Metadata URI and hash: additional metadata, describing the asset and normally stored off-chain. URI is reference to metadata location and hash is used to prove data validity. The metadata format and hashing method are arbitrary and  not observable by the protocol.
* Collection index: the seller can create multiple rNFT collections and choose for each offer, where corresponding rNFTs are issued.
* Offer validity: the starting and ending date when the buyers can commit to the offer.
* Redemption parameters: the definition when the rNFT can be redeemed. It can be the same for all rNFTs (fixed starting and ending date), or can be dynamic and starts only when the buyer commits and lasts for a predefined period.
* Dispute period: the length of the period during which the asset must be delivered.
* Resolution period:the length of the period during which the dispute must be resolved.
* Royalty info: the addresses and corresponding percentages that are awarded to them on every secondary trade.
* Dispute resolver: the identifier of the dispute resolver that resolves the escalated dispute.
* Creator: information whether offer is created by the seller or the buyer (default: seller)
* Buyer ID: the buyer's Boson account identifier. Used only if the offer is created by the buyer.
* Mutualizer: the address of the contract that covers the Dispute Resolver Fee. If set to 0, the Fee is covered by the seller.

You can read more about other use cases at the bottom of the page in [#advanced-use-cases](#advanced-use-cases "mention")

### TypeScript SDK

{% tabs fullWidth="true" %}
{% tab title="Create an offer" %}
The seller creates an offer, which allows the buyers to commit.

<pre class="language-typescript"><code class="lang-typescript">const MSEC_PER_DAY = 24 * 60 * 60 * 1000;
<strong>
</strong><strong>const offerArgs = {
</strong>    collectionIndex: "0",
    price: parseEther("0.03"),
    sellerDeposit: parseEther("0.01"),
    agentId: "0",
    buyerCancelPenalty: parseEther("0.01"),
    quantityAvailable: 10,
    validFromDateInMS: Date.now() + 1000,
    validUntilDateInMS: Date.now() + 20 * MSEC_PER_DAY,
    voucherRedeemableFromDateInMS: Date.now() + 1000,
    voucherRedeemableUntilDateInMS: Date.now() + 30 * MSEC_PER_DAY,
    disputePeriodDurationInMS: 40 * MSEC_PER_DAY,
    voucherValidDurationInMS: 0,
    resolutionPeriodDurationInMS: 50 * MSEC_PER_DAY,
    exchangeToken: 0x00000000000000000000000000000000, // native token
    disputeResolverId: "1",
    metadataUri: "https://offerMetadata.uri",
    metadataHash: "0xba5348e4fa8d40f569f0a83ab38ab799a3ef1a35b1",
    creator: 0, // seller
}

await sellerCoreSDK.createOffer(offerArgs)
</code></pre>

{% endtab %}

{% tab title="Void an offer" %}
The seller can make an offer uncommittable before it expires. The existing commits and exchanges are unaffected.

```typescript
const offerId = "5";

await sellerCoreSDK.voidOffer(offerId);
```

Multiple offers can be voided in a single transaction.
{% endtab %}

{% tab title="Extend an offer" %}
The seller extends the offer validity before it expires. The existing commits and exchanges are unaffected.

```typescript
const offerId = "5";
const offer = await coreSDK.getOfferById(offerId);
const newValidUntil = offer.validUntilDate + 1000;

await sellerCoreSDK.extendOffer(offerId, newValidUntil.toString());
```

Multiple offers can be extended in a single transaction.

```typescript
const offerId1 = "5";
const offerId2 = "10";
const offer = await coreSDK.getOfferById(offerId);
const newValidUntil = offer.validUntilDate + 1000;

await sellerCoreSDK.extendOfferBatch([offerId1, offerId2], newValidUntil.toString());
```

{% endtab %}

{% tab title="Commit to an offer" %}
For seller-initiated offers, the buyer commits to an offer and receives a tradeable rNFT.

```typescript
const offerId = "5";

await buyerCoreSDK.commitToOffer(offerId);
```

For buyer-initiated offers, the seller commits. The seller can provide additional info, unknown to the buyer at offer creation time (collection index, royalty info and mutualizer). The buyer receives a tradeable rNFT.

```typescript
const offerId = "5";
const sellerParams = {
    collectionIndex: "0",
    royaltyInfo: {
        recipients: [],
        bps: [],
    }
    mutualizerAddress: "0x00000000000000000000000000000000",
}

await sellerCoreSDK.commitToBuyerOffer(offerId, sellerParams);
```

{% endtab %}
{% endtabs %}

### Solidity

{% tabs fullWidth="true" %}
{% tab title="Create an offer" %}
The seller creates an offer, which allows the buyers to commit.

```solidity
IBosonOfferHandler bosonProtocol = IBosonOfferHandler(_bosonProtocolAddress);

uint256 MSEC_PER_DAY = 24 * 60 * 60 * 1000;

BosonTypes.Offer memory offer = BosonTypes.Offer({
    id: 0, // will be ignored and auto-assigned
    sellerId: 1, // existing seller ID
    price: 30000000000000000, // 0.03 ETH
    sellerDeposit: 10000000000000000, // 0.01 ETH
    buyerCancelPenalty: 10000000000000000, // 0.01 ETH
    quantityAvailable: 10,
    exchangeToken: address(0), // native token (ETH)
    priceType: BosonTypes.PriceType.Static,
    creator: BosonTypes.OfferCreator.Seller,
    metadataUri: "https://offerMetadata.uri",
    metadataHash: "0xba5348e4fa8d40f569f0a83ab38ab799a3ef1a35b1",
    voided: false,
    collectionIndex: 0,
    royaltyInfo: new BosonTypes.RoyaltyInfo[](0) // no royalties
});

BosonTypes.OfferDates memory offerDates = BosonTypes.OfferDates({
    validFrom: block.timestamp + 1000, 
    validUntil: (block.timestamp + 1000) + (20 * MSEC_PER_DAY),
    voucherRedeemableFrom: block.timestamp + 2000,
    voucherRedeemableUntil: (block.timestamp * 2000) + (30 * MSEC_PER_DAY) // 30 days
});

BosonTypes.OfferDurations memory offerDurations = BosonTypes.OfferDurations({
    disputePeriod: 40 * MSEC_PER_DAY, // 40 days
    voucherValid: 0,
    resolutionPeriod: 50 * MSEC_PER_DAY // 50 days
});

BosonTypes.DRParameters memory drParameters = BosonTypes.DRParameters({
    disputeResolverId: 1,
    mutualizerAddress: address(0) // self-mutualization
});

uint256 agentId = 0; // no agent
uint256 feeLimit = 100000000000000000; // 0.1 ETH

bosonProtocol.createOffer(offer, offerDates, offerDurations, drParameters, agentId, feeLimit);
```

Multiple offers can be created in a single transaction.

```solidity
IBosonOfferHandler bosonProtocol = IBosonOfferHandler(_bosonProtocolAddress);

uint256 MSEC_PER_DAY = 24 * 60 * 60 * 1000;

// Create array of 3 offers
BosonTypes.Offer[] memory offers = new BosonTypes.Offer[](3);
BosonTypes.OfferDates[] memory offerDatesArray = new BosonTypes.OfferDates[](3);
BosonTypes.OfferDurations[] memory offerDurationsArray = new BosonTypes.OfferDurations[](3);
uint256[] memory disputeResolverIds = new uint256[](3);
uint256[] memory agentIds = new uint256[](3);
uint256[] memory feeLimits = new uint256[](3);
BosonTypes.DRParameters[] memory drParametersArray = new BosonTypes.DRParameters[](3);

// Configure each offer
for (uint256 i = 0; i < 3; i++) {
    offers[i] = BosonTypes.Offer({
        id: 0, // will be ignored and auto-assigned
        sellerId: 1, // existing seller ID
        price: 30000000000000000, // 0.03 ETH
        sellerDeposit: 10000000000000000, // 0.01 ETH
        buyerCancelPenalty: 10000000000000000, // 0.01 ETH
        quantityAvailable: 10,
        exchangeToken: address(0), // native token (ETH)
        priceType: BosonTypes.PriceType.Static,
        creator: BosonTypes.OfferCreator.Seller,
        metadataUri: "https://offerMetadata.uri",
        metadataHash: "0xba5348e4fa8d40f569f0a83ab38ab799a3ef1a35b1",
        voided: false,
        collectionIndex: 0,
        royaltyInfo: new BosonTypes.RoyaltyInfo[](0) // no royalties
    });

    offerDatesArray[i] = BosonTypes.OfferDates({
        validFrom: (block.timestamp * 1000) + 1000,
        validUntil: (block.timestamp * 1000) + (20 * MSEC_PER_DAY),
        voucherRedeemableFrom: (block.timestamp * 1000) + 1000,
        voucherRedeemableUntil: (block.timestamp * 1000) + (30 * MSEC_PER_DAY)
    });

    offerDurationsArray[i] = BosonTypes.OfferDurations({
        disputePeriod: 40 * MSEC_PER_DAY,
        voucherValid: 0,
        resolutionPeriod: 50 * MSEC_PER_DAY
    });
    
    drParametersArray[i] = BosonTypes.DRParameters({
        disputeResolverId: 1,
        mutualizerAddress: address(0) // self-mutualization
    });

    agentIds[i] = 0; // no agent
    feeLimits[i] = 100000000000000000; // 0.1 ETH
}

bosonProtocol.createOfferBatch(offers, offerDatesArray, offerDurationsArray, drParametersArray, agentIds, feeLimits);
```

{% endtab %}

{% tab title="Void an offer" %}
The seller can make an offer uncommittable before it expires. The existing commits and exchanges are unaffected.

```solidity
IBosonOfferHandler bosonProtocol = IBosonOfferHandler(_bosonProtocolAddress);
uint256 offerId = 1;

bosonProtocol.voidOffer(offerId);
```

Multiple offers can be voided in a single transaction.

```solidity
IBosonOfferHandler bosonProtocol = IBosonOfferHandler(_bosonProtocolAddress);
uint256[] memory offerIds = new uint256[](3);
offerIds[0] = 1;
offerIds[1] = 2;  
offerIds[2] = 3;

bosonProtocol.voidOfferBatch(offerIds);
```

{% endtab %}

{% tab title="Extend an offer" %}
The seller extends the offer validity before it expires. The existing commits and exchanges are unaffected.

```solidity
IBosonOfferHandler bosonProtocol = IBosonOfferHandler(_bosonProtocolAddress);
uint256 offerId = 5;
(, , BosonTypes.OfferDates memory offerDates, , , ,) = bosonProtocol.getOffer(offerId);
uint256 newValidUntil = offerDates.validUntil + 1000;

bosonProtocol.extendOffer(offerId, newValidUntil);
```

Multiple offers can be extended in a single transaction.

```solidity
IBosonOfferHandler bosonProtocol = IBosonOfferHandler(_bosonProtocolAddress);
uint256 offerId1 = 5;
uint256 offerId2 = 10;

(, , BosonTypes.OfferDates memory offerDates, , , ,) = bosonProtocol.getOffer(offerId1);
uint256 newValidUntil = offerDates.validUntil + 1000;

uint256[] memory offerIds = new uint256[](2);
offerIds[0] = offerId1;
offerIds[1] = offerId2;

bosonProtocol.extendOfferBatch(offerIds, newValidUntil);
```

{% endtab %}

{% tab title="Commit to an offer" %}
For seller-initiated offers, the buyer commits to an offer and receives a tradeable rNFT.

```solidity
IBosonExchangeCommitHandler bosonProtocol = IBosonExchangeHandler(_bosonProtocolAddress);
address payable buyer = address(this);
uint256 offerId = 5;

bosonProtocol.commitToOffer(buyer, offerId);
```

For buyer-initiated offers, the seller commits. The seller can provide additional info, unknown to the buyer at offer creation time (collection index, royalty info and mutualizer). The buyer receives a tradeable rNFT.

```solidity
IBosonExchangeCommitHandler bosonProtocol = IBosonExchangeHandler(_bosonProtocolAddress);
uint256 offerId = 5;
BosonTypes.SellerOfferParams memory sellerOfferParams = BosonTypes.SellerOfferParams({
    collectionIndex: 1, 
    royaltyInfo: new BosonTypes.RoyaltyInfo[](0), // no royalties
    mutualizerAddress: address(0)
}); 

bosonProtocol.commitToBuyerOffer(offerId);
```

{% endtab %}

{% tab title="Create and offer and commit" %}
The seller and buyer can negotiate the offer parameters off-chain. Once they agree on them, one party signs the parameters and the other submits them the the protocol. The one who signed them is considered the offer creator, while the one submitting them is committing to that offer.

#### The other party signed the offer, and this contract is submitting the transaction

```solidity
IBosonExchangeCommitHandler bosonProtocol = IBosonExchangeHandler(_bosonProtocolAddress);
BosonTypes.FullOffer memory fullOffer;
address offerCreator = 0x2a91A0148EE62fA638bE38C7eE05c29a3e568dD8;
address payable committer = address(this);
bytes calldata signature = 0x...; // must be provided by other signer;
uint256 conditionalTokenId = 0;
BosonTypes.SellerOfferParams memory sellerParams; // use the default parameters

fullOffer.offer = BosonTypes.Offer({
    id: 0, // will be ignored and auto-assigned
    sellerId: 1, // existing seller ID
    price: 30000000000000000, // 0.03 ETH
    sellerDeposit: 10000000000000000, // 0.01 ETH
    buyerCancelPenalty: 10000000000000000, // 0.01 ETH
    quantityAvailable: 10,
    exchangeToken: address(0), // native token (ETH)
    priceType: BosonTypes.PriceType.Static,
    creator: BosonTypes.OfferCreator.Seller,
    metadataUri: "https://offerMetadata.uri",
    metadataHash: "0xba5348e4fa8d40f569f0a83ab38ab799a3ef1a35b1",
    voided: false,
    collectionIndex: 0,
    royaltyInfo: new BosonTypes.RoyaltyInfo[](0) // no royalties
});

fullOffer.offerDates = BosonTypes.OfferDates({
    validFrom: block.timestamp + 1000, 
    validUntil: (block.timestamp + 1000) + (20 * MSEC_PER_DAY),
    voucherRedeemableFrom: block.timestamp + 2000,
    voucherRedeemableUntil: (block.timestamp * 2000) + (30 * MSEC_PER_DAY) // 30 days
});

fullOffer.offerDurations = BosonTypes.OfferDurations({
    disputePeriod: 40 * MSEC_PER_DAY, // 40 days
    voucherValid: 0,
    resolutionPeriod: 50 * MSEC_PER_DAY // 50 days
});

fullOffer.drParameters = BosonTypes.DRParameters({
    disputeResolverId: 1,
    mutualizerAddress: address(0) // self-mutualization
});

fullOffer.feeLimit = 100000000000000000; // 0.1 ETH

bosonProtocol.createOfferAndCommit(fullOffer, offerCreator, committer, signature, conditionalTokenId, sellerParams);
```

#### The other party is submitting the transaction, and this contract uses EIP1271 verification

```typescript
import { IERC1271 } from "@openzeppelin/contracts/interfaces/IERC1271.sol";

function isValidSignature(bytes32, bytes calldata) public view override returns (bytes4) {
    // validate the signature, e.g. ECDSA verification
    ...
    
    // If everything is ok, return the correct magic value
    return IERC1271.isValidSignature.selector;
}
```

{% endtab %}
{% endtabs %}

### Widget

{% tabs fullWidth="true" %}
{% tab title="Commit to an offer" %}
The buyer commits to an offer and receives a tradeable rNFT.

1. Add the following `<script>` entries, either in `<head>` or `<body>` of their website:

```html
<script type="text/javascript" src="https://widgets.bosonprotocol.io/scripts/zoid/zoid.min.js"></script>
<script type="text/javascript" src="https://widgets.bosonprotocol.io/scripts/commit-button.js"></script>
```

2. Add the following code wherever you want to display the commit button:

```html
<div id="container"></div>

<script>
  const instance = CommitButton({
    configId: "testing-80002-0",
    context: "iframe",
    productUuid: "086b32-3fcd-00d1-0624-67513e85415c",
    sellerId: "138",
    modalMargin: "2%",
    lookAndFeel: "modal",
    disabled: false,
    buttonStyle: {
      minWidth: "100px",
      minHeight: "100px",
      shape: "rounded",
      color: "white"
    },
    onGetDimensions: function (dimensions) {
      const { offsetHeight, offsetWidth } = dimensions;
      document.querySelector(
        "#container"
      ).style.height = `${offsetHeight}px`;
      document.querySelector(
        "#container"
      ).style.minWidth = `${offsetWidth}px`;
    }
  });

  instance.render("#container");
</script>
```

You can also update properties dynamically with `updateProps(updatedPropertiesObject)`:

```html
<script>
    let disabled = false;
    const instance = CommitButton({
      /* ... */
      disabled,
    });
    
    instance.render("#container");
    function toggleDisableState() {
      disabled = !disabled;
      instance.updateProps({ disabled });
    }
  </script>

<button onclick="toggleDisableState()" style="margin: 20px;">toggle disable state</button>
```

The Commit button has been created with [zoid](https://github.com/krakenjs/zoid/tree/main) so you can use their drivers to have [components in your favorite framework](https://github.com/krakenjs/zoid/blob/main/docs/api/component.md#react).

### Discover more... <a href="#discover-more" id="discover-more"></a>

The Commit Widget is part of the React Component library from Boson Core Component you can discover on this [Storybook page](https://main--65f314a856a256708dd840ea.chromatic.com/?path=/story/widgets-commit--commit). Also, you can play around with the Commit button itself [Storybook page](https://main--65f314a856a256708dd840ea.chromatic.com/?path=/story/visual-components-buttons-commitbutton--base).

You can find an example HTML file which embeds the commit button on the the widgets subdomain: <https://widgets.bosonprotocol.io/scripts/commit-button-example.html>
{% endtab %}
{% endtabs %}

### Advanced use cases

<details>

<summary>Vouchers with a fixed validity period</summary>

In basic offer, the voucher validity starts at commit time and ends after the predefined amount. Each voucher gets its own end of validity.

Alternatively, the validity period can be set to be the same for all the vouchers. The redemption period starts and ends regardless of commitment time.

* Voucher redeemable to must be higher than Voucher redeemable from
* Voucher valid must be 0

</details>

<details>

<summary>Unlimited offers</summary>

Same as basic offer, except:

* Quantity available is 2^256-1
* In normal offers, quantity is decreased at every commit, and once it reaches 0, committing becomes impossible. In unlimited offers, the quantity is never decreased. Committing is stopped once the offer expires, it’s voided or the seller deposit cannot be covered by the available funds

</details>

<details>

<summary>Non-default collection</summary>

Sellers can have multiple collections, where rNFTs are issued, which allows better organization. Each seller has a default collection, which is initialized when the seller is created. If they want they can add new collections later.

<br>

To assign an offer to a non-default collection

* Set collection index to non-zero value (collection must exist)
* If the collection does not exist yet, the seller must create it before creating the offer

</details>

<details>

<summary>Offer with royalties</summary>

To add royalty recipients to the offer:

* Populate the Royalty info with list of recipients and corresponding percentages
* The recipients must be allowlisteded by the seller’s admin
* The royalty percentages in the offer must be higher than the minimal percentages defined by the admin
* The total royalty percentage must be lower than max percentage defined by the protocol
* The offer’s royalty recipients can be changed at any time

</details>

<details>

<summary>Offer with an agent</summary>

Part of the offer proceeds can be paid out to an agent of choice. The percentage that the agent takes is defined by the agent itself.

* Set agentId to non-zero value

</details>

<details>

<summary>Absolute zero offer</summary>

Special offer type, where

* Price = 0
* Seller deposit = 0
* Buyer cancelation penalty = 0
* DR can also be set to 0 in this case and the dispute cannot be raised
* Can be used to distribute the freebies (the offer can also be token-gated if the distribution needs to be restricted)

</details>

<details>

<summary>Token-gated offers</summary>

Offers can be restricted, so not every one can commit to it.

* Create offer as normal
* Define the conditions
  * Evaluation method:
    * Threshold: you need some amount of tokens (applicable to ERC20, ERC1155)
    * SpecificToken: you need an exact token from a range (applicable to ERC721)
  * TokenType (ERC20, ERC721, ERC1155)
  * Token contract address
  * Gating type
    * Per address: how many times a single address commits to an offer
    * Per token id: how many times the same token can be used for committing
  * minTokenId (minimal token Id that can be used; applicable for “SpecificToken” method)
  * Threshold (the amount of tokens needed; applicable for “Threshold” method)
  * Max commits (the number of times the same address/token can be used for committing)
  * maxTokenId (maximal token Id that can be used; applicable for “SpecificToken” method)
* Create a group with offer(s) and condition. Multiple offers can be gated by the same condition.
* If the condition already exists, a new offer can be added to it.

</details>

<details>

<summary>Perminted rNFT</summary>

Normally rNFT is issued only after the commit. If the seller wants to list in on external marketplace before that, they can permit the rNFTs. They are similar to normal rNFTs, except they are not associated with any exchange in the protocol (i.e. redemption period is inactive). Once the 1st transfer happens, the protocol exchange is created and the period starts. The recipient becomes the 1s buyer.

* Create offer as normal
* Call reserve range to specify how many rNFT can be preminted
* Permint the tokens directly on the rNFT contract
* List the tokens on marketplace of choice

The proceeds from the sale go to rNFT contract from where they can be moved to the protocol and then withdrawn by the seller.

Before the commit happens, the seller must deposit enough funds to cover both the seller deposit and the price.

Preminted offers can be combined with all the non-default features from above (fixed validity period, non-default collection, royalties, agent offers, unlimited offers, token-gated offers).<br>

</details>

<details>

<summary>Price discovery offers</summary>

If the price is unknown at the offer creation date, the seller can create a price discovery offer and put it into an external price discovery mechanism. This can be auctions, AMMs or any other price discovery that is settled on chain. The price is then observed in the protocol at the commit time.

* Create and offer and set price type to PriceDiscovery
* Call reserve range to specify how many rNFT can be preminted
* Permint the tokens directly on the rNFT contract
* List the tokens on marketplace of choice
* After the price is established via an external PD discovery, the seller must finalize it in the protocol.
* The seller deposit must be in the protocol before the finalization, while the price is encumbered during the finalization.

</details>
