Skip to content

Commit

Permalink
Fetch openlongs from transfer single events (#1157)
Browse files Browse the repository at this point in the history
* started on function

* open long with details working

* split up functions

* combined open longs working

* remove comment

* refactor hook

* remove hook in desktop

* depracate hook

* cleanup methods on readhyperdrive

* remove negative

* Checkin update longs split on read hyperdrive

Co-authored-by: Danny Delott <DannyDelott@users.noreply.github.com>

* read hyperdrive methods finished

* adds custom type

* update comments

* cleanup types

* Update depracation type

* updates per comments

---------

Co-authored-by: Danny Delott <DannyDelott@users.noreply.github.com>
  • Loading branch information
jackburrus and DannyDelott authored Jun 7, 2024
1 parent 743c618 commit 7517dd1
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Long } from "@delvtech/hyperdrive-viem";
import { Long, OpenLongPositionReceived } from "@delvtech/hyperdrive-viem";
import { useQuery } from "@tanstack/react-query";
import { makeQueryKey } from "src/base/makeQueryKey";
import { useReadHyperdrive } from "src/ui/hyperdrive/hooks/useReadHyperdrive";
Expand All @@ -10,6 +10,7 @@ interface UseOpenLongsOptions {
}

/**
* @deprecated Use useOpenLongPositions instead to include longs that have been transferred to the account from another address.
* Returns the list of longs that the account currently has open.
*/
export function useOpenLongs({
Expand All @@ -31,3 +32,48 @@ export function useOpenLongs({

return { openLongs, openLongsStatus };
}

/**
* Returns the list of longs the account currently has open. This includes longs that have been transferred to the account from another address.
* TODO: Rename this hook to useOpenLongs once the old useOpenLongs hook is removed.
*/
export function useOpenLongPositions({
account,
hyperdriveAddress,
}: UseOpenLongsOptions): {
openLongPositionsReceived: OpenLongPositionReceived[] | undefined;
openLongPositionsReceivedStatus: "error" | "success" | "loading";
} {
const readHyperdrive = useReadHyperdrive(hyperdriveAddress);
const queryEnabled = !!readHyperdrive && !!account && !!hyperdriveAddress;
const {
data: openLongPositionsReceived,
status: openLongPositionsReceivedStatus,
} = useQuery({
enabled: queryEnabled,
queryKey: makeQueryKey("allOpenLongs", { account, hyperdriveAddress }),
queryFn: queryEnabled
? async () => {
const allLongs = await readHyperdrive.getOpenLongPositions({
account,
});

const openLongPositionsReceived = await Promise.all(
allLongs.map(async (long) => ({
assetId: long.assetId,
value: long.value,
maturity: long.maturity,
details: await readHyperdrive.getOpenLongDetails({
assetId: long.assetId,
account,
}),
})),
);

return openLongPositionsReceived;
}
: undefined,
});

return { openLongPositionsReceived, openLongPositionsReceivedStatus };
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ import { DEFAULT_EXTRA_DATA } from "src/hyperdrive/constants";
import { calculateAprFromPrice } from "src/hyperdrive/utils/calculateAprFromPrice";
import { convertSharesToBase } from "src/hyperdrive/utils/convertSharesToBase";
import { hyperwasm } from "src/hyperwasm";
import { ClosedLong, Long } from "src/longs/types";
import {
ClosedLong,
Long,
OpenLongPositionReceivedWithoutDetails,
} from "src/longs/types";
import { ClosedLpShares } from "src/lp/ClosedLpShares";
import { LP_ASSET_ID } from "src/lp/assetId";
import { ReadContractModelOptions, ReadModel } from "src/model/ReadModel";

import { decodeAssetFromTransferSingleEventData } from "src/pool/decodeAssetFromTransferSingleEventData";
import { MarketState, PoolConfig, PoolInfo } from "src/pool/types";
import { calculateShortAccruedYield } from "src/shorts/calculateShortAccruedYield";
import { ClosedShort, OpenShort } from "src/shorts/types";
Expand Down Expand Up @@ -801,7 +807,145 @@ export class ReadHyperdrive extends ReadModel {
return Object.values(openLongs).filter((long) => long.bondAmount);
}

// TODO: Rename this to getOpenLongs once this function replaces the existing getOpenLongs
async getOpenLongPositions({
account,
options,
}: {
account: `0x${string}`;
options?: ContractReadOptions;
}): Promise<OpenLongPositionReceivedWithoutDetails[]> {
const toBlock = getBlockFromReadOptions(options);

const transfersReceived = await this.contract.getEvents("TransferSingle", {
filter: { to: account },
toBlock,
});
const transfersSent = await this.contract.getEvents("TransferSingle", {
filter: { from: account },
toBlock,
});

const longsReceived = transfersReceived.filter((event) => {
const { assetType } = decodeAssetFromTransferSingleEventData(
event.data as `0x${string}`,
);
return assetType === "LONG";
});

const longsSent = transfersSent.filter((event) => {
const { assetType } = decodeAssetFromTransferSingleEventData(
event.data as `0x${string}`,
);
return assetType === "LONG";
});

// Put open and long events in block order. We spread openLongEvents first
// since you have to open a long before you can close one.
const orderedLongEvents = [...longsReceived, ...longsSent].sort(
(a, b) => Number(a.blockNumber) - Number(b.blockNumber),
);

const openLongs: Record<string, OpenLongPositionReceivedWithoutDetails> =
{};

orderedLongEvents.forEach((event) => {
const assetId = event.args.id.toString();

const long: OpenLongPositionReceivedWithoutDetails = openLongs[
assetId
] || {
assetId,
maturity: decodeAssetFromTransferSingleEventData(
event.data as `0x${string}`,
).timestamp,
value: 0n,
};

const isLongReceived = event.args.to === account;
if (isLongReceived) {
const updatedLong: OpenLongPositionReceivedWithoutDetails = {
...long,
value: long.value + event.args.value,
};
openLongs[assetId] = updatedLong;
return;
}

const isLongSent = event.args.from === account;
if (isLongSent) {
// If a user closes their whole position, we should remove the whole
// position since it's basically starting over
if (event.args.value === long.value) {
delete openLongs[assetId];
return;
}
// otherwise just subtract the amount of bonds they closed and baseAmount
// they received back from the running total
const updatedLong: OpenLongPositionReceivedWithoutDetails = {
...long,
value: long.value - event.args.value,
};
openLongs[assetId] = updatedLong;
}
});
return Object.values(openLongs).filter((long) => long.value);
}

async getOpenLongDetails({
assetId,
account,
options,
}: {
assetId: bigint;
account: `0x${string}`;
options?: ContractReadOptions;
}): Promise<Long | undefined> {
const decimals = await this.getDecimals();
const allLongPositions = await this.getOpenLongPositions({
account,
options,
});

const longPosition = allLongPositions.find((p) => p.assetId === assetId);

if (!longPosition) {
throw new Error(
`No position with asset id: ${assetId} found for account ${account}`,
);
}

const openLongEvents = await this.contract.getEvents("OpenLong", {
filter: { trader: account },
});

const closeLongEvents = await this.contract.getEvents("CloseLong", {
filter: { trader: account },
});

const allOpenLongDetails = this._calcOpenLongs({
openLongEvents,
closeLongEvents,
decimals,
});

const openLongDetails = allOpenLongDetails.find(
(details) =>
details.assetId.toString() === longPosition.assetId.toString(),
);
// If no details exists for the position, the user must have just received
// some longs via transfer but never opened them themselves.
// OR If the amounts aren't the same, then they may have opened some and
// received some from another wallet. In this case, we still can't be sure
// of the details, so we return undefined.
if (!openLongDetails || openLongDetails.bondAmount !== longPosition.value) {
return;
}

return openLongDetails;
}
/**
* @deprecated Use ReadHyperdrive.getOpenLongPositions and ReadHyperdrive.getOpenLongDetails instead to retrieve all longs opened or received by a user.
* Gets the active longs opened by a specific user.
* @param account - The user's address
* @param options.toBlock - The end block, defaults to "latest"
Expand Down
6 changes: 5 additions & 1 deletion packages/hyperdrive-js-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,11 @@ export type { ClosedShort, OpenShort, Short } from "src/shorts/types";
// Longs
export { calculateAprFromPrice } from "src/hyperdrive/utils/calculateAprFromPrice";
export { calculateMatureLongYieldAfterFees } from "src/longs/calculateMatureLongYieldAfterFees";
export type { ClosedLong, Long } from "src/longs/types";
export type {
ClosedLong,
Long,
OpenLongPositionReceived,
} from "src/longs/types";

// LP
export type { ClosedLpShares } from "src/lp/ClosedLpShares";
Expand Down
13 changes: 13 additions & 0 deletions packages/hyperdrive-js-core/src/longs/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,16 @@ export interface ClosedLong extends Long {
baseAmount: bigint;
closedTimestamp: bigint;
}

// TODO: This is a temporary type until all the long positions are migrated to the new format with details used for the long which includes all bond details if those are able to be calculated.
export interface OpenLongPositionReceived {
assetId: bigint;
value: bigint;
maturity: bigint;
details: Long | undefined;
}
// TODO: This is a temporary type for describing the OpenLongPositionReceived without the details field. This will be a position a user has received from another account and we will not be able to calculate the bond details for it.
export type OpenLongPositionReceivedWithoutDetails = Omit<
OpenLongPositionReceived,
"details"
>;

0 comments on commit 7517dd1

Please sign in to comment.