This project solves the problem of allowing payers of token streams to create streams and fund them after the fact, i.e. first create the stream, then fund it later when funds are available.
For example, Nouns DAO can use Token Buyer with Streamer and create proposals that:
- Create a Stream
- Ask TokenBuyer's Payer contract to send tokens to the newly created Stream when it has the funds
- TokenBuyer acuiqres the funds, sends them to Payer, Payer funds the Stream
- The Stream's recipient can start withdrawing funds
-
- Creates new Streams using minimal clones
- Supports predicting new Stream contract addresses, making it easier to compose DAO proposals, since a proposal author can know the address of the Stream the proposal will create before it's created
-
- Supports custom start and end timestamps
- Does not enforce upfront funding, making it usable for DAOs and other payers that acquire payment tokens post Stream creation
To run smart contract tests, make sure you have Foundry installed, then run:
forge install
forge testRun Foundry's local node:
anvilCopy one of anvil's test account's address and private key, to use in commands below.
Deploy StreamFactory:
forge script script/DeployStreamFactory.s.sol --rpc-url http://127.0.0.1:8545 --broadcast --sender <anvil test account address> --private-key <anvil test account private key>Deploy MockToken, which you can use to mint tokens to your test Streams:
forge script script/DeployMockToken.s.sol --rpc-url http://127.0.0.1:8545 --broadcast --sender <anvil test account address> --private-key <anvil test account private key>Go to ./broadcast/DeployStreamFactory.s.sol/31337/run-latest.json and copy the value of contractAddress for StreamFactory.
Go to ./broadcast/DeployMockToken.s.sol/31337/run-latest.json and copy the value of contractAddress for ERC20Mock.
Copy another anvil test account and private key to use as your stream recipient.
If you want to create a stream that starts or ends in the future, you can use foundry.toml to override anvil's first block timestamp, or run cast block latest and copy the timestamp of the current block as a starting point to your stream.
Simulate stream creation:
cast call --rpc-url http://127.0.0.1:8545 --private-key <your first anvil test account private key> <your StreamFactory address> "createStream(address,uint256,address,uint256,uint256)" <your second test account, the recipient of the stream> <the token amount to stream, e.g. 1000> <your ERC20Mock contract address> <stream start timestamp, e.g. as taken from running cast block latest above> <stream end timestamp, e.g. start time + 1000 to make it predictable>The output will look something like:
0x00000000000000000000000033f456c89902746ffed70041f8bc8672e3a171fdThis is the expected new stream address, you just need to trim it down a bit, in this case to be: 0x33f456c89902746ffed70041f8bc8672e3a171fd.
Execute stream creation using the same inputs, by running the same command as above, only using cast send instead of cast call.
To make sure you're stream has been created, get the recipient's balance; it should be greater than zero:
cast call --rpc-url http://127.0.0.1:8545 --private-key <a test account private key> <stream contract address> "balanceOf(address)(uint256)" <stream recipient address>To withdraw, you'll need to mint tokens to fund the stream, then call withdraw using the recipient account:
cast send --rpc-url http://127.0.0.1:8545 --private-key <a test account private key> <the ERC20Mock contract address> "mint(address,uint256)" <the stream contract address> <the stream amount, e.g. 1000>
cast send --rpc-url http://127.0.0.1:8545 --private-key <the payer or recipient account private key> <the stream contract address> "withdraw(uint256)" <the withdrawal amount, should not exceed recipient current balance>In ./subgraph/networks.json, make sure StreamFactory's address matches the one you recently deployed.
Spin up your local node: (you need Docker installed and running in the background)
yarn local-nodeThen run:
yarn
yarn codegen
yarn build:local
yarn create:local
yarn deploy:localYou should now be able to query the subgraph and see your recently created streams and any withdrawals and cancellation events related to it. You can use Postman or any other HTTP request tool, send a POST request to http://localhost:8000/subgraphs/name/streamer with a GraphQL query like:
query {
streams (limit: 10) {
id
createdAt
createdBy
payer
recipient
tokenAmount
tokenAddress
startTime
stopTime
withdrawals {
id
withdrawnAt
executedBy
amount
}
cancellations {
id
cancelledAt
cancelledBy
payerBalance
recipientBalance
}
}
}
Copy ./env.example to ./.env and set your values.
Deploy StreamFactory:
forge script script/DeployStreamFactory.s.sol --verify -vvvvv --rpc-url https://sepolia.infura.io/v3/<infura API key> --broadcast --sender <your deployer account address> -i 1Deploy ERC20Mock:
forge script script/DeployMockToken.s.sol --verify -vvvvv --rpc-url https://sepolia.infura.io/v3/<infura API key> --broadcast --sender <your deployer account address> -i 1At the time of writing, the hosted service does not support Sepolia, but we can still run a local node.
In ./subgraph/networks.json, make sure StreamFactory's address matches the most recent deployment you want to use; you might find it under ./broadcast/DeployStreamFactory.s.sol/11155111/run-latest.json.
Copy ./subgraph/.env.example to ./subgraph/.env and set network name and RPC env vars - these are used in docker-compose.yml.
Spin up your local node: (you need Docker installed and running in the background)
yarn local-nodeThen run:
yarn
yarn codegen
yarn build:sepolia
yarn create:sepolia
yarn deploy:sepoliaQuerying works as outlined above for local testing.
| Contract | Address |
|---|---|
| StreamFactory | 0x0fd206FC7A7dBcD5661157eDCb1FFDD0D02A61ff |
| Stream | 0x0b9dFf1aba32A9fa95011C7f097ec672F689038F |
| Contract | Address |
|---|---|
| StreamFactory | 0xc08a287eCB16CeD801f28Bb011924f7DE5Cc53a3 |
| Stream | 0x20ffE6b3b08f5009788812583D42C5b42bf44447 |