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
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, so 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
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 for exact request and response fields, and the CLI Command Reference for command syntax.
Prerequisites
- Your DALP system has the token sale add-on factory available.
- You have authenticated and configured a client. See API integration 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 action. Configuration, activation, and settlement are issuer-side actions; buying and refund claims are investor-side actions.
Amount and time units
Amount fields use raw base units. Scale the sale token caps, purchase limits, soft cap, and minimum token amounts to the sale token decimals, and scale the buy amount to the decimals of the payment currency you send. Price ratios are scaled by 1e18. Timestamps for sale start, presale end, and vesting are Unix seconds.
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.
const created = await client.addons.tokenSale.create({
body: {
tokenAddress: "0xTOKEN",
saleStart: "1767225600",
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.
const createdWithPresale = await client.addons.tokenSale.create({
body: {
tokenAddress: "0xTOKEN",
saleStart: "1767225600",
saleDuration: 2592000,
hardCap: "1000000000000000000000000",
presale: {
endTime: "1767405600",
discountMultiplier: "800000000000000000",
maxPerAddress: "100000000000000000000",
whitelist: ["0xBUYER1", "0xBUYER2"],
},
walletVerification,
},
});Configure sale controls
Configure controls while the sale is in setup, before you activate it. 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 start, duration, and cliff for purchased tokens. |
| 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 before you activate; otherwise activation reverts with an insufficient balance error.
Set the purchase limits and soft cap against the sale address you created:
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 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.
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.
const now = await client.addons.tokenSale.currentTime({ query: {} });await client.addons.tokenSale.buy({
body: {
saleAddress,
currency: "0xUSDC",
amount: "1000000000000000000",
minTokenAmount: "950000000000000000",
walletVerification,
},
});If the sale sets a terms hash, an investor acknowledges the terms before buying:
const saleAddress = "0xSALE";
await client.addons.tokenSale.acknowledgeTerms({
body: { saleAddress, walletVerification },
});Pause and resume the sale while it is live when you need to hold purchases:
const saleAddress = "0xSALE";
await client.addons.tokenSale.pauseSale({
body: { saleAddress, walletVerification },
});
await client.addons.tokenSale.unpauseSale({
body: { saleAddress, walletVerification },
});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.
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 — for example, 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.
Scale and verify against the active chain. List and read are scoped to the active chain resolved from your system context; DALP does not aggregate sales across chains in one response.
When you need the chain timestamp the sale uses for scheduling, read it from the current-time endpoint:
const now = await client.addons.tokenSale.currentTime({ query: {} });For authenticated callers without admin, token manager, or sale admin roles, the read endpoint resolves the caller's effective buyer address and filters tokenSalePurchases to that buyer. An investor-scoped integration using this endpoint for full purchase reconciliation would undercount activity; call the read endpoint with an operator-scoped caller when you need all purchases for the sale.
Finalize and settle
End the sale window, then finalize it to settle accounting. Finalizing determines whether the sale met its soft cap.
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:
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 },
});When a sale fails its soft cap, an investor claims a refund per payment currency they paid in:
const saleAddress = "0xSALE";
await client.addons.tokenSale.claimRefund({
body: { saleAddress, currency: "0xUSDC", walletVerification },
});Investors withdraw purchased tokens through the token withdrawal operation once their tokens are claimable for the sale. Where vesting applies, tokens release on the configured schedule.
CLI coverage
The DALP CLI exposes the same flow under dalp token-sales. Create the sale first, configure controls before activation, then activate, sell, and settle.
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 claim-refund --address 0xSALE --currency 0xUSDCThe 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
Exchange rates
Reference for the current DALP exchange rates API, including list, supported currencies, current pair reads, history queries, feed selection, inverse-pair resolution, collection filters, pagination, and response envelopes.
XvP settlement flows
Create, approve, execute, and monitor XvP settlement flows through DALP APIs, SDKs, CLI commands, and polling.