# Token sale offering flows

Source: https://docs.settlemint.com/docs/api-reference/offerings/token-sale-offering-flows
Create, configure, activate, buy, finalize, and settle a token sale offering through DALP APIs, SDK methods, and CLI commands.



# Token sale offering flows [#token-sale-offering-flows]

A token sale offering runs a primary sale for an asset: investors buy tokens with one or more payment currencies, and the issuer settles funds and tokens after the sale closes. Use this flow to create a sale, configure its controls, activate it, accept purchases, finalize accounting, and handle withdrawals or refunds.

DALP exposes token sales through the API, SDK, and CLI. The public API and SDK share the same sale model: you can create and operate a sale programmatically and reconcile it through the platform UI or CLI.

A token sale is a system add-on. Your DALP system must have the token sale add-on factory available before you create a sale.

## When to use this flow [#when-to-use-this-flow]

Use a token sale offering when you run a primary distribution for an asset and need on-chain controls for pricing, purchase limits, presale access, vesting, and post-sale settlement.

The flow has three phases:

* **Configure**: create the sale, then set payment currencies, purchase limits, soft cap, terms, presale whitelist, and vesting before you activate it.
* **Sell**: activate the sale and accept purchases. Pause and resume the sale while it is live if you need to.
* **Settle**: end and finalize the sale, then withdraw funds and tokens, or let investors claim refunds when the sale fails its soft cap.

Do not use this page as a substitute for the generated contract. Use the [API Reference](/docs/api-reference/reference/openapi) for exact request and response fields, and the [CLI Command Reference](/docs/developers/cli/command-reference) for command syntax.

## Prerequisites [#prerequisites]

* Your DALP system has the token sale add-on factory available.
* You have authenticated and configured a client. See [API integration getting started](/docs/api-reference/reference/getting-started).
* You send acting context with each request through the wallet verification your client attaches. The SDK and CLI examples below assume a configured `walletVerification`.
* The caller holds the operator role required for the operation. Configuration and activation are issuer operations; so is post-sale settlement. Buying and refund claims are investor operations.

## Amount and time units [#amount-and-time-units]

Amount fields use raw base units. Express the sale token caps, purchase limits, soft cap, and minimum token amounts in the sale token's decimals, and express the buy amount in the decimals of the payment currency you send. Price ratios use 1e18 precision. Timestamps for sale start, presale end, and vesting are Unix seconds.

## Create a sale [#create-a-sale]

Create a sale with the token address, a future sale start time, a duration in seconds, and a hard cap. You can supply payment currencies, purchase limits, a soft cap, presale settings, and vesting in the same request, or add them later with the configuration operations below.

```ts fixture=dalp-client
// saleStart must be a future chain timestamp. Derive it from the current time
// with a generous buffer so it stays ahead even on test chains.
const saleStart = String(Math.floor(Date.now() / 1000) + 86400);

const created = await client.addons.tokenSale.create({
  body: {
    tokenAddress: "0xTOKEN",
    saleStart,
    saleDuration: 2592000,
    hardCap: "1000000000000000000000000",
    paymentCurrencies: [{ currency: "0xUSDC", priceRatio: "1000000000000000000" }],
    walletVerification,
  },
});

if (!("data" in created)) {
  throw new Error(`Sale creation is still processing: ${created.statusUrl}`);
}

const saleAddress = created.data.tokenSaleId;
```

Sale creation runs on-chain through the system factory. By default the request returns `202 Accepted` with a `statusUrl` that tracks the queued transaction. Poll that URL until the operation reaches a terminal state, or send `Prefer: wait=N` (RFC 7240) to wait synchronously. The SDK waits synchronously by default, so the example above resolves to the created sale in most cases.

To run a presale, set the `presale` fields at create time. The presale end time, discount multiplier, and per-address cap are fixed during creation; you can only update the whitelist after that.

```ts fixture=dalp-client
// Both saleStart and the presale endTime must be future chain timestamps, with the presale ending after the sale opens.
// Use a generous buffer so the values stay ahead even on test chains.
const presaleSaleStart = String(Math.floor(Date.now() / 1000) + 86400);
const presaleEndTime = String(Math.floor(Date.now() / 1000) + 86400 + 604800);

const createdWithPresale = await client.addons.tokenSale.create({
  body: {
    tokenAddress: "0xTOKEN",
    saleStart: presaleSaleStart,
    saleDuration: 2592000,
    hardCap: "1000000000000000000000000",
    presale: {
      endTime: presaleEndTime,
      discountMultiplier: "800000000000000000",
      maxPerAddress: "100000000000000000000",
      whitelist: ["0xBUYER1", "0xBUYER2"],
    },
    walletVerification,
  },
});
```

## Configure sale controls [#configure-sale-controls]

Configure controls while the sale is in setup, prior to activation. Each operation runs on-chain and follows the same queued-transaction model as create.

| Operation                | SDK method                                    | What it sets                                                            |
| ------------------------ | --------------------------------------------- | ----------------------------------------------------------------------- |
| Add payment currency     | `addons.tokenSale.addPaymentCurrency`         | An accepted ERC-20 payment currency and its price ratio.                |
| Remove payment currency  | `addons.tokenSale.removePaymentCurrency`      | Removes an accepted payment currency.                                   |
| Set purchase limits      | `addons.tokenSale.setPurchaseLimits`          | Minimum and maximum purchase amounts per investor.                      |
| Set soft cap             | `addons.tokenSale.setSoftCap`                 | The soft cap below which the sale is treated as failed.                 |
| Set terms hash           | `addons.tokenSale.setTermsHash`               | The terms hash investors acknowledge before buying.                     |
| Configure vesting        | `addons.tokenSale.configureVesting`           | Vesting schedule for purchased tokens: start time, duration, and cliff. |
| Add to presale list      | `addons.tokenSale.addToPresaleWhitelist`      | Wallet addresses allowed to buy during the presale window.              |
| Remove from presale list | `addons.tokenSale.removeFromPresaleWhitelist` | Removes wallet addresses from the presale whitelist.                    |

The presale end time, discount multiplier, and per-address cap are set at create time and cannot be changed later. The whitelist can be updated after creation.

Activation requires the sale contract to hold at least the `hardCap` amount of sale tokens. Mint or transfer the full token inventory to the sale address first; otherwise the activation call reverts with an insufficient balance error.

Set the purchase limits and soft cap against the sale address you created:

```ts fixture=dalp-client group=token-sale-config
const saleAddress = "0xSALE";

await client.addons.tokenSale.setPurchaseLimits({
  body: {
    saleAddress,
    minPurchase: "100000000000000000000",
    maxPurchase: "10000000000000000000000",
    walletVerification,
  },
});

await client.addons.tokenSale.setSoftCap({
  body: {
    saleAddress,
    softCap: "250000000000000000000000",
    walletVerification,
  },
});
```

## Activate and buy [#activate-and-buy]

Before you activate the sale, fund the sale contract with at least the hard cap amount of sale tokens. `activateSale` checks the contract's token balance and reverts with `InsufficientTokenBalance` if it is below the hard cap. Transfer or mint the sale inventory to the sale address after creation and before activation.

Activate the sale to open it for purchases. After activation, an investor buys tokens by sending a payment currency amount and a minimum token amount that protects against slippage.

```ts fixture=dalp-client group=token-sale-buy
const saleAddress = "0xSALE";

await client.addons.tokenSale.activate({
  body: { saleAddress, walletVerification },
});
```

Activation changes the sale status, but purchases are still rejected until the chain time reaches the configured `saleStartTime`. If you created the sale with a future start time, poll `currentTime` to check the chain clock before you open purchases.

```ts fixture=dalp-client
const now = await client.addons.tokenSale.currentTime({ query: {} });
```

```ts fixture=dalp-client group=token-sale-buy
await client.addons.tokenSale.buy({
  body: {
    saleAddress,
    currency: "0xUSDC",
    amount: "1000000000000000000",
    minTokenAmount: "950000000000000000",
    walletVerification,
  },
});
```

When the sale sets a terms hash, the investor must call `acknowledgeTerms` first. The platform rejects a `buy` call from any investor who has not acknowledged the current terms hash.

```ts fixture=dalp-client
const saleAddress = "0xSALE";

await client.addons.tokenSale.acknowledgeTerms({
  body: { saleAddress, walletVerification },
});
```

To hold purchases while the sale is live, pause it and resume when ready:

```ts fixture=dalp-client
const saleAddress = "0xSALE";

await client.addons.tokenSale.pauseSale({
  body: { saleAddress, walletVerification },
});

await client.addons.tokenSale.unpauseSale({
  body: { saleAddress, walletVerification },
});
```

## Monitor a sale [#monitor-a-sale]

Read sale state through the list and read endpoints. Use list for collections and read when you already know the sale address.

```ts fixture=dalp-client group=token-sale-monitor
const saleAddress = "0xSALE";

const sales = await client.addons.tokenSale.list({
  query: {
    sortBy: "createdAt",
    sortDirection: "desc",
    filters: [{ id: "systemAddon", operator: "eq", value: "0xSALEADDON" }],
  },
});

const detail = await client.addons.tokenSale.read({
  params: { saleAddress },
});
```

List supports pagination and sorting by sale start time, sale end time, creation time, and total sold. The response `status` field reflects the sale phase: setup, presale, public sale, paused, ended, success, or failed. DALP computes that status per request against the active chain's clock, so a presale advances to the public-sale phase once its presale end time is crossed. Because status is computed at read time, it is not a filterable or facetable field; group or filter by status on the response payload.

List and read are scoped to the active chain resolved from your system context. DALP does not aggregate sales across chains in one response.

To check the chain timestamp the sale uses for scheduling, call the current-time endpoint:

```ts fixture=dalp-client
const now = await client.addons.tokenSale.currentTime({ query: {} });
```

Role-based filtering applies to the read endpoint. Callers without admin, token manager, or sale admin roles see only their own purchases: the platform resolves the caller's effective buyer address and filters `tokenSalePurchases` to that buyer. Use an operator-scoped caller to read all purchases for the sale.

## Finalize and settle [#finalize-and-settle]

End the sale window, then finalize it to settle accounting. Finalizing determines whether the sale met its soft cap.

```ts fixture=dalp-client group=token-sale-settle
const saleAddress = "0xSALE";

await client.addons.tokenSale.end({
  body: { saleAddress, walletVerification },
});

await client.addons.tokenSale.finalize({
  body: { saleAddress, walletVerification },
});
```

After a successful sale, the issuer withdraws collected funds for each payment currency and withdraws any unsold tokens.

```ts fixture=dalp-client
const saleAddress = "0xSALE";

await client.addons.tokenSale.withdrawFunds({
  body: { saleAddress, currency: "0xUSDC", recipient: "0xRECIPIENT", walletVerification },
});

await client.addons.tokenSale.withdrawUnsoldTokens({
  body: { saleAddress, recipient: "0xRECIPIENT", walletVerification },
});
```

## Investor claims [#investor-claims]

When a sale fails its soft cap, an investor claims a refund per payment currency they paid in:

```ts fixture=dalp-client
const saleAddress = "0xSALE";

await client.addons.tokenSale.claimRefund({
  body: { saleAddress, currency: "0xUSDC", walletVerification: buyerWalletVerification },
});
```

After a successful sale, investors withdraw their purchased tokens once the sale is claimable. The contract sends tokens to the caller's address, so the investor must call this with their own wallet verification.

```ts fixture=dalp-client
const saleAddress = "0xSALE";

await client.addons.tokenSale.withdrawTokens({
  body: { saleAddress, walletVerification: buyerWalletVerification },
});
```

## CLI coverage [#cli-coverage]

The DALP CLI exposes the same flow under `dalp token-sales`. Create the sale first, configure controls before activation, then activate and sell. End and finalize the sale to settle.

```bash
dalp token-sales list
dalp token-sales read 0xSALE
dalp token-sales create \
  --token-address 0xTOKEN \
  --sale-start 2026-06-01T09:00:00.000Z \
  --sale-duration 604800 \
  --hard-cap 1000000000000000000000000
dalp token-sales add-payment-currency --address 0xSALE --currency 0xUSDC --price-ratio 1000000000000000000
dalp token-sales set-purchase-limits --address 0xSALE --min-purchase 100000000000000000000 --max-purchase 10000000000000000000000
dalp token-sales set-soft-cap --address 0xSALE --soft-cap 250000000000000000000000
dalp token-sales activate 0xSALE
dalp token-sales buy --address 0xSALE --currency 0xUSDC --amount 1000000000000000000 --min-token-amount 950000000000000000
dalp token-sales pause 0xSALE
dalp token-sales unpause 0xSALE
dalp token-sales end 0xSALE
dalp token-sales finalize 0xSALE
dalp token-sales withdraw-funds --address 0xSALE --currency 0xUSDC --recipient 0xRECIPIENT
dalp token-sales withdraw-unsold-tokens --address 0xSALE --recipient 0xRECIPIENT
dalp token-sales withdraw-tokens 0xSALE
dalp token-sales claim-refund --address 0xSALE --currency 0xUSDC
```

The CLI also covers vesting, presale whitelist, terms hash, and payment-currency management. Use the API or SDK for scenarios the CLI flags do not cover.

## Related references [#related-references]

* [API integration getting started](/docs/api-reference/reference/getting-started)
* [API Reference](/docs/api-reference/reference/openapi)
* [CLI Command Reference](/docs/developers/cli/command-reference)
* [XvP settlement flows](/docs/api-reference/settlement/xvp-settlement-flows)
