# Drips Docs
## Accessing Drips
Drips is an open-source application and protocol built on Ethereum, a publicly-accessible, global blockchain. While we've built the Drips App for simple interaction with Drips' underlying smart contracts, there are a multitude of alternative tools that enable interaction with the Drips Protocol, some of which are listed below.
* [The Drips SDK][sd] is a Javascript SDK that allows any application or user-created code to interact directly with the Drips smart contracts and utilize the full feature set of the protocol in whatever way they wish. Drips contracts can be freely integrated into any website, dapp or application at any time using the SDK.
* [Etherscan](https://etherscan.io/) is a block explorer and analytics platform for Ethereum. Other networks have similar block explorers, often also operated by Etherscan. The Drips contracts can be accessed using these block explorers at the addresses specified on the [Smart Contract and Subgraph Details](the-protocol/smart-contract-details) page. Other networks have similar block explorers.
* [ethBuild][eb] is an educational sandbox for web3, including drag-and-drop programming and open source building blocks. With ethBuild, you can call a function on an Ethereum contract (for example, to create a Drip or Split configuration) by sending it a transaction with the function arguments in the data.
* [MyEtherWallet][me] is a free, client side interface helping you interact with the Ethereum blockchain. Their easy-to-use, open-source platform allows you to generate wallets, interact with (Drips) smart contracts, and more.
* The [web3.js Library][w3] makes it easy to interact with smart contracts on the Ethereum blockchain, including the Drips smart contracts.
* The [ethers.js Library][et] is another Javascript library for interacting directly with Ethereum smart contracts, including the Drips smart contracts.
[sd]: https://github.com/drips-network/sdk
[sc]: the-protocol/smart-contract-details
[eb]: https://eth.build/
[me]: https://www.myetherwallet.com/
[w3]: https://web3js.readthedocs.io/en/v1.7.5/
[et]: https://docs.ethers.io/v5/
## FAQ
#### Are there fees for using Drips?
The Drips Protocol is free to use and does not impose any fees on users. Users will still need to pay gas
fees to interact with the Drips Contracts as they would with any contract on Ethereum.
Third-party apps building on Drips may choose to impose fees of their own.
#### Can I split or stream funds directly to exchange-managed Ethereum addresses?
No. When an address receives funds on Drips, the owner of that address needs to *collect* them before they are transferred to their wallet. Exchanges typically offer non-custodial addresses specifically for receiving ERC-20 tokens on various networks, but offer no way to sign contract interactions from those addresses. As a result, funds sent on Drips to such a custodial, exchange-managed address may be unrecoverable. For this reason, only split or stream funds to self-custodial wallets, such as externally-owned wallets (EOAs) or smart contract wallets (multisigs) like Safe.
#### Do I need to have ETH in my account to send or receive tokens using Drips?
Users of Drips generally do need to have ETH (or the analogous gas token to ETH on networks other than Ethereum)
available in their wallet to cover transaction fees, if they wish to create streams or Splits to
stream or share funds with others. Users do not need to have ETH in their accounts to receive
funds from other users who are streaming or Splitting to them. However, they will need to have
a small amount of ETH available in their account when they are ready to collect funds sent to
them (again, to cover gas costs of sending the collect transaction). In certain situations, Drips may "sponsor" the claiming of projects or collecting of funds. In this case, the transaction screen will indicate that gas is "paid for by Drips".
#### What happens to funds that have been streamed to a Github repository that is never claimed by its owner?
For now, nothing happens to these funds and they will remain in Drips waiting for the repository owner to claim them forever. It's possible that in the future the governance of the Drips protocol will decide to change this, so that funds given to a Github repository that go unclaimed are donated to an alternative public goods fund like Gitcoin, or to some other cause.
## Managing Drip List and project visibility
import Figure from '../../components/Figure'
By default, newly-claimed projects and created Drip Lists are publicly visible across the Drips App, including on their owner's profiles. However, as the owner of a project or Drip List, you may choose to *hide* them. In addition, Drip Lists transferred to another address must be explicitly un-hidden before appearing on the new owner's public profile.
### Controlling visibility of a project or Drip List
In order to hide or un-hide a project or Drip List, connect your wallet, navigate to the project or Drip List in question, click "Edit" next to its name, and toggle the "Hidden" switch. Lastly, confirm the change in your wallet.
#### Visibility of transferred Drip Lists
Drip Lists are represented on-chain as ERC-721 tokens (NFTs), and may thus be transferred between Ethereum addresses. Due to the nature of the blockchain, anyone may send any other address any token(s) they own, even without explicit consent by the receiving party. In order to prevent unwanted Drip Lists to inadvertedly appear on users' profiles, Drip Lists are **automatically hidden** upon transfer, with the caveats listed below. Should you receive a Drip List that you wish to display on your profile, simply un-hide it as described above.
#### Effects of hiding a project or Drip List
When a project or Drip List is hidden, it will:
* no longer appear on their owner's public Drips profile,
* be hidden behind a "Show hidden" toggle on the owner's own dashboard,
* be greyed out on any other projects, Drip Lists, or users that already split or donated to the now-hidden project or Drip List pre-hiding,
* no longer allow creating new donations or splits to it via the official Drips App, and
* be hidden from Google and other search engines.
However, due to Drips being an open & transparent, blockchain-based protocol:
* Hidden projects or Drip Lists are **not** hidden on the Ethereum blockchain, and may be discovered by looking at raw blockchain data itself.
* Users with direct links to hidden projects or Drip Lists **can still access them**, but the app will display a banner informing them that the project or Drip List is hidden.
* If other projects or Drip Lists are (still) splitting funds to a now-hidden project or Drip List, the hidden project will **still appear** on those entities' pages, but will be greyed out and marked as hidden.
* While the Drips App will generally prevent new funds from being sent to hidden projects or Drip Lists, **it is still possible to send funds** by interacting directly with the underlying smart contracts, through third-party apps, or if a split to a now-hidden project was established before it was hidden.
## Usage with a Safe multisig
You can use the Drips App as a Safe App for conveniently proposing transactions to your Safe.
#### Launching Drips as a Safe App
Within your Safe's UI, simply navigate to the *Apps* tab and launch *Drips*. This is supported on Ethereum mainnet and [https://safe.filecoin.io/](https://safe.filecoin.io/), but on other networks you may need to add the respective Drips App URL as a "Custom Safe App". For example, on Optimism, click "Add custom Safe App" and enter `https://optimism.drips.network` as the URL.
#### Interacting with Drips through a Safe
You can generally interact with Drips the same way as you would with an EOA wallet. There are a few things to consider, however.
##### Edits to streams or balances only apply at the time the transaction is executed
When you create, pause or edit a stream, the edits will only apply at the exact moment your Safe executes the transaction, not at the timestamp of proposal. For instance, this means that an outgoing stream that you pause will continue streaming until your Safe executes the pause transaction.
When scheduling a stream for the future, ensure that the transaction is executed before its scheduled start date. Scheduled streams begin streaming at the time of transaction confirmation *or* any scheduled start date, depending on which date is later.
##### Some transactions are invalidated by updates to a Drips account's state
Many interactions with the Drips app rely on a specific configuration of the account's stream receivers. Such transactions may fail if the configuration is altered between the proposal of the transaction and its actual execution. Thus, configuration-altering interactions must be made with extra care, because they may invalidate other pending transactions.
Such interactions include:
* Adding funds (Does not invalidate other pending transactions)
* Collecting funds (Does not invalidate other pending transactions)
* Creating a stream (Invalidates other pending transactions)
* Editing a stream (Invalidates other pending transactions)
* Deleting a stream (Invalidates other pending transactions)
* Pausing / un-pausing a stream (Invalidates other pending transactions)
When editing streams, all interactions are *scoped per ERC-20 token*, meaning that a transaction changing streams for token A never invalidates another interacting with token B.
Additionally, edits to your Splits receivers will invalidate other transactions altering Splits receivers, but are not invalidated by any edits to your account's stream receivers.
Consider the following example:
* You propose a transaction that creates a new stream streaming token A.
* Before executing that transaction, you propose another transaction that adds funds for a token A.
* You execute the 1st transaction and a new stream is created, altering your account's state of stream receivers.
* The 2nd transaction would fail if executed, because it relies on the account's specific state before the new stream was created.
However, the following scenario works fine, because adding funds does not alter your account's stream receiver state:
* You propose a transaction that adds funds for a token A to your Drips account.
* Before executing that transaction, you propose another transaction that creates a new stream streaming token A.
* You execute the first transaction, and funds are added to your Drips account.
* You execute the second transaction, and the new stream is created.
## Claim your open-source project
import Figure from '../../components/Figure'
import Video from '../../components/Video'
A *Project* on Drips represents an open-source repository on GitHub, with support for other Git hosts coming soon. Every GitHub repository starts out on Drips as *unclaimed*. In this state, it can already be funded with a Drip List, or added as a dependency of another project. At any point, the project's maintainers can *claim* their project on Drips, configure a list of *maintainers* and *dependencies*, and collect any previously-received earnings. Future incoming funds are then automatically split as configured by the project maintainers, resulting in deeply-nested dependencies receiving their allocation.
:::caution
Want to claim your project for Drips for Privacy? Make sure you're using the [Optimism deployment of the Drips app](https://optimism.drips.network).
:::
:::info
If you haven't yet, learn more about the Drips Network's fundamentals in our [introduction](/).
:::
### How funds reach projects
Using the Drips app, individuals or organizations wishing to financially support their dependencies [can do so with a *Drip List*](support-your-dependencies/overview). Funds sent to Drip Lists are automatically *split* among those listed (projects, Ethereum addresses, or other Drip Lists) monthly, on the last Thursday of every month. When funds reach a project from a Drip List, they are further split according to the project's dependencies and maintainers, resulting in a global, fundable *dependency tree*.
### Your repository on Drips
To find your repository's associated "project" on Drips, simply launch the Drips App, paste its GitHub URL into the search bar, and click **Jump to GitHub repo**. On the resulting project screen, you can see whether your project already accumulated any *claimable funds*, and quickly jump into the claim process by clicking **Claim project**.
### How Drips manages project access
Drips is built on Ethereum, a global, decentralized blockchain. On Drips, every claimed project is owned by an *Ethereum address*. You can learn more about Ethereum and create your own personal Ethereum address by [downloading and installing an Ethereum wallet app](https://ethereum.org/en/wallets/).
As part of the claim process, the on-chain Drips Smart Contracts ensure that a particular Ethereum address should be able to manage a project and its funds by checking whether its default branch on GitHub includes a `FUNDING.json` file in the root directory. Drips uses [Lit Protocol](https://litprotocol.com/) to compute a cryptographic proof of the `FUNDING.json` file's contents that is verifiable on-chain. This proof is then submitted to the Drips Smart Contracts, which verify it and update the project's owner accordingly.
The `FUNDING.json` file includes an Ethereum address, which will subsequently be granted access to the repository on Drips. Your real FUNDING.json file will include your own Ethereum address, and will automatically be generated for you as part of the claiming process.
Access is managed separately for different chains, for example Filecoin. For example, below the same placeholder address has access to the project on Ethereum and Filecoin networks.
```json
{
"drips": {
"ethereum": {
"ownedBy": "0x0000000000000000000000000000000000000000"
},
"filecoin": {
"ownedBy": "0x0000000000000000000000000000000000000000"
}
}
}
```
:::info
If your project is led by a group of multiple individuals, you may consider using a [Safe multisig](https://safe.global/) to claim it on Drips. This will enable a specific set of individuals to agree on changes to how the project splits incoming funds in the future. Learn how to use Drips with a Safe multisig [here](advanced/usage-with-a-safe).
:::
### Claiming your project
You can enter the claim process by launching the Drips App, connecting your Ethereum wallet, and navigating to **Projects → Claim project**. As part of the flow, you'll be asked to commit a `FUNDING.json` file to the repository's *default* branch. If in order to do so you need to wait for a PR to be reviewed, you can simply resume the flow later.
Once the `FUNDING.json` file has been verified, you'll be asked to configure a list of *maintainers* and *dependencies* that incoming funds will be split to, and customize your project's appearance on Drips.
:::info
To claim your project, you will need a small amount of ETH in your connected Ethereum wallet to cover Ethereum's network ["gas fee"](https://ethereum.org/en/developers/docs/gas/).
:::
After you're done, your project will be claimed, and any future earnings will be split according to your configuration.
#### Editing your project
You can edit your project's appearance and split configuration at any point in the future by navigating to it from the Projects tab, and clicking **Edit** on the thing you'd like to change.
#### Re-claiming with a different owner
If you need to change the owner of an already-claimed project to a different Ethereum address, you can do so by re-claiming the project:
1. Connect the **new** owner wallet to the Drips app.
2. Navigate to **Projects → Claim project** and enter the project's GitHub URL.
3. You'll see a message that the project has already been claimed. Continue the flow to re-claim it.
4. Update the `FUNDING.json` file on your repo's default branch with the new owner's Ethereum address, and verify it.
5. Configure maintainers and dependencies as desired, and complete the flow.
The project's owner will be updated to the new wallet address.
:::caution
Re-claiming a project updates its **owner address** and **splits configuration**. The new owner will have full control over the project's splits and metadata going forward.
:::
### Collecting earnings
If your project already has claimable funds, the claim process will immediately apply the percentage splits to maintainers and dependencies you set up. If you yourself received a portion of the claimable funds, you will be able to immediately transfer them to your Ethereum wallet by clicking **Collect** on the top right.
### Settlement of future funds
Funds in the global Drips Dependency Tree are automatically split accordingly:
* On Ethereum, *once per month* on every last Thursday, resulting in *monthly settlement*.
* On Filecoin, *once per day*, resulting in *daily settlement*.
* On OP Mainnet, *once per day*, resulting in *daily settlement*.
This means that once a project is claimed, funds may reach your project monthly or daily, and will automatically be forwarded according to your configured splits to maintainers and dependencies.
The exact time at which settlement will occur on the indicated day may vary due to gas cost optimization measures.
You can at any point review the next settlement day in the app's Collect modal, but please note that the settlement doesn't occur exactly at midnight, but rather at some point throughout the indicated day, roughly within the UTC time zone.
If you are a recipient of funding on Drips, you can check for and collect any earnings by clicking "Collect" in the top-right corner after connecting your wallet.
## Administering a RetroPGF Round
import Figure from '../../components/Figure'
As a round administrator, you are responsible for managing the lifecycle of your RetroPGF round—from reviewing applications to publishing results and facilitating payouts. This guide covers the key administrative tasks after your round has been [created and published](/rpgf/create-your-round).
### 1. Reviewing Applications
Once your round's application period begins, submissions will start appearing on the **"All applications"** screen of your round's page. Initially, all new submissions have a `Pending` status.
Before the voting period starts, you must review these pending applications. You can select one or more applications from the list and use the panel on the right-hand side to approve or reject them in bulk.
* **Pending & Rejected Applications**: These are only visible to you (the admins) and the original applicant.
* **Approved Applications**: These are immediately made public on the round page (excluding any private data fields). Only approved applications will be visible to badgeholders during the voting phase.
### 2. Badgeholder Voting
After application intake and review, [designated badgeholders can start casting their ballots](/rpgf/vote-on-a-round).
#### Reviewing Ballots
Click **Ballots -> View All** on the round screen while signed in as admin to view submitted ballots. You can export a list of all ballots, including which badgeholder voted for which application and how many votes they allocated, by clicking the "Download CSV" button in the top-right corner.
#### Editing The Badgeholder List
You can add additional badgeholders freely up until the **Distribution** phase beings. Please note that only badgeholders that have not yet cast a ballot can be *removed* from the round. Once a badgeholder has been added, they can immediately cast their ballot from the round view.
#### Advanced: Uploading a ballot on behalf of a badgeholder
In some cases, it may be operationally necessary for an admin to upload a ballot on behalf of a badgeholder. To do so, click the "Manually upload ballot" button on the **Ballots** screen. You will need to choose the Ethereum address of the badgeholder and upload a CSV or Excel file containing their ballot, matching the [format for badgeholder ballot uploads](http://localhost:5174/rpgf/vote-on-a-round#4-alternative-voting-via-spreadsheet-upload).
:::warning
Uploading a ballot on behalf of a badgeholder should only be done in exceptional circumstances, as it circumvents the standard voting process and may raise concerns about ballot integrity. Ensure you have explicit permission from the badgeholder before proceeding.
A ballot uploaded by an admin will:
* not be signed by the badgeholder's wallet, but the admin's.
* be marked in the audit log as having been uploaded by an admin.
:::
### 3. Publishing Results
After the voting period has officially ended, your next step is to tally the votes and publish the final results.
#### Tallying Votes
Navigate to the "All applications" screen, where a new card will prompt you to prepare the results. Drips provides several automated methods for tallying votes:
* **Sum**: The total number of votes received by each project across all ballots.
* **Average**: The average number of votes received by a project across all ballots.
* **Median**: The median number of votes received by a project across all ballots.
* **Spreadsheet Upload (CSV or XLSX)**: If you want to use a custom allocation method, you can upload a spreadsheet with your results. This process is similar to [uploading a ballot as a spreadsheet](/rpgf/vote-on-a-round#uploading-a-ballot-as-a-spreadsheet). You will first need to export the approved applications for your round, then re-upload the spreadsheet with at least `ID` and `Allocation` columns, where `ID` is the application ID and `Allocation` is the result you want to associate to that application.
#### Reviewing and Publishing
Once you select a tallying method and calculate the results, they will appear on the "All applications" screen. **At this stage, the results are visible only to you and other round admins.** You can review the outcome and sort the list by the amount of votes received, as well as download a CSV of all applications, which now includes the calculated results as well.
When you are ready, click the **Publish results** button to make the final results public to everyone.
### 4. Distributing Rewards
Rewards for RPGF rounds are sent via [Drip Lists](/support-your-dependencies/overview) directly to the winners' Drips Projects (their claimed GitHub repositories).
#### Automatically Preparing a Drip List
After you publish the results, a button to **"Prepare a Drip List"** will appear. This powerful feature automatically generates a new Drip List where the percentage splits match the final vote distribution.
:::warning
Percentages on Drip Lists have a maximum resolution of 4 digits past the decimal. This means that depending on the chosen ERC-20 token's resolution, final distribution amounts may not *perfectly* match the round results after being sent to the Drip List.
:::
Once created, this Drip List will be publicly displayed in the "Distribution" section of your round's page. Anyone—including your organization, sponsors, or community members—can then fund this list with a one-time donation or a continuous stream of any ERC-20 token.
#### Manually Linking Drip Lists
You also have the option to create one or more Drip Lists manually and link them to your round. In the "Distribution" section, click **"Edit linked lists"** to add or remove any Drip List. Please note that linking or unlinking a Drip List only affects which lists are displayed publicly on your round's page.
### 5. Exporting Round Data
For transparency, analysis, or record-keeping, you can download raw data from your round at any time.
#### Application Data
Navigate to the **"All applications"** page and click the "Download" button in the top right. This will export a CSV file containing all submitted data for every application, with one application per line. **This export includes all private fields.**
#### Vote Data
After the voting phase has started, navigate to the **"Ballots"** section of your round. The "Download CSV" button here will export a file containing the raw voting data. The CSV includes one line for application on each ballot, detailing which badgeholder voted for which application, the number of votes they allocated, and an application ID that can be used to cross-reference the data with the applications export.
### Audit Logs
For security and compliance, Drips maintains an immutable audit log of all actions taken on your round. This includes actions such as approving or rejecting applications, publishing results, users submitting applications, KYC providers verifying identities, and more.
To view the audit log for your round, head to **Settings** → **Audit log**.
## Applying to a RetroPGF Round
import Figure from '../../components/Figure'
Applying to a RetroPGF round on Drips allows you to showcase your past contributions and be considered for funding by the round organizers. This guide explains the application process from the perspective of an applicant.
You can submit applications to any round that is currently in the "Registration" phase.
### Before You Apply: Your Drips Project
All applications to an RPGF round must be submitted on behalf of a Drips *Project*. A Project on Drips is a claimed GitHub repository that can receive and split funds automatically.
This means you must [claim a GitHub repository on Drips](/get-support/claim-your-repository) before or during the application process.
:::info
**Not an open-source project?**
If you are an individual or organization without a dedicated GitHub repository, you can still apply by creating an otherwise empty GitHub repository to represent your entity on Drips. We are working on allowing applications with only an Ethereum address in the future.
:::
:::info
**On supported networks including Filecoin, Drips will cover the transaction fee ("gas") required for claiming a project.** You can disable gasless transactions under Settings → Gasless transactions.
:::
#### How Payouts Work
If your application is successful, any rewards from the round will be paid out directly to your Drips Project. These funds will then be automatically distributed according to your Project's configured splits between maintainers and dependencies.
For example, if your Project is configured to forward 20% of incoming funds to its dependencies, that same 20% of any RetroPGF rewards sent to your project by the round organizers will be automatically forwarded to the open-source projects you rely on.
:::info
While optional, forwarding even a small percentage of funds to your software dependencies is a **great way to support the projects that enable your success**. Consider adding your key dependencies to your Project's splits for a fairer, more equitable open-source ecosystem.
:::
### How to Apply
During a round's application phase, follow these steps to submit your application.
#### 1. Start Your Application
Navigate to the round's public page. If the application period is open, you will see an **Apply now** button in the top-right corner. Click it, then connect your Ethereum wallet and sign in with a free signature to begin.
#### 2. Select Your Project
You will be asked to select which of your claimed Drips Projects you are applying with. If you haven't claimed your project yet, you can use the **Claim new project** button to start that process first.
#### 3. Pick a category
Choose from one of the available categories for the given round. The category you choose determines the application form fields shown in the next step.
#### 4. Fill Out the Application Form
Fill out the application form fields truthfully. The questions and required information are configured by the round's organizers to help round voters evaluate your project's impact.
:::danger
Fields **NOT** marked with a lock icon (🔒) are public. Any information you enter into these fields will be permanently published to the blockchain and will be publicly visible to everyone. Only enter sensitive information into fields that are marked as private.
:::
#### 5. Submit Your Application
Once you have filled out all the required fields, click submit. Submitting an application requires a blockchain transaction to create a permanent, verifiable record of your submission.
:::info
**On supported networks including Filecoin, Drips will cover the transaction fee ("gas") for you.** You can disable gasless transactions under **Settings** → **Gasless transactions**.
:::
:::info
Your application's public information will be uploaded to IPFS and **attested to on-chain** via the [Ethereum Attestation Service (EAS)](https://attest.sh/). After the round organizers have reviewed and approved your application, it will be publicly visible on Drips.
:::
### After You've Applied
#### Checking Your Application Status
After submitting, you can view your application and check its status. Initially, its status will be `Pending`. The round organizers will review it and change its status to either `Approved` or `Rejected`.
* **Approved** applications become publicly visible on the round page (excluding any private data).
* **Pending** and **Rejected** applications are only visible to you and the round admins.
If your application was rejected by the round organizers, you have the option to "re-submit" it by making an edit anytime during the application period (See [Updating An Application](#updating-an-application)). Doing so will move its status back to `Pending`.
To see all your submissions, navigate to the round's main page, sign in, and filter the list of applications by **"Own applications"**.
#### Verifying Your Identity (KYC/KYB)
The round's organizers may require you to complete an external KYC (Know Your Customer) / KYB (Know Your Business) identitify verification process before you can receive any rewards. If KYC/KYB is required, you will see a notification on your application page with a link to start the process. Follow the instructions provided to complete your verification.
:::info
The specific provider used may vary between rounds. If you have any questions or concerns about the KYC/KYB process, please contact the round organizers directly.
:::
#### Updating An Application
You can update your application at any time during the round's application phase. To do so, navigate to the application you'd like to edit, ensure you're signed in as the applicant,
and click "Edit" in the top right. Make the desired adjustments, then confirm with your wallet.
Please note that **updating your application will move it back `Pending` status**, meaning it will need to be reviewed again by the round organizers.
:::info
**Want to change your application's logo or avatar?** Please update the avatar of the application's underlying Drips Project by clicking Edit on the project view. After updating the project avatar, your application's avatar is automatically going to update some time later (maximum 12 hours).
:::
:::info
Application information is fully historized. Anyone can see the history of an application by clicking the "History" button in the top-right corner of the application view.
:::
#### Receiving Rewards
Rewards are sent **directly to the Drips project you applied with**. Once sent by the round organizers, the funds received by your project will be split according to your configured list of maintainers and (optionally) dependencies, and become collectable by their recipients. You can check whether your connected address has any collectable funds by connecting your wallet to the Drips app and clicking the "Collect" button in the top-right corner.
:::info
Please note that depending on the chain that rewards are being distributed on, funds may reach you only after a short [settlement period](/get-support/claim-your-repository#settlement-of-future-funds).
:::
## Create a RetroPGF Round
import Figure from '../../components/Figure'
Retroactive Public Goods Funding (RetroPGF) on Drips allows communities to fund projects and contributors for their past impact. This process is organized into *Rounds*, each with its own timeline, budget, and participants.
Creating a round begins with a private *Draft*. In this state, you and other designated *Admins* can collaboratively set up all the necessary parameters before making it public. Once all required settings are configured, you can *publish* the round, opening it up for applications and voting.
:::warning
Drips RPGF is currently only available for select partners. Interested in using Drips to run your RetroPGF round? [Get in touch](https://drips-network.notion.site/1e4c52c9599781b5a0aeca3e8eb1b3e4).
:::
### Getting Started: Creating a Draft
To start, navigate to the **RetroPGF** tab in the main Drips app menu. You may be asked to sign in with your wallet, which does not require a transaction or gas fees. An RPGF round on Drips always happens on one specific chain, so make sure you select the chain you want to create your round on using the network picker on the top right.
On the "Your rounds" page, you will see all the rounds you are currently managing. Click the **+ New round** button to create a new draft.
You will be taken to the main page for your new draft round. On the right, a **TO-DO** list tracks the required configuration steps you need to complete before you can publish.
### Configuring Your Draft Round
Your round remains a private draft, invisible to the public, until you explicitly publish it. You can configure the settings in any order. The TO-DO list will update as you save your changes for each section.
#### Name, Look & Feel
First, give your round a unique identity. In the **Representation** settings (or by clicking **Settings** on the main draft page), you can configure the following:
* **Round name**: The public name of your funding round.
* **Emoji & Color**: Used to visually distinguish your round across the app.
* **URL**: A custom, human-readable URL slug for your round's public page.
* **Description**: A detailed description of your round's goals and purpose. This field supports Markdown for rich text formatting.
#### Schedule
The **Schedule** section is where you define the timeline for your round's different phases:
* **Application intake**: The period when projects can submit their applications to be considered for funding.
* **Voting**: The period when designated *badgeholders* will vote on the submitted applications.
* **Results**: The date and time when the voting results will be finalized and made public.
:::caution
Once your round is published, the schedule cannot be changed. Double-check all dates and times before publishing.
:::
#### Badgeholders & Voting
In the **Voting** section, you define who can vote and the rules of the voting process:
* **Votes per voter**: The total number of votes each badgeholder can allocate across all projects.
* **Maximum votes per project**: The maximum number of votes any single project can receive from a single badgeholder.
* **Badgeholder guidelines link**: A URL pointing to a document that outlines the rules and expectations for badgeholders. Badgeholders will need to confirm having read this page before being able submit their ballot.
* **Badgeholders**: The list of Ethereum addresses that will be granted voting power in the round. You can add them one by one, or upload a CSV with one Ethereum address per line.
#### Forms & Categories
Lastly, set up the application form for your round on the **Applications** tab.
##### Creating Forms
First, create one or more custom application forms by choosing from a variety of field types, including email, markdown, URL, and more. You can freely add, configure, and re-order fields within the form creator.
:::info
You can mark specific fields as **Private**. The content submitted to private fields will only be visible to the round's admins, which is useful for collecting sensitive information.
:::
:::danger
If you ask for any private or otherwise sensitive information, you **must** mark the field as private. All fields not marked as private will be publicly visible, and **immutably written on-chain** as part of the application attestation process.
:::
##### Creating Application Categories
Next, create at least one application category. Each category is linked to a specific application form. When applying, applicants will select which category they are applying to, and the corresponding form will be presented to them.
### Optional Configuration
While in the draft stage, you can also configure several optional settings to tailor the round to your specific needs.
#### Adding Round Admins
You can collaborate on configuring a draft round with multiple people. In the **Admins** section, you can add the Ethereum addresses of other users you want to grant administrative access to. Admins can edit all round settings, view private application data, and export voting data.
#### KYC/KYB
Drips has built-in KYC/KYB support to help you comply with regulatory requirements. To enable this, please [get in touch](https://drips-network.notion.site/1e4c52c9599781b5a0aeca3e8eb1b3e4) with the Drips team.
### Publishing Your Round
Once you have completed all the items on the **TO-DO** list, the **Publish round** button will become active.
Clicking this button will prompt you with a final confirmation. Remember, publishing is a permanent action—the round's URL and schedule cannot be edited later.
After you confirm, your round will be live and accessible at its custom URL! You can use the **Share** button to easily spread the word. Now, projects and contributors can begin submitting their applications according to the schedule you defined.
## RetroPGF on Drips
import Figure from '../../components/Figure'
Retroactive Public Goods Funding (RPGF) is a powerful mechanism for funding impactful work that has already been completed. Instead of funding promises about future work, RPGF allows communities to identify and reward projects and individuals who have already provided demonstrable value. This "results-based" approach helps ensure that funds are directed effectively towards proven contributors.
Drips provides a comprehensive, on-chain platform to run your own RPGF rounds from start to finish.
:::warning
Drips RPGF is currently only available for select partners. Interested in using Drips to run your RetroPGF round? [Get in touch](https://drips-network.notion.site/1e4c52c9599781b5a0aeca3e8eb1b3e4).
:::
### The Anatomy of an RPGF Round
On Drips, the entire RPGF process is organized into discrete events called *Rounds*. While every round is unique, they generally follow three main stages:
1. **Application**: Projects and individuals submit applications to showcase their past work and impact, hoping to be considered for funding.
2. **Voting**: A group of designated voters, often called *badgeholders*, review the applications and vote to allocate the round's funding pool among the applicants they believe have created the most value.
3. **Payout**: Once voting is complete, the results are tallied and the funds are distributed to the winning applicants based on the voting outcomes.
### Why Run Your RPGF Round on Drips?
Drips is designed to be the best place to run your RPGF program. Our suite of tools provides a seamless experience for round administrators, applicants, and badgeholders alike.
#### ↔️ A Complete End-to-End Solution
Drips handles the entire lifecycle of your funding round. From building a custom application form and managing submissions to facilitating the voting process and executing the final payouts, our platform is a one-stop solution.
#### 💸 Payouts Directly to GitHub Repositories
Leveraging the core Drips protocol, you can distribute funds directly to open-source projects via their GitHub repositories.This eliminates the massive operational overhead of chasing down wallet addresses and ensures that funding reaches projects and their declared dependencies automatically.
#### 📈 Amplified impact
Projects receiving funds on Drips can forward a percentage of their funds to their software dependencies, which can greatly amplify the reach of your RetroPGF round.
#### 🔒 Built-in KYC/KYB
Drips works directly with different third-party providers to offer KYC/KYB services to round organizers. This makes it easy to ensure compliance with regulatory requirements without adding friction for applicants.
#### ✏️ Fully Customizable Application Forms
Every funding round is different. Drips features a flexible form builder that allows you to create a bespoke application experience tailored to your round's specific needs. You can add various field types and even mark certain fields as "private" to securely collect sensitive information that will only be visible to round administrators. Public application data is also attested to on-chain by applicants, ensuring transparency.
#### 🔍 Transparent & Verifiable Data
For full transparency and analysis, round admins can easily download the raw application and voting data at any time. This data is provided in a simple CSV format, allowing you to build custom dashboards, perform deeper analysis, or simply keep a record of your round's history.
#### 🌟 A Best-in-Class User Experience
We believe that great tools lead to great outcomes. We've obsessed over the details to provide a world-class experience for every participant. Applicants enjoy a straightforward submission process, while badgeholders are equipped with an intuitive interface to thoughtfully review applications and cast their votes.
### Where to Go Next
Whether you're looking to run a funding round, apply for a grant, or help evaluate contributions, here are the guides for you.
#### For Round Organizers
If you want to create and manage your own RPGF round, these guides cover the entire process from start to finish.
* [**Create your round**](/rpgf/create-your-round): A step-by-step walkthrough of setting up your round's parameters, schedule, and application form.
* [**Administering your round**](/rpgf/administering-your-round): Learn how to review applications, publish results, and distribute rewards.
#### For Applicants
If you are a project or contributor looking to get recognized and funded for your past work, this guide is for you.
* [**Apply to a round**](/rpgf/apply-to-a-round): Learn how to prepare your Drips Project and submit a strong application.
#### For Badgeholders
If you've been selected as a voter to help allocate funds based on your expertise, this guide explains your role.
* [**Vote on a round**](/rpgf/vote-on-a-round): A complete guide to the voting process, from building your ballot to submitting your votes.
## Voting in a RetroPGF Round
import Figure from '../../components/Figure'
As a *badgeholder* in a Drips RetroPGF Round, your role is to evaluate the contributions of applicants and allocate votes to reward the impact you believe they've created. This guide will walk you through the voting process.
To vote in a round, your Ethereum address must have been designated as a badgeholder by the round's administrators.
:::info
To understand the full lifecycle of a round, you can read our [introduction to RetroPGF](/rpgf/overview).
:::
### How to Vote
The voting process is designed to be straightforward, allowing you to focus on making informed decisions. Your progress is saved locally in your browser as you build your ballot, so you can take your time and resume at any point.
#### 1. Access the Round
First, navigate to the public page of the round you wish to vote on. Once the voting phase is active, you will see a prompt to begin. Connect the wallet that has been designated as a badgeholder and sign in. This is a free, gas-less signature to verify your identity.
Once signed in, click the **Vote now** button in the top-right corner of the page to enter the voting interface.
:::info
**Don't see the vote button?** Double-check you're connected with the correct voter wallet address. If you are but the Vote Now button still doesn't appear, reach out to the round's organizers.
:::
#### 2. Build Your Ballot (Pick Applications)
The first step is to review the list of all accepted applications. You can browse, search, and filter to find the projects you want to support. If the round organizers have provided voting guidelines, you will be prompted to review them here.
You now have two options to proceed:
1. **Use the Web Interface:** Select applications by clicking the checkbox next to their name. This will add them to your personal ballot, which you will configure in the next step.
2. **Use a Spreadsheet:** If you prefer voting in a spreadsheet, skip to the [**Voting via spreadsheet**](#4-alternative-voting-via-spreadsheet-upload) section below.
#### 3. Allocate Votes (Web Interface)
If you used the web interface to pick applications, click 'Assign votes' (or 'Step 3' in the voting panel) to proceed to your ballot. Here, you will see only the applications you chose.
Enter the amount of votes you want to assign to each one.
The interface will show you how many votes you have available to allocate in total, and will respect the "maximum votes per project" limit if one was set by the round's admins.
#### 4. Alternative: Voting via Spreadsheet Upload
If you clicked **Vote using spreadsheet** in Step 2, a new window will open. This method allows you to download all applications, enter your votes in a spreadsheet program (like Excel or Google Sheets), and upload the file to submit your *entire* ballot at once.
The process is as follows:
1. **Download the ballot template:** Click 'Download CSV' or 'Download XLSX' to get a file of all approved applications, including all data displayed in the Web UI.
2. **Make your vote allocations:** Open the file and find the **'Allocation'** column. Enter your votes (as integers, no fractions) for the projects you wish to support. Leave the 'Allocation' cell blank or enter '0' for projects you don't want to vote for.
3. **Upload your ballot:** Save your edited spreadsheet (as CSV or XLSX) and upload it in the 'Upload your result' section. Click 'Submit ballot' to confirm.
:::info
The spreadsheet must contain at least the `ID` and `Allocation` columns to be valid. Submitting a spreadsheet will overwrite any ballot selections you may have previously made in the web interface.
:::
#### 5. Submit Your Ballot
**If using the web interface:** Once you are satisfied with your vote allocation, click the 'Submit ballot' button at the bottom of the page.
**If using the spreadsheet upload:** Your ballot is submitted when you click 'Submit ballot' in the upload modal.
:::caution
**Your ballot is private.**
Once submitted, your ballot is recorded securely. Its contents are only visible to the round's administrators to ensure voter privacy.
:::
### Editing Your Ballot
You can change your mind at any time while the voting period is still active.
To edit your submission, simply return to the round's page. The button in the top-right will now say **Edit your ballot**. Clicking it will take you back to the voting interface, which will be pre-filled with your most recent submission (whether it came from the UI or a spreadsheet upload).
From here, you can:
* **Edit in the UI:** Manually adjust your project selections and vote allocations, then click 'Submit ballot' again.
* **Overwrite with a new spreadsheet:** Click the **Update using spreadsheet** button. This will open the upload modal, and submitting a new file will replace your entire existing ballot.
You can edit and resubmit as many times as you like before the voting period ends.
import Figure from '../../components/Figure'
## Drip Lists
On Drips, anyone can create a *Drip List* to flexibly send funds to a list of up to 200 open-source GitHub repositories, Ethereum addresses, or other Drip Lists at a time. As project maintainers come to Drips to claim their funds, they themselves are asked to provide a list of other projects they depend on, and can choose to automatically forward a percentage of their earnings to them. In this article, we'll go over how you can create your own *Drip List*, and start supporting it with continuous or instant donations using any ERC-20 token.
:::info
If you haven't yet, learn more about the Drips Dependency Tree in our [introduction](/).
:::
### Creating your Drip List
To create a Drip List, open the [Drips App](https://www.drips.network/app), connect your wallet, and navigate to **Drip Lists → Create Drip List**. You'll be asked to specify which up to 200 individual GitHub repositories, Ethereum addresses, or other Drip Lists you’d like to forward what percentage of funds. Then, you’ll have a chance to immediately start supporting your list with a continuous donation or an instant one-time donation. Once done, funds will start moving immediately, and anyone can make donations to your list at any time.
#### Collaborative Drip Lists
Alternatively, you can invite any number of *collaborators* to vote on the recipients for a new Drip List with a [Collaborative Drip List](/support-your-dependencies/collaborative-drip-lists/creating-a-collaborative-drip-list).
:::info
If you choose to include someone else’s Drip List on yours, **make sure that you trust the list’s owner**, because they may at any point in the future change its recipients.
:::
### How funds flow through a Drip List
You can think of your Drip List as a personal “money router” that automatically *splits* any incoming funds to its recipients after each [settlement period](/get-support/claim-your-repository#settlement-of-future-funds) (monthly on Ethereum Mainnet, daily on other chains). Anyone can establish one or more continuous or one-time donations to your list.
#### Continuous donations
A continuous donation on Drips is an ongoing *stream* of funds, with per-second settlement.
Let's say you'd like to support the projects on your Drip List with 500 USDC every month. To do so, you create a continuous donation to your list, configure it to stream 500 USDC over 30 days, and then top up your USDC balance on Drips with an initial amount. As soon as your account is funded, you'll see USDC funds stream to your list in real-time. The benefit of *streams* over monthly payments is that you're in full control: You can pause, alter or completely stop your stream even in the middle of the month. As your balance decreases, you can flexibly *top up* your Drips account with additional USDC to keep the stream running, or at any point withdraw any not-yet-streamed funds that are still on your Drips account.
:::info
Apart from supporting your Drip List with a continuous donation, you can also establish arbitrary *Token Streams* to any Ethereum address for any other usecase. To do so, head over to **Streams → Create Stream** in the Drips App.
:::
#### Instant one-time donations
If you prefer a single contribution, you can make an instant one-time donation to any Drip List. Once the donation is confirmed, it is queued for the next [settlement period](/get-support/claim-your-repository#settlement-of-future-funds) and will be distributed alongside any continuous donations.
#### Settlement of funds
While Continuous Donation streams to your Drip List is calculated per-second, funds in the global Drips Dependency Tree are automatically *split* after each [settlement period](/get-support/claim-your-repository#settlement-of-future-funds), which varies by network (monthly on Ethereum Mainnet, daily on other networks). This means that any funds you or others donate to your Drip List end up with the projects on the list (and, if applicable, their dependencies) once that period completes.
You can at any point review the next settlement window by clicking the "Collect" button in the top-right, but please note that the settlement doesn't occur exactly at midnight, and the timing depends on the network where the Drip List operates.
As a result of this, you can edit the recipients on your Drip List before the settlement finalizes, and this will cause *all* funds sent to your list within the last settlement period to be split according to the new configuration.
:::info
If you'd like to learn more about the technical details behind settlement, you can read through our technical [Drips inner workings](/the-protocol/advanced/drips-inner-workings) article.
:::
#### Supporting other Drip Lists
Drip Lists are *nestable*, meaning anyone can add anyone elses Drip List to *their* Drip List, and forward a specifc percentage of *its* income to the projects contained on the nested list.
## Accounts in Drips
In this section we take a look at the Drips Protocol's driver-based account model and review the types of user accounts that are available in Drips V2 today.
### Background and Introduction
Drips V2 comes with a driver-based account model, which opens the door to enabling many different types of user accounts to exchange funds with one another in Drips.
What exactly does this mean? It means, for instance, that end-users can choose to stream or Split funds from an account that is directly associated with their Ethereum address, similar to how an ordinary Ethereum wallet works. Or they can choose to create one or more NFT-based accounts that will each have its own separate balance and Drips and Splits configurations, but will all be controlled by the Ethereum address holding the NFTs.
Under the hood, each of these account types is enabled by an account driver which has been added into the Drips smart contracts by the Drips Team, and we expect that even more drivers will be deployed in the future to add support for new types of accounts.
We'll dive into all of the details below, but from a high-level the most important things to keep in mind are:
* **In Drips, multiple types of user accounts are able to control funds, including Ethereum addresses, NFT-based accounts and even GitHub repositories.**
* **This is possible because of Drips' use of an extensible system of "account drivers", which govern the implementations of different account types through smart contracts.**
#### What Do We Mean by "Accounts" in Drips?
First, let's briefly review what we mean by an "account". For the purposes of Drips, what we mean is:
* A unique identifier that corresponds one-to-one to an account that can send and receive funds (e.g. using streams or Splits) in the Drips Protocol.
* A way to authenticate critical actions on that account, like withdrawing funds, or setting up new streams and Splits configurations.
With that out of the way, let's take a look at some more of the technical details.
### Drips Account IDs
As mentioned above, every user identity in Drips has an account ID. This account ID is a 32 byte number, where the bytes in the number are used to encode two other "component" IDs, as shown below:
![Diagram 1][u1]
The **Driver ID** is the ID of the driver in the Drips smart contracts, which is responsible for Authenticating and managing funds for the account.
The **Driver Sub-Account ID** is the ID of the account's specific place within the range of IDs managed by the driver, and is used to distinguish it from all of the other accounts managed by that driver.
It's worth noting that most end-users will likely never have to consider such low-level technical details when using Drips, but in some cases, developers will. For developers building on Drips, the important thing to remember about account IDs is that every account ID always points to a single driver in Drips (which "manages" that ID, in a sense and authorizes the user's access), as well as a single "sub-account" in the space of accounts managed by that driver, which serves to separate the account's funds and configurations from those of others.
### Drips Account Drivers
Now let's dive a bit deeper into what an account driver is and how it works. As discussed above, to allow for flexibility and extensibility in the types of accounts supported in Drips, we introduced the concept of account drivers in Drips V2. Each driver is responsible for managing a range of account IDs where the first 4 bytes of the account ID matches the ID with which the driver is registered in Drips.
#### AddressDriver
To understand this better through an example, let's consider AddressDriver, which is registered under ID 0 in Drips. This driver was the very first driver created by the Drips Team and its purpose is to enable each Ethereum address to manage a unique account in Drips.
We can see how this works by looking at the code in the AddressDriver smart contract.
As we can see, AddressDriver contains a `setDrips(...)` method which, when called, "Sets the message sender's drips configuration."
Looking into the code a bit more, we see that AddressDriver also contains the helper methods `callerAccountId()` and `calcAccountId(address userAddr)`, which translate the message sender's address into a unique account ID within the range of account IDs controlled by AddressDriver.
When `setDrips(...)` is called in AddressDriver, we can see that `callerAccountId()` is called to get the account ID for the message sender, and then another call is made to the `setDrips(...)` method in the Drips smart contract, passing this calculated account ID as a parameter. The end result is that AddressDriver
translates between the identity feature it's interested in (in this case, the message sender's Ethereum address) and the more general account IDs utilized by
the Drips.
#### NFTDriver
AddressDriver is probably the easiest driver to understand, because it intuitively matches how we most often think about accounts within Ethereum-based web3 apps. Namely, for most web3 apps we think "I connect with my Ethereum wallet (and its address)".
However, another driver which may be even more useful to developers looking to build on Drips is NFTDriver. Instead of allowing a user to make changes to the "main" Drips account for
their Ethereum address, with NFTDriver, developers can enable users to create an unlimited number of NFT-based accounts that are specific to their app, each with its own account balance and streaming settings. In practice, this kind of functionality is something that many app developers are interested in.
Similar to AddressDriver, we can see how NFTDriver translates between NFTs and Drips account IDs by looking at the code. The first thing we notice is that
NFTDriver itself is an ERC-721 contract which can mint and burn new NFT-based Drips accounts based on calls to `mint(...)`, which is callable by anyone.
We can also see that the methods `_useNextTokenId()` and `nextTokenId()`, which are called in turn by `mint(...)`, play similar roles to the role that `callerAccountId()` and `calcAccountId(address userAddr)` play in AddressDriver.
In particular, when `setDrips(...)` and other permissioned methods in NFTDriver are called, the code uses the modifier `onlyHolder(uint256 tokenId)` to check that the
method is being called by the holder of the NFT token. Assuming it is, the call is then passed on to the corresponding method (e.g. in this case `setDrips()`) in the Drips smart contract, and the token ID is passed to Drips as the account ID of the account identity to make changes for.
Here again, we can see how NFTDriver acts as a kind of translation layer between the account and the authorization features of interest (i.e. "does the sender hold this NFT?") and the more generalized account IDs required by Drips. The end result is that developers building on Drips are able to mint new
NFT-based accounts, which will each have their own balances and settings for streaming and splitting, whenever they wish. And each such NFT also corresponds to a single underlying account ID in Drips.
Having looked at AddressDriver and NFTDriver in detail, we can now see how the Drips Protocol's extensible account model enables many different types of accounts to be used, all of which still function similarly and interchangeably at the lowest level of the protocol.
### User Accounts and the Drips Javascript SDK
When interacting with Drips through the Javascript SDK, most of these details are abstracted away from developers by convenience classes that hide the full complexity - including account IDs - for most common actions developers will wish to take. For instance, to build on the SDK and allow an end-user to collect funds streamed to them through Drips, a developer simply creates an [AddressDriverClient][ad] and calls the `collect()` method, which has a signature that looks like this:
\> public async collect(tokenAddress: string, transferToAddress: string): Promisecontracttransaction
You'll notice that this method signature contains no mention of any account IDs - it's all just ordinary Ethereum addresses (here \`\` is the Ethereum address that the user wishes to collect their funds to).
On the other hand, for developers looking to build more sophisticated applications on top of Drips, it will likely be necessary for them to have a full understanding
of the Drips account model and in some cases to interact with account IDs rather than simple Ethereum addresses, even if they are building on top of the SDK.
For instance,
one area where account IDs are heavily used is in the Drips subgraph, where many entity types in the subgraph schema refer to account IDs rather than Ethereum addresses and the
corresponding query methods in the [DripsSubgraphClient][ds] in the SDK take accountId-based parameter values and also returns data that includes accountIds. So
developers wishing to work directly with that data in its raw form will need to understand what the account IDs mean and how to unpack them.
At the same time, as time goes on the Drips Team will likely be adding more and more convenience classes and helper code to the SDK to make working with
account IDs as simple and easy as possible, in cases where it is required.
#### RepoDriver
TBD
#### ImmutableSplitsDriver
TBD
### Wrapping Up
In this section, we did a deep dive into some of the technical aspects of Drips Protocol's flexible driver-based approach to managing user accounts. We also looked at the
drivers the Drips Team shipped with Drips V2, which provide developers and users with the option to set up and manage Drips accounts based on either Ethereum addresses or NFTs. Hopefully you enjoyed this somewhat technical adventure :)
One last note, the Drips Team plans to release more account drivers in the coming months, which should enable entirely new and different kinds of accounts in Drips. Stay tuned for more details!
[ad]: https://github.com/radicle-dev/drips-js-sdk/blob/v2/src/AddressDriver/AddressDriverClient.ts
[ds]: https://github.com/radicle-dev/drips-js-sdk/blob/v2/src/DripsSubgraph/DripsSubgraphClient.ts
[u1]: /img/drips_user_identity-1.png
## Drips v2 features
The goal of Drips v2 is to be the most powerful and feature-complete decentralized, autonomous, and non-custodial protocol for streaming and splitting funds in web3, with the following key features.
#### Multi-Token Support (Any ERC20)
Drips v2 allows streaming of any ERC20 token.
#### No Wrapped Tokens
Some streaming protocols require users to first wrap their tokens, introducing additional contracts as well as assumptions around trust and solvency. Drips v2 allows users to natively stream any ERC20 token - no wrapped tokens required.
#### Gas-Optimized for Many-to-One Streams
Most web3 streaming protocols only allow users to receive streams from small numbers of other users, and cannot practically support designs where hundreds or thousands of subscribers stream to a single recipient. In contrast, Drips v2 is architected to be extremely gas-efficient for both one-to-one streaming and many-to-one streaming use cases at real-world scales, targeting Ethereum mainnet.
#### Scheduled Drips
With Drips v2, users and developers have the ability to schedule streams to start and end at specific times in the future, allowing more precision around the total amount of funds streamed.
#### Shared Stream Balances
While other streaming protocols require users to top-up each stream that they create, leading to many transactions and high gas costs to maintain streams over time, Drips allows users to fund and top-up multiple streams using a single balance and a single transaction.
#### A Flexible Identity Model
Drips v2 also introduces a new, more flexible model for user identity and accounts, that provides additional options for different ways that users can create accounts and engage with the protocol. For example, an NFT or a Git repository can control an account in Drips.
#### One Smart Contract / One Graph
Drips uses one smart contract for streaming and splitting, enabling effortless and flexible token routing. For instance, in Drips, when one user streams funds to another, the recipient can set up a Splits configuration to automatically and continuously share a portion of all incoming funds streamed to them with whomever they wish.
## Overview
The Drips protocol is a fully decentralized, non-custodial, autonomous, and gas-optimized protocol allowing you to schedule and structure your ERC-20 token transactions to particular addresses. The information provided herein are for educational purposes only.
Disclaimer: The Drips protocol operates in a fully decentralized and autonomous manner, like the Ethereum protocol with which it is integrated. Therefore, no entity or person controls or is in any way responsible for the ongoing operation, running, or functioning of the Drips protocol, nor does any entity or person have custody of any funds raised or streamed in the Drips protocol. You acknowledge that you are solely responsible for any interaction with the Drips protocol and any harm, damage or loss that may occur. There is no warranty, express or implied, for the Drips protocol and/or the software and/or any content. The Drips protocol and in particular the software is of experimental nature and available for use to the public on an “as-is” basis and no representations or warranties of any kind are made with respect to the Drips protocol, its operations and functionality, or its fitness for any specific purpose. BY ACCESSING THE DRIPS PROTOCOL, YOU ACKNOWLEDGE, UNDERSTAND AND AGREE THAT YOU USE THE DRIPS PROTOCOL AND THE SOFTWARE AT YOUR OWN RISK AND YOU ASSUME ANY COSTS, RESPONSIBILITY AND/OR ANY LIABILITY FOR ANY DAMAGES OR LOSSES YOU MAY INCUR, INCLUDING ANY SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF YOUR USE, OR INABILITY TO USE, THE DRIPS PROTOCOL AND/OR THE SOFTWARE.
There are three ways in which funds can be flowing in Drips protocol:
* **Streaming** is moving funds between users over a period of time at a fixed per-second rate.
* **Giving** is transferring an amount of funds between users immediately.
* **Splitting** is transferring a fixed fraction of funds received by one user to another user.
### Streaming
The streaming functionality sends funds over a period of time. To start streaming you need to configure a list of stream receivers and top up your streamable balance. Once you configure those, the funds flow will start automatically and will stop when the balance runs out. You can change the balance at any time, either top up more funds or withdraw any amount from what hasn't been streamed yet. The balance is updated every second, and there's no way to withdraw already-streamed funds. The list of receivers can be changed at any time with an immediate effect, but it'll never affect the past, only the streaming behavior during the present timestamp and in the future. For each ERC-20 token every user has a separate streams configuration and a separate balance, so you can configure streaming different tokens independently.
The streams receivers list contains between 0 and 100 entries. By default, every account has an empty receivers list. An empty list means that there are no receivers, so nothing will be streamed to anybody. The list must be sorted, and it can't contain duplicate entries. Only streams receivers on your currently set list are receiving tokens streamed from you, so, when you add a receiver or remove one, it respectively starts or stops streaming to them. Every entry consists of a receiver user ID and a streaming rate, which is expressed in tokens per second. To increase precision the amount streamed every second has 9 extra decimals, so you express streaming rates with precision higher than one full token per second. For example, you can stream 2.5 tokens per second or 0.001 tokens per second. See [Streaming Fractional Amounts](/the-protocol/advanced/fractional-amounts) for more details. Optionally, each stream receivers list entry may contain a start timestamp and a maximum duration. If the start timestamp is in the future, streaming will be postponed until that timestamp. If the duration is set, streaming will stop after it elapses. The duration is relative to the start timestamp, or, if the start timestamp isn't set, the timestamp when the stream configuration is updated.
Both balance and receivers list updates are done by calling the `setStreams` function of the [driver](/the-protocol/accounts-in-drips#drips-account-drivers) managing your user ID. You can't change the balance without updating the streams receivers list, but it's fine if both the old and the new lists are identical. You also can't update the streams receivers list without changing the balance, but it's fine if the balance change is zero.
For details about how streaming is implemented see [Drips Inner Workings](/the-protocol/advanced/drips-inner-workings).
### Giving
The Giving functionality transfers an amount of tokens to the chosen user. It's different from streaming because it's a one-time, immediate operation, and there's no streaming configuration involved. Since the funds are transferred into the protocol while giving, there is no need for a user to have a separate balance dedicated to a specific giving operation.
Giving is done by calling the `give` function of the [driver](/the-protocol/accounts-in-drips#drips-account-drivers) managing your account ID.
### Splitting
The Splitting functionality divides received funds and transfers them to other users. To split, you need to configure a list of splits receivers. Next, all funds you setup to be split will be distributed according to that configuration. Each user has a single splits receivers list which is applied to all ERC-20 tokens, so all received funds are split in the same way.
Updating the splits configuration doesn't split funds that haven't been split yet. This means that unless funds are actually split, there's no guarantee that the current configuration will be used to split the funds, the user who received the funds may route them however they feel. If a splits configuration needs to be trusted to never change, `ImmutableSplitsDriver` may be used to create a dummy user who's only able to set their splits configuration once and can't change it afterward.
The splits receivers list contains between 0 and 200 entries. By default, every user has an empty receivers list set. An empty list means that no funds are split, and 100% of the funds go to the user who received them. Each splits receiver on the list has a percentage assigned to them, which defines the fraction of received funds that will be transferred to that user when splitting. All the list entries must add up to no more than 100%, in which case the receiving user won't get any funds, and will transfer all received funds to others. The list must be sorted, and it can't contain duplicate entries.
Splits configuration is updated by calling the `setSplits` function of the [driver](/the-protocol/accounts-in-drips#drips-account-drivers) managing your user ID.
### Receiving flow
Received funds go through a few steps before they can be collected.
* **Receiving drips** and **squeezing drips** gathers funds that have been dripped to you.
* **Splitting** gets all received and squeezed drips, given funds and funds received from splitting,
and distributes them according to the current splits configuration.
* **Collecting** transfers out of the protocol funds left after splitting.
![Diagram 1][img1]
#### Receive streams
By receiving streams, you get the funds streamed to you in the past from all users. It doesn't matter how many people have been streaming. Receiving funds from them always has the same cost depending only on the length of the period from which you're receiving streams. You must receive streams for each ERC-20 token separately, each of them has an independent state.
You only can receive streams from the already finished cycles. The entire timeline is globally divided into cycles of constant length defined on deployment, usually on a scale of days or weeks. Every timestamp during which somebody is streaming to you falls into a cycle. All the funds that have been streamed from anybody to you during a cycle become receivable only when the cycle ends, so nobody can change their streams anymore during that cycle. This reduces gas cost of receiving streams. See [Streams Inner Workings](/the-protocol/advanced/drips-inner-workings) for more technical details.
Receiving streams is done by calling the `receiveStreams` function on the `Drips` contract. Anybody can call this function for any user ID. This is fine because the users can't be hurt when somebody triggers receiving streams for them, the caller only covers the gas cost, but the effects are the same, the user gets their streams received and prepared to be split. This function can't be used to perform a DoS attack on the user.
#### Squeeze streams
Squeezing streams is a companion to receiving streams. While receiving can be done only on funds streamed during the finished cycles, squeezing can be done only on funds streamed during the current cycle, which isn't finished yet. It allows receiving streams without waiting for the cycle to end, but it's more expensive in terms of gas usage. You must squeeze not only for each ERC-20 token separately, but also for each user from whom you want to squeeze streams. Any funds you don't squeeze will be receivable when the cycle ends.
In order to squeeze streams from a user you need to provide that user's complete history of streams configurations from any point in the past until their current configuration. That history will be then analyzed to see how much they have streamed to you during the current cycle until now, and these funds will be squeezed. You don't need to squeeze the entire history you provide, you can choose which historical configurations you want to analyze, and skip all the rest. This is useful when a historical configuration has already been squeezed by you in the past or doesn't yield enough value to justify the gas cost, either because it was so short-lived, didn't stream to you at a high enough rate, or just didn't include you as a receiver.
Squeezing streams is done by calling `squeezeStreams` on the `Drips` contract. Anybody can call this function for any user ID. This is fine because the users can't be hurt when somebody triggers squeezing streams for them, the caller only covers the gas cost, but the effects are the same, the user gets their streams squeezed and prepared to be split. This function can't be used to perform a DoS attack on the user.
#### Split
Splitting distributes funds you've received from all sources among users who you've configured as your [splits receivers](/the-protocol/overview#splitting). Splitting is done on your current splittable balance, which aggregates your received and squeezed streams, funds given to you, and funds split to you. The funds are split according to your current splits receivers list. Each of its entries contains a user ID and a percentage of splittable funds that they will receive. Any funds not split among the receivers will become collectable by you.
Splitting is done by calling the `split` function on the `Drips`? contract. Anybody can call this function for any user ID, but they must pass the splits configuration that is currently set by that user, so their funds are always split according to their will. This makes setting splits receivers list somewhat bonding because right until you update it, anybody can split funds you've received according to that configuration.
Receiving streams, squeezing streams and splitting being callable by anybody constitute a highly efficient system where funds are never stuck and can be always pushed to keep flowing in the network of splits. If you have a splits receiver configured, and you have some funds streamed, given or split to you, your splits receiver can perform all the necessary actions to get their splits from you, without bothering you. As the protocol is autonomous it runs without your intervention and the funds will keep moving in the network as long as your receiver benefits from it. This can go even further, because a splits receiver of your splits receiver may trigger a cascade of splitting to get their funds, it can go arbitrarily deep.
#### Collect
The Collecting functionality takes the funds left after splitting and transfer them out of the protocol, e.g. into your wallet. Collecting doesn't trigger splitting, you need to split in order to have collectable funds, even if you have an empty splits receivers list.
Collecting is done by calling the `collect` function of the [driver](/the-protocol/accounts-in-drips#drips-account-drivers) managing your user ID.
[img1]: /img/overview1.png
## Security
At Drips, we take security seriously and have taken a number of steps to ensure the security of our smart contracts on Ethereum.
#### High test coverage and fuzz tests
Our codebase has a high level of unit test coverage and we used Foundry to fuzz-test the key features of our contracts.
#### Security Reviews
A complete audit of the smart contracts has been completed by [Spearbit](https://spearbit.com/) as of November 2023:
See the [Spearbit Network Security Review](https://drive.google.com/file/d/1qj0tCxzkgQONzWTENFk5MKroH5D6PUVG/view?usp=drive_link)
Previously to the Spearbit audit, we also worked with an independent security auditor to conduct smart contract security reviews during the development of the contracts:
See the [Security Report](https://drive.google.com/file/d/1DoekwJ-D2p00rJ87-NoG0pj_hBv-rkYa/view?usp=drive_link)
In addition, a member of the Drips core team is also a security auditor, so we were able to leverage their expertise in this area to perform an internal audit:
See the [Internal Report](https://hackmd.io/6_EgTHw6TVGlfxls0iKMAQ?view)
We also worked with Code4rena to conduct an audit contest where 26 wardens conducted analysis of the protocol and smart contracts and provided feedback:
See the [Code4rena Report](https://code4rena.com/reports/2023-01-drips)
Drips has a public [bug bounty program with Immunefi](https://immunefi.com/bounty/drips/).
`BridgedGovernor` and `Giver` contracts were audited separately, see the [Cantina Security Report](https://drive.google.com/file/d/19EjEwN0LhGfWFqptZTZrOWv4vA7-exUM/view?usp=drive_link).
The `RepoDriver` migration to Gelato was audited separately, see the [Cantina Security Report](https://drive.google.com/file/d/10LQ26EWpyIWm8G1PCSel8C_ZoShQBEHa/view?usp=drive_link).
The `NetiveTokenUnwrapper`, `NFTDriver` custom URIs, `GelatoTaskOwner`, `RepoSubAccountDriver` and `RepoDeadlineDriver` were audited separately, see the [Cantina Security Report](https://drive.google.com/file/d/1GR6PD7gquDkFN1A3QUL3f0Pi9xH1-0T-/view?usp=drive_link).
#### Certora
Additionally, we have worked with the [Certora](https://www.certora.com/) team to formally verify certain parts of our smart contracts. This involved mathematically proving the correctness of our code, which helps to identify and eliminate potential vulnerabilities.
While we are excited about the potential of using formal verification to improve the security of our smart contracts, it's important to note that this technology is still in its early stages and at an experimental level for Drips.
However, we believe that it is important to stay at the forefront of technology and explore new approaches to smart contract security. By doing so, we hope to gain valuable insights and improve our security measures over time.
You can read the [report from Certora](https://drive.google.com/file/d/1kWKDzjbTl69Vhl_HVyJjnwRojNgSzeDB/view?usp=drive_link), or explore [our work on invariants with the Certora prover in Github.](https://github.com/radicle-dev/drips-contracts/tree/certora)
#### Risk
Of course, it's important to note that despite our efforts to ensure the security of our smart contracts, there is always a certain level of risk involved.
:::warning
No system is completely immune to security threats, and it's impossible to guarantee that our smart contracts will never be exploited.
:::
## Smart Contract details
### Ethereum Mainnet
| Key | Value |
| :---------------------------- | :----------------------------------------- |
| Chain | mainnet |
| Deployment time | 2023-07-13T12:30:27Z |
| Commit hash | afeba55f70a968ded7c0797a4211faa856e28fa0 |
| Wallet | 0x823204FFd4fAa09fbf2AAc51A290233e829991a1 |
| Deterministic deployer | 0x4e59b44847b379578588920cA78FbF26c0B4956C |
| CREATE3 factory | 0x6aa3d87e99286946161dca02b97c5806fc5ed46f |
| DripsDeployer salt | DripsDeployer |
| DripsDeployer | 0x0c1Ea3a5434Bf8F135fD0c7258F0f25219fDB27f |
| Drips | 0xd0Dd053392db676D57317CD4fe96Fc2cCf42D0b4 |
| Drips cycle seconds | 604800 |
| Drips logic | 0xb0C9B6D67608bE300398d0e4FB0cCa3891E1B33F |
| Drips admin | 0x8dA8f82d2BbDd896822de723F55D6EdF416130ba |
| Caller | 0x60F25ac5F289Dc7F640f948521d486C964A248e5 |
| AddressDriver | 0x1455d9bD6B98f95dd8FEB2b3D60ed825fcef0610 |
| AddressDriver ID | 0 |
| AddressDriver logic | 0x3Ea1e774f98cc4C6359bbCB3238E3e60365Fa5c9 |
| AddressDriver admin | 0x8dA8f82d2BbDd896822de723F55D6EdF416130ba |
| NFTDriver | 0xcf9c49B0962EDb01Cdaa5326299ba85D72405258 |
| NFTDriver ID | 1 |
| NFTDriver logic | 0x3B11537D0d4276Ba9e41FFe04e9034280bd7af50 |
| NFTDriver admin | 0x8dA8f82d2BbDd896822de723F55D6EdF416130ba |
| ImmutableSplitsDriver | 0x1212975c0642B07F696080ec1916998441c2b774 |
| ImmutableSplitsDriver ID | 2 |
| ImmutableSplitsDriver logic | 0x2c338CDf00dFd5A9B3B6b0b78BB95352079AAF71 |
| ImmutableSplitsDriver admin | 0x8dA8f82d2BbDd896822de723F55D6EdF416130ba |
| RepoDriver | 0x770023d55D09A9C110694827F1a6B32D5c2b373E |
| RepoDriver ID | 3 |
| RepoDriver AnyApi operator | 0xa928d4b087AD35C46BA83331d8eEddb83152319b |
| RepoDriver AnyApi job ID | 9af746c7cfbc415c9737b239df9a30ab |
| RepoDriver AnyApi default fee | 1620000000000000000 |
| RepoDriver logic | 0xfC446dB5E1255e837E95dB90c818C6fEb8e93ab0 |
| RepoDriver admin | 0x8dA8f82d2BbDd896822de723F55D6EdF416130ba |
This deployment is based on the 'v2\_ethereum\_deploy' tag from the Drips Smart Contracts repository.
### Sepolia Testnet
| Key | Value |
| :---------------------------- | :----------------------------------------- |
| Chain | sepolia |
| Deployment time | 2023-07-15T10:41:56Z |
| Commit hash | afeba55f70a968ded7c0797a4211faa856e28fa0 |
| Wallet | 0x341a08926dCa7fa7D135F96E4d76b696e5f6d38d |
| Deterministic deployer | 0x4e59b44847b379578588920cA78FbF26c0B4956C |
| CREATE3 factory | 0x6aa3d87e99286946161dca02b97c5806fc5ed46f |
| DripsDeployer salt | DripsDeployerTest1 |
| DripsDeployer | 0xa6030dD9D31FA2333Ee9f7feaCa6FB23c42a1d96 |
| Drips | 0x74A32a38D945b9527524900429b083547DeB9bF4 |
| Drips cycle seconds | 604800 |
| Drips logic | 0xf103BDDB82B6177e5fE53c50351E33F4f3df955B |
| Drips admin | 0x341a08926dCa7fa7D135F96E4d76b696e5f6d38d |
| Caller | 0x09e04Cb8168bd0E8773A79Cc2099f19C46776Fee |
| AddressDriver | 0x70E1E1437AeFe8024B6780C94490662b45C3B567 |
| AddressDriver ID | 0 |
| AddressDriver logic | 0x298F37fFd4B31d216B8954968cEe7EC5273CB891 |
| AddressDriver admin | 0x341a08926dCa7fa7D135F96E4d76b696e5f6d38d |
| NFTDriver | 0xdC773a04C0D6EFdb80E7dfF961B6a7B063a28B44 |
| NFTDriver ID | 1 |
| NFTDriver logic | 0xa6bD78d98720E2eA4B3E2887be7bA212C3aC5977 |
| NFTDriver admin | 0x341a08926dCa7fa7D135F96E4d76b696e5f6d38d |
| ImmutableSplitsDriver | 0xC3C1955bb50AdA4dC8a55aBC6d4d2a39242685c1 |
| ImmutableSplitsDriver ID | 2 |
| ImmutableSplitsDriver logic | 0xf5573880ECB9975E1645C8D18ef1A0393c685CC1 |
| ImmutableSplitsDriver admin | 0x341a08926dCa7fa7D135F96E4d76b696e5f6d38d |
| RepoDriver | 0xa71bdf410D48d4AA9aE1517A69D7E1Ef0c179b2B |
| RepoDriver ID | 3 |
| RepoDriver AnyApi operator | 0x0F9c6BCdE15dfFFD95Cfa8F9167b19B433af1abE |
| RepoDriver AnyApi job ID | 9af746c7cfbc415c9737b239df9a30ab |
| RepoDriver AnyApi default fee | 150000000000000000 |
| RepoDriver logic | 0x7A9a2a29B8d98922Ea2E70c73B123e36C95d1515 |
| RepoDriver admin | 0x341a08926dCa7fa7D135F96E4d76b696e5f6d38d |
## Applicant metrics
import Figure from '../../components/Figure'
When contributors apply to work on an issue, maintainers see a **Code Metrics** scorecard summarizing their public GitHub activity. This page explains how each metric is calculated and what the categorical labels mean.
### What Data Is Included
#### Rolling 3-Year Window
All Code Metrics are calculated over a rolling **3-year window** (\~1096 days), ending at the time metrics are computed.
#### Eligible Pull Requests
PR-based metrics only count pull requests that received **at least one comment or one review**. This filters out self-merges and automated dependency bumps, focusing on collaborative work.
#### Reviews and Issues
The **Reviews** and **Issues Opened** metrics come from your GitHub activity over the same rolling 3-year window.
### How Bins Work
Each metric value is compared against a GitHub-wide benchmark of \~3.9 million users who opened at least one collaborative PR over a three-year reference period. Your percentile rank determines your categorical bin:
| Percentile Range | Bin |
| :--------------- | :------------- |
| 0–24 | Very Low |
| 25–49 | Low |
| 50–74 | Medium |
| 75–89 | High |
| 90–98 | Very High |
| 99+ | Extremely High |
:::info
For **PR Drop Rate** and **Average Merge Latency**, *lower* values are better. The percentile represents your raw ranking in the population distribution, and the interface indicates that lower values are preferable for these metrics by coloring their lowest bins as green and the highest bins as red.
:::
### Metric Calculations
#### OSS Activity Score
A composite score summarizing open-source collaboration activity.
**Formula:**
`OSS Activity Score = (0.40 × Reviews percentile) + (0.35 × PRs Opened percentile) + (0.25 × Issues Opened percentile)`
Each component is first converted to a percentile against the benchmark, then combined. The resulting score is itself compared to the benchmark for OSS Activity Scores to determine the bin.
#### Total Opened PRs
**Formula:** `count(eligible PRs created in window)`
Compared against benchmark thresholds for opened PR counts.
#### Total Merged PRs
**Formula:** `count(eligible PRs created in window where state = MERGED)`
Compared against benchmark thresholds for merged PR counts.
#### PR Merge Rate
**Formula:** `Total Merged PRs / Total Opened PRs`
A ratio from 0–100%. Compared against benchmark thresholds for merge rates.
#### PR Drop Rate
**Formula:** `Closed without merge / Opened non-draft PRs`
Draft PRs are excluded. Lower values are better for this metric.
#### Average Merge Latency (hrs)
**Formula:** `mean(merged_at - created_at)` across eligible merged PRs, in hours.
Lower values are better for this metric.
#### Reviews
**Formula:** `count(PR reviews submitted in window)`
Compared against benchmark thresholds for review counts.
#### Issues Opened
**Formula:** `count(issues opened in window)`
Compared against benchmark thresholds for issue counts.
### When a Bin May Be Hidden
Bins require enough activity to be statistically meaningful. You may see the raw value while the bin is hidden.
| Metric | Bin Appears When |
| :----------------------------------------- | :--------------------------------------------------------------------- |
| Total Opened PRs | ≥ 1 eligible PR |
| Total Merged PRs | ≥ 1 eligible PR |
| PR Merge Rate | ≥ 20 eligible PRs opened |
| PR Drop Rate | ≥ 20 eligible non-draft PRs opened |
| Average Merge Latency | ≥ 20 eligible merged PRs |
| Reviews, Issues Opened, OSS Activity Score | ≥ 10 total OSS activities (PRs + Reviews + Issues) and ≥ 1 eligible PR |
### Languages Profile
The **Languages** section shows your programming language breakdown as percentages, computed across your **lifetime PR history** (not windowed).
For each PR, a weight is calculated from `additions + deletions` (or `changed_files` if zero). This weight is distributed across languages using the repository's language breakdown, then normalized into a final percentage.
### Population Baseline
Benchmark thresholds are derived from GitHub Archive data covering **October 2022 through September 2025**. This baseline is fixed because GitHub's Events API changed its payload structure in late 2025, preventing consistent extension. Your individual metrics are still computed fresh from the GitHub GraphQL API—only the population reference points are frozen.
### Frequently Asked Questions
#### How often are my metrics updated?
Your metrics are computed when you first sign up for Drips Wave. After that, they are refreshed periodically via a background process. The rolling 3-year window moves forward with each refresh, so your metrics will naturally reflect your most recent activity over time.
#### Why don't my GitHub stats match exactly?
The scorecard applies filters that differ from GitHub's default profile views. Only PRs that received at least one comment or review are counted, which excludes self-merges and automated dependency bumps. Additionally, all metrics except the language profile are calculated over a rolling 3-year window rather than your full history, and only public repositories are included.
#### Why do my metrics show "Unknown" or "Not enough data" despite having activity?
If your GitHub account has enough data to satisfy the minimum thresholds for a metric but still shows "Unknown," the metrics may still be computing in the background. This process can take some time after initially signing up. If it's been more than an hour and you still see "Unknown," please [contact support](https://www.drips.network/wave/support).
## Drips Wave
import Figure from '../../components/Figure'
Drips Wave is a recurring bounty cycle designed to help ecosystems attract developers, accelerate maintenance, and grow their communities. Instead of one-off grants or long-term commitments, Wave creates a predictable rhythm of contribution where developers can "Fix, Merge, and Earn."
By focusing on short, structured cycles, typically running for one week every month, Wave bridges the gap between available funding and the day-to-day work that maintainers need help with.
:::warning
Drips Wave is currently launching with select partners, starting with the Stellar ecosystem in January 2026. Interested in running a Wave for your community? [Get in touch](https://drips-network.notion.site/1e4c52c9599781b5a0aeca3e8eb1b3e4).
:::
### Who is Drips Wave for?
* **Ecosystems** such as the Stellar Development Foundation use Drips Wave to broadly incentivize contributions across their entire open-source ecosystem.
* **Project Maintainers** within those ecosystems use Wave to attract new contributions, clear their backlog, and grow their community.
* **Contributors** earn rewards while building a verifiable portfolio of work in top-tier open-source projects.
### The Anatomy of a Wave
One or more **Wave Programs** run on a predictable cycle, with **Waves** occurring at regular intervals. Each Wave Program is centered around a specific open-source ecosystem (e.g., Stellar). A Wave is a synchronized event that concentrates community energy. While parameters can be adjusted, a typical Wave follows this lifecycle:
1. **Scoping**: Ecosystem maintainers apply their repositories to a Wave Program. Once approved by the program's organizers, maintainers add issues that they want the community to tackle during the upcoming Wave. These issues are tagged with specific Point values based on their complexity and impact.
2. **The Sprint**: The Wave opens for a set period (e.g., one week). Contributors browse available tasks, log in with their GitHub accounts, and submit pull requests to fix issues. For each resolved issue, contributors earn Points.
3. **Reward**: Progress is tracked automatically via points and leaderboards. Once the cycle concludes and work is verified, rewards are distributed to the successful contributors according to their share of Points earned during the Wave.
### Why Run a Wave Program on Drips?
Wave is built to solve the "maintenance gap" in open-source funding. It provides the tooling to turn a budget into tangible code progress with minimal friction.
#### 🔄 Predictable Rhythm
Regularity breeds engagement. By running monthly cycles, you create a habit for contributors to return to your ecosystem. It keeps existing developers engaged while providing a reliable schedule for new ones to join.
#### 🎯 Focused Impact
Grants are great for big ideas, but Waves are for getting things done. Maintainers can direct community power toward specific roadmap blockers, bug fixes, or documentation needs that often fall through the cracks.
#### 🚀 Accessible Entry Points
For new developers, a massive codebase can be intimidating. Waves present scoped, manageable tasks that allow contributors to build a track record and earn trust (and income) quickly. It is an ideal funnel for discovering future core contributors.
#### 🤖 Automated Operations
Drips handles the heavy lifting of tracking contributions, managing the review flow, and calculating rewards. This reduces the administrative burden on maintainers, allowing them to focus on code reviews rather than spreadsheets.
### Getting Started
Whether you are an ecosystem looking to boost activity or a developer ready to earn, here is how to get started.
#### For Maintainers
Learn how to leverage Drips Wave to clear your backlog and grow your contributor base.
* [**Participating in a Wave**](/wave/maintainers/participating-in-a-wave): Learn how to apply your repositories for a Wave Program, add issues to a Wave, and manage contributions.
* [**Understanding Points & Rewards**](/wave/points-and-rewards): A breakdown of how contributions are valued and how payouts work.
* [**Applicant metrics**](/wave/applicant-metrics): How the Code Metrics scorecard and language profile are calculated.
#### For Contributors
Ready to fix, merge, and earn?
* [**Join a Wave**](/wave/contributors/solving-issues-and-earning-rewards): How to find active Waves, and start contributing.
* [**Understanding Points & Rewards**](/wave/points-and-rewards): A breakdown of how contributions are valued and how payouts work.
* [**Applicant metrics**](/wave/applicant-metrics): How the Code Metrics scorecard and language profile are calculated.
## Understanding Points & Rewards
import Figure from '../../components/Figure'
Drips Wave uses a transparent, points-based system to quantify value and distribute rewards. Whether you are a Maintainer scoping work or a Contributor solving issues, understanding how points flow is key to maximizing your success in a Wave.
### The Point System
Every contribution in a Wave is valued in **Points**. These points act similar to "shares" in the total Reward Pool for the given Wave. The more points a contributor earns relative to others, the larger their slice of the total rewards.
:::warning
**Important disclaimer**: Drips and Wave Program Organizers reserve the right to adjust the reward formula, manually adjust points, deduct points or withhold rewards for misbehavior, and/or distribute portions of the reward pool outside the standard Points-based formula (e.g., to incentivize maintainers contributing to the program). Depending on the Wave Program, additional rules may apply, such as a maximum cap on rewards per contributor. Please read the [Terms and Rules page](/wave/terms-and-rules) for additional details.
:::
#### Base Points & Complexity
When a Maintainer adds an issue to a Wave Program, they assign a **Complexity Level**. This determines the fixed number of points awarded upon resolution.
| Complexity | Description | Total Points |
| :---------- | :------------------------------------------------------------------------------------------- | :----------- |
| **Trivial** | Typos, minor copy changes, or very small bug fixes. | **100** |
| **Medium** | Standard feature work or involved bug fixes. Includes a 50-point complexity bonus. | **150** |
| **High** | Complex architecture, refactors, or new integrations. Includes a 100-point complexity bonus. | **200** |
### Calculating Rewards
At the end of a Wave cycle, the total Reward Budget (e.g., $50,000) is distributed among all contributors based on their share of the total points earned during that specific Wave.
#### The Formula
Your payout is not a fixed dollar amount per issue. Instead, it is dynamic based on the total activity in the Wave. At the end of each Wave, we calculate your percentage of all points issued during the Wave, and allocate the reward pool accordingly.
#### The Leaderboard
Participants can track their standing in real-time via **Wave Program → Leaderboard**. A higher rank indicates a higher point total, which directly correlates to a larger payout.
:::warning
**Important disclaimer**: Drips and Wave Program Organizers reserve the right to adjust the reward formula, manually adjust points, deduct points or withhold rewards for misbehavior, and/or distribute portions of the reward pool outside the standard Points-based formula (e.g., to incentivize maintainers contributing to the program). Depending on the Wave Program, additional rules may apply, such as a maximum cap on rewards per contributor. Please read the [Terms and Rules page](/wave/terms-and-rules) for additional details.
:::
### Payout Distribution
Once a Wave concludes and the seven-day Compliment window closes, the distribution process begins.
1. **Calculation**: The leaderboard is finalized and individual allocations are calculated.
2. **Withdrawals**: Your Drips Wave account will reflect any earned rewards, which you can withdraw to a wallet. You'll receive an email notification when your rewards are available. See [Withdrawing Your Rewards](/wave/withdrawing-rewards) for detailed instructions.
#### Requirements to Receive Funds
To withdraw your rewards, you must have a verified identity (**KYC**). Since identity verification is required to apply for any issue in the first place, this will already be in place by the time you earn points.
:::danger
**KYC is Mandatory**: Identity verification is required to apply for issues and to withdraw any rewards you earn. See [Verifying your identity](/wave/contributors/solving-issues-and-earning-rewards#verifying-your-identity) for details.
:::
### Frequently Asked Questions
#### What happens if an issue isn't finished on time?
Wave cycles are strict timeboxes. If an issue is not marked as **Resolved** before the Wave ends, no points are awarded for that cycle.
* **For Contributors**: If you are close to finishing, communicate with your maintainer.
* **For Maintainers**: If the work is good but blocked by external factors, consider marking it resolved to ensure the contributor is rewarded. Otherwise, the issue rolls over to the next Wave.
#### Can Maintainers earn points?
Generally, points are reserved for Contributors solving issues. However, Wave Programs may have specific separate budgets or mechanisms to reward Maintainers for their administrative work (scoping, reviewing, and merging).
#### Who decides the complexity?
The Maintainer has the final say on an issue's complexity. If a Contributor believes an issue turned out to be much harder than "Trivial," they should discuss this with the Maintainer, who can update the complexity level before the issue is resolved.
## Drips Wave - Terms and Conditions
### 1. Introduction
Bounty sprints (“**Drips Wave Programs**”) in which you may participate as a contributor or maintainer (“**you**”, “**your**” or “**Participant**”), are hosted on the platform (“**Wave Platform**”) developed by the Public Goods Association (“**Association**”, “**we**”, “**our**”), a Swiss association with its registered seat is in Zug. Drips Wave Programs are designed to reward genuine impact within a designated third-party ecosystem. Each Drips Wave Program may consist of a series of waves (“**Drips Waves**”).
These terms and conditions (“**Terms**”) govern the access to and the participation in the Drips Wave Programs, in particular but without limitation, the rights and obligations of Participants arising from or in connection with the Drips Wave Programs. The Terms, together with any and all other documents referred to herein and any documentation made available on [https://docs.drips.network/wave/](https://docs.drips.network/wave/), govern your access to and participation in Drips Wave Programs, including all related tools, web applications, smart contracts, and Application Programming Interfaces (APIs) made available thereon.
These Terms may be updated and modified from time to time. The Terms as so updated and modified are deemed effective as of the date they are published on the website [https://docs.drips.network/wave/terms-and-rules](https://docs.drips.network/wave/terms-and-rules). It is the Participant’s responsibility to periodically check for the most recent version of the Terms.
By ticking the acceptance box, and/or by accessing, applying to participate in, or participating in any Drips Wave Program, you accept and agree to be bound by these Terms and any and all other terms, documents or policies incorporated herein by reference.
### 2. Responsibility
Drips Wave Programs are initiated by third-party ecosystems (“**Wave Program Organizers**”). The Association is responsible for organizing the Drips Wave Programs and hosting the Drips Wave Programs on the website [https://www.drips.network/wave](https://www.drips.network/wave) (“**Drips Wave App**”).
### 3. Drips Wave Program Participants
#### 3.1 Maintainers
A maintainer (“**Maintainer**”) may submit a repository, containing a set of technical issues, for consideration in a Drips Wave Program. The Association and the Wave Program Organizer retain sole discretion to select which repositories are admitted to a Wave Program and may remove a repository at any time during the Drips Wave Program. If your repository was rejected, you may appeal the decision from the **Maintainers → Orgs and Repos** dashboard in the Drips Wave app by clicking **Appeal** on the rejected repository. Appeals submitted through any other channel will not be considered.
The selection of a repository for a Drips Wave Program does not transfer ownership or control of that repository to the Association. The Association does not own, operate, manage, or control any repositories applying to or participating in a Drips Wave Program.
#### 3.2 Contributors
Contributors (“**Contributors**”) apply to work on issues listed in repositories and, subject to the impact of their contributions, may become eligible to earn Wave Rewards.
### 4. Eligibility
To be eligible to participate in any Drips Wave Program, the Participant represents and warrants that:
* You are 18 years old or qualify as an adult in your country of residence.
* You, including your affiliates, officers, and directors, are not a citizen or resident of, and are not located in, any jurisdiction that is subject to comprehensive sanctions or embargoes imposed by the United Nations, Switzerland, the European Union, the United States, or any other relevant authorities.
* Neither you nor any of your affiliates, officers, or directors are listed on, or owned or controlled by any person or entity listed on, the Overall List of Sanctioned Individuals, Entities and Organizations maintained by the Swiss State Secretariat for Economic Affairs (SECO), the EU Consolidated List of Persons, Groups, and Entities Subject to EU Financial Sanctions, the U.S. Department of Commerce’s Denied Persons or Entity List, the U.S. Department of Treasury’s Specially Designated Nationals or Blocked Persons Lists, the U.S. Department of State’s Debarred Parties List, or any other national or international sanctions or embargo list.
* You, including your affiliates, officers, and directors, are not a resident or citizen of, and are not located in, any geographic area designated as non-cooperative with international anti-money-laundering (AML) principles or procedures by any intergovernmental organization, including the Financial Action Task Force (FATF).
* You, including your affiliates, officers, and directors, are not domiciled in and are not organized under the laws of any jurisdiction whose legislation conflicts with the Drips Wave Programs.
The Participant acknowledges and agrees that all determinations regarding eligibility, participation in Drips Wave Programs, and entitlement to Wave Rewards (as defined in Section 5) are made at the sole, absolute, and final discretion of the Association. This implies that the Association may accept, reject, suspend, or remove any Participant from any Drips Wave Program at any time, for any reason.
### 5. Waves Rewards
#### 5.1 Wave Rewards Calculation
For each Wave, a maximum reward pool (“**Total Reward Pool Size**”) is determined by the Association.
Wave Rewards (“**Wave Rewards**”) are granted to Participants based on their performance within a Wave Program. Wave Rewards are calculated according to a point system (“**Points**”) developed by the Association to assess a Participant’s contributions to the Wave Program Organizers’ ecosystem. Points have no monetary value and do not constitute a currency, a means of exchange, or a store of value.
Unless expressly stated otherwise, Points earned during a Wave or a Wave Program do not carry over to any subsequent Waves or future Wave Programs and automatically expire upon termination of the applicable Wave Program.
The calculation and application of the exchange rate between Points and the Token are determined solely at the discretion of the Association and may be modified at any time. The Association may also establish limits on the maximum Wave Reward a Participant may be entitled to receive.
All decisions made by the Association in relation to Wave Rewards are final.
#### 5.2 Wave Rewards Denomination
Wave Rewards are generally paid in the token of the Wave Program Organizer (“**Token**”). However, the Association may, for compliance, operational, or other legitimate reasons, settle Wave Rewards in other cryptocurrencies or in fiat currencies.
#### 5.3 Wave Rewards Allocation
The Participant understands that, in order to claim any Wave Reward, the Association or its appointed service provider (“**KYC Service Provider**”) may require all information, documentation, and evidence reasonably requested to complete the know-your-customer or know-your-business verification process (“**KYC / KYB Check**”). The Participant understands that the outcome of the KYC/KYB Check lies solely within the discretion of the Association and/or the KYC Service Provider. The Participant further accepts that the scope and requirements of the KYC / KYB Check may evolve over time and that additional information or documentation may be requested at a later stage.
Successful completion of the KYC / KYB Check, as well as continued compliance with these Terms, is a prerequisite for the allocation of any Wave Rewards.
#### 5.4 Unclaimed Wave Rewards
If a Participant fails to claim their Wave Rewards within a reasonable period of time as determined by the Association, the Association may deem such Rewards to be unclaimed. In such circumstances, the Association is entitled to retain, reallocate, or otherwise repurpose the unclaimed Wave Rewards for use in subsequent Drips Wave Programs. Participants acknowledge and agree that they shall have no further rights or claims to any Wave Rewards deemed unclaimed under this clause.
#### 5.5 Other Variables
In addition to Points, the Association may, at its sole discretion, consider other qualitative or quantitative factors to reward Maintainers’ contribution to Drips Wave Programs. The Association may therefore allocate part of the Total Reward Pool directly to a Maintainer, independently of the point‑based calculation.
### 6. Prohibited Activities
The Participant shall refrain from engaging in any of the following prohibited activities (“**Prohibited Activities**”):
* Engaging in any activity intended to generate Points or Wave Rewards through low-effort or bad-faith submissions, including but not limited to:
* Submitting pull requests or contributions consisting solely of trivial typo corrections or similar low-value changes for the purpose of accumulating Points;
* Submitting code, documentation, or other contributions generated by large language models (“**LLMs**”) that are of low quality, untested, or not understood by the Contributor;
* Requesting repeated assignment to issues without reviewing their scope or without verifying whether the issue has already been assigned to another Contributor; or
* Submitting code, documentation, or any other contribution that infringes, or may infringe, third‑party intellectual property rights, or that contains viruses, malware, or any other malicious components.
* Manipulating the Point or Wave Reward mechanisms, for example by:
* Creating or using multiple identities or accounts to apply for issues, influence Points, or affect rankings;
* Coordinating with others to submit or approve low-quality work, manipulate outcomes, or otherwise distort the integrity of the Drips Wave Programs; or
* Submitting work that is not the Contributor’s own without proper attribution or authorization.
* Introducing, attempting to introduce, or suggesting the introduction of vulnerabilities, backdoors, malware, or any other harmful code into any repository, or into the Drips Wave App and/or Wave Platform, supporting platform or the Network.
* Interfering with, circumventing, or compromising the security, availability, or integrity of the Drips Wave App and/or Wave Platform associated infrastructure;
* Disrupting, compromising, or otherwise damaging data or property owned by the Association or any other party;
* Interfering with, or attempting to interfere with, the access of any user, host, or network, including, without limitation, by sending viruses, overloading, flooding, spamming, or creating, encouraging, or implementing Sybil attacks;
* Attempting to circumvent geographic restrictions;
* Encouraging or enabling any other individual or entity to engage in any of the foregoing or otherwise violate these Terms.
If the Association determines, in its sole discretion, that a Participant has engaged in any Prohibited Activity, it may take the actions set forth in Section 7.
### 7. Remedies and Enforcement
In the event that a Participant breaches these Terms, the Association may take any corrective or enforcement measures it deems appropriate. Such measures may include, without limitation, deducting Points earned in current or previous Waves, and temporarily or permanently suspending the Participant’s access to the Drips Wave App and their participation in any current or future Drips Wave Programs. The Association may also withhold or cancel any pending or future Wave Rewards or reclaim Wave Rewards that have already been distributed.
The Association reserves all other rights and remedies available under applicable law.
### 8. Wallet
Claiming the Tokens from the Wave Rewards may require reliance on or an integration with third party products (e.g., a wallet, a network, or blockchain) that neither the Association nor the Wave Program Organizer control. In particular, neither the Association nor the Wave Program Organizer has control over the private keys of the Participant and the use of the wallet is subject to the terms and conditions of the respective third-party wallet provider. Neither the Association nor the Wave Program Organizer has custody or control of the wallets and Participants are solely responsible for the security of their wallet. Neither the Association nor the Wave Program Organizer is not responsible for managing and maintaining the security of Participant’s wallets.
Tokens allocated to the Participant’s address can only be accessed with the Participant’s access data and/or private key. The Participant understands and accepts that if its private key or wallet password were lost or stolen, the access to the Participant’s Tokens allocated to the Participant’s address would be unrecoverable and would be permanently lost. Neither the Association nor the Wave Program Organizer has control over the Participant’s Tokens; therefore, the Participant shall have no recourse to seek any refunds, recovery or replacements from neither the Association, nor the Wave Program Organizer in the event that he/she cannot access the wallet and/or Tokens anymore and/or any Tokens are lost or stolen.
### 9. Data Privacy Policy
The Association handles Participant’s personal information in accordance with Swiss law and its privacy policy, available at [https://www.drips.network/legal/privacy](https://www.drips.network/legal/privacy).
### 10. No Fees
No fees are associated with the participation in the Drips Wave Programs. Participants may have to pay network gas fees to claim any Wave Rewards.
### 11. Warranty Disclaimer and Exclusion of Liability
The access and use of the Drips Wave App and related infrastructure is made at your own risk. You understand and agree that the Drips Wave App and supporting infrastructure is provided on an “as is” and “as available” basis and that the Association expressly disclaims all warranties or conditions of any kind, whether express, implied, statutory or otherwise.
The Association disclaims all liability for any losses arising from (i) failures or malfunctions of any blockchain or network, (ii) bugs or vulnerabilities in the Wave Platform Network, the Drips Wave App and respective supporting technology or any smart contract, (iii) code mergers into repositories (iv) hacks, exploits, or malicious attacks, and (v) any third-party services, products, or infrastructure (including wallets).
Except in cases of gross negligence, fraud, or willful misconduct, the Association shall not be liable to the Participant for any damages arising out of or in connection with these Terms, including, without limitation, indirect or consequential damages such as loss of profit, loss of business opportunity, or reputational harm.
### 12. Indemnification
To the fullest extent permitted by applicable law, the Participant will indemnify, defend and hold harmless the Association, the Association’s respective past, present, and future members of the board, founders, employees, officers, directors, contractors, consultants, suppliers, vendors, service providers, developers, subsidiaries, affiliates, agents, representatives, predecessors, successors and assignees (“**Association Parties**”) from and against all claims, demands, actions, damages, losses, costs and expenses (including attorneys’ fees) that arise from or relate to any violation of these Terms, including any inaccuracies in the provided representations and warranties, and any ensuing regulatory enforcement actions, whether arising from the Participant’s actions or omissions. The Association reserves the right to exercise sole control over the defense, at the Participant’s expense, of any claim subject to indemnification under this Section. This indemnity is in addition to, and not in lieu of, any other indemnities set forth in a written agreement between the Participant and the Association.
### 13. Intellectual Property
The Association retains all rights, title and interest in and to its intellectual property rights.
Participation in Drips Wave Programs does not assign, transfer, or otherwise convey to the Association any rights, title, or interests - including intellectual property rights - in any materials, code, or documentation contributed, made available or created by a Participant in connection with the Drips Wave Programs.
The Contributor undertakes to contribute to repositories under the applicable open‑source licenses as communicated on the Drips Wave App and/or in the relevant repository.
### 14. Tax Considerations
The Participant will bear sole responsibility for any and all taxes, levies, charges, and fees (including, where applicable, VAT) that may arise from the participation in the Drips Wave Programs, the allocation, receipt, holding, transfer, or use of Tokens provided pursuant to or otherwise associated with these Terms. The Participant is solely responsible for identifying and meeting all tax obligations as required by applicable laws.
By accepting these Terms, the Participant acknowledges that neither the Association nor any Association Party shall be held responsible for any tax obligations of the Participant. The Participant also agrees to comply with all relevant tax laws, and to indemnify and hold harmless the Association and Association Parties against any liabilities arising from non-payment of such taxes or fees by the Participant.
### 15. Relationship of the Parties
The Association and the Participant are independent parties. These Terms do not create, nor are they intended to create a partnership, franchise, joint venture, agency, fiduciary or employment relationship between the Association and the Participant.
### 16. Severability
If any provision of these Terms is invalid, illegal or unenforceable in any jurisdiction, such invalidity, illegality or unenforceability shall not affect any other provision of the Terms or invalidate or render unenforceable such provision in any other jurisdiction. Upon such determination that any provision is invalid, illegal or unenforceable, the Terms shall be modified to effectuate the original intent of the original provision as closely as possible.
### 17. Governing Law and Jurisdiction
These Terms and all claims relating to or arising out of these Terms, or the breach thereof, whether in contract, tort or otherwise, shall be governed by Swiss Law, excluding Swiss choice-of-law principles.
Any dispute, controversy or claim arising out of, or in relation to, these Terms, including the validity, invalidity, breach, or termination thereof, shall be resolved by the ordinary courts in Zug, Switzerland.
## Withdrawing Your Rewards
import Figure from '../../components/Figure'
After a Wave ends and your contributions have been evaluated, your earnings are converted into **Reward Grants**. Each grant represents a portion of the reward pool you've earned, payable in USDC on the Stellar network. When a new grant is available, you'll receive a notification email.
This guide walks you through the process of withdrawing your rewards to your Stellar wallet.
### Prerequisites
Before you can withdraw rewards, you must:
1. **Complete KYC Verification**: Navigate to **Settings → Profile** and complete the identity verification process. You cannot withdraw any funds until your KYC status is approved.
2. **Have a Stellar Wallet**: You'll need a Stellar wallet that supports USDC. Popular options include [LOBSTR](https://lobstr.co/) (mobile app), [Ledger](https://www.ledger.com/) (via Ledger Live or compatible wallet), or any other wallet compatible with the Stellar network. You can also withdraw to a centralized exchange, provided it supports **USDC deposits specifically on the Stellar network**.
3. **Activate the wallet and enable the USDC Trustline**:
* **Activation:** Your wallet must hold a small minimum balance of **XLM** (Stellar Lumens) to be active.
* **Trustline:** You must explicitly enable the "USDC" asset for your wallet (this is called "setting a trustline"). Instructions for enabling USDC on the popular LOBSTR wallet can be found [here](https://lobstr.freshdesk.com/support/solutions/articles/151000001061-adding-custom-assets-in-lobstr). Please ensure to enable the "real" USDC asset, issued by Circle (issuer address: `GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN`), and not any similarly named assets from other issuers.
* **Exchange Users:** If you are withdrawing directly to an exchange that supports USDC deposits on Stellar, the exchange's wallet generally already holds XLM and the trustline. You typically don't need to set this manually, but you **must** provide the correct **Memo** with your withdrawal request.
:::info
Our system attempts to detect if your address has a valid USDC trustline to help prevent common errors. However, this check is only for your convenience, and does not guarantee that your wallet or exchange is fully compatible or will successfully credit the funds. **We strongly recommend requesting a test transaction first** to verify that your specific wallet or exchange can receive USDC from our system before requesting the full withdrawal.
:::
### Viewing Your Grants
To view your reward grants:
1. Navigate to **Wave → Reward Grants** in the Drips Wave app.
2. You'll see a list of all your grants, showing the Wave program name, amount, and current status.
Click on any grant to view its details, including the full transaction history for that grant.
### Grant Statuses
Each grant has a status indicating where it is in the withdrawal process:
| Status | Description |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| **Withdrawable** | The grant is ready. You can request a test transaction or full withdrawal. |
| **Test Requested** | A $1 test transaction has been requested and is being processed. |
| **Test Sent** | The test transaction has been sent on-chain to your provided address. Please verify receipt in your wallet before proceeding. |
| **Withdrawal Pending** | A full withdrawal has been requested and is being processed. |
| **Withdrawal Complete** | The full amount has been sent to your wallet. |
### The Withdrawal Process
We recommend a two-step withdrawal process to ensure your wallet is correctly configured:
#### Step 1: Request a Test Transaction
Before withdrawing your full grant amount, we strongly recommend requesting a **test transaction** first. This sends $1 from your grant balance to your wallet to verify everything is set up correctly.
1. From the grant details page, click **Request test transaction**.
2. Enter your Stellar wallet address (starts with `G`).
3. If your wallet or exchange requires a **memo**, enter it in the memo field. This is common for centralized exchanges.
4. Review the details on the confirmation screen and check the box to confirm you understand the terms.
5. Click **Confirm** to submit the request.
The test transaction has now been requested. It's usually sent within a few minutes, but in rare cases may take up to 3 days.
:::warning
**Important:**
Drips Wave is not responsible for funds lost due to incorrect addresses, missing Memos, incompatible wallets, or exchange policies (such as minimum deposit limits). By requesting a withdrawal, you acknowledge that you have verified the destination details.
**Exchange Users:**
1. **Network Selection:** Ensure you select the **Stellar (XLM)** network. Do **not** select Ethereum (ERC-20), Solana, or any other chain.
2. **Memos:** A missing Memo may result in loss of funds on exchanges.
3. **Minimum Deposits:** Some exchanges require a minimum deposit (e.g., 5 or 10 USDC). If the $1 test transaction is below this limit, it may not appear in your account.
:::
#### Step 2: Request Full Withdrawal
Once your test transaction has been successfully received:
1. Return to the grant details page and click **Request full withdrawal**.
2. Your wallet address and memo from the successful test transaction will be pre-filled for convenience.
3. Review all details carefully on the confirmation screen.
4. Check the confirmation box to acknowledge the terms and conditions.
5. Click **Confirm** to submit your withdrawal request.
### Processing Time
**Test transactions** are usually sent within a few minutes, but in rare cases may take up to 3 days. **Full withdrawals** are usually processed within 1–3 business days. You'll receive an email notification when your transaction has been completed. If your transaction is taking longer than expected, please check the transaction history for any updates or contact our [support team](https://www.drips.network/wave/support).
### Transaction History
Every grant maintains a complete transaction history. From the grant details page, you can view:
* **Transaction type**: Test or Withdrawal
* **Amount**: The USD value of the transaction
* **Destination**: The Stellar address (hover to see full address and copy)
* **Memo**: The memo included with the transaction, if any
* **Status**: Pending, Complete, or Cancelled
* **Requested date**: When you submitted the request
* **Completed date**: When the transaction was finalized (if complete)
* **Transaction hash**: A link to view the transaction on Stellar Expert (if complete). This link serves as proof that the funds were sent on-chain.
### Troubleshooting
#### My test transaction failed or didn't arrive
* **Check Minimums:** If withdrawing to an exchange, did the $1 transaction meet their minimum deposit requirement? (It may be credited only after you deposit enough to reach the threshold).
* **Check Address/Memo:** Verify your wallet address is correct and you provided the correct memo, if required.
* **Check Trustline:** Ensure you have not removed the USDC trustline since requesting the transaction.
* **Check XLM Balance:** Check that your wallet is funded with enough **XLM** (Stellar requires a minimum balance of Lumens for accounts to receive other assets).
#### I sent to the wrong address
If your transaction is still pending, **cancel the withdrawal on your Reward Grants Dashboard and [contact our support team immediately](https://www.drips.network/wave/support)**. If it has already been completed, unfortunately, it cannot be reversed due to the nature of blockchain transactions.
#### My exchange requires a memo but I forgot to include one
If your transaction is still pending, **cancel the withdrawal on your Reward Grants Dashboard and [contact our support team immediately](https://www.drips.network/wave/support)**. If it has already been completed, unfortunately, it cannot be reversed due to the nature of blockchain transactions.
In this case, please **contact your exchange's support team as soon as possible**. Exchanges may be able to manually credit deposits that are missing memos, but this is not guaranteed.
#### My grant expired
Grants have an expiration date. If you don't withdraw before this date, you may forfeit your rewards. Check the grant details page for the expiration date and ensure you complete the withdrawal process in time.
### Frequently Asked Questions
#### Why do I need to verify my identity (KYC)?
KYC (Know Your Customer) verification is required for regulatory compliance. We cannot process any withdrawals without completed identity verification.
#### Can I withdraw to any wallet?
You can withdraw to any Stellar wallet that supports USDC on Stellar and has the USDC trustline enabled.
#### Can I withdraw directly to a centralized exchange?
If the exchange supports direct USDC deposits on the Stellar network, yes. However, you must always include the correct memo (if provided by your exchange) with your withdrawal request. Exchanges use memos to credit incoming transfers to the right account. Always check your exchange's deposit instructions carefully to ensure USDC-Stellar support is available in your region. **We recommend requesting a test transaction first to ensure the address and memo are correct.**
#### What currency are rewards paid in?
All rewards are paid in USDC (USD Coin) on the Stellar network.
#### Is there a minimum withdrawal amount?
If you received a grant, you can withdraw it either in full or request a test transaction of $1 first. Partial withdrawals of amounts other than $1 or the full grant amount are not supported.
#### Can I change my wallet address after requesting a withdrawal?
While a withdrawal request generally cannot be changed once submitted, if you realize you made a mistake before it is processed, please [contact our support team](https://www.drips.network/wave/support) immediately.
#### What if I have multiple grants?
Each grant is managed independently. You can request tests and / or full withdrawals for each grant separately.
## Handling renamed GitHub repositories
import Figure from '../../../components/Figure'
Due to Drips' unique decentralized and permissionless approach to Public Goods Funding, there are a few extra considerations when it comes to handling funds sent to GitHub repositories on Drips that were later renamed on GitHub. Let's dive a bit deeper into why that is, and how best to handle it.
### Background
Every public GitHub repository gets a project profile on Drips and may be funded by anyone. Behind the scenes, our on-chain Smart Contracts assign an `accountId` to every possible GitHub repository URL, and then allow access to those accounts through an ownership assertation system that involves adding a `FUNDING.json` file to a particular GitHub repository.
:::info
Read more about the access control system for Drips projects [here](/get-support/claim-your-repository). If you'd like to dive deeper into Drips' account system, go [here](/the-protocol/accounts-in-drips).
:::
As a result of this, anyone can theoretically split or directly send funds to any possible GitHub repository URL, even if that URL does not (currently) correspond to an existing repo. In practice, the Drips App will automatically validate any entered GitHub URL when creating splits, and...
* reject attempts to split to a non-existent GitHub repo URL,
* automatically replace URLs to a repo that has been renamed with its new URL,
* automatically correct the casing of URLs to the exact casing as set in the repo name on GitHub (because GitHub repo URLs are case-insensitive).
In addition, when navigating to a project profile on Drips corresponding to the old URL of a now-renamed GitHub repo, the app will display a disclaimer that links to the new Drips project, and hide the "Become a Supporter" section.
In practice, this means that any *new* funds split or directly sent to your renamed repo on Drips will go to the Drips project corresponding with its new, up-to-date URL. However, this doesn't apply to any funds split or sent to your project *before* its repo has been renamed — those will stay with the Drips project corresponding to the *old* repo URL. In addition, a different project or Drip List that has been set to split to that old URL will continue splitting any new funds it receives to that same outdated Drips project.
But, of course **no funds are ever lost**: GitHub always redirects all traffic to an outdated repo URL to the new URL / name of that repo. This makes it possible to simply claim an "outdated" Drips project using the same `FUNDING.json` file present within the newly-renamed repo.
### Recommended approach
This recommended approach for handling a renamed GitHub repo ensures that you only need to maintain a single set of maintainer and dependency splits, that *all* funds sent to both the outdated and new Drips projects are distributed to. This is achieved by claiming both projects (the outdated one that received funds, and the new, renamed one), and setting the outdated project to split 100% of its funds to the new one.
#### Step-by-step
##### Claim the old project
1. Navigate to the Drips project profile corresponding to your repo's old URL. **Tip**: When entering a GitHub repo URL into the search bar, the app usually automatically redirects you to the renamed project. You can get around this by appending `?exact` to the project profile URL — for example: `https://drips.network/app/projects/github/gh-username/old-repo-name?exact`
2. Click "Claim project" to initiate the claim flow, and follow the instructions.
3. When prompted to choose the maintainer / dependency split percentage, drag the slider so that all funds are going to "dependencies".
4. Configure a single "dependency": Your own, newly renamed repo. To do so, just paste the (renamed) GitHub repo URL into the splits editor.
5. Continue following the instructions until you've successfully claimed the old project.
All funds received by the old project are now claimable on the new project.
:::info
In some cases, the funds received by your old project may not make their way over to the new, renamed project for a while — this is temporary. They will be moved latest by the next [settlement date](/get-support/claim-your-repository#settlement-of-future-funds).
:::
##### Claim the new project
1. Navigate to your new, still unclaimed Drips project.
2. Click "Claim project" to initiate the claim flow, and follow the instructions.
3. Configure maintainers & dependencies according to your wishes. All past & future funds sent to both your old & new Drips projects will be distributed accordingly.
4. Continue following the instructions until you've successfully claimed the old project.
That's it! All claimable funds should now have been distributed according to your splits configuration. If this hasn't happened yet, you may have to wait until the next [settlement date](/get-support/claim-your-repository#settlement-of-future-funds).
## Managing Custom Datasets
Custom datasets allow round admins to enrich applications with additional data, giving badgeholders more context during the voting process. You can upload one or more CSV files, and the data from these sets will be displayed as distinct cards on the relevant application pages, and also be included in all CSV exports of application data.
### 1. Creating a Custom Dataset
First, you need to define a new dataset for your round.
1. Navigate to your round's **Settings** page from the main round dashboard.
2. Select the **"Custom datasets"** tab from the sidebar.
3. Click the **"Add custom dataset"** button.
4. In the dialog box, provide a **Name** for your dataset. This name will be used as the title for the data block on application pages (e.g., "Code Metrics," "Social Media Engagement").
5. Click **"Create custom dataset"**.
Your new dataset will initially be hidden from the public, and is now ready for you to upload data.
:::info
You can create a maximum of five custom datasets for a round.
:::
### 2. Uploading Data
Once you've created a dataset, you can upload a CSV file containing the data you want to display.
To link data to the correct application, your CSV file must contain a column that uniquely identifies each project. This identifier must match the project's name or another unique key associated with the application. The remaining columns in your CSV will be displayed as key-value pairs.
:::info
You can include a maximum of ten fields with each dataset.
:::
#### Example CSV Structure:
| applicationId | Number of PRs | Number of commits |
| :----------------------------------- | :------------ | :---------------- |
| ceb88e03-7c13-479a-bbaa-3609bb075d8d | Many | 345 |
| 9c35153b-50e4-43f8-89e5-2a66ac903a4a | Some | 120 |
There are a few rules for the CSV:
1. The first column must always be called `applicationId` and contain the UUID for a particular application. You can get a list of applications by exporting application data from the **All Applications** screen.
2. All following columns are the contents of your dataset. The column name is the publicly visible name of the field, so ensure it is formatted properly.
To upload your file:
1. Find your dataset in the list and click the **Upload** icon.
2. Select the CSV file from your computer.
3. Click **"Upload data"** to import it.
:::warning
Uploading a new CSV file will completely replace any existing data within that custom dataset.
:::
### 3. Managing Dataset Visibility
By default, newly created datasets are hidden from public view. You must set a dataset to "visible" for it to appear on application pages and be included in exports.
* Hidden Datasets: Visible only to round admins.
* Visible Datasets: Displayed publicly on application pages for anyone, and included in CSV exports.
To change the visibility:
1. Click the **Edit** icon (pencil) next to the dataset you want to manage.
2. Toggle the **"Visible publicly"** switch.
3. Click **"Update custom dataset"** to save your changes.
Once a dataset is visible, its data will automatically appear as a new section on the corresponding application pages.
### 4. Deleting a Custom Dataset
If you no longer need a dataset, you can permanently remove it.
1. Find the dataset in the list on the **Settings → Custom datasets** page.
2. Click the **Delete** icon (trash can).
3. Confirm the action in the dialog box.
:::info
Deleting a dataset is permanent and will remove it from all application pages and future data exports. This action cannot be undone.
:::
## API Reference
### `createDripsSdk(blockchainClient, ipfsMetadataUploaderFn?, options?)`
Creates the main SDK instance with access to all modules.
**Parameters:**
* `blockchainClient`: Viem `PublicClient`/`WalletClient`, Ethers `Provider`/`Signer`, or custom adapter
* `ipfsMetadataUploaderFn?`: Optional IPFS metadata uploader function (required for write operations)
* `options?`: Configuration object
**Returns:** `DripsSdk` instance with modules: `dripLists`, `donations`, `utils`, `funds`, `constants`
**Example:**
```typescript
const sdk = createDripsSdk(walletClient, ipfsUploader);
```
### `sdk.dripLists.calculateId(salt, minter)`
Calculates the Drip List ID for given minter and salt.
**Parameters:**
* `salt: bigint` - Salt value for uniqueness
* `minter: Address` - Address that would mint the Drip List
**Returns:** `Promise` - The Drip List ID
**Example:**
```typescript
const id = await sdk.dripLists.calculateId(123n, '0x1234...');
```
### `sdk.dripLists.getById(id, chainId)`
Fetches a DripList by ID and chain ID.
**Parameters:**
* `id: bigint` - The Drip List ID
* `chainId: number` - The chain ID
**Returns:** `Promise` - The DripList or null if not found
```typescript
type DripList = {
id: bigint;
name?: string;
description?: string;
minter: Address;
owner: Address;
splits: {
list: SdkSplitsReceiver[];
hash: Hash;
};
latestVotingRound?: VotingRound;
support: SupportData;
chainId: number;
};
```
**Example:**
```typescript
const dripList = await sdk.dripLists.getById(123n, 11155111);
```
### `sdk.dripLists.prepareCreate(dripList)`
Prepares context for creating a new Drip List.
**Parameters:**
* `dripList: NewDripList`
```typescript
type NewDripList = {
isVisible: boolean;
receivers: ReadonlyArray; // Must sum to 1,000,000 weight
salt?: bigint; // Optional, random if not provided
name?: string;
description?: string | null;
transferTo?: Address; // Transfer ownership, defaults to minter
batchedTxOverrides?: BatchedTxOverrides;
latestVotingRoundId?: string;
};
```
**Returns:** `Promise`
```typescript
type PrepareDripListCreationResult = {
newDripListId: bigint;
newDripListName?: string;
newDripListDescription?: string;
preparedTxs: PreparedTx[];
metadataIpfsHash?: Hash;
};
```
**Example:**
```typescript
const result = await sdk.dripLists.prepareCreate({
isVisible: true,
name: 'My Support List',
description: 'Supporting awesome projects',
receivers: [
{ type: 'project', url: 'https://github.com/owner/repo1', weight: 500000 },
{ type: 'address', address: '0x1234...', weight: 500000 },
],
});
```
### `sdk.dripLists.create(dripList)`
Creates a new Drip List.
**Parameters:**
* `dripList: NewDripList`
**Returns:** `Promise`
```typescript
type CreateDripListResult = {
newDripListId: bigint;
newDripListName?: string;
newDripListDescription?: string;
metadataIpfsHash?: Hash;
txResponses: TxResponse[];
};
```
**Example:**
```typescript
const result = await sdk.dripLists.create({
isVisible: true,
name: 'Developer Fund',
receivers: [
{ type: 'project', url: 'https://github.com/owner/repo', weight: 1000000 },
],
});
```
### `sdk.dripLists.prepareUpdate(config)`
Prepares context for updating a Drip List.
**Parameters:**
* `config: DripListUpdateConfig`
**Returns:** `Promise`
```typescript
type PrepareDripListUpdateResult = {
preparedTxs: PreparedTx[];
metadataIpfsHash?: Hash;
};
```
**Example:**
```typescript
const result = await sdk.dripLists.prepareUpdate({
dripListId: 123n,
chainId: 11155111,
name: 'Updated Name',
});
```
### `sdk.dripLists.update(config)`
Updates a Drip List.
**Parameters:**
* `config: DripListUpdateConfig`
**Returns:** `Promise`
```typescript
type UpdateDripListResult = {
txResponses: TxResponse[];
metadataIpfsHash?: Hash;
};
```
**Example:**
```typescript
const result = await sdk.dripLists.update({
dripListId: 123n,
chainId: 11155111,
receivers: [{ type: 'address', address: '0x5678...', weight: 1000000 }],
});
```
### `sdk.donations.prepareOneTime(donation)`
Prepares a transaction for making a one-time donation.
**Parameters:**
* `donation: OneTimeDonation`
```typescript
type OneTimeDonation = {
receiver: SdkReceiver;
amount: string; // Human-readable format (e.g., "10.5")
erc20: Address;
tokenDecimals: number;
batchedTxOverrides?: BatchedTxOverrides;
};
```
**Returns:** `Promise` - Ready for execution
**Example:**
```typescript
const preparedTx = await sdk.donations.prepareOneTime({
receiver: { type: 'address', address: '0x1234...' },
amount: '10.5',
erc20: '0xTokenAddress...',
tokenDecimals: 18,
});
```
### `sdk.donations.sendOneTime(donation)`
Sends a one-time donation.
**Parameters:**
* `donation: OneTimeDonation`
**Returns:** `Promise` - Transaction response from blockchain
**Example:**
```typescript
const txResponse = await sdk.donations.sendOneTime({
receiver: { type: 'drip-list', accountId: 123n },
amount: '50.0',
erc20: '0xTokenAddress...',
tokenDecimals: 18,
});
```
### `sdk.donations.prepareContinuous(donation)`
Prepares context for a continuous donation stream.
**Parameters:**
* `donation: ContinuousDonation`
```typescript
type ContinuousDonation = {
erc20: Address;
amount: string; // Amount to stream per timeUnit
timeUnit: TimeUnit; // DAY, WEEK, MONTH, etc.
tokenDecimals: number;
receiver: SdkReceiver;
name?: string;
startAt?: Date; // Defaults to "now"
durationSeconds?: bigint; // If omitted, runs until funds run out
topUpAmount?: string; // Amount to add when setting stream
batchedTxOverrides?: BatchedTxOverrides;
};
```
**Returns:** `Promise`
```typescript
type PrepareContinuousDonationResult = {
preparedTxs: PreparedTx[];
metadataIpfsHash?: Hash;
streamConfig: StreamConfig;
};
```
**Example:**
```typescript
const result = await sdk.donations.prepareContinuous({
receiver: { type: 'project', url: 'https://github.com/owner/repo' },
amount: '100',
timeUnit: TimeUnit.MONTH,
erc20: '0xTokenAddress...',
tokenDecimals: 18,
name: 'Monthly Support',
topUpAmount: '1200', // 12 months worth
});
```
### `sdk.donations.sendContinuous(donation)`
Sends a continuous donation by preparing and executing transaction.
**Parameters:**
* `donation: ContinuousDonation`
**Returns:** `Promise`
```typescript
type SendContinuousDonationResult = {
txResponses: TxResponse[];
metadataIpfsHash?: Hash;
streamConfig: StreamConfig;
};
```
**Example:**
```typescript
const result = await sdk.donations.sendContinuous({
receiver: { type: 'address', address: '0x1234...' },
amount: '10',
timeUnit: TimeUnit.DAY,
erc20: '0xTokenAddress...',
tokenDecimals: 18,
durationSeconds: 86400n * 30n, // 30 days
});
```
### `sdk.funds.getWithdrawableBalances(chainId)`
Fetches withdrawable balances for connected user on specific chain.
**Parameters:**
* `chainId: number` - The chain ID for target network
**Returns:** `Promise`
```typescript
type UserWithdrawableBalances = {
accountId: bigint;
balances: TokenWithdrawableBalance[];
};
type TokenWithdrawableBalance = {
tokenAddress: Address;
withdrawableAmount: bigint;
receivableAmount: bigint;
splittableAmount: bigint;
collectedAmount: bigint;
};
```
**Example:**
```typescript
const balances = await sdk.funds.getWithdrawableBalances(11155111);
```
### `sdk.funds.prepareCollection(config)`
Prepares transaction for collecting funds from streams and splits.
**Parameters:**
* `config: CollectConfig`
```typescript
type CollectConfig = {
accountId: bigint;
currentReceivers: SdkSplitsReceiver[];
tokenAddresses: ReadonlyArray;
batchedTxOverrides?: BatchedTxOverrides;
shouldSkipSplit?: boolean;
shouldAutoUnwrap?: boolean; // Unwrap wrapped native tokens
shouldSkipReceive?: boolean;
squeezeArgs?: SqueezeArgs[]; // For squeezing streams from specific senders
transferToAddress?: Address; // Transfer to address, defaults to signer
};
```
**Returns:** `Promise` - Ready for execution
**Example:**
```typescript
const preparedTx = await sdk.funds.prepareCollection({
accountId: 123n,
currentReceivers: [],
tokenAddresses: ['0xTokenAddress...'],
shouldAutoUnwrap: true,
});
```
### `sdk.funds.collect(config)`
Collects funds for an account.
**Parameters:**
* `config: CollectConfig`
**Returns:** `Promise` - Transaction response from blockchain
**Example:**
```typescript
const txResponse = await sdk.funds.collect({
accountId: 123n,
currentReceivers: [],
tokenAddresses: ['0xTokenAddress...'],
});
```
### `sdk.utils.calcAddressId(address)`
Calculates the AddressDriver account ID for a given address.
**Parameters:**
* `address: Address` - The address to compute account ID for
**Returns:** `Promise` - The calculated account ID
**Example:**
```typescript
const accountId = await sdk.utils.calcAddressId('0x1234...');
```
### `sdk.utils.calcProjectId(forge, name)`
Calculates the RepoDriver account ID for a project.
**Parameters:**
* `forge: Forge` - The forge provider (currently only 'github')
* `name: ProjectName` - The project name in format 'owner/repo'
**Returns:** `Promise` - The calculated account ID
**Example:**
```typescript
const projectId = await sdk.utils.calcProjectId('github', 'owner/repo');
```
### `sdk.utils.encodeStreamConfig(config)`
Encodes a StreamConfig into bigint representation.
**Parameters:**
* `config: StreamConfig` - The stream config to encode
**Returns:** `bigint` - Packed stream config
**Example:**
```typescript
const packed = sdk.utils.encodeStreamConfig({
dripId: 123n,
amountPerSec: 1000000000n,
start: 1640995200n,
duration: 86400n,
});
```
### `sdk.utils.decodeStreamConfig(packed)`
Decodes a bigint stream config into StreamConfig object.
**Parameters:**
* `packed: bigint` - The encoded stream config
**Returns:** `StreamConfig` - Validated stream config object
**Example:**
```typescript
const config = sdk.utils.decodeStreamConfig(packed);
```
### `sdk.utils.resolveDriverName(accountId)`
Resolves driver name from account ID.
**Parameters:**
* `accountId: bigint` - The account ID
**Returns:** Driver name ("address", "nft", "immutableSplits", "repo", "repoSubAccount")
**Example:**
```typescript
const driverName = sdk.utils.resolveDriverName(123n);
```
### `sdk.utils.resolveAddressFromAddressDriverId(accountId)`
Resolves address from AddressDriver account ID.
**Parameters:**
* `accountId: bigint` - The AddressDriver account ID
**Returns:** `Address` - The resolved checksummed address
**Example:**
```typescript
const address = sdk.utils.resolveAddressFromAddressDriverId(123n);
```
### `sdk.utils.buildTx(request)`
Builds a `PreparedTx` that can be executed by a blockchain adapter.
**Parameters:**
* `request` - Transaction request with ABI, function name, arguments, and contract address
**Returns:** `PreparedTx` ready for execution
**Example:**
```typescript
const preparedTx = sdk.utils.buildTx(request);
```
### Key Data Types
#### Receivers
```typescript
type SdkReceiver =
| { type: 'project'; url: string }
| { type: 'drip-list'; accountId: bigint }
| { type: 'sub-list'; accountId: bigint }
| { type: 'address'; address: Address }
| { type: 'ecosystem-main-account'; accountId: bigint };
type SdkSplitsReceiver = SdkReceiver & { weight: number };
```
#### Stream Configuration
```typescript
type StreamConfig = {
dripId: bigint;
amountPerSec: bigint; // With AMT_PER_SEC_EXTRA_DECIMALS precision
start: bigint; // Timestamp in seconds
duration: bigint; // Duration in seconds, 0 = until funds run out
};
enum TimeUnit {
SECOND = 1,
MINUTE = 60,
HOUR = 3600,
DAY = 86400,
WEEK = 604800,
MONTH = 2592000, // 30 days
YEAR = 31536000, // 365 days
}
```
#### Constants (`sdk.constants`)
```typescript
const dripsConstants = {
MAX_SPLITS_RECEIVERS: 200,
TOTAL_SPLITS_WEIGHT: 1_000_000,
AMT_PER_SEC_MULTIPLIER: 1_000_000_000,
AMT_PER_SEC_EXTRA_DECIMALS: 9,
CYCLE_SECS: 604800, // 1 week
};
```
### Utility Functions (Standalone)
#### IPFS Integration
**`createPinataIpfsMetadataUploader(options)`**
Creates IPFS metadata uploader using Pinata.
**Parameters:** `{ pinataJwt: string, pinataGateway: string }`
**Returns:** `IpfsMetadataUploaderFn`
**Example:**
```typescript
const ipfsUploader = createPinataIpfsMetadataUploader({
pinataJwt: 'your-jwt',
pinataGateway: 'your-gateway',
});
```
#### Blockchain Adapters
**Ethers Support:**
* `createEthersReadAdapter(provider)` - Read-only operations
* `createEthersWriteAdapter(signer)` - Read/write operations
**Viem Support:**
* `createViemReadAdapter(publicClient)` - Read-only operations
* `createViemWriteAdapter(walletClient)` - Read/write operations
**Example:**
```typescript
// Ethers
const readAdapter = createEthersReadAdapter(provider);
const writeAdapter = createEthersWriteAdapter(signer);
// Viem
const readAdapter = createViemReadAdapter(publicClient);
const writeAdapter = createViemWriteAdapter(walletClient);
```
***
For more detailed TypeScript definitions and advanced usage, please refer to the [GitHub repository](https://github.com/drips-network/sdk).
## Blockchain Client Support
The SDK supports `Viem`, `Ethers v6` or `custom` blockchain clients.
### Viem Support
#### PublicClient (Read-only)
```typescript
import { createPublicClient, http } from 'viem';
import { sepolia } from 'viem/chains';
const publicClient = createPublicClient({
chain: sepolia,
transport: http(),
});
const sdk = createDripsSdk(publicClient);
// ✅ Can query data
// ❌ Cannot perform write operations
```
#### WalletClient (Read + Write)
```typescript
import { createWalletClient, http } from 'viem';
import { sepolia } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
const account = privateKeyToAccount('0x...');
const walletClient = createWalletClient({
account,
chain: sepolia,
transport: http(),
});
const ipfsUploader = createPinataIpfsMetadataUploader({
pinataJwt: 'your-pinata-jwt',
pinataGateway: 'your-pinata-gateway',
});
const sdk = createDripsSdk(walletClient, ipfsUploader);
// ✅ Can query data
// ✅ Can perform write operations
```
### Ethers v6 Support
#### Provider (Read-only)
```typescript
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider('rpc-endpoint');
const sdk = createDripsSdk(provider);
// ✅ Can query data
// ❌ Cannot perform write operations
```
#### Signer (Read + Write)
```typescript
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider('rpc-endpoint');
const signer = new ethers.Wallet('0x...', provider);
const ipfsUploader = createPinataIpfsMetadataUploader({
pinataJwt: 'your-pinata-jwt',
pinataGateway: 'your-pinata-gateway',
});
const sdk = createDripsSdk(signer, ipfsUploader);
// ✅ Can query data
// ✅ Can perform write operations
```
### Custom Adapters
For advanced use cases, you can provide custom blockchain adapters:
#### Read-only Custom Adapter
```typescript
import { ReadBlockchainAdapter } from '@drips-network/sdk';
const customReadAdapter: ReadBlockchainAdapter & { type: 'custom' } = {
type: 'custom',
call: async (request) => {
// Your custom implementation
return result;
},
// ... other required methods
};
const sdk = createDripsSdk(customReadAdapter);
```
#### Write-capable Custom Adapter
```typescript
import { WriteBlockchainAdapter } from '@drips-network/sdk';
const customWriteAdapter: WriteBlockchainAdapter & { type: 'custom' } = {
type: 'custom',
call: async (request) => {
// Your custom implementation
return result;
},
sendTransaction: async (preparedTx) => {
// Your custom transaction sending logic
return txResponse;
},
// ... other required methods
};
const sdk = createDripsSdk(customWriteAdapter, ipfsUploader);
```
## 💧Drips SDK
The **Drips SDK** is a TypeScript library for building applications that interact with the [Drips protocol](https://github.com/drips-network/contracts).
It wraps much of the [Drips App](https://www.drips.network)’s functionality to make common operations re-usable.
The Drips SDK exposes a type-safe API for Drip Lists creation and management, one-time and continuous donations, fund collection, and utility functions.
### Table of Contents
* [Installation](#installation)
* [Quick Start](#quick-start)
* [Initialization](#initialization)
* [Blockchain Client Support](#blockchain-client-support)
* [IPFS Metadata Uploader](#ipfs-metadata-uploader)
* [GraphQL Configuration](#graphql-configuration)
* [Modules](#modules)
* [Error Handling](#error-handling)
* [API Reference](#api-reference)
### Installation
::::steps
#### Install the SDK
:::code-group
```bash [npm]
npm install @drips-network/sdk
```
```bash [yarn]
yarn add @drips-network/sdk
```
```bash [pnpm]
pnpm add @drips-network/sdk
```
:::
#### Install Viem Peer Dependency
:::code-group
```bash [npm]
npm install viem
```
```bash [yarn]
yarn add viem
```
```bash [pnpm]
pnpm add viem
```
:::
#### Optionally Install Ethers v6
:::code-group
```bash [npm]
npm install ethers
```
```bash [yarn]
yarn add ethers
```
```bash [pnpm]
pnpm add ethers
```
:::
::::
:::info
The Drips SDK is built on top of Viem, making it a required peer dependency.
Support for Ethers v6 is included to accommodate applications that rely on Ethers.
:::
### Quick Start
:::steps
#### Import the SDK
```typescript
import {
createDripsSdk,
createPinataIpfsMetadataUploader,
} from '@drips-network/sdk';
import { createPublicClient, createWalletClient, http } from 'viem';
import { sepolia } from 'viem/chains';
```
#### Create a blockchain client
```typescript
// Read-only client for querying data
const publicClient = createPublicClient({
chain: sepolia,
transport: http(),
});
// Or a wallet client for write operations
const walletClient = createWalletClient({
chain: sepolia,
transport: http(),
account: '0x...', // Your wallet address
});
```
#### Initialize the SDK
```typescript
// For read-only operations
const sdk = createDripsSdk(publicClient);
// For write operations (requires metadata uploader)
const ipfsUploader = createPinataIpfsMetadataUploader({
pinataJwt: 'your-pinata-jwt',
pinataGateway: 'your-pinata-gateway',
});
const sdk = createDripsSdk(walletClient, ipfsUploader);
```
#### Start using the SDK
```typescript
// Get a Drip List
const dripList = await sdk.dripLists.getDripListById('123');
// Get withdrawable balances for the connected account
const balances = await sdk.funds.getWithdrawableBalances(11155111); // Sepolia
// Use the SDK utilities
const addressId = sdk.utils.calcAddressId('0x...');
```
:::
### Initialization
#### Function Signature
```typescript
function createDripsSdk(
blockchainClient: SupportedBlockchainClient,
ipfsMetadataUploaderFn?: IpfsMetadataUploaderFn,
options?: DripsSdkOptions
): DripsSdk;
```
#### Parameters
| Parameter | Type | Required | Description |
| ------------------------ | ---------------------------------- | -------- | ----------------------------------------- |
| `blockchainClient` | `SupportedBlockchainClient` | ✅ | Blockchain client for network interaction |
| `ipfsMetadataUploaderFn` | `IpfsMetadataUploaderFn` | ⚠️ | Required for write operations only |
| `options` | `DripsSdkOptions` | ❌ | Optional configuration |
##### Options Configuration
The `options` parameter allows you to customize the SDK's behavior:
```typescript
type DripsSdkOptions = {
readonly graphql?: {
readonly url?: string;
readonly apiKey?: string;
};
};
```
| Option | Type | Default | Description |
| ------------------------ | -------- | ------------------------------- | ------------------------------------------ |
| `options.graphql.url` | `string` | Drips production API deployment | Custom GraphQL endpoint URL |
| `options.graphql.apiKey` | `string` | `undefined` | API key for authenticated GraphQL requests |
**Example Usage:**
```typescript
const sdk = createDripsSdk(blockchainClient, ipfsUploader, {
graphql: {
url: 'https://custom-drips-api.com/graphql',
apiKey: 'your-api-key',
},
});
```
For detailed GraphQL configuration examples, see the [GraphQL Configuration](#graphql-configuration) section.
:::warning
All **write operations** require:
* A blockchain client with signing capability:
* **Viem**: `WalletClient` with a connected `account`
* **Ethers**: `Signer` with a connected `provider`
* Connection to the target **blockchain network**
* An **IPFS metadata uploader**
:::
### Blockchain Client Support
The SDK supports `Viem`, `Ethers v6` or `custom` blockchain clients.
#### Viem Support
##### PublicClient (Read-only)
```typescript
import { createPublicClient, http } from 'viem';
import { sepolia } from 'viem/chains';
const publicClient = createPublicClient({
chain: sepolia,
transport: http(),
});
const sdk = createDripsSdk(publicClient);
// ✅ Can query data
// ❌ Cannot perform write operations
```
##### WalletClient (Read + Write)
```typescript
import { createWalletClient, http } from 'viem';
import { sepolia } from 'viem/chains';
import { privateKeyToAccount } from 'viem/accounts';
const account = privateKeyToAccount('0x...');
const walletClient = createWalletClient({
account,
chain: sepolia,
transport: http(),
});
const ipfsUploader = createPinataIpfsMetadataUploader({
pinataJwt: 'your-pinata-jwt',
pinataGateway: 'your-pinata-gateway',
});
const sdk = createDripsSdk(walletClient, ipfsUploader);
// ✅ Can query data
// ✅ Can perform write operations
```
#### Ethers v6 Support
##### Provider (Read-only)
```typescript
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider('rpc-endpoint');
const sdk = createDripsSdk(provider);
// ✅ Can query data
// ❌ Cannot perform write operations
```
##### Signer (Read + Write)
```typescript
import { ethers } from 'ethers';
const provider = new ethers.JsonRpcProvider('rpc-endpoint');
const signer = new ethers.Wallet('0x...', provider);
const ipfsUploader = createPinataIpfsMetadataUploader({
pinataJwt: 'your-pinata-jwt',
pinataGateway: 'your-pinata-gateway',
});
const sdk = createDripsSdk(signer, ipfsUploader);
// ✅ Can query data
// ✅ Can perform write operations
```
#### Custom Adapters
For advanced use cases, you can provide custom blockchain adapters:
##### Read-only Custom Adapter
```typescript
import { ReadBlockchainAdapter } from '@drips-network/sdk';
const customReadAdapter: ReadBlockchainAdapter & { type: 'custom' } = {
type: 'custom',
call: async (request) => {
// Your custom implementation
return result;
},
// ... other required methods
};
const sdk = createDripsSdk(customReadAdapter);
```
##### Write-capable Custom Adapter
```typescript
import { WriteBlockchainAdapter } from '@drips-network/sdk';
const customWriteAdapter: WriteBlockchainAdapter & { type: 'custom' } = {
type: 'custom',
call: async (request) => {
// Your custom implementation
return result;
},
sendTransaction: async (preparedTx) => {
// Your custom transaction sending logic
return txResponse;
},
// ... other required methods
};
const sdk = createDripsSdk(customWriteAdapter, ipfsUploader);
```
### IPFS Metadata Uploader
The IPFS metadata uploader is required for "write" SDK instances.
#### Built-in Pinata Support
The SDK provides out-of-the-box support for [Pinata](https://docs.pinata.cloud/quickstart):
```typescript
import { createPinataIpfsMetadataUploader } from '@drips-network/sdk';
const ipfsUploader = createPinataIpfsMetadataUploader({
pinataJwt: 'your-pinata-jwt-token',
pinataGateway: 'your-pinata-gateway-url',
});
```
#### Custom IPFS Uploader
You can implement your own IPFS uploader for different services:
```typescript
import { IpfsMetadataUploaderFn, Metadata } from '@drips-network/sdk';
const customIpfsUploader: IpfsMetadataUploaderFn = async (
metadata
) => {
// Upload metadata to your preferred IPFS service
const result = await yourIpfsService.upload(metadata);
return result.cid; // Return the IPFS CID as Hash
};
const sdk = createDripsSdk(walletClient, customIpfsUploader);
```
### GraphQL Configuration
The SDK connects to Drips' GraphQL Multi-Chain API for querying blockchain data and metadata.
#### Default Configuration
```typescript
const sdk = createDripsSdk(blockchainClient);
// Uses the Drips production API deployment.
```
#### Custom GraphQL Endpoint
```typescript
const sdk = createDripsSdk(blockchainClient, ipfsUploader, {
graphql: {
url: 'https://your-custom-drips-api.com/graphql',
},
});
```
#### Authenticated Requests
```typescript
const sdk = createDripsSdk(blockchainClient, ipfsUploader, {
graphql: {
url: 'https://your-custom-drips-api.com/graphql',
apiKey: 'your-api-key',
},
});
```
### Modules
The SDK is organized into modules, each handling specific aspects of Drips.
#### `dripLists` Module
Manages Drip Lists.
**Features:**
* Create and update Drip Lists
* Query Drip List metadata and configuration
* Calculate Drip List IDs
#### `donations` Module
Handles one-time and continuous donations.
**Features:**
* One-time donations with immediate transfers
* Continuous donations with streaming functionality
* Support for various recipient types
#### `funds` Module
Manages fund collection and withdrawal operations.
**Features:**
* Query withdrawable balances
* Prepare and execute fund collection
* Multi-token support
#### `utils` Module
Provides utility functions for common calculations and operations.
**Features:**
* ID calculations for various entity types
* Stream configuration encoding/decoding
* Transaction building helpers
* Address resolution utilities
### Error Handling
The SDK throws `DripsError` instances for all SDK-related failures. These errors extend the standard JavaScript `Error` class with additional context information.
#### DripsError Properties
| Property | Type | Description |
| --------- | ---------------- | --------------------------------------------------------------- |
| `message` | `string` | Error message prefixed with `[Drips SDK]` |
| `name` | `string` | Always `"DripsError"` |
| `cause` | `unknown` | The underlying error that caused this failure (if any) |
| `meta` | `DripsErrorMeta` | Additional context about the error, including operation details |
#### Example Usage
```typescript
import { DripsError } from '@drips-network/sdk';
try {
const result = await sdk.dripLists.getDripListById('123');
} catch (error) {
if (error instanceof DripsError) {
console.error('SDK Error:', error.message);
console.error('Operation:', error.meta?.operation);
console.error('Context:', error.meta);
console.error('Underlying cause:', error.cause);
}
}
```
### API Reference
#### `createDripsSdk(blockchainClient, ipfsMetadataUploaderFn?, options?)`
Creates the main SDK instance with access to all modules.
**Parameters:**
* `blockchainClient`: Viem `PublicClient`/`WalletClient`, Ethers `Provider`/`Signer`, or custom adapter
* `ipfsMetadataUploaderFn?`: Optional IPFS metadata uploader function (required for write operations)
* `options?`: Configuration object
**Returns:** `DripsSdk` instance with modules: `dripLists`, `donations`, `utils`, `funds`, `constants`
**Example:**
```typescript
const sdk = createDripsSdk(walletClient, ipfsUploader);
```
#### `sdk.dripLists.calculateId(salt, minter)`
Calculates the Drip List ID for given minter and salt.
**Parameters:**
* `salt: bigint` - Salt value for uniqueness
* `minter: Address` - Address that would mint the Drip List
**Returns:** `Promise` - The Drip List ID
**Example:**
```typescript
const id = await sdk.dripLists.calculateId(123n, '0x1234...');
```
#### `sdk.dripLists.getById(id, chainId)`
Fetches a DripList by ID and chain ID.
**Parameters:**
* `id: bigint` - The Drip List ID
* `chainId: number` - The chain ID
**Returns:** `Promise` - The DripList or null if not found
```typescript
type DripList = {
id: bigint;
name?: string;
description?: string;
minter: Address;
owner: Address;
splits: {
list: SdkSplitsReceiver[];
hash: Hash;
};
latestVotingRound?: VotingRound;
support: SupportData;
chainId: number;
};
```
**Example:**
```typescript
const dripList = await sdk.dripLists.getById(123n, 11155111);
```
#### `sdk.dripLists.prepareCreate(dripList)`
Prepares context for creating a new Drip List.
**Parameters:**
* `dripList: NewDripList`
```typescript
type NewDripList = {
isVisible: boolean;
receivers: ReadonlyArray; // Must sum to 1,000,000 weight
salt?: bigint; // Optional, random if not provided
name?: string;
description?: string | null;
transferTo?: Address; // Transfer ownership, defaults to minter
batchedTxOverrides?: BatchedTxOverrides;
latestVotingRoundId?: string;
};
```
**Returns:** `Promise`
```typescript
type PrepareDripListCreationResult = {
newDripListId: bigint;
newDripListName?: string;
newDripListDescription?: string;
preparedTxs: PreparedTx[];
metadataIpfsHash?: Hash;
};
```
**Example:**
```typescript
const result = await sdk.dripLists.prepareCreate({
isVisible: true,
name: 'My Support List',
description: 'Supporting awesome projects',
receivers: [
{ type: 'project', url: 'https://github.com/owner/repo1', weight: 500000 },
{ type: 'address', address: '0x1234...', weight: 500000 },
],
});
```
#### `sdk.dripLists.create(dripList)`
Creates a new Drip List.
**Parameters:**
* `dripList: NewDripList`
**Returns:** `Promise`
```typescript
type CreateDripListResult = {
newDripListId: bigint;
newDripListName?: string;
newDripListDescription?: string;
metadataIpfsHash?: Hash;
txResponses: TxResponse[];
};
```
**Example:**
```typescript
const result = await sdk.dripLists.create({
isVisible: true,
name: 'Developer Fund',
receivers: [
{ type: 'project', url: 'https://github.com/owner/repo', weight: 1000000 },
],
});
```
#### `sdk.dripLists.prepareUpdate(config)`
Prepares context for updating a Drip List.
**Parameters:**
* `config: DripListUpdateConfig`
**Returns:** `Promise`
```typescript
type PrepareDripListUpdateResult = {
preparedTxs: PreparedTx[];
metadataIpfsHash?: Hash;
};
```
**Example:**
```typescript
const result = await sdk.dripLists.prepareUpdate({
dripListId: 123n,
chainId: 11155111,
name: 'Updated Name',
});
```
#### `sdk.dripLists.update(config)`
Updates a Drip List.
**Parameters:**
* `config: DripListUpdateConfig`
**Returns:** `Promise`
```typescript
type UpdateDripListResult = {
txResponses: TxResponse[];
metadataIpfsHash?: Hash;
};
```
**Example:**
```typescript
const result = await sdk.dripLists.update({
dripListId: 123n,
chainId: 11155111,
receivers: [{ type: 'address', address: '0x5678...', weight: 1000000 }],
});
```
#### `sdk.donations.prepareOneTime(donation)`
Prepares a transaction for making a one-time donation.
**Parameters:**
* `donation: OneTimeDonation`
```typescript
type OneTimeDonation = {
receiver: SdkReceiver;
amount: string; // Human-readable format (e.g., "10.5")
erc20: Address;
tokenDecimals: number;
batchedTxOverrides?: BatchedTxOverrides;
};
```
**Returns:** `Promise` - Ready for execution
**Example:**
```typescript
const preparedTx = await sdk.donations.prepareOneTime({
receiver: { type: 'address', address: '0x1234...' },
amount: '10.5',
erc20: '0xTokenAddress...',
tokenDecimals: 18,
});
```
#### `sdk.donations.sendOneTime(donation)`
Sends a one-time donation.
**Parameters:**
* `donation: OneTimeDonation`
**Returns:** `Promise` - Transaction response from blockchain
**Example:**
```typescript
const txResponse = await sdk.donations.sendOneTime({
receiver: { type: 'drip-list', accountId: 123n },
amount: '50.0',
erc20: '0xTokenAddress...',
tokenDecimals: 18,
});
```
#### `sdk.donations.prepareContinuous(donation)`
Prepares context for a continuous donation stream.
**Parameters:**
* `donation: ContinuousDonation`
```typescript
type ContinuousDonation = {
erc20: Address;
amount: string; // Amount to stream per timeUnit
timeUnit: TimeUnit; // DAY, WEEK, MONTH, etc.
tokenDecimals: number;
receiver: SdkReceiver;
name?: string;
startAt?: Date; // Defaults to "now"
durationSeconds?: bigint; // If omitted, runs until funds run out
topUpAmount?: string; // Amount to add when setting stream
batchedTxOverrides?: BatchedTxOverrides;
};
```
**Returns:** `Promise`
```typescript
type PrepareContinuousDonationResult = {
preparedTxs: PreparedTx[];
metadataIpfsHash?: Hash;
streamConfig: StreamConfig;
};
```
**Example:**
```typescript
const result = await sdk.donations.prepareContinuous({
receiver: { type: 'project', url: 'https://github.com/owner/repo' },
amount: '100',
timeUnit: TimeUnit.MONTH,
erc20: '0xTokenAddress...',
tokenDecimals: 18,
name: 'Monthly Support',
topUpAmount: '1200', // 12 months worth
});
```
#### `sdk.donations.sendContinuous(donation)`
Sends a continuous donation by preparing and executing transaction.
**Parameters:**
* `donation: ContinuousDonation`
**Returns:** `Promise`
```typescript
type SendContinuousDonationResult = {
txResponses: TxResponse[];
metadataIpfsHash?: Hash;
streamConfig: StreamConfig;
};
```
**Example:**
```typescript
const result = await sdk.donations.sendContinuous({
receiver: { type: 'address', address: '0x1234...' },
amount: '10',
timeUnit: TimeUnit.DAY,
erc20: '0xTokenAddress...',
tokenDecimals: 18,
durationSeconds: 86400n * 30n, // 30 days
});
```
#### `sdk.funds.getWithdrawableBalances(chainId)`
Fetches withdrawable balances for connected user on specific chain.
**Parameters:**
* `chainId: number` - The chain ID for target network
**Returns:** `Promise`
```typescript
type UserWithdrawableBalances = {
accountId: bigint;
balances: TokenWithdrawableBalance[];
};
type TokenWithdrawableBalance = {
tokenAddress: Address;
withdrawableAmount: bigint;
receivableAmount: bigint;
splittableAmount: bigint;
collectedAmount: bigint;
};
```
**Example:**
```typescript
const balances = await sdk.funds.getWithdrawableBalances(11155111);
```
#### `sdk.funds.prepareCollection(config)`
Prepares transaction for collecting funds from streams and splits.
**Parameters:**
* `config: CollectConfig`
```typescript
type CollectConfig = {
accountId: bigint;
currentReceivers: SdkSplitsReceiver[];
tokenAddresses: ReadonlyArray;
batchedTxOverrides?: BatchedTxOverrides;
shouldSkipSplit?: boolean;
shouldAutoUnwrap?: boolean; // Unwrap wrapped native tokens
shouldSkipReceive?: boolean;
squeezeArgs?: SqueezeArgs[]; // For squeezing streams from specific senders
transferToAddress?: Address; // Transfer to address, defaults to signer
};
```
**Returns:** `Promise` - Ready for execution
**Example:**
```typescript
const preparedTx = await sdk.funds.prepareCollection({
accountId: 123n,
currentReceivers: [],
tokenAddresses: ['0xTokenAddress...'],
shouldAutoUnwrap: true,
});
```
#### `sdk.funds.collect(config)`
Collects funds for an account.
**Parameters:**
* `config: CollectConfig`
**Returns:** `Promise` - Transaction response from blockchain
**Example:**
```typescript
const txResponse = await sdk.funds.collect({
accountId: 123n,
currentReceivers: [],
tokenAddresses: ['0xTokenAddress...'],
});
```
#### `sdk.utils.calcAddressId(address)`
Calculates the AddressDriver account ID for a given address.
**Parameters:**
* `address: Address` - The address to compute account ID for
**Returns:** `Promise` - The calculated account ID
**Example:**
```typescript
const accountId = await sdk.utils.calcAddressId('0x1234...');
```
#### `sdk.utils.calcProjectId(forge, name)`
Calculates the RepoDriver account ID for a project.
**Parameters:**
* `forge: Forge` - The forge provider (currently only 'github')
* `name: ProjectName` - The project name in format 'owner/repo'
**Returns:** `Promise` - The calculated account ID
**Example:**
```typescript
const projectId = await sdk.utils.calcProjectId('github', 'owner/repo');
```
#### `sdk.utils.encodeStreamConfig(config)`
Encodes a StreamConfig into bigint representation.
**Parameters:**
* `config: StreamConfig` - The stream config to encode
**Returns:** `bigint` - Packed stream config
**Example:**
```typescript
const packed = sdk.utils.encodeStreamConfig({
dripId: 123n,
amountPerSec: 1000000000n,
start: 1640995200n,
duration: 86400n,
});
```
#### `sdk.utils.decodeStreamConfig(packed)`
Decodes a bigint stream config into StreamConfig object.
**Parameters:**
* `packed: bigint` - The encoded stream config
**Returns:** `StreamConfig` - Validated stream config object
**Example:**
```typescript
const config = sdk.utils.decodeStreamConfig(packed);
```
#### `sdk.utils.resolveDriverName(accountId)`
Resolves driver name from account ID.
**Parameters:**
* `accountId: bigint` - The account ID
**Returns:** Driver name ("address", "nft", "immutableSplits", "repo", "repoSubAccount")
**Example:**
```typescript
const driverName = sdk.utils.resolveDriverName(123n);
```
#### `sdk.utils.resolveAddressFromAddressDriverId(accountId)`
Resolves address from AddressDriver account ID.
**Parameters:**
* `accountId: bigint` - The AddressDriver account ID
**Returns:** `Address` - The resolved checksummed address
**Example:**
```typescript
const address = sdk.utils.resolveAddressFromAddressDriverId(123n);
```
#### `sdk.utils.buildTx(request)`
Builds a `PreparedTx` that can be executed by a blockchain adapter.
**Parameters:**
* `request` - Transaction request with ABI, function name, arguments, and contract address
**Returns:** `PreparedTx` ready for execution
**Example:**
```typescript
const preparedTx = sdk.utils.buildTx(request);
```
### Key Data Types
#### Receivers
```typescript
type SdkReceiver =
| { type: 'project'; url: string }
| { type: 'drip-list'; accountId: bigint }
| { type: 'sub-list'; accountId: bigint }
| { type: 'address'; address: Address }
| { type: 'ecosystem-main-account'; accountId: bigint };
type SdkSplitsReceiver = SdkReceiver & { weight: number };
```
#### Stream Configuration
```typescript
type StreamConfig = {
dripId: bigint;
amountPerSec: bigint; // With AMT_PER_SEC_EXTRA_DECIMALS precision
start: bigint; // Timestamp in seconds
duration: bigint; // Duration in seconds, 0 = until funds run out
};
enum TimeUnit {
SECOND = 1,
MINUTE = 60,
HOUR = 3600,
DAY = 86400,
WEEK = 604800,
MONTH = 2592000, // 30 days
YEAR = 31536000, // 365 days
}
```
#### Constants (`sdk.constants`)
```typescript
const dripsConstants = {
MAX_SPLITS_RECEIVERS: 200,
TOTAL_SPLITS_WEIGHT: 1_000_000,
AMT_PER_SEC_MULTIPLIER: 1_000_000_000,
AMT_PER_SEC_EXTRA_DECIMALS: 9,
CYCLE_SECS: 604800, // 1 week
};
```
### Utility Functions (Standalone)
#### IPFS Integration
**`createPinataIpfsMetadataUploader(options)`**
Creates IPFS metadata uploader using Pinata.
**Parameters:** `{ pinataJwt: string, pinataGateway: string }`
**Returns:** `IpfsMetadataUploaderFn`
**Example:**
```typescript
const ipfsUploader = createPinataIpfsMetadataUploader({
pinataJwt: 'your-jwt',
pinataGateway: 'your-gateway',
});
```
#### Blockchain Adapters
**Ethers Support:**
* `createEthersReadAdapter(provider)` - Read-only operations
* `createEthersWriteAdapter(signer)` - Read/write operations
**Viem Support:**
* `createViemReadAdapter(publicClient)` - Read-only operations
* `createViemWriteAdapter(walletClient)` - Read/write operations
**Example:**
```typescript
// Ethers
const readAdapter = createEthersReadAdapter(provider);
const writeAdapter = createEthersWriteAdapter(signer);
// Viem
const readAdapter = createViemReadAdapter(publicClient);
const writeAdapter = createViemWriteAdapter(walletClient);
```
***
For more detailed TypeScript definitions and advanced usage, please refer to the [GitHub repository](https://github.com/drips-network/sdk).
## Error Handling
The SDK throws `DripsError` instances for all SDK-related failures. These errors extend the standard JavaScript `Error` class with additional context information.
### DripsError Properties
| Property | Type | Description |
| --------- | ---------------- | --------------------------------------------------------------- |
| `message` | `string` | Error message prefixed with `[Drips SDK]` |
| `name` | `string` | Always `"DripsError"` |
| `cause` | `unknown` | The underlying error that caused this failure (if any) |
| `meta` | `DripsErrorMeta` | Additional context about the error, including operation details |
### Example Usage
```typescript
import { DripsError } from '@drips-network/sdk';
try {
const result = await sdk.dripLists.getDripListById('123');
} catch (error) {
if (error instanceof DripsError) {
console.error('SDK Error:', error.message);
console.error('Operation:', error.meta?.operation);
console.error('Context:', error.meta);
console.error('Underlying cause:', error.cause);
}
}
```
## GraphQL Configuration
The SDK connects to Drips' GraphQL Multi-Chain API for querying blockchain data and metadata.
### Default Configuration
```typescript
const sdk = createDripsSdk(blockchainClient);
// Uses the Drips production API deployment.
```
### Custom GraphQL Endpoint
```typescript
const sdk = createDripsSdk(blockchainClient, ipfsUploader, {
graphql: {
url: 'https://your-custom-drips-api.com/graphql',
},
});
```
### Authenticated Requests
```typescript
const sdk = createDripsSdk(blockchainClient, ipfsUploader, {
graphql: {
url: 'https://your-custom-drips-api.com/graphql',
apiKey: 'your-api-key',
},
});
```
## Initialization
### Function Signature
```typescript
function createDripsSdk(
blockchainClient: SupportedBlockchainClient,
ipfsMetadataUploaderFn?: IpfsMetadataUploaderFn,
options?: DripsSdkOptions
): DripsSdk;
```
### Parameters
| Parameter | Type | Required | Description |
| ------------------------ | ---------------------------------- | -------- | ----------------------------------------- |
| `blockchainClient` | `SupportedBlockchainClient` | ✅ | Blockchain client for network interaction |
| `ipfsMetadataUploaderFn` | `IpfsMetadataUploaderFn` | ⚠️ | Required for write operations only |
| `options` | `DripsSdkOptions` | ❌ | Optional configuration |
#### Options Configuration
The `options` parameter allows you to customize the SDK's behavior:
```typescript
type DripsSdkOptions = {
readonly graphql?: {
readonly url?: string;
readonly apiKey?: string;
};
};
```
| Option | Type | Default | Description |
| ------------------------ | -------- | ------------------------------- | ------------------------------------------ |
| `options.graphql.url` | `string` | Drips production API deployment | Custom GraphQL endpoint URL |
| `options.graphql.apiKey` | `string` | `undefined` | API key for authenticated GraphQL requests |
**Example Usage:**
```typescript
const sdk = createDripsSdk(blockchainClient, ipfsUploader, {
graphql: {
url: 'https://custom-drips-api.com/graphql',
apiKey: 'your-api-key',
},
});
```
For detailed GraphQL configuration examples, see the [GraphQL Configuration](/advanced/drips-sdk/graphql-configuration) section.
:::warning
All **write operations** require:
* A blockchain client with signing capability:
* **Viem**: `WalletClient` with a connected `account`
* **Ethers**: `Signer` with a connected `provider`
* Connection to the target **blockchain network**
* An **IPFS metadata uploader**
:::
## Installation
::::steps
#### Install the SDK
:::code-group
```bash [npm]
npm install @drips-network/sdk
```
```bash [yarn]
yarn add @drips-network/sdk
```
```bash [pnpm]
pnpm add @drips-network/sdk
```
:::
#### Install Viem Peer Dependency
:::code-group
```bash [npm]
npm install viem
```
```bash [yarn]
yarn add viem
```
```bash [pnpm]
pnpm add viem
```
:::
#### Optionally Install Ethers v6
:::code-group
```bash [npm]
npm install ethers
```
```bash [yarn]
yarn add ethers
```
```bash [pnpm]
pnpm add ethers
```
:::
::::
:::info
The Drips SDK is built on top of Viem, making it a required peer dependency.
Support for Ethers v6 is included to accommodate applications that rely on Ethers.
:::
## IPFS Metadata Uploader
The IPFS metadata uploader is required for "write" SDK instances.
### Built-in Pinata Support
The SDK provides out-of-the-box support for [Pinata](https://docs.pinata.cloud/quickstart):
```typescript
import { createPinataIpfsMetadataUploader } from '@drips-network/sdk';
const ipfsUploader = createPinataIpfsMetadataUploader({
pinataJwt: 'your-pinata-jwt-token',
pinataGateway: 'your-pinata-gateway-url',
});
```
### Custom IPFS Uploader
You can implement your own IPFS uploader for different services:
```typescript
import { IpfsMetadataUploaderFn, Metadata } from '@drips-network/sdk';
const customIpfsUploader: IpfsMetadataUploaderFn = async (
metadata
) => {
// Upload metadata to your preferred IPFS service
const result = await yourIpfsService.upload(metadata);
return result.cid; // Return the IPFS CID as Hash
};
const sdk = createDripsSdk(walletClient, customIpfsUploader);
```
## Modules
The SDK is organized into modules, each handling specific aspects of Drips.
### `dripLists` Module
Manages Drip Lists.
**Features:**
* Create and update Drip Lists
* Query Drip List metadata and configuration
* Calculate Drip List IDs
### `donations` Module
Handles one-time and continuous donations.
**Features:**
* One-time donations with immediate transfers
* Continuous donations with streaming functionality
* Support for various recipient types
### `funds` Module
Manages fund collection and withdrawal operations.
**Features:**
* Query withdrawable balances
* Prepare and execute fund collection
* Multi-token support
### `utils` Module
Provides utility functions for common calculations and operations.
**Features:**
* ID calculations for various entity types
* Stream configuration encoding/decoding
* Transaction building helpers
* Address resolution utilities
## 💧Drips SDK
The **Drips SDK** is a TypeScript library for building applications that interact with the [Drips protocol](https://github.com/drips-network/contracts).
It wraps much of the [Drips App](https://www.drips.network)'s functionality to make common operations re-usable.
The Drips SDK exposes a type-safe API for Drip Lists creation and management, one-time and continuous donations, fund collection, and utility functions.
### Table of Contents
* [Installation](/advanced/drips-sdk/installation)
* [Quick Start](/advanced/drips-sdk/quick-start)
* [Initialization](/advanced/drips-sdk/initialization)
* [Blockchain Client Support](/advanced/drips-sdk/blockchain-client-support)
* [IPFS Metadata Uploader](/advanced/drips-sdk/ipfs-metadata-uploader)
* [GraphQL Configuration](/advanced/drips-sdk/graphql-configuration)
* [Modules](/advanced/drips-sdk/modules)
* [Error Handling](/advanced/drips-sdk/error-handling)
* [API Reference](/advanced/drips-sdk/api-reference)
## Quick Start
:::steps
#### Import the SDK
```typescript
import {
createDripsSdk,
createPinataIpfsMetadataUploader,
} from '@drips-network/sdk';
import { createPublicClient, createWalletClient, http } from 'viem';
import { sepolia } from 'viem/chains';
```
#### Create a blockchain client
```typescript
// Read-only client for querying data
const publicClient = createPublicClient({
chain: sepolia,
transport: http(),
});
// Or a wallet client for write operations
const walletClient = createWalletClient({
chain: sepolia,
transport: http(),
account: '0x...', // Your wallet address
});
```
#### Initialize the SDK
```typescript
// For read-only operations
const sdk = createDripsSdk(publicClient);
// For write operations (requires metadata uploader)
const ipfsUploader = createPinataIpfsMetadataUploader({
pinataJwt: 'your-pinata-jwt',
pinataGateway: 'your-pinata-gateway',
});
const sdk = createDripsSdk(walletClient, ipfsUploader);
```
#### Start using the SDK
```typescript
// Get a Drip List
const dripList = await sdk.dripLists.getDripListById('123');
// Get withdrawable balances for the connected account
const balances = await sdk.funds.getWithdrawableBalances(11155111); // Sepolia
// Use the SDK utilities
const addressId = sdk.utils.calcAddressId('0x...');
```
:::
## Creating a collaborative Drip List
import Figure from '../../../components/Figure'
import Video from '../../../components/Video'
With a Collaborative Drip Lists, a group of people can come together and vote on the recipients of a Drip List before it gets deployed on-chain. This makes it easy to settle on a list of dependencies with distributed teams, and even allows running larger Public Goods Funding programs on Drips.
### Creating a collaborative Drip List
To create a new collaborative Drip List, simply click **Drip Lists → Create Drip List**, and select "Collaborate on recipients". You'll be asked to provide a list of *collaborators*, which can be any selection of ETH addresses, and when the voting should end. Additionally, you can choose to hide the list of collaborators and their votes from the public.
Once created, a collaborative Drip List lives off-chain until *published* after voting is concluded. During the voting period, *collaborators* may submit their votes.
:::info
Currently, voting is only possible for creating an entirely new Drip List. Soon, it'll also be possible to vote on a new set of receivers for an existing Drip List.
:::
#### Publisher
The address that creates the collaborative Drip List is considered its *publisher*. As the publisher, you may delete the list after creating it, and *publish* it on-chain after voting is concluded. Once published, the list is on-chain, and can be funded with support streams or one-time donations by anyone.
:::info
Collaborative Drip Lists may be shared with their URL, and appear on the publisher's public profile the same way a non-collaborative Drip List does.
:::
#### Collaborators
You can configure up to five thousand individual Ethereum addresses to act as "collaborators" for your collaborative Drip List.
Each collaborator will be able to submit a vote within the voting period you configured. Once the collaborative Drip List has been submitted, a "Submit your vote" button will appear on the Drip List's page if the app is connected to a collaborator's wallet.
While voting, collaborators are asked to submit what they think the recipients of the list should be. All votes are tallied continously, and a preview of what the list would look like if voting were to end right now is displayed (unless you chose to "hide collaborators").
##### Hide collaborators
If you enable "Hide collaborators" during the Drip List creation flow, only you as the publisher may reveal the list of collaborators and their individual votes. Anyone else will only be able to submit and see their own vote. In this mode, votes are only tallied at the end of the voting period, and only the final results are automatically publicized. No-one but the publisher may see who voted for what, though it is possible to check whether a particular address has submitted a vote or not. Please note that while our systems don't expose the full list of collaborators and their votes to the public in this mode, both are stored off-chain in a private database operated by the Public Goods Association.
#### Restricting possible recipients
By default, collaborators can add any possible recipient and assign percentages during the voting process. You can optionally choose to specify up to two hundred Ethereum addresses, open-source GitHub repositories, or other Drip Lists that the possible recipients of the vote will be restricted to.
In this mode, collaborators can only assign percentages to the specified possible recipients, and not add any others. Collaborators can choose to assign 0% to one or more of the possible recipients, as long as the total percentage allocation of their vote adds up to 100%.
### How votes are tallied
Every collaborator is asked to vote for what they think the final Drip List should look like — specifically, what Projects, ETH addresses, or other Drip Lists should receive what percentage of funds. The final result of the voting round is simply an average percentage of a particular recipient across all votes, capped to the top 200 recipients by overall percentage.
## Voting on a list
import Video from '../../../components/Video'
Voting on a collaborative Drip List is quick, easy, and entirely gasless. Here's a step-by-step guide on how to submit your vote.
### Step-by-step
1. **Navigate to the Drip List in voting.** Either follow a link directly to the Drip List in voting, or open the Drips App and find its creator's profile by using the Search button in the top-right corner.
2. **Connect your wallet.** Click "Connect" in the top-right corner and choose the wallet address that you are eligible to vote with.
3. **Click "Cast your vote".** If the currently-connected wallet address is a collaborator for the given list, the button will appear in the "Collaborators" section automatically.
4. **Assign percentages.** If the list has a set of pre-configured recipients, assign percentages to them according to your will. If there are no pre-configured recipients, first add any set of open-source GitHub repositories, other Drip Lists, or Ethereum addresses. Your percentages must add up to 100% before you can proceed.
5. **Submit your vote.** Once your percentages are assigned, click Submit, and confirm the vote signature in your wallet. Voting is off-chain, and entirely gasless. You'll be able to change your vote at any point within the voting period.
6. **Wait for the results.** If the Drip List in question is configured to hide collaborators, you'll be able to see the final results at the end of the voting period. If its collaborators are public, you can immediately see a running tally of what the list would look like if voting were to end right now.
#### Voting with a Safe Multisig
If your collaborator address is managed by a Safe Multisig, follow the instructions in [Usage with a Safe](/advanced/usage-with-a-safe) to launch Drips within the Safe\{Wallet} web app. You'll be able to vote by following the same steps outlined above, except your vote signature will require reaching quorum on your Safe.
### How votes are tallied
Every collaborator is asked to vote for what they think the final Drip List should look like — specifically, what Projects, ETH addresses, or other Drip Lists should receive what percentage of funds. The final result of the voting round is simply an average percentage of a particular recipient across all votes, capped to the top 200 recipients by overall percentage.
## Account metadata
The Drips Protocol includes functionality allowing general metadata information to be associated with user accounts. Like setting streams or Splits, the authority to add such metadata ultimately rests with the end-user who controls the account. However apps building on Drips can build on this capability to store any kind of metadata they wish (with their end-users' permission).
Knowing that we can't predict every metadata-related use case that developers may be interested in, we've designed metadata in Drips to be extremely flexible, but some examples of use cases that we envision are:
* Being able to mark a Drips NFT-based account as "associated" with a specific web3 application.
* Being able to attach "user profile"-related information to a Drips account (e.g. name, link to a profile image in IPFS, etc.)
* Being able to attach descriptive information to streams and Splits that are configured for the account (perhaps stored as an array or map).
#### Adding Account Metadata
Associating metadata with an account is simple. After creating an NFTDriverClient, your app can call `NFTDriverClient.emitAccountMetadata`:
```
emitAccountMetadata(
tokenId: string,
accountMetadata: AccountMetadataStruct[])
: Promise
```
As you can see, calling this method emits an EVM event that contains an array of `AccountMetadataStruct`s, which can be populated by the app developer however they wish:
```
AccountMetadataStruct: {
key: PromiseOrValue;
value: PromiseOrValue
}
```
## Batching with Caller
`Caller` adds a number of powerful functionalities to Drips related to batching of function calls and delegation of access control.
Specifically, it is also a smart contract included with the Drips V2 deployment that builds on EIP-712 and EIP-2771.
Some examples of use cases that `Caller` enables include:
##### *Batching sequences of calls to a contract.*
The contract API may consist of many functions which need to be called in sequence, but it may not offer a composite function performing exactly that sequence. It's expensive, slow and unreliable to create a separate transaction for each step.
To solve that problem create a batch of calls and submit it to `callBatched`.
##### *Batching sequences of calls to multiple contracts.*
It's a common pattern to submit an ERC-2612 permit to approve a smart contract
to spend the user's ERC-20 tokens before running that contract's logic.
Unfortunately unless the contract's API accepts signed messages for the token it requires
creating two separate transactions making it as inconvenient as a regular approval.
The solution is again to use `callBatched` because it can call multiple contracts. Just create a batch first calling the ERC-20 contract and then the contract needing the tokens.
##### *Setting up a proxy address.*
Sometimes a secure but inconvenient-to-use address like a cold wallet or a multisig needs to have a proxy or an operator. That operator is temporarily trusted, but later it must be revoked or rotated. To achieve this, first `authorize` the proxy using the safe address and then use that proxy to act on behalf of the secure address using `callAs`. Later, when the proxy address needs to be revoked, either the secure address or the proxy itself can `unauthorize` the proxy address and perhaps `authorize` another address.
##### *Setting up operations callable by others.*
Some operations may benefit from being callable either by trusted addresses or by anybody. To achieve this deploy a smart contract executing these operations via `callAs` and, if you need that too, implementing a custom authorization. Finally, `authorize` this smart contract to act on behalf of your address.
##### *Batching dynamic sequences of calls.*
Some operations need to react dynamically to the state of the blockchain. For example an unknown amount of funds is retrieved from a smart contract, which then needs to be dynamically split and used for different purposes. To do this, first deploy a smart contract performing that logic. Next, call `callBatched` which first calls `authorize` on the `Caller` itself authorizing the new contract to perform `callAs`, then calls that contract and finally `unauthorize`'s it. This way the contract can perform any logic it needs on behalf of your address, but only once.
##### *Gasless transactions.*
It's an increasingly common pattern to use smart contracts without necessarily spending Ether. This is achieved with gasless transactions where the wallet signs an ERC-712 message and somebody else submits the actual transaction executing what the message requests. It may be executed by another wallet or by an operator expecting to be repaid for the spent Ether in other assets. You can achieve this with `callSigned`, which allows anybody to execute a call on behalf of the signer of a message. `Caller` doesn't deal with gas, so if you're using a gasless network, it may require you to specify the gas needed for the entire call execution.
##### *Executing batched calls with authorization or signature.*
You can use both `callAs` and `callSigned` to call `Caller` itself, which in turn can execute batched calls on behalf of the authorizing or signing address. It also applies to `authorize` and `unauthorize`, they too can be called using `callAs`, `callSigned` or `callBatched`.
## Drips inner workings
This page offers an introduction to the core mechanics of how streaming is implemented in smart contracts. The implementation can be found on [Github](https://github.com/radicle-dev/drips-contracts) in `Drips.sol`.
### Design Principles
Drips is a protocol for any EVM-based blockchain that allows users to set up and manage continuous transfers of funds from one account to another over time. We refer to such transfers as "streams".
![Diagram 1][img1]
Technically, tokens that are streamed are not sent directly to the recipient's address. Instead, the `Drips` contract keeps track of the sender and recipient's balances and allows the receiver to collect funds whenever they wish. Anyone who has worked with Ethereum and other EVM-based blockchains knows that there are significant limitations with regards to gas and transaction costs. Because of this, the Drips protocol was carefully designed to be as scalable and gas-efficient as possible. In particular, we wanted Drips to be able to support use cases like sponsorship or streaming memberships, where a single user (or org) may be receiving funds from hundreds or thousands of senders.
With these requirements in mind, let's consider the case of a receiver collecting funds that have been streamed to them. The obvious technical design for a streaming system like Drips would be to store a list of all streams being sent to each receiver and then simply iterate the list whenever collect() is called. However, storing and iterating a list with hundreds or thousands of entries quickly becomes cost-prohibitive from a gas perspective on Ethereum and might even exceed the total gas available in a block in the worst case.
Instead, to increase efficiency, `Drips` works internally with the concept of `cycles` and all funds being sent to a given recipient for a given cycle are aggregated and stored together as a pooled amount in the Drips smart contracts. In fact, for greater efficiency, it is not even the pooled stream amounts themselves that are stored, but rather the "deltas", or changes in amount streamed, from one cycle to the next.
#### Drips Cycles
Each cycle defines a fixed time interval so that every block is assigned to exactly one cycle based on its timestamp. Cycles are indexed starting from 1 and cycle times and indexes are the same for all Drips users.
![Diagram 2][img2]
#### Scheduled Drips
When a Drip is configured, the sender can decide whether funds will start flowing immediately (i.e. in the same block that the transaction is processed)
or whether the flow of funds will be **scheduled** to start at some future time. Similarly, a user can (optionally) choose to impose an explicit duration for how long funds will be streamed. We refer to streams with an explicitly defined start time or duration as "scheduled".
#### Stream Funds
Any user can stream funds to another user. The state of a sender for a specific ERC20 token can be described with the following attributes:
* **Balance** - balance of tokens that the sender holds in their account.
* **Set of DripsConfigs** - configurations for Drips that the sender is streaming to other users (if any).
Based on the set of drips, a total funding rate per cycle can be derived. The balance is automatically reduced by the funding rate every second and the same amount is credited to the sender's receivers.
When the sender's balance reaches an amount lower than the per-second funding rate, the funding is stopped. This process doesn't actually require updates every second. Instead, its effects are calculated on the fly whenever they are needed. Thus the contract state is updated only when the funding parameters are altered by the users.
The sender balance is manually increased by topping up, which requires sending some ERC-20 tokens from the user's wallet to the `Drips` contract. The opposite operation is withdrawal, which results in removing tokens from the contract back to the user wallet.
In order to start sending, the only requirements are that the sender has a non-zero balance and a non-empty list of receivers. As soon as the sender's configuration is updated to match these criteria, the flow of tokens starts. First, the funding period is calculated. Its start is the current block timestamp and its end is the moment on which the balance will run out (unless some streams have been **scheduled** as discussed above).
#### Receiving Funds
There are no setup steps for one to become a receiver. Any user can receive funds at any time, from any sender. The only function of this role is the collection of funds sent by others.
Each receiver maintains a mapping for all cycles, which can be imagined as a timeline. Here, we see the timeline of a receiver who is receiving funds from two different senders. Each of the senders has sent different amounts over different periods of time. In this example there is a overlap between the two senders:
![Diagram 3][img3]
If a sender starts streaming to a receiver this timeline is modified. For each ERC20 token a separate timeline of tokens exists. As discussed above, in order to maximize efficiency and minimize gas, we only store deltas of rate changes per-cycle, rather than full details about each stream configuration. Every second, funds from the sender’s pool account are credited to the sender’s receivers according to the funding rate.
**Received**
![Diagram 4][img4]
The `Drips` needs to be able to keep track of how much was sent to each receiver in a given cycle. We do this by aggregating the amounts being streamed to the receiver and then representing the aggregate per-cycle amounts in the form of delta-based rate changes between each cycle. This allows us to calculate the funds that the receiver will receive in each not-yet collected cycle.
### Receiver Deltas
While it is useful to consider **amounts received per cycle** for purposes of explanation, in reality, for efficiency we instead store the **deltas** of change for each cycle as discussed above. If no delta is stored for a specific cycle, we can assume the previous cycle receiving rate should be applied for the current one.
In our example **sender A** wants to stream **5** per cycle.
If the sender would start/end exactly at the beginning of a cycle we would have a
* start delta (+5)
* end delta (-5)
Which we would store on the timeline of the receiver.
#### Calculation of Deltas
Normally, a stream will not start exactly at the beginning of a cycle. A stream will start most of the time during an ongoing cycle.
Therefore, we need to split the start and end deltas into two separate deltas.
Let’s take a look at the example from **sender A**, how to calculate the deltas.
![Diagram 5][img5]
**Deltas: Start Sending**
The sender will be sending 1 per second or 5 per cycle. The senders usually are sending constant per-cycle amounts over long periods of time, so the added values tend to create long series of constant numbers, in this case, 5s.
We exploit this observation to turn the per-cycle flows of funds into **deltas** relative to the previous cycles. Now that we need to store data only for cycles where the funding rate changes, it's very cheap. This is what the contract actually stores: a mapping from cycle numbers to deltas.
In the case of **sender 1** the total change of the per-cycle delta is +5 to start sending.
The current cycle isn't fully affected though, only 3 out of 5 seconds are sending.
It's effectively going to transfer only the amount of 3, which is reflected in the +3 delta change.
On the other hand, the next cycle and the ones after it are going to transfer the full 5.
This is expressed with the +2 delta change, which turns 2 per cycle into the full 5 per cycle
When funding is stopped, the deltas need to be reverted. To do that, essentially the same process is applied, just with negative deltas.
In this case, the reverting is split into 2 cycles too, one with -4 and the other with -1.
### Storing Deltas
For our complete example, we can see the delta calculation for both **sender 1** and **sender 2**.
From the perspective of the receiver each cycle has one delta value. This delta value is modified if sender start/stops a stream in this specific cycle.
![Diagram 6][img6]
The stored delta for a cycle in the timeline can be seen as the sum of individuals sender deltas for that cycle.
**Actual Storage In The Contract**
![Diagram 7][img7]
Here we can see to represent the two senders in the timeline of the receiver, we only modified 5 different cycle deltas.
### Collecting
The receiver can at any time collect the funds sent to them. The contract calculates the total amount and then transfers it out to the receiver's wallet. The collected amount is always everything available to be collected at the given moment.
As shown in the previous sections, the collectable amount is described with deltas, one per cycle. The receiver stores the number of the first cycle, for which the funds haven't been collected yet, which assures that funds can be collected only once.
#### Updating or Cancelling Drips
Of course it's quite likely that a sender may want to update their streams at some point in the future, either to change the amount being streamed, or perhaps to stop streaming entirely. It is important to know that while such changes are permitted, it is only possible to change streams configurations for the future and never the past. Specifically, any funds already sent to another user in the past cannot be recovered by the sender if they change their mind. In technical terms, it is not possible to change the funding rate of a past cycle, or for any time in the current cycle which has already passed.
For simplification let's assume, each stream starts and ends exactly at cycle beginning. In the real implementation, we need to split each delta into two individual ones.
**Example: previous Deltas**
Let say the current configuration is that **sender 1** streams **5** per cycle.
![Diagram 8][img8]
**Example: Delete (stop stream at current block.timestamp)**
![Diagram 9][img9]
The earliest time we can stop an ongoing stream is the current block.timestamp.
We add a `-5` stop delta for the current timestamp and need to remove the outdated end time.
**Update Example**
**Update: Increase rate to 2 per second and stream one cycle longer**
The sender wants to increase the amount per second and wants to stream for one cycle more.
This change should happen immediately. (current block.timestamp)
![Diagram 10][img10]
In that case, technically the stream is split into two parts and the following operations need to be performed
* add new end time of first stream (new end is now) -5
* remove first stream outdated end time
* previously a -5 is stored
* by adding a +5 is is cancelled to zero
* add second stream starting from now + 10
* add end time second stream -10
[img1]: /img/drips_inner_workings1.png
[img2]: /img/drips_inner_workings2.png
[img3]: /img/drips_inner_workings3.png
[img4]: /img/drips_inner_workings4.png
[img5]: /img/drips_inner_workings5.png
[img6]: /img/drips_inner_workings6.png
[img7]: /img/drips_inner_workings7.png
[img8]: /img/drips_inner_workings8.png
[img9]: /img/drips_inner_workings9.png
[img10]: /img/drips_inner_workings10.png
## Fractional amounts
### Introduction: token decimals
If you know how token decimals work, feel free to skip this section.
We, the web3 users, are familiar with the notion of tokens being dividable into smaller parts. E.g. you can have 2.5 USDC, or you can receive 0.03 Ether. This in fact is not how the tokens are handled by the computers and the blockchains. The reality is that the tokens are indivisible, but all the quantities are much larger than what's presented to us, which enables the creation of an illusion of fractions. Every token has defined a number of decimals, in the case of USDC it's 6, in the case of Ether 18, and so on. The number of decimals is applied when displaying the amounts for the users. E.g. if your wallet states that you have 2.5 USDC, the actual amount stored in the smart contract has 6 extra digits and is 2,500,000 (`2.5 * 10 ^ 6`). When you want to transfer 0.003 Ether, the wallet sends in the transaction an amount with 18 extra digits, or in this case 30,000,000,000,000,000 (`0.03 * 10 ^ 18`). This convention makes handling tokens simpler for computers because they don't need to work with fractions. On the other hand for the users, the visible amounts are easy to work with, because they are small, have relatively high per-unit monetary value, and offer convenient fractions.
### Language used in this section
This page uses the term "token unit" when talking about tokens as seen by the smart contract. For example, 0.000,001 USDC or `10 ^ -18` Ether (1 Wei) are both token units. Token units are not dividable into smaller parts, their amounts are expressed with integers.
### Streaming rate has a sub-token precision
When setting up streams, each receiver is configured with an integer per-second streaming rate, `amtPerSec`. This rate is expressed in token units with 9 extra decimals, which lets express rates with precision higher than 1 token unit per second.
For example, the user wants to stream 0.000,001 USDC per second. USDC has 6 decimals, so the actual rate they want is 1 token unit per second. The streams configuration requires the rate to have 9 extra decimals, so the `amtPerSec` needs to be set to value 1,000,000,000. Streaming 0.000,001 USDC per second adds up to 2.592 USDC per 30 days. Let's say that the user feels that it's too much, now they want to stream only 1 USDC per 30 days. This is 0.385,802,469 token units per second, to which we add 9 decimals and end up passing as `amtPerSec` the integer value 385,802,469.
| | Example token amount representation |
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| Tokens seen by the user | 0.05 USDC |
| Token units handled by smart contracts | 50,000 (USDC has 6 decimals, so we calculate `0.05 * 10 ^ 6`) |
| Passed as `amtPerSec` as a per second streaming rate | 50,000,000,000,000 (`amtPerSec` requires adding 9 extra decimals to the number of token units, so we calculate `50,000 * 10 ^ 9`) |
### The API requires 9 extra decimals only in `StreamConfig`'s `amtPerSec`
In the Drips API per-second rates are always wrapped in `StreamConfig` packed structures, they are never passed or emitted as standalone integers. All instances of `StreamConfig` have their `amtPerSec` expressed as token units with 9 extra decimals. On the other hand, no other values follow that convention, all the balances and amounts are always expressed as regular numbers of whole token units without any extra precision or fractions.
### Streamed amounts are always whole token units
When streaming, both the sender's balance and the amounts receivable by the receivers are always whole token units, and at all times they add up to the amount put into the protocol by the sender. There are no losses or temporarily stuck token units, the protocol is precise and sound when handling assets. This holds even when streaming at rates that are not expressible as whole token units per second. Partially streamed token units are kept in the sender's balance, but as soon as they add up to whole units, they're moved from the sender and become receivable by the receiver. This makes the amounts actually streamed on each timestamp change from second to second within a range of 1 token unit per second, but when observed over a period of time, the streamed token units add up to the rate which was configured.
For example, the user wants to stream 0.000,001,4 USDC per second, which is 1.4 token units per second. The streamed amount on the first second will be 1.4, so only 1 token unit will be subtracted from the sender's balance and moved to the receiver, 0.4 will stay on the sender's side. During the next second, another 1.4 token units are being streamed, to which 0.4 from the previous second is added resulting in 1.8 token units sent. Again 1 is moved from the sender to the receiver and 0.8 stays on the sender. Finally, during the third second, another 1.4 is streamed, which with 0.8 from the previous cycles adds up to 2.2. This time 2 token units are taken from the sender's balance and made receivable by the receiver, and 0.2 stays on the sender's side waiting to be streamed during the upcoming seconds.
### Technical details of how streamed fractions add up
When streaming with a given rate the Nth second of each cycle moves the same amount from the sender to the receiver. It doesn't matter when streaming starts or ends, the exact amount streamed during a given second depends only on the rate and the position of the timestamp in the cycle. The first second of each cycle always moves `floor(amtPerSec)`, but every other timestamp T moves `floor(T % cycleSecs * amtPerSec) - floor((T - 1) % cycleSecs * amtPerSec)`.
The result of this behavior is that operations that update streams without altering the streaming rate, for example, a change of the balance, do not disrupt the existing streams. This makes calculating the amount streamed over a given period of time easy to do using just the rate and the cycle length.
Unfortunately, it may lead to surprising behaviors when streaming at extremely low rates. For example, a user streams 0.001 token units per second, so a single token unit should be moved to the receiver once every 1000 seconds. Depending on when exactly streaming starts, a token unit may be moved on the first second of streaming or it may be moved after 999 seconds, to the user it may look random. Consecutive token units will be moved as expected, once per 1000 seconds. The only exception is when a cycle ends, because then the unmoved fractions will be cleared, and the countdown to moving the next token unit will be reset to 1000 seconds.
The cycle boundaries behavior determines the lowest possible streaming rate, which is 1 token unit per cycle. If a streaming rate is lower than that, a stream configuration is invalid, because it would cause no movement of tokens. The minimum valid amount per second of 1 token unit per cycle is exposed by the `Drips` contract as `minAmtPerSec`.
## FAQ for Maintainers
import Figure from '../../../components/Figure'
These frequently asked questions cover the essentials of nominating issues, managing time, and understanding how your contributors get paid.
### How do I add issues to a Wave?
You can add issues in two ways:
1. **Via the Drips App (Recommended):** Navigate to **Maintainers → Issues**, select the issue, and click **Add to Wave**. This allows you to immediately set the Complexity Level (Trivial, Medium, or High).
2. **Via GitHub:** You can simply apply the specific Wave Program label (e.g., `Stellar Wave`) to an issue directly on GitHub.
* *Note:* Issues added via GitHub are automatically assigned **Trivial** complexity (100 Points). To increase the value for harder tasks, you must log in to the Drips App and edit the issue's complexity.
### What is the expected time commitment?
A Wave is a sprint, so responsiveness is key.
* **Before the Wave:** Spend 1-2 hours selecting and scoping "good first issues."
* **During the Wave (1 week):** Check your dashboard daily to review applications and assign contributors. This prevents bottlenecks.
* **End of Wave:** Prioritize reviewing and merging PRs so contributors earn their points before the deadline. You also have a 7-day window after the Wave ends to issue Compliments.
### How do I distribute the funds?
You do not need to calculate or distribute funds yourself. Your job is simply to mark the issue as **Resolved** in the Drips interface or on GitHub.
* Drips Wave automatically calculates the contributor's share of the pool based on the issue's Point value.
* Payouts are sent strictly to the contributor's registered wallet address.
### How is the funding pool determined?
Each Wave (sprint) has a fixed Reward Budget (e.g., $10,000) set by the Wave Program Organizers. This amount is visible on the Explore page. This pool is shared among all contributors in that specific Wave based on the points they earn.
### What if a contributor disputes my decision?
If a contributor feels their work was unfairly rejected or scored:
1. **Communicate:** Try to resolve it via GitHub comments first. Often, it's a misunderstanding about the scope or code quality.
2. **Escalate:** If the disagreement persists, please direct them to [Drips Wave Support](https://www.drips.network/wave/support). We will review the case and mediate if necessary.
### What happens if my repository application is rejected?
You can appeal the decision from the **Maintainers → Orgs and Repos** dashboard by clicking **Appeal** on the rejected repository. You'll be asked what development work and improvements you've made since the rejection — appeals are only reconsidered when there's been substantive change.
You can submit your first appeal two weeks after the rejection, there's a one-month cooldown after a declined appeal, and each repository can be appealed at most three times. See [Participating in a Wave](/wave/maintainers/participating-in-a-wave#if-your-repository-is-rejected) for details.
**Appeals must be submitted through the app.** Any appeals sent by email or through other channels will be ignored.
### Why do I need to verify my phone number?
Depending on certain behind-the-scenes factors, both maintainers and contributors may occasionally be asked to verify a phone number before being able to participate in a Wave. This is a security measure designed to prevent fraudulent activity and ensure the integrity of the program. If you are prompted to verify your phone number, please follow the instructions provided in the app to complete the verification process. This typically involves receiving a code via SMS and entering it into the app to confirm your identity.
In accordance with its [privacy policy](https://www.drips.network/legal/privacy), Drips does not use your phone number for any purpose other than verification.
## Participating in a Wave
import Figure from "../../../components/Figure";
As a Project Maintainer, Drips Wave allows you to leverage your community to accelerate development. By identifying "good first issues" or critical maintenance tasks, you can attract contributors who are ready to build. Participation is entirely free for maintainers, and Wave Organizers (e.g. the Stellar Development Foundation) provide a funding pool to reward contributors for their work, allowing you to incentivize contributions without impacting your budget.
To participate, you join a **Wave Program** (e.g., the Stellar Wave Program). A Program is a long-running initiative that spawns recurring **Waves** (sprints). You add issues to the Program, and they become available for contributors to tackle during the next active Wave.
### Setting Up Your Organization
To participate, you must first onboard your GitHub organization and apply your repositories to a Wave Program.
1. **Sign In**: Go to [the Drips Wave app](https://www.drips.network/wave) and log in with your GitHub account.
2. **Navigate to Onboarding**: In the main navigation, select **Maintainers → Orgs and Repos**.
3. **Install the App**: Follow the prompts to install the **Drips Wave GitHub App** on the organization hosting your repositories. This allows Drips to track issues and labels.
4. **Sync and Apply**: Select the public repositories you wish to sync. You must explicitly apply these repositories to a specific Wave Program. Only apply to Programs that are relevant to your ecosystem.
5. **Wait for Approval**: Repository applications require approval from the Wave Program organizers. You will receive an email and an in-app notification once your repositories have been accepted. You can also check the status of your application at any time on the **Maintainers → Orgs and Repos** dashboard.
#### If your repository is rejected
If your application is declined, you can appeal the decision directly from the **Maintainers → Orgs and Repos** dashboard — find the rejected repository and click **Appeal**. Everything happens in the app; there's no need to email or otherwise contact us.
When you appeal, we'll ask what development work and improvements you've made since the rejection. Appeals are only reconsidered when there has been **substantive change** — meaningful improvements to the code, project quality, activity, or the specific concerns raised in the review. If nothing material has changed, the decision won't change.
A few rules apply:
* You can submit your first appeal **two weeks** after the initial rejection.
* If an appeal is declined, there's a **one-month cooldown** before you can appeal again.
* Each repository can be appealed a **maximum of three times**.
A rejected repository **cannot be re-applied** — appealing is the only way to have the decision reconsidered.
:::warning
**Appeals must be submitted through the Drips Wave app.** Appeals sent by email or through any other channel will be ignored.
:::
### Adding Issues to a Wave Program
Once your repository is approved, issues from that repo will appear in your **Maintainers → Issues** dashboard. From here, you can select which issues to add to the Wave Program.
#### Setting Complexity and Points
When adding an issue, you must assign a complexity level. This determines the total Points a contributor earns for resolving it:
* **Trivial**: Worth **100 Points** (Base). Best for typos, small bug fixes, or minor copy changes.
* **Medium**: Worth **150 Points** (Base + 50 Complexity Bonus). Standard features or involved bug fixes.
* **High**: Worth **200 Points** (Base + 100 Complexity Bonus). Complex features, refactors, or new integrations.
#### The GitHub Label Workflow
Alternatively, you can manage issues directly from GitHub without leaving your code.
When an issue is added to a Program, the Drips Wave bot will post a comment and apply a label (e.g., `Stellar Wave`). You can also **add** an issue to a Program simply by applying this label yourself on GitHub. This only works if the repository has already been approved for that specific Wave Program.
### During a Wave
As soon as a Wave cycle begins, contributors can start applying to work on the issues you have added to the Program. You will be notified via email and in-app notifications when you receive applications.
#### Assigning Contributors
Speed is critical. Maintainers are urged to review applications and assign contributors quickly to ensure they have enough time to resolve the issue before the Wave ends.
When reviewing applicants, you will see a **Code Metrics** scorecard and **Languages** profile sourced from their GitHub activity. This includes metrics like Total Merged PRs, PR Merge Rate, and an OSS Activity Score, with categorical bins comparing the contributor against \~3.9 million GitHub users. For details on how these metrics are calculated, see [Applicant metrics](/wave/applicant-metrics).
You can accept an application in two ways:
1. **Via Dashboard**: Use the **Maintainers → Issues** dashboard to view applicant stats and accept one.
2. **Via GitHub**: Simply assign the contributor to the issue directly on GitHub.
##### Explicitly rejecting an application
Usually, you do not need to explicitly reject applications. When you assign an applicant, all other pending applications for that issue are automatically marked as "inactive", resulting in them no longer counting toward the contributor's application limits.
However, if you have received several applications already, but none of them fit your needs, consider **explicitly rejecting** those applicants. This allows the contributors to immediately know they won't be working on that issue, so they can focus on applying to other issues instead of waiting for a response from you. To reject an application, simply click the "Reject" button on the application card.
#### Tracking Progress
The **Maintainers → Issues** dashboard is your central hub. It allows you to filter and quickly see:
* Issues with pending applications.
* Issues with open Pull Requests requiring review.
* Issues that are assigned but stale.
### Resolution and Rewards
When a contributor submits a Pull Request, you review and merge it as normal. Once the issue is marked as resolved
during an active Wave, the contributor earns the Points allocated to that issue.
:::info
**Timing is Critical:** Issues must be marked as resolved **before the Wave ends** for contributors to receive
their points for that cycle. If a submitted PR is high quality but cannot be merged yet for reasons outside the
contributor's control (e.g., feature freeze or blocked dependencies), we strongly urge you to mark the issue as
resolved anyway. This ensures the contributor is fairly rewarded for their work.
:::
#### Leaving Reviews
After an issue is resolved, both the maintainer and the assigned contributor can leave a **two-way review** within
**14 days** of the issue being closed. Reviews help build reputation and improve collaboration across the
ecosystem.
##### How It Works
Each review consists of:
1. **Overall Experience** (required): Rate the collaboration as "Below expectations", "Alright", or "Exceeded
expectations".
2. **Category Ratings** (optional): Rate specific aspects on a 1–5 star scale.
3. **Comment** (optional): Provide open-ended feedback (up to 5,000 characters).
The specific rating categories depend on your role:
**When a maintainer reviews a contributor:**
* **Communication Quality** — How well did the contributor communicate progress, questions, and blockers?
* **Code Quality** — How clean, well-structured, and maintainable was the contributor's code?
* **Timeliness** — How responsive was the contributor while working on this issue?
* **Problem Solving** — How well did the contributor handle challenges and find solutions independently?
**When a contributor reviews a maintainer:**
* **Communication Quality** — How well did the maintainer communicate expectations, feedback, and decisions?
* **Issue Clarity** — How clear and well-defined was the issue description and its requirements?
* **Repo Code Quality** — How well-organized and easy to work with was the repository's codebase?
* **Timeliness** — How responsive was the maintainer when reviewing PRs and answering questions?
:::info
**Reviews are anonymous.** Maintainers and contributors only receive an anonymous rating summary after the Wave
ends. Please be honest and truthful in your reviews. Note that other members of your GitHub organization can view
and re-submit a review left on behalf of the org.
:::
You can update your review at any time before the 14-day deadline. After the deadline passes, reviews can no longer
be submitted or changed.
#### Issue Rollover
If an issue is added to a Wave Program but is not resolved during the current Wave, it automatically remains in the
Program. It will be eligible for applications again in the next Wave cycle unless you explicitly remove it.
## Points Budgets
Wave Programs may have two points budgets configured: a **per-repo budget** that caps the points any single repository can contribute per Wave, and a **per-org budget** that caps the combined points across all of an org's approved repos per Wave. These caps help ensure that no one repo or org dominates the reward pool, encouraging a more balanced distribution of issues across all participating repositories.
### How It Works
When points budgets are configured on a Wave Program, an issue counts toward the budget at its **pre-multiplier points** — that's the issue's base points plus its complexity bonus, before any featured-repo multiplier is applied. Every active issue from a given repo counts against that repo's per-repo budget, and every active issue from any of an org's approved repos also counts against that org's per-org budget.
For example, with a per-repo budget of 500 points and a per-org budget of 2,000 points, a single repo could resolve up to five Trivial issues (100 points each) per Wave, and any one of the org's approved repos could draw from the shared 2,000-point org pool — but the combined points across all of the org's repos cannot exceed 2,000.
Both caps are enforced whenever an issue is added to the Program — whether through the dashboard or the GitHub label workflow. If adding an issue would cause either the repo or the org to exceed its budget, the action is blocked.
Points budgets reset at the end of each Wave. Resolved issues from past Waves no longer count against either budget, while unresolved issues carry over until they are completed or removed.
### How to stay within your budget
* Consider which of your issues are the most impactful every Wave, and prioritize adding those to the Program.
* Set complexity levels accurately and fairly. Balance trivial and complex issues to maximize your impact while staying within budget.
* Ahead of a new Wave, review which unresolved issues you previously added to the Wave Program are still relevant, and remove any that are no longer a priority. This frees up budget for new issues that may arise later in the Wave cycle.
### Viewing Your Budget
You can see your current budget status on the **Maintainers → Orgs & Repos** page:
* The **Orgs** section shows one card per org you're a member of (with at least one approved repo), with a Wave Program selector and the org's points used / per-org budget for that Wave.
* The **Repo Applications** section shows each approved repo with its points used / per-repo budget.
In both places the "points used" value is the total pre-multiplier points (base + complexity bonus) currently counting against the budget for the active Wave.
#### Budget Enforcement
Both budgets are checked in three places:
* **Dashboard**: Adding an issue or increasing an issue's complexity returns an error if either the repo's or the org's budget would be exceeded.
* **GitHub label**: If someone applies the Wave label to an issue that would push the repo or the org over budget, the label is automatically removed and the Drips Wave bot posts a comment explaining which budget was exceeded.
* **Re-opening issues**: If you close an issue that has not been resolved (i.e. no points awarded, for example if "closed as not planned"), it will remain in the Wave Program, but no longer count against either budget. If you later re-open that issue, it will again count against both budgets. If re-opening the issue would cause the repo or the org to exceed its current remaining points budget, it will automatically be removed from the Wave Program.
Decreasing an issue's complexity, or removing an issue from a Wave Program is of course **always allowed**, even if the repo or org is currently over budget.
:::info
If your repo has been **featured** by the Wave Program organizers with a points multiplier, the budget is counted **before** the multiplier is applied. This means featured repos can add the same number of issues as non-featured ones — for example, a repo with a 2x multiplier and a 500-point budget can still add five Trivial issues, not just two.
:::
#### Budget Resets Per Wave
Both budgets reset at the end of each Wave cycle:
* **Resolved issues** from past Waves fall off the budgets entirely — they no longer count toward either limit.
* **Unresolved issues** carry over and continue to count against both budgets until they are completed or removed.
This means that at the start of a new Wave cycle, your available budget depends only on any unresolved issues still active in the Program.
### FAQ
#### I need more points in my budget
If you have a strong case for needing more points in your per-repo or per-org budget — particularly if it's a monorepo, or an org with many active repos — please open a support ticket on the [Drips Discord](https://discord.gg/BakDKKDpHF) and provide details about your situation.
## Repo Application Limits
Wave Programs may limit how many repositories can be applied within a single Wave cycle. There are two types of repo application limits: a **per-user limit** that caps how many repos any one person can apply across all of their organizations, and a **per-org limit** that caps how many repos can be applied on behalf of a single organization across all of its members. These limits help keep applications manageable and fair when a Program receives more interest than it can review.
Either, both, or neither limit may be set. When a Program has no limits configured, you can apply as many repos as you like each Wave cycle.
### How It Works
Every repo you apply counts as one application toward the current Wave cycle. When limits are configured, each application is counted in two places at once:
* Toward **your personal limit** — a single pool shared across **all** the organizations you belong to.
* Toward **its organization's limit** — a pool shared across **all** members of that organization.
Because each application counts against both, the number of repos you can actually apply for a given org is whichever limit runs out first. For example, if your personal limit leaves you 2 applications but one of your orgs has only 1 application slot left this cycle, you can apply just 1 more repo from that org — even though you personally have room for 2.
A consequence worth keeping in mind: your personal limit is a shared pool. If you belong to several orgs, applying repos from one org draws down the same personal allowance you'd use for the others.
### Limits Reset Each Wave
Both limits reset at the start of every Wave cycle. Your usage from previous cycles never counts against the current one, so each new Wave begins with your full allowance.
**Rejected applications still count toward the current cycle.** Applying a repo consumes a slot whether or not it's ultimately approved, so a rejected application uses part of your allowance until the cycle resets. A rejected repo **cannot be re-applied** — to have the decision reconsidered, [appeal it](/wave/maintainers/participating-in-a-wave#if-your-repository-is-rejected) from your dashboard instead.
### What You'll See When Applying
When you apply repos to a Wave Program that has limits configured, the application flow starts with an **Application limits** step:
* It leads with your **personal** remaining applications for the cycle — the single number that governs how much you can apply overall.
* It surfaces any organization whose own remaining slots are **lower** than your personal remaining, since those are the only ones that would restrict you further. Organizations with more room than your personal allowance aren't shown, because your personal limit is what applies in that case.
The repo selection step then enforces these limits up front: you won't be able to select more repos than your personal allowance permits, and repos belonging to an organization that has reached its limit can't be selected.
### FAQ
#### I need to apply more repos than my limit allows
If you have a strong case for needing a higher per-user or per-org application limit — for example an organization with many active repositories — please open a support ticket on the [Drips Discord](https://discord.gg/BakDKKDpHF) and provide details about your situation.
## FAQ for Contributors
import Figure from '../../../components/Figure'
These frequently asked questions cover how to find work, how the point system translates to money, and what is required to get paid.
### How do I find issues to work on?
You can find issues in two ways:
1. **Drips Wave App:** Navigate to the **Explore** page, click on an active Wave (e.g., Stellar Wave 1), and browse the list of available issues.
2. **GitHub:** You can search the specific ecosystem's repositories for issues with the Wave label (e.g., `label:"Stellar Wave"`).
### How do I apply for an issue?
Once you find an issue in the Drips App, click **Apply**. You will see a text field where you can write a short message to the maintainer.
* **Tip:** Be concise. Mention your relevant experience or link to a previous PR to increase your chances of being assigned.
### Do I need to pay to participate?
No. Participation is free. However, you must complete **KYC (Know Your Customer)** verification before you can apply for any issue, and you'll need a valid wallet address to withdraw any rewards you earn. See [Verifying your identity](/wave/contributors/solving-issues-and-earning-rewards#verifying-your-identity) and [Withdrawing Your Rewards](/wave/withdrawing-rewards) for full instructions.
### How are my earnings calculated?
Your earnings are not a fixed dollar amount per issue. Instead, they are based on your share of the total points earned by the community.
* **Base Points:** Every issue has a set value (100, 150, or 200 Points) based on complexity.
* **Compliments:** Maintainers can award bonus points for exceptional work.
* **The Formula:** `(Your Points / Total Points in Wave) * Total Reward Budget` = Your Payout.
### When will I receive rewards?
Payouts are distributed after the Wave concludes and the 7-day "Compliment Window" for maintainers has closed. See [Withdrawing Your Rewards](/wave/withdrawing-rewards) for detailed instructions on how to claim and withdraw your earnings.
### What happens if I can't finish an issue on time?
If you realize you cannot complete the issue before the Wave ends, please **notify the maintainer immediately**.
* This allows them to reassign the issue to someone else so the work can still get done.
* If the issue is not resolved by the end of the Wave, you will not receive points for it in that cycle.
### My PR got merged, but I didn't receive points. Why?
Points are awarded when an issue you were assigned to through Drips Wave is "Closed as Completed" on GitHub. If your PR was merged but the issue was not closed by the maintainer, you can politely remind them to close the issue so you can receive your points. As soon as the issue is closed, the points will be automatically awarded to you.
Please ensure you link your PRs to issues using keywords like "Closes #\" in your PR description. This will make GitHub automatically close the issue when your PR is merged, resulting in immediate points allocation. Read more about how to link an issue and PR in [GitHub's documentation](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue).
If a maintainer is unresponsive for a prolonged period of time and causing delayed points, you can head to the "Tickets" channel on our [Discord](https://discord.gg/BakDKKDpHF) and click "I didn't receive Points for an Issue", or reach out to our [support team](https://www.drips.network/wave/support) with a link to the closed issue and your PR.
#### My issue was closed during an active Wave, but I still didn't receive points.
If your issue was closed but you did not receive points during an active Wave, please head to the "Tickets" channel on our [Discord](https://discord.gg/BakDKKDpHF) and click "I didn't receive Points for an Issue", or reach out to our [support team](https://www.drips.network/wave/support) with a link to the closed issue and your PR.
#### My issue isn't being marked as closed and the maintainer is unresponsive. What should I do?
If the maintainer is unresponsive and your issue isn't being marked as closed, please head to the "Tickets" channel on our [Discord](https://discord.gg/BakDKKDpHF) and click "I didn't receive Points for an Issue", or reach out to our [support team](https://www.drips.network/wave/support) with a link to the closed issue and your PR. Our team will review the situation and ensure any genuinely earned points are awarded appropriately.
### What is the Leaderboard?
The Leaderboard (found under **Wave Program → Leaderboard**) is a real-time ranking of all contributors based on points earned. Since the reward pool is shared proportionally, a higher rank on the leaderboard directly corresponds to a larger share of the reward budget.
### What is the Code Metrics scorecard?
When you apply to an issue, maintainers see a summary of your public GitHub activity including metrics like Total Merged PRs, PR Merge Rate, and an OSS Activity Score. These help maintainers evaluate applicants quickly. For a full explanation of how each metric is calculated and what the categorical bins mean, see [Applicant metrics](/wave/applicant-metrics).
### Why do I need to verify my identity to apply for issues?
Identity verification (KYC) is a regulatory prerequisite for distributing any rewards. We ask for it upfront — before you apply — rather than at payout time, so that once you've completed an issue and earned points there are no delays when it's time to withdraw. Doing it early also helps keep reward distribution fair across the program by preventing duplicate accounts.
The flow is handled by our verification partner [Sumsub](https://sumsub.com/) and usually takes under five minutes. See [Verifying your identity](/wave/contributors/solving-issues-and-earning-rewards#verifying-your-identity) for step-by-step instructions.
### Why do I need to verify my phone number?
Depending on certain behind-the-scenes factors, both maintainers and contributors may occasionally be asked to verify a phone number before being able to participate in a Wave. This is a security measure designed to prevent fraudulent activity and ensure the integrity of the program. If you are prompted to verify your phone number, please follow the instructions provided in the app to complete the verification process. This typically involves receiving a code via SMS and entering it into the app to confirm your identity.
In accordance with its [privacy policy](https://www.drips.network/legal/privacy), Drips does not use your phone number for any purpose other than verification.
## Solving Issues & Earning Rewards
import Figure from "../../../components/Figure";
Drips Wave turns open-source contribution into a structured, rewarding experience. As a contributor, you can browse curated issues from top-tier ecosystems, apply to work on them, and earn rewards based on your impact.
By participating in a Wave, you can:
* **Earn:** Receive a share of the reward pool for every confirmed contribution.
* **Build:** Create a verifiable portfolio of work in top-tier ecosystems.
* **Connect:** Engage directly with maintainers and established projects.
### Getting Started
Before you can apply for any issue, you'll need to verify your identity — this is required so that you can withdraw any rewards you earn later without delays.
1. **Sign In**: Go to [the Drips Wave app](https://www.drips.network/wave) and log in with your GitHub account.
2. **Verify your identity**: Navigate to **Settings → Profile** and follow the instructions to complete identity verification (KYC). See [Verifying your identity](#verifying-your-identity) below for details.
3. **Withdrawing rewards**: Once contributions have been tallied after a Wave ends, you'll be able to withdraw any earned rewards to a wallet. See [Withdrawing Your Rewards](/wave/withdrawing-rewards) for detailed instructions on how to set up your wallet and request payouts.
:::danger
**KYC is Mandatory**: You must complete identity verification (KYC) before you can apply for any issue, so we can be sure you'll be able to withdraw any rewards you earn. See [Verifying your identity](#verifying-your-identity) below.
:::
:::warning
**Important disclaimer**: Drips and Wave Program Organizers reserve the right to adjust the reward formula, manually adjust points, deduct points or withhold rewards for misbehavior, and/or distribute portions of the reward pool outside the standard Points-based formula (e.g., to incentivize maintainers contributing to the program). Depending on the Wave Program, additional rules may apply, such as a maximum cap on rewards per contributor. Please read the [Terms and Rules page](/wave/terms-and-rules) for additional details.
:::
### Verifying your identity
Before you can apply to any issue in a Wave, you'll need to complete a quick identity verification (KYC). We ask for it upfront so that, once you've earned rewards by completing an issue, there are no delays when it's time to withdraw — KYC is a regulatory prerequisite for receiving any funds.
#### How to verify
1. Sign in to [the Drips Wave app](https://www.drips.network/wave).
2. Open **Settings → Profile**, or follow the **Verify identity** prompt that appears after you log in or try to apply to an issue.
3. Complete the verification flow with our partner [Sumsub](https://sumsub.com/). You'll need a photo of a government-issued ID, a short selfie, and a proof of address — a recent bank statement or utility bill that shows your full name and address works well.
Most contributors finish in under five minutes, and verification is typically approved automatically within minutes. We'll notify you by email when your status changes.
#### If verification doesn't go through
If verification fails and is marked as retryable, you can restart the flow from **Settings → Profile**. If it's permanently rejected, reach out at [support@drips.network](mailto\:support@drips.network) and we'll help you sort it out.
Identity documents are handled by our verification partner under their own privacy policy. Drips only stores the verification result, not the documents themselves. Learn more in our [privacy policy](https://www.drips.network/legal/privacy).
### Finding Work
Once your profile is ready, navigate to the **Explore** page. Here you will see active **Wave Programs** (e.g., Stellar) and their currently running **Waves**.
Each Wave card displays the **Reward Budget** (the total pot available) and the **Time Window** (when the sprint ends). Click the "Issues" button on the Wave Program view to jump into a list of available issues. You can filter this list to only show unassigned issues, or issues without any applications.
### Applying for an Issue
When you find an issue that matches your skills:
1. **Read the Scope**: Ensure you understand the requirements and can complete the task within the Wave's
timeframe.
2. **Check the Value**: Note the issue's complexity (Trivial, Medium, or High), which determines its Points value.
3. **Submit your application**: This signals your intent to the maintainer.
#### Application Limits
To ensure fair access to issues and encourage contributors to focus on completing their current work,
the following limits apply:
* **Pending Application Limit:** You can have up to 15 pending applications at a time. You can "free up a slot" by resolving an issue you've been accepted for, or withdrawing pending applications. Applications for issues that are resolved or cancelled don't count toward this limit.
* **Per-Organization Limit:** You can be assigned to at most 4 issues per organization across a single Wave.
This prevents any single contributor from monopolizing issues within one ecosystem and gives more
people the opportunity to participate.
You can always check on which issues are currently counting against your limits for a specific Wave Program on your [application limits dashboard](https://www.drips.network/wave/contributors/limit-details).
##### Automated Limit Release
When you submit a PR and link it to a Wave issue (e.g., by including `Closes #\` in your PR description), the Drips Wave bot may automatically perform a high-level review of your PR. If deemed to be a substantial contribution and adequately resolve the issue, it may automatically exclude the linked issue from your application limit, allowing you to already apply for one more additional issue while still awaiting a review.
Please note that this AI-assisted functionality is **experimental**. The bot may not always assess every PR, and there is currently no way to manually request an automatic limit release.
You can find out which of your applications no longer count against your limit on your [application limits dashboard](https://www.drips.network/wave/contributors/limit-details).
#### Writing a Strong Application
Maintainers often receive multiple applications for a single issue. To stand out:
* **Be Concise**: Keep your application message short and professional.
* **Show Relevance**: Briefly mention why you are a good fit (e.g., "I have experience with this library" or "I fixed a similar bug in repo X").
When you apply, maintainers see your **Code Metrics** scorecard and **Languages** profile next to your application. For details on how these metrics are calculated, see [Applicant metrics](/wave/applicant-metrics).
### The Contribution Workflow
#### 1. Assignment
Applying notifies the maintainer of your interest.
* **Wait for Assignment**: Do not start coding yet! The issue is only yours once the maintainer officially assigns you. You will receive a notification when this happens.
* **Check Your Dashboard**: You can track all your applications and active assignments in the **Contributors → Issues** dashboard.
#### 2. Solving the Issue
Once assigned, work on the issue as you normally would.
* Fork the repository (if necessary).
* Create a branch and write your code.
* Open a **Pull Request (PR)** on GitHub.
#### 3. Review and Resolution
The maintainer will review your PR. They may request changes. Once the maintainer is satisfied,
they will **Merge** your PR and mark the issue as **Resolved** in Drips.
**This is the moment you earn your Points.**
You will receive an in-app notification confirming the award. You can also track your total
earnings and view a detailed log of every point awarded on the **Points History** screen.
After the issue is resolved, you'll have **14 days** to leave an anonymous review of the
maintainer. See [Reviews](#reviews) below for details.
### Understanding Rewards
Your financial reward is calculated based on the **Points** you earn relative to all other contributors in the Wave.
#### How Points Work
Every issue has a base value based on complexity:
* **Trivial**: 100 Points
* **Medium**: 150 Points
* **High**: 200 Points
#### Reviews
After an issue you worked on is resolved, both you and the maintainer can leave a **two-way
review** within **14 days** of the issue being closed. Reviews are a way to provide feedback on
the collaboration and help build reputation across the ecosystem.
As a contributor, you can rate the maintainer on:
* **Communication Quality** — How well did the maintainer communicate expectations, feedback, and
decisions?
* **Issue Clarity** — How clear and well-defined was the issue description and its requirements?
* **Repo Code Quality** — How well-organized and easy to work with was the repository's codebase?
* **Timeliness** — How responsive was the maintainer when reviewing PRs and answering questions?
You'll also select an **Overall Experience** rating ("Below expectations", "Alright", or "Exceeded
expectations") and can optionally leave a written comment. Star ratings for individual categories
are optional.
Similarly, the maintainer can review you on Communication Quality, Code Quality, Timeliness, and
Problem Solving.
:::info
**Reviews are anonymous.** You will only receive an anonymous rating summary after the Wave ends.
Please be honest and truthful. You can update your review at any time before the 14-day deadline.
:::
#### The Leaderboard
You can view the **Leaderboard** by navigating to **Wave Program → Leaderboard**. This shows your real-time standing against other participants. Since the reward pool is shared, your final payout depends on your percentage of the total points earned by the community during the Wave.
:::warning
**Important disclaimer**: Drips and Wave Program Organizers reserve the right to adjust the reward formula, manually adjust points, deduct points or withhold rewards for misbehavior, and/or distribute portions of the reward pool outside the standard Points-based formula (e.g., to incentivize maintainers contributing to the program). Depending on the Wave Program, additional rules may apply, such as a maximum cap on rewards per contributor. Please read the [Terms and Rules page](/wave/terms-and-rules) for additional details.
:::
### Payouts
When the Wave cycle concludes:
1. **Calculation**: The leaderboard is finalized and each contributor's share of the total points awarded during the Wave is calculated.
2. **Distribution**: If you are eligible for rewards, you'll be able to claim your reward in the Drips Wave App. You'll receive an email notification when your rewards are available. See [Withdrawing Your Rewards](/wave/withdrawing-rewards) for details on how to claim and withdraw your earnings.