Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
branch = "main"
path = "repos/airdrops"
url = "https://github.com/sablier-labs/airdrops"
[submodule "repos/evm-utils"]
branch = main
path = repos/evm-utils
url = https://github.com/sablier-labs/evm-utils
[submodule "repos/flow"]
branch = "main"
path = "repos/flow"
Expand Down
48 changes: 24 additions & 24 deletions bun.lock

Large diffs are not rendered by default.

164 changes: 122 additions & 42 deletions cli/autogen-reference.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,26 @@ find $airdrops -type f -name "*.md" -delete
find $flow -type f -name "*.md" -delete
find $lockup -type f -name "*.md" -delete

set_sidebar_position() {
local file=$1
local position=$2
echo "$(echo -en '---\nsidebar_position: '$position'\n---\n'; cat $file)" > $file
}

lint() {
contracts=docs/reference/$1/contracts

# Format the docs with Prettier
bun prettier --log-level silent --write $contracts
# Cache find result to avoid redundant filesystem scan
all_md_files=$(find $contracts -type f -name "*.md")

# Remove the italic asterisks added by `forge doc`: https://github.com/foundry-rs/foundry/issues/4540
sd --string-mode "\*" "" $(find $contracts -type f -name "*.md")
# Fix malformed code block endings with asterisks (e.g., ```* should be ```)
# This pattern appears in forge-generated docs and breaks markdown rendering
sd '```\*' '```' $all_md_files

# Re-format the docs with Prettier
# Remove remaining italic asterisks added by `forge doc`: https://github.com/foundry-rs/foundry/issues/4540
sd --string-mode "\*" "" $all_md_files

# Format the docs with Prettier
bun prettier --log-level silent --write $contracts
}

Expand All @@ -55,9 +65,6 @@ run() {
# Define the contracts directory
contracts=docs/reference/$repo/contracts

# Delete the current contracts references
find $contracts -type f -name "*.md" -delete

# Copy over the auto-generated files
rsync --archive \
--exclude "README.md" \
Expand All @@ -71,30 +78,77 @@ run() {
# Delete empty *.sol directories
find $contracts -type d -empty -delete

# Cache find result to avoid redundant filesystem scans throughout this function
all_md_files=$(find $contracts -type f -name "*.md")

# Replace the interface with hyperlinks
sd "\{I(\w+)\}" "[I\$1](/$contracts/interfaces/interface.I\$1.md)" $(find $contracts -type f -name "*.md")
sd "\{I(\w+)\}" "[I\$1](/$contracts/interfaces/interface.I\$1.md)" $all_md_files

if [ "$repo" = "airdrops" ]; then
# Airdrops-specific abstract patterns
sd "\{Sablier(\w+)Base\}" "[Sablier\${1}Base]($contracts/abstracts/abstract.Sablier\${1}Base.md)" $all_md_files
sd "\{SablierFactoryMerkle(\w+)\}" "[SablierFactoryMerkle\$1](/$contracts/contract.SablierFactoryMerkle\$1.md)" $all_md_files
sd "\{SablierMerkleLockup\}" "[SablierMerkleLockup]($contracts/abstracts/abstract.SablierMerkleLockup.md)" $all_md_files

# The Airdrops has certain references to the Lockup
sd "\{SablierLockup\}" "[SablierLockup](/reference/lockup/contracts/contract.SablierLockup.md)" $(find $airdrops -type f -name "*.md")
sd "\{SablierLockup\}" "[SablierLockup](/reference/lockup/contracts/contract.SablierLockup.md)" $all_md_files

# Airdrops-only evm-utils abstract
sd "\bAdminable\b" "[Adminable](/$contracts/abstracts/abstract.Adminable.md)" $all_md_files
fi

if [ "$repo" = "lockup" ]; then
# Lockup-specific abstract patterns
sd "\{SablierLockup(Dynamic|Linear|Tranched)\}" "[SablierLockup\$1]($contracts/abstracts/abstract.SablierLockup\$1.md)" $all_md_files
sd "\{SablierLockup(Dynamic|Linear|Tranched)\.(\w+)\}" "[SablierLockup\$1.\$2]($contracts/abstracts/abstract.SablierLockup\$1.md#\$2)" $all_md_files

# Fix anchor casing for createWith* functions in types/ (Docusaurus lowercases all anchors)
types_files=$(find $contracts/types -type f -name "*.md")
sd "(#createWithDurationsLD)" "#createwithdurationsld" $types_files
sd "(#createWithDurationsLL)" "#createwithdurationsll" $types_files
sd "(#createWithDurationsLT)" "#createwithdurationslt" $types_files
sd "(#createWithTimestampsLD)" "#createwithtimestampsld" $types_files
sd "(#createWithTimestampsLL)" "#createwithtimestampsll" $types_files
sd "(#createWithTimestampsLT)" "#createwithtimestampslt" $types_files

# Fix some invalid references in Lockup
sd "InvalidWithdrawalInWithdrawMultiple.md" "ISablierLockupBase.md#invalidwithdrawalinwithdrawmultiple" $(find $contracts -type f -name "*.md")
sd "/node_modules/forge-std/src/mocks/MockERC721.sol/contract.MockERC721.md" "https://eips.ethereum.org/EIPS/eip-165" $(find $contracts -type f -name "*.md")
sd "InvalidWithdrawalInWithdrawMultiple.md" "ISablierLockup.md#invalidwithdrawalinwithdrawmultiple" $all_md_files
sd "/docs/reference/lockup/contracts/interfaces/interface.InvalidStreamInCancelMultiple.md" "/docs/reference/lockup/contracts/interfaces/interface.ISablierLockup.md#invalidstreamincancelmultiple" $all_md_files
sd "/node_modules/forge-std/src/mocks/MockERC721.sol/contract.MockERC721.md" "https://eips.ethereum.org/EIPS/eip-165" $all_md_files
sd "/node_modules/@arbitrum/token-bridge-contracts/contracts/tokenbridge/libraries/ERC165.sol/abstract.ERC165.md#supportsinterface" "https://eips.ethereum.org/EIPS/eip-165" $all_md_files
fi

if [ "$repo" = "flow" ]; then
# Flow-specific abstract patterns
sd "\{SablierFlowState\}" "[SablierFlowState]($contracts/abstracts/abstract.SablierFlowState.md)" $all_md_files
fi

# Link to evm-utils abstracts (plain text - copied locally)
# Comptrollerable: all repos (airdrops, lockup, flow)
sd "\bComptrollerable\b" "[Comptrollerable](/$contracts/abstracts/abstract.Comptrollerable.md)" $all_md_files

# Batch and NoDelegateCall: lockup and flow only
if [ "$repo" = "lockup" ] || [ "$repo" = "flow" ]; then
sd "\bBatch\b" "[Batch](/$contracts/abstracts/abstract.Batch.md)" $all_md_files
sd "\bNoDelegateCall\b" "[NoDelegateCall](/$contracts/abstracts/abstract.NoDelegateCall.md)" $all_md_files
fi

# Replace the abstract contract references with hyperlinks (global fallback)
sd "\{Sablier(\w+)State\}" "[Sablier\${1}State]($contracts/abstracts/abstract.Sablier\${1}State.md)" $all_md_files

# Replace the contract references with hyperlinks
# Note: abstract contracts won't work
sd "\{Sablier(\w+)Base\}" "[Sablier\${1}Base]($contracts/abstracts/abstract.Sablier\${1}Base.md)" $(find $contracts -type f -name "*.md")
sd "\{Sablier(\w+)\}" "[Sablier\$1](/$contracts/contract.Sablier\$1.md)" $(find $contracts -type f -name "*.md")
sd "\{Sablier(\w+)\}" "[Sablier\$1](/$contracts/contract.Sablier\$1.md)" $all_md_files

# Fix external contract references that don't have docs
if [ "$repo" = "lockup" ]; then
sd "\[SablierComptroller\]\(/docs/reference/lockup/contracts/contract\.SablierComptroller\.md\)" "**SablierComptroller**" $all_md_files
fi

# Update the hyperlinks to use the directory structure of the docs website
# We need the capturing group to avoid replacing the "Git Source" URLs
sd "src/abstracts/\w+\.sol/([\w.]+)" $contracts'/abstracts/$1' $(find $contracts -type f -name "*.md")
sd "src/interfaces/\w+\.sol/([\w.]+)" $contracts'/interfaces/$1' $(find $contracts -type f -name "*.md")
sd "src/\w+\.sol/([\w.]+)" $contracts/'$1' $(find $contracts -type f -name "*.md")
sd "src/abstracts/\w+\.sol/([\w.]+)" $contracts'/abstracts/$1' $all_md_files
sd "src/interfaces/\w+\.sol/([\w.]+)" $contracts'/interfaces/$1' $all_md_files
sd "src/\w+\.sol/([\w.]+)" $contracts/'$1' $all_md_files
}

# ---------------------------------------------------------------------------- #
Expand All @@ -105,14 +159,9 @@ run() {
run "lockup"

# Reorder the contracts in the sidebar
contract=$lockup/contract.SablierLockup.md
echo "$(echo -en '---\nsidebar_position: 1\n---\n'; cat $contract)" > $contract

contract=$lockup/contract.SablierBatchLockup.md
echo "$(echo -en '---\nsidebar_position: 1\n---\n'; cat $contract)" > $contract

contract=$lockup/contract.LockupNFTDescriptor.md
echo "$(echo -en '---\nsidebar_position: 3\n---\n'; cat $contract)" > $contract
set_sidebar_position $lockup/contract.SablierLockup.md 1
set_sidebar_position $lockup/contract.SablierBatchLockup.md 1
set_sidebar_position $lockup/contract.LockupNFTDescriptor.md 3

lint "lockup"

Expand All @@ -124,17 +173,14 @@ lint "lockup"
run "airdrops"

# Reorder the contracts in the sidebar
contract=$airdrops/contract.SablierMerkleFactory.md
echo "$(echo -en '---\nsidebar_position: 2\n---\n'; cat $contract)" > $contract

contract=$airdrops/contract.SablierMerkleInstant.md
echo "$(echo -en '---\nsidebar_position: 3\n---\n'; cat $contract)" > $contract

contract=$airdrops/contract.SablierMerkleLL.md
echo "$(echo -en '---\nsidebar_position: 3\n---\n'; cat $contract)" > $contract

contract=$airdrops/contract.SablierMerkleLT.md
echo "$(echo -en '---\nsidebar_position: 3\n---\n'; cat $contract)" > $contract
set_sidebar_position $airdrops/contract.SablierFactoryMerkleInstant.md 2
set_sidebar_position $airdrops/contract.SablierFactoryMerkleLL.md 2
set_sidebar_position $airdrops/contract.SablierFactoryMerkleLT.md 2
set_sidebar_position $airdrops/contract.SablierFactoryMerkleVCA.md 2
set_sidebar_position $airdrops/contract.SablierMerkleInstant.md 3
set_sidebar_position $airdrops/contract.SablierMerkleLL.md 3
set_sidebar_position $airdrops/contract.SablierMerkleLT.md 3
set_sidebar_position $airdrops/contract.SablierMerkleVCA.md 3

lint "airdrops"

Expand All @@ -146,10 +192,44 @@ lint "airdrops"
run "flow"

# Reorder the contracts in the sidebar
contract=$flow/contract.SablierFlow.md
echo "$(echo -en '---\nsidebar_position: 1\n---\n'; cat $contract)" > $contract

contract=$flow/contract.FlowNFTDescriptor.md
echo "$(echo -en '---\nsidebar_position: 2\n---\n'; cat $contract)" > $contract
set_sidebar_position $flow/contract.SablierFlow.md 1
set_sidebar_position $flow/contract.FlowNFTDescriptor.md 2

lint "flow"

# ---------------------------------------------------------------------------- #
# EVM Utils #
# ---------------------------------------------------------------------------- #

# Generate the raw docs with Forge for evm-utils (temporarily)
cd repos/evm-utils
rm -rf ./docs
forge doc
cd ../../

# Define evm-utils contracts directory (temporary)
evmutils_temp=repos/evm-utils/docs/src/src

# Copy relevant evm-utils abstracts to each repo
# Airdrops: Adminable, Comptrollerable
cp $evmutils_temp/Adminable.sol/abstract.Adminable.md $airdrops/abstracts/ 2>/dev/null || true
cp $evmutils_temp/Comptrollerable.sol/abstract.Comptrollerable.md $airdrops/abstracts/ 2>/dev/null || true

# Lockup: Batch, Comptrollerable, NoDelegateCall
cp $evmutils_temp/Batch.sol/abstract.Batch.md $lockup/abstracts/ 2>/dev/null || true
cp $evmutils_temp/Comptrollerable.sol/abstract.Comptrollerable.md $lockup/abstracts/ 2>/dev/null || true
cp $evmutils_temp/NoDelegateCall.sol/abstract.NoDelegateCall.md $lockup/abstracts/ 2>/dev/null || true

# Flow: Batch, Comptrollerable, NoDelegateCall
cp $evmutils_temp/Batch.sol/abstract.Batch.md $flow/abstracts/ 2>/dev/null || true
cp $evmutils_temp/Comptrollerable.sol/abstract.Comptrollerable.md $flow/abstracts/ 2>/dev/null || true
cp $evmutils_temp/NoDelegateCall.sol/abstract.NoDelegateCall.md $flow/abstracts/ 2>/dev/null || true

# Fix broken links in copied evm-utils abstracts (remove interface links that don't exist)
sd "\[IAdminable\]\(/src/interfaces/IAdminable\.sol/interface\.IAdminable\.md\)" "IAdminable" $airdrops/abstracts/abstract.Adminable.md
sd "\[IComptrollerable\]\(/src/interfaces/IComptrollerable\.sol/interface\.IComptrollerable\.md\)" "IComptrollerable" $airdrops/abstracts/abstract.Comptrollerable.md $lockup/abstracts/abstract.Comptrollerable.md $flow/abstracts/abstract.Comptrollerable.md
sd "\[IBatch\]\(/src/interfaces/IBatch\.sol/interface\.IBatch\.md\)" "IBatch" $lockup/abstracts/abstract.Batch.md $flow/abstracts/abstract.Batch.md
sd "\[INoDelegateCall\]\(/src/interfaces/INoDelegateCall\.sol/interface\.INoDelegateCall\.md\)" "INoDelegateCall" $lockup/abstracts/abstract.NoDelegateCall.md $flow/abstracts/abstract.NoDelegateCall.md

# Clean up the evm-utils temp docs (we don't need to keep them)
rm -rf repos/evm-utils/docs
4 changes: 2 additions & 2 deletions docs/api/03-identifiers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ title: "Identifiers"

### Contracts

Onchain, each Lockup and Flow contract assigns a [`streamId`](/reference/lockup/contracts/abstracts/abstract.SablierLockupBase#nextstreamid) to each stream, which is the same as the ERC-721 [`tokenId`](https://docs.openzeppelin.com/contracts/5.x/api/token/erc721) (each stream is tokenized as an ERC-721).
Onchain, each Lockup and Flow contract assigns a [`streamId`](/reference/lockup/contracts/abstracts/abstract.SablierLockupState#nextstreamid) to each stream, which is the same as the ERC-721 [`tokenId`](https://docs.openzeppelin.com/contracts/5.x/api/token/erc721) (each stream is tokenized as an ERC-721).

The `streamId`/ `tokenId` in the contract is a simple numerical value (a `uint256`). For example, number `42` is a valid `streamId`/ `tokenId`.

You will always need this value when interacting with Sablier via a JSON-RPC endpoint because it is required by every contract method associated with a stream, e.g. [`streamedAmountOf`](/reference/lockup/contracts/interfaces/interface.ISablierLockupBase#streamedamountof).
You will always need this value when interacting with Sablier via a JSON-RPC endpoint because it is required by every contract method associated with a stream, e.g. [`streamedAmountOf`](/reference/lockup/contracts/interfaces/interface.ISablierLockup#streamedamountof).

### Indexers

Expand Down
2 changes: 1 addition & 1 deletion docs/concepts/14-glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ A data object that encapsulates amounts to be unlocked at the start of the strea

### Vesting Math

[A public library](/reference/lockup/contracts/libraries/library.VestingMath) used by the Lockup protocol to calculate
[A public library](/reference/lockup/contracts/libraries/library.LockupMath) used by the Lockup protocol to calculate
the amount of vested tokens at any given time.

## Merkle Airdrop
Expand Down
4 changes: 1 addition & 3 deletions docs/concepts/lockup/04-tranches.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ Where:
- The sum of all tranche amounts must equal the deposit amount.
- The block gas limit enforces a limit to how many tranches there can be in a stream.
- If someone creates a stream with an excessively large number of tranches, the transaction would revert as it
wouldn't fit within a block. You can fetch the limit using
[MAX_TRANCHE_COUNT](/reference/lockup/contracts/contract.SablierLockup#max_count). Alternatively, you can find the
limit for each chain [here](https://github.com/sablier-labs/lockup/blob/main/script/Base.s.sol#L90-L131).
wouldn't fit within a block. In such cases, make sure to simulate the transaction first.
- The timestamps must be sorted in ascending order. It's not possible for the $(i-1)^{th}$ timestamp to be greater than
$i^{th}$ timestamp (given that we're dealing with an increasing monotonic function).
5 changes: 2 additions & 3 deletions docs/guides/05-snapshot-voting.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ For the best results, we recommend using the primary policies.
The withdrawable amount counts tokens that have been streamed but not withdrawn yet by the recipient.

This is provided using the
[`withdrawableAmountOf`](/reference/lockup/contracts/abstracts/abstract.SablierLockupBase#withdrawableamountof) contract
method.
[`withdrawableAmountOf`](/reference/lockup/contracts/contract.SablierLockup#withdrawableamountof) contract method.

Voting power: realized (present).

Expand Down Expand Up @@ -151,7 +150,7 @@ Aggregates historical amounts that have already been streamed to the recipient.
included.

It relies on the `streamedAmountOf` method in the
[SablierLockupBase](/reference/lockup/contracts/abstracts/abstract.SablierLockupBase#streamedamountof) contract.
[SablierLockup](/reference/lockup/contracts/contract.SablierLockup#streamedamountof) contract.

:::caution Caveats

Expand Down
2 changes: 1 addition & 1 deletion docs/guides/lockup/examples/05-hooks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Lockup contract's admin.

:::info

[`allowToHook`](/reference/lockup/contracts/interfaces/interface.ISablierLockupBase#allowtohook) is an irreversible
[`allowToHook`](/reference/lockup/contracts/interfaces/interface.ISablierLockup#allowtohook) is an irreversible
operation, i.e., once a contract has been added to the allowlist, it can never be removed. This is to ensure stronger
immutability and decentralization guarantees. Once a recipient contract is allowlisted, integrators should NOT have to
trust us to keep their contract on the allowlist.
Expand Down
12 changes: 6 additions & 6 deletions docs/guides/lockup/examples/stream-management/02-withdraw.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ the withdrawal to an alternative address of their choice.

There are four withdrawal functions:

1. [`withdraw`](/reference/lockup/contracts/abstracts/abstract.SablierLockupBase#withdraw): withdraws a specific amount
1. [`withdraw`](/reference/lockup/contracts/contract.SablierLockup#withdraw): withdraws a specific amount
of tokens.
2. [`withdrawMax`](/reference/lockup/contracts/abstracts/abstract.SablierLockupBase#withdrawmax): withdraws the maximum
2. [`withdrawMax`](/reference/lockup/contracts/contract.SablierLockup#withdrawmax): withdraws the maximum
withdrawable amount of tokens.
3. [`withdrawMaxAndTransfer`](/reference/lockup/contracts/abstracts/abstract.SablierLockupBase#withdrawmaxandtransfer):
3. [`withdrawMaxAndTransfer`](/reference/lockup/contracts/contract.SablierLockup#withdrawmaxandtransfer):
withdraws the maximum withdrawable amount and transfers the NFT.
4. [`withdrawMultiple`](/reference/lockup/contracts/abstracts/abstract.SablierLockupBase#withdrawmultiple): withdraws
4. [`withdrawMultiple`](/reference/lockup/contracts/contract.SablierLockup#withdrawmultiple): withdraws
specific amounts of tokens from multiple streams at once.

To call any of these functions, you need to have created a stream. If you don't have one yet, go back to the
Expand All @@ -44,7 +44,7 @@ assigning the `StreamManagement` contract as the recipient. Then, you can use th

In this example, the withdrawal address and withdrawal amount are hard-coded for demonstration purposes. However, in a
production environment, these values would likely be adjustable parameters determined by the user. Alternatively, you
can use [`withdrawableAmountOf`](/reference/lockup/contracts/abstracts/abstract.SablierLockupBase#withdrawableamountof)
can use [`withdrawableAmountOf`](/reference/lockup/contracts/contract.SablierLockup#withdrawableamountof)
function to determine how much amount of tokens is available to withdraw.

In addition to the `withdraw` function, there is the `withdrawMax` function, which you can use to withdraw the maximum
Expand All @@ -55,7 +55,7 @@ withdrawable amount of tokens at the time of invocation:
</CodeBlock>

What `withdrawMax` does is call the
[`withdrawableAmountOf`](/reference/lockup/contracts/abstracts/abstract.SablierLockupBase#withdrawableamountof) function
[`withdrawableAmountOf`](/reference/lockup/contracts/contract.SablierLockup#withdrawableamountof) function
and pass its value to `withdraw`.

Similar to `withdrawMax`, you can use `withdrawMaxAndTransfer` to withdraw the maximum withdrawable tokens and at the
Expand Down
4 changes: 2 additions & 2 deletions docs/guides/lockup/examples/stream-management/03-cancel.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ recipient will need to withdraw it.

There are two functions that can be used to cancel streams:

1. [`cancel`](/reference/lockup/contracts/abstracts/abstract.SablierLockupBase#cancel): cancels a single stream
2. [`cancelMultiple`](/reference/lockup/contracts/abstracts/abstract.SablierLockupBase#cancelmultiple): cancels multiple
1. [`cancel`](/reference/lockup/contracts/contract.SablierLockup#cancel): cancels a single stream
2. [`cancelMultiple`](/reference/lockup/contracts/contract.SablierLockup#cancelmultiple): cancels multiple
streams at once

To call any of these functions, you need to have created a cancelable stream. If you don't have one yet, go back to the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Renouncing a stream means that the sender of the stream will no longer be able t
sender wants to give up control of the stream.

To renounce a stream, you can use
[`renounce`](/reference/lockup/contracts/abstracts/abstract.SablierLockupBase#renounce).
[`renounce`](/reference/lockup/contracts/contract.SablierLockup#renounce).

Before invoking this function, ensure that you have an active, cancelable stream with the sender set to the
`StreamManagement` contract. Once the stream is created, you can use the `renounce` function like this:
Expand Down
Loading