Skip to content

Commit

Permalink
Group passed linear streams by token address to actually leverage bat…
Browse files Browse the repository at this point in the history
…ch creation
  • Loading branch information
mudrila committed Aug 14, 2024
1 parent 6acebc9 commit 1cf5c3a
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 36 deletions.
16 changes: 16 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"js-big-decimal": "^1.3.12",
"lodash.camelcase": "^4.3.0",
"lodash.debounce": "^4.0.8",
"lodash.groupby": "^4.6.0",
"lodash.isequal": "^4.5.0",
"moralis": "^2.26.3",
"react": "^18.2.0",
Expand Down Expand Up @@ -100,6 +101,7 @@
"@testing-library/react": "^14.2.1",
"@types/lodash.camelcase": "^4.3.9",
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.groupby": "^4.6.9",
"@types/lodash.isequal": "^4.5.8",
"@types/react": "^18.0.17",
"@types/react-dom": "^18.0.6",
Expand Down
89 changes: 53 additions & 36 deletions src/hooks/streams/useCreateSablierStream.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import groupBy from 'lodash.groupby';
import { useCallback } from 'react';
import { zeroAddress, encodeFunctionData, erc20Abi, Address, Hex } from 'viem';
import { Address, Hex, encodeFunctionData, erc20Abi, getAddress, zeroAddress } from 'viem';
import SablierV2BatchAbi from '../../assets/abi/SablierV2Batch';
import { SablierV2LockupLinearAbi } from '../../assets/abi/SablierV2LockupLinear';
import {
Expand All @@ -17,7 +18,7 @@ import {
} from '../../types/sablier';

type LinearStreamInputs = {
totalAmount: string;
totalAmount: bigint;
asset: SablierAsset;
recipient: Address;
schedule: StreamSchedule;
Expand Down Expand Up @@ -96,17 +97,15 @@ export default function useCreateSablierStream() {
if (!streamDuration) {
throw new Error('Stream duration can not be 0');
}

const tokenCalldata = prepareStreamTokenCallData(totalAmountInTokenDecimals);
const basicStreamData = prepareBasicStreamData(recipient, totalAmountInTokenDecimals);
const assembledStream = {
...basicStreamData,
durations: { cliff: cliffDuration, total: streamDuration + cliffDuration }, // Total duration has to include cliff duration
};

return { tokenCalldata, assembledStream };
return assembledStream;
},
[prepareBasicStreamData, prepareStreamTokenCallData],
[prepareBasicStreamData],
);

const prepareFlushStreamTx = useCallback((stream: BaseSablierStream, to: Address) => {
Expand Down Expand Up @@ -155,46 +154,64 @@ export default function useCreateSablierStream() {
const preparedStreamCreationTransactions: { calldata: Hex; targetAddress: Address }[] = [];
const preparedTokenApprovalsTransactions: { calldata: Hex; targetAddress: Address }[] = [];

linearStreams.forEach((streamData, index) => {
const recipient = recipients[index];
const tokenAddress = streamData.asset.address;
const schedule =
streamData.scheduleType === 'duration' && streamData.scheduleDuration
? streamData.scheduleDuration.duration
: {
startDate: streamData.scheduleFixedDate!.startDate.getTime(),
endDate: streamData.scheduleFixedDate!.endDate.getTime(),
};
const cliff =
streamData.scheduleType === 'duration'
? streamData.scheduleDuration!.cliffDuration
: streamData.scheduleFixedDate?.cliffDate !== undefined
? {
startDate: streamData.scheduleFixedDate!.cliffDate.getTime(),
endDate: streamData.scheduleFixedDate!.cliffDate.getTime(),
}
: undefined;

// @todo - Smarter way would be to batch token approvals and streams creation, and not just build single approval + creation transactions for each stream
const { tokenCalldata, assembledStream } = prepareLinearStream({
recipient,
...streamData,
totalAmount: streamData.amount.value,
asset: streamData.asset,
schedule,
cliff,
const groupedStreams = groupBy(linearStreams, 'asset.address');
Object.keys(groupedStreams).forEach(assetAddress => {
const assembledStreams: ReturnType<typeof prepareLinearStream>[] = [];
const streams = groupedStreams[assetAddress];
const tokenAddress = getAddress(assetAddress);
let totalStreamsAmount = 0n;

streams.forEach((streamData, index) => {
if (!streamData.amount.bigintValue || streamData.amount.bigintValue <= 0n) {
console.error(
'Error creating linear stream - stream amount must be bigger than 0',
streamData,
);
throw new Error('Stream total amount must be greater than 0');
}
totalStreamsAmount += streamData.amount.bigintValue;
const recipient = recipients[index];
const schedule =
streamData.scheduleType === 'duration' && streamData.scheduleDuration
? streamData.scheduleDuration.duration
: {
startDate: streamData.scheduleFixedDate!.startDate.getTime(),
endDate: streamData.scheduleFixedDate!.endDate.getTime(),
};
const cliff =
streamData.scheduleType === 'duration'
? streamData.scheduleDuration!.cliffDuration
: streamData.scheduleFixedDate?.cliffDate !== undefined
? {
startDate: streamData.scheduleFixedDate!.cliffDate.getTime(),
endDate: streamData.scheduleFixedDate!.cliffDate.getTime(),
}
: undefined;

const assembledStream = prepareLinearStream({
recipient,
...streamData,
totalAmount: streamData.amount.bigintValue,
asset: streamData.asset,
schedule,
cliff,
});
assembledStreams.push(assembledStream);
});

const sablierBatchCalldata = encodeFunctionData({
abi: SablierV2BatchAbi,
functionName: 'createWithDurationsLL', // Another option would be to use createWithTimestampsLL. Essentially they're doing the same, `WithDurations` just simpler for usage
args: [sablierV2LockupLinear, tokenAddress, [assembledStream]],
args: [sablierV2LockupLinear, tokenAddress, assembledStreams],
});

preparedStreamCreationTransactions.push({
calldata: sablierBatchCalldata,
targetAddress: sablierV2Batch,
});

const tokenCalldata = prepareStreamTokenCallData(totalStreamsAmount);

preparedTokenApprovalsTransactions.push({
calldata: tokenCalldata,
targetAddress: tokenAddress,
Expand All @@ -203,7 +220,7 @@ export default function useCreateSablierStream() {

return { preparedStreamCreationTransactions, preparedTokenApprovalsTransactions };
},
[prepareLinearStream, sablierV2Batch, sablierV2LockupLinear],
[prepareLinearStream, prepareStreamTokenCallData, sablierV2Batch, sablierV2LockupLinear],
);

return {
Expand Down

0 comments on commit 1cf5c3a

Please sign in to comment.