From fd5343a02e639f52bc7e5b71e9f014c6a14b9430 Mon Sep 17 00:00:00 2001 From: jackburrus Date: Thu, 6 Feb 2025 16:46:21 -0700 Subject: [PATCH 1/6] adds zaps to read hyperdrive constructor --- apps/sdk-sandbox/scripts/example.ts | 284 +++++++++++++++++- .../src/hyperdrive/ReadHyperdrive.ts | 162 +++++----- 2 files changed, 354 insertions(+), 92 deletions(-) diff --git a/apps/sdk-sandbox/scripts/example.ts b/apps/sdk-sandbox/scripts/example.ts index 07e3b9597..2c209ecb2 100644 --- a/apps/sdk-sandbox/scripts/example.ts +++ b/apps/sdk-sandbox/scripts/example.ts @@ -1,21 +1,279 @@ import { Drift } from "@delvtech/drift"; import { viemAdapter } from "@delvtech/drift-viem"; -import { ReadHyperdrive } from "@delvtech/hyperdrive-js"; -import { publicClient } from "../client"; +import { fixed } from "@delvtech/fixed-point-wasm"; +import { appConfig } from "@delvtech/hyperdrive-appconfig"; +import { + ReadHyperdrive, + ReadWriteHyperdrive, + zapAbi, +} from "@delvtech/hyperdrive-js"; +import { Address, encodePacked, maxInt256 } from "viem"; +import { publicClient, walletClient } from "../client"; -const drift = new Drift(viemAdapter({ publicClient })); +const zapsConfig = appConfig.zaps[707]; +const drift = new Drift(viemAdapter({ publicClient, walletClient })); -const pool = new ReadHyperdrive({ - address: "0x324395D5d835F84a02A75Aa26814f6fD22F25698", +const poolAddress = "0x324395D5d835F84a02A75Aa26814f6fD22F25698"; +const earliestBlock = 20180617n; + +// Write instance for transactions +const writePool = new ReadWriteHyperdrive({ + address: poolAddress, + drift, + earliestBlock, +}); + +// Read instance (includes zapAddress) +const readPool = new ReadHyperdrive({ + address: poolAddress, drift, + zapAddress: zapsConfig.address, + earliestBlock, }); -const kind = await pool.getKind(); -const config = await pool.getPoolConfig(); +const poolContract = drift.contract({ + abi: writePool.contract.abi, + address: poolAddress, +}); + +// SAMPLE ASSET ID AND MATURITY +const assetId: bigint = + 452312848583266388373324160190187140051835877600158453279131187532665187456n; +const maturity = 1754524800n; + +async function openLongPosition() { + try { + const account = walletClient?.account.address as Address; + + const beforeDetails = await readPool.getOpenLongDetails({ + account, + assetId, + options: { block: "latest" }, + }); + console.log("openLongDetails before:", beforeDetails); + + const { result, request } = await publicClient.simulateContract({ + abi: writePool.contract.abi, + address: poolAddress, + functionName: "openLong", + account, + gas: 16125042n, + args: [ + BigInt(30e18), // 30 base tokens (DAI) + 1n, + 1n, + { + asBase: true, + destination: account, + extraData: "0x", + }, + ], + }); + + const openTxHash = await walletClient?.writeContract(request); + if (!openTxHash) throw new Error("No open transaction hash received"); + + const txReceipt = await publicClient.waitForTransactionReceipt({ + hash: openTxHash, + }); + await drift.cache.clear(); + + if (!txReceipt) throw new Error("No open transaction receipt received"); + if (txReceipt.status !== "success") { + console.error("Open Transaction failed:", txReceipt); + throw new Error("Open Transaction failed"); + } + console.log("Open tx receipt status:", txReceipt.status); + + const afterDetails = await readPool.getOpenLongDetails({ + account, + assetId, + }); + console.log("openLongDetails after:", afterDetails); + + // Approve zap (auxiliary) contract to manage the long position + const approvalReceipt = await writePool.contract.write("setApproval", { + amount: maxInt256, + tokenID: assetId, + operator: zapsConfig.address, + }); + console.log("Approval tx hash:", approvalReceipt); + + // Simulate closeLong to preview base amount out + const { result: previewBaseAmountOut } = + await publicClient.simulateContract({ + abi: writePool.contract.abi, + address: poolAddress, + functionName: "closeLong", + account, + args: [ + maturity, + BigInt(40e18), // 40 base tokens (DAI) + 1n, + { + asBase: true, + destination: account, + extraData: "0x", + }, + ], + }); + console.log("Preview base out:", fixed(previewBaseAmountOut).format()); + + // Execute the zap close operation + const swapTx = await walletClient + ?.writeContract({ + abi: zapAbi, + chain: publicClient.chain, + address: zapsConfig.address, + functionName: "closeLongZap", + gas: 16125042n, + args: [ + poolAddress, + maturity, + BigInt(40e18), + 1n, + { + destination: zapsConfig.address, + asBase: true, + extraData: "0x", + }, + { + amountIn: previewBaseAmountOut, + amountOutMinimum: 1n, + deadline: (await publicClient.getBlock()).timestamp + 60n, + path: encodePacked( + ["address", "uint24", "address"], + [ + "0x6B175474E89094C44Da98b954EedeAC495271d0F", // DAI + 100, // 0.01% fee tier + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC + ], + ), + recipient: account, + }, + false, + ], + }) + .catch((err) => { + console.error("closeLongZap failed:", err); + throw err; + }); + if (!swapTx) throw new Error("No close position transaction hash received"); + + const receipt = await publicClient.waitForTransactionReceipt({ + hash: swapTx, + }); + await drift.cache.clear(); + + const openLongDetailsAfterZap = await readPool.getOpenLongDetails({ + account, + assetId, + }); + console.log("openLongDetails after zap close:", openLongDetailsAfterZap); + + return txReceipt; + } catch (error) { + console.error("Failed to open long position:", error); + throw error; + } +} + +async function closeAllPositions() { + const account = walletClient?.account.address as Address; + const block = await publicClient.getBlock(); + + // Example: manually defined positions + const manualPositions = [ + { + assetId: + 452312848583266388373324160190187140051835877600158453279131187532664323456n, + maturity: 1753660800n, + bondAmount: 93219890613508425843n, // Example: 100 bonds + }, + // Add more positions as needed + ]; + + for (const position of manualPositions) { + try { + console.log("\nProcessing position:", position); + + // Approve zap contract to manage the position + const approvalReceipt = await poolContract.write("setApproval", { + amount: maxInt256, + tokenID: position.assetId, + operator: zapsConfig.address, + }); + console.log("Approval tx hash:", approvalReceipt); + + // Preview base out from closeLong + const { result: previewBaseAmountOut } = + await publicClient.simulateContract({ + abi: writePool.contract.abi, + address: poolAddress, + functionName: "closeLong", + account, + args: [ + position.maturity, + position.bondAmount, + 1n, + { + asBase: true, + destination: account, + extraData: "0x", + }, + ], + }); + console.log("Preview base out:", fixed(previewBaseAmountOut).format()); + + // Execute zap close for the position + const swapTx = await walletClient?.writeContract({ + abi: zapAbi, + chain: publicClient.chain, + address: zapsConfig.address, + functionName: "closeLongZap", + args: [ + poolAddress, + position.maturity, + position.bondAmount, + 1n, + { + destination: zapsConfig.address, + asBase: true, + extraData: "0x", + }, + { + amountIn: previewBaseAmountOut, + amountOutMinimum: 1n, + deadline: (await publicClient.getBlock()).timestamp + 60n, + path: encodePacked( + ["address", "uint24", "address"], + [ + "0x6B175474E89094C44Da98b954EedeAC495271d0F", + 100, + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + ], + ), + recipient: account, + }, + false, + ], + }); + const openLongDetails = await writePool.getOpenLongDetails({ + account, + assetId: position.assetId, + }); + console.log("openLongDetails:", openLongDetails); + console.log("Closed position tx hash:", swapTx); + } catch (error) { + console.error(`Failed to close position ${position.assetId}:`, error); + } + } +} + +async function main() { + // Uncomment the function call you need + await openLongPosition(); + // await closeAllPositions(); +} -console.log(` - address: ${pool.address} - kind: ${kind} - baseToken: ${config.baseToken} - sharesToken: ${config.vaultSharesToken} -`); +main().catch(console.error); diff --git a/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts b/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts index 4890df155..3cb3b48ad 100644 --- a/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts +++ b/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts @@ -16,6 +16,7 @@ import { MAX_UINT256, NULL_BYTES, SECONDS_PER_YEAR } from "src/base/constants"; import { ReadContractClientOptions } from "src/drift/ContractClient"; import { ReadClient } from "src/drift/ReadClient"; import { getBlockOrThrow } from "src/drift/getBlockOrThrow"; +import { zapAbi, ZapAbi } from "src/exports"; import { fixed } from "src/fixed-point"; import { HyperdriveAbi, hyperdriveAbi } from "src/hyperdrive/abi"; import { decodeAssetFromTransferSingleEventData } from "src/hyperdrive/assetId/decodeAssetFromTransferSingleEventData"; @@ -43,12 +44,20 @@ import { hyperwasm } from "src/hyperwasm"; import { ReadErc20 } from "src/token/erc20/ReadErc20"; import { ReadEth } from "src/token/eth/ReadEth"; -export interface ReadHyperdriveOptions extends ReadContractClientOptions {} +export interface ReadHyperdriveOptions extends ReadContractClientOptions { + zapAddress?: Address; +} export class ReadHyperdrive extends ReadClient { readonly address: Address; readonly contract: ReadContract; + /** + * The optional address of the zap contract. + */ + readonly zapAddress?: Address; + readonly zapContract?: ReadContract; + /** * @hidden */ @@ -58,16 +67,26 @@ export class ReadHyperdrive extends ReadClient { cache, cacheNamespace, drift, + zapAddress, ...rest }: ReadHyperdriveOptions) { super({ debugName, drift, ...rest }); this.address = address; + this.zapAddress = zapAddress; this.contract = this.drift.contract({ abi: hyperdriveAbi, address, cache, cacheNamespace, }); + if (zapAddress) { + this.zapContract = this.drift.contract({ + abi: zapAbi, + address: zapAddress, + cache, + cacheNamespace, + }); + } } async getKind(): Promise { @@ -770,10 +789,9 @@ export class ReadHyperdrive extends ReadClient { } }); - return Object.values(openLongs).filter((long) => long.bondAmount); + return Object.values(openLongs).filter((long) => long.bondAmount > 0n); } - // TODO: Rename this to getOpenLongs once this function replaces the existing getOpenLongs async getOpenLongPositions({ account, options, @@ -790,72 +808,50 @@ export class ReadHyperdrive extends ReadClient { toBlock: options?.block, }); - const longsReceived = transfersReceived.filter((event) => { - const { assetType } = decodeAssetFromTransferSingleEventData( - event.data as `0x${string}`, - ); - return assetType === "LONG"; - }); - - const longsSent = transfersSent.filter((event) => { + const isLongEvent = ( + event: ContractEvent, + ) => { const { assetType } = decodeAssetFromTransferSingleEventData( event.data as `0x${string}`, ); return assetType === "LONG"; - }); + }; + const longsReceived = transfersReceived.filter(isLongEvent); + const longsSent = transfersSent.filter(isLongEvent); - // 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( + const orderedEvents = [...longsReceived, ...longsSent].sort( (a, b) => Number(a.blockNumber) - Number(b.blockNumber), ); const openLongs: Record = {}; - orderedLongEvents.forEach((event) => { - const assetId = event.args.id.toString(); - - const long: OpenLongPositionReceivedWithoutDetails = openLongs[ - assetId - ] || { - assetId, - maturity: decodeAssetFromTransferSingleEventData( - event.data as `0x${string}`, - ).timestamp, + for (const event of orderedEvents) { + const assetIdStr = event.args.id.toString(); + const assetData = decodeAssetFromTransferSingleEventData( + event.data as `0x${string}`, + ); + + const position = openLongs[assetIdStr] ?? { + assetId: event.args.id, + maturity: assetData.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; + if (event.args.to === account) { + position.value += event.args.value; + } else if (event.args.from === account) { + position.value -= event.args.value; + if (position.value === 0n) { + delete openLongs[assetIdStr]; + continue; } - // 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); - } + openLongs[assetIdStr] = position; + } + return Object.values(openLongs).filter((long) => long.value > 0n); + } async getOpenLongDetails({ assetId, account, @@ -864,47 +860,55 @@ export class ReadHyperdrive extends ReadClient { assetId: bigint; account: `0x${string}`; options?: ContractReadOptions; - }): Promise { - const allLongPositions = await this.getOpenLongPositions({ - account, - options, - }); - - const longPosition = allLongPositions.find((p) => p.assetId === assetId); - - if (!longPosition) { + }): Promise { + // Ensure the account has an open long position for this asset. + const allPositions = await this.getOpenLongPositions({ account, options }); + const position = allPositions.find((p) => p.assetId === assetId); + if (!position) { throw new HyperdriveSdkError( `No position with asset id: ${assetId} found for account ${account}`, ); } + // Fetch the standard OpenLong and CloseLong events. const openLongEvents = await this.contract.getEvents("OpenLong", { - filter: { trader: account }, + filter: { trader: account, assetId }, }); - const closeLongEvents = await this.contract.getEvents("CloseLong", { - filter: { trader: account }, + filter: { trader: account, assetId }, }); - const allOpenLongDetails = this._calcOpenLongs({ - openLongEvents, - closeLongEvents, + // Handle transfers sent to the auxiliary contract. + const transfersSentToAux = await this.contract.getEvents("TransferSingle", { + filter: { from: account, to: this.zapAddress }, + toBlock: options?.block, }); - 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; + if (transfersSentToAux.length) { + const accountTxHashes = transfersSentToAux.map( + ({ transactionHash }) => transactionHash, + ); + // Fetch CloseLong events emitted by the auxiliary contract in the relevant block range. + const allAuxCloses = await this.contract.getEvents("CloseLong", { + filter: { trader: this.zapAddress, assetId }, + fromBlock: transfersSentToAux[0].blockNumber, + toBlock: transfersSentToAux.at(-1)?.blockNumber, + }); + // Only include events that occurred in the same transactions. + const auxClosesForAccount = allAuxCloses.filter(({ transactionHash }) => + accountTxHashes.includes(transactionHash as `0x${string}`), + ); + for (const event of auxClosesForAccount) { + closeLongEvents.push(event); + } } - return openLongDetails; + // Calculate net open long using the helper. + const calculatedLongs = this._calcOpenLongs({ + openLongEvents, + closeLongEvents, + }); + return calculatedLongs[0]; } /** * @deprecated Use ReadHyperdrive.getOpenLongPositions and ReadHyperdrive.getOpenLongDetails instead to retrieve all longs opened or received by a user. From c9da6057a55c577a370806b1cf4805dfd8f543e8 Mon Sep 17 00:00:00 2001 From: jackburrus Date: Fri, 7 Feb 2025 10:30:12 -0700 Subject: [PATCH 2/6] update viem and public client --- apps/sdk-sandbox/client.ts | 15 +++++++++++++++ apps/sdk-sandbox/package.json | 2 +- apps/sdk-sandbox/scripts/example.ts | 10 +++++++--- yarn.lock | 2 +- 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/apps/sdk-sandbox/client.ts b/apps/sdk-sandbox/client.ts index 482bc77d1..0e97099d4 100644 --- a/apps/sdk-sandbox/client.ts +++ b/apps/sdk-sandbox/client.ts @@ -8,6 +8,20 @@ import { privateKeyToAccount } from "viem/accounts"; export const publicClient: PublicClient = createPublicClient({ transport: http(process.env.RPC_URL), + chain: { + id: 707, + name: "Zaps Cloudchain", + nativeCurrency: { + name: "ETH", + symbol: "ETH", + decimals: 18, + }, + rpcUrls: { + default: { + http: [process.env.RPC_URL!], + }, + }, + }, }); export const walletClient = process.env.WALLET_PRIVATE_KEY @@ -15,6 +29,7 @@ export const walletClient = process.env.WALLET_PRIVATE_KEY account: privateKeyToAccount( process.env.WALLET_PRIVATE_KEY as `0x${string}`, ), + chain: publicClient.chain, transport: http(process.env.RPC_URL), }) : undefined; diff --git a/apps/sdk-sandbox/package.json b/apps/sdk-sandbox/package.json index ed83ac9ce..dbb288491 100644 --- a/apps/sdk-sandbox/package.json +++ b/apps/sdk-sandbox/package.json @@ -17,7 +17,7 @@ "@delvtech/hyperdrive-appconfig": "*", "@delvtech/hyperdrive-js": "*", "@delvtech/hyperdrive-wasm": "*", - "viem": "^2.9.2" + "viem": "^2.22.19" }, "devDependencies": { "@types/node": "^20.14.2", diff --git a/apps/sdk-sandbox/scripts/example.ts b/apps/sdk-sandbox/scripts/example.ts index 2c209ecb2..7d3757375 100644 --- a/apps/sdk-sandbox/scripts/example.ts +++ b/apps/sdk-sandbox/scripts/example.ts @@ -38,8 +38,8 @@ const poolContract = drift.contract({ // SAMPLE ASSET ID AND MATURITY const assetId: bigint = - 452312848583266388373324160190187140051835877600158453279131187532665187456n; -const maturity = 1754524800n; + 452312848583266388373324160190187140051835877600158453279131187532665273856n; +const maturity = 1754611200n; async function openLongPosition() { try { @@ -55,6 +55,7 @@ async function openLongPosition() { const { result, request } = await publicClient.simulateContract({ abi: writePool.contract.abi, address: poolAddress, + chain: publicClient.chain, functionName: "openLong", account, gas: 16125042n, @@ -70,7 +71,9 @@ async function openLongPosition() { ], }); - const openTxHash = await walletClient?.writeContract(request); + const openTxHash = await walletClient?.writeContract({ + ...request, + }); if (!openTxHash) throw new Error("No open transaction hash received"); const txReceipt = await publicClient.waitForTransactionReceipt({ @@ -104,6 +107,7 @@ async function openLongPosition() { await publicClient.simulateContract({ abi: writePool.contract.abi, address: poolAddress, + chain: publicClient.chain, functionName: "closeLong", account, args: [ diff --git a/yarn.lock b/yarn.lock index c61a85b84..3f2e37136 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17878,7 +17878,7 @@ vfile@^6.0.0, vfile@^6.0.1: unist-util-stringify-position "^4.0.0" vfile-message "^4.0.0" -viem@2.9.2, viem@^2.1.1, viem@^2.22.19, viem@^2.7.8, viem@^2.9.2: +viem@2.9.2, viem@^2.1.1, viem@^2.22.19, viem@^2.7.8: version "2.9.2" resolved "https://registry.yarnpkg.com/viem/-/viem-2.9.2.tgz#29bd2425222be136bf969a90dd3a8d6032221904" integrity sha512-GRakUTNiYE9W+vL+Be9JkQfzWnkczerHtSpEe2JR/jEGTYZAs4shrA4WLgiaVCI9JxpnduZhQfRWNvy2dlyP2g== From 8e8e73d1acefd615bc641035dff55864d9f5ad35 Mon Sep 17 00:00:00 2001 From: jackburrus Date: Fri, 7 Feb 2025 11:45:14 -0700 Subject: [PATCH 3/6] rename to auxiliaryContractAddress --- apps/sdk-sandbox/scripts/example.ts | 12 ++--- .../src/hyperdrive/ReadHyperdrive.ts | 47 +++++++------------ 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/apps/sdk-sandbox/scripts/example.ts b/apps/sdk-sandbox/scripts/example.ts index 7d3757375..046a0d62b 100644 --- a/apps/sdk-sandbox/scripts/example.ts +++ b/apps/sdk-sandbox/scripts/example.ts @@ -27,7 +27,7 @@ const writePool = new ReadWriteHyperdrive({ const readPool = new ReadHyperdrive({ address: poolAddress, drift, - zapAddress: zapsConfig.address, + auxiliaryContractAddress: zapsConfig.address, earliestBlock, }); @@ -190,9 +190,9 @@ async function closeAllPositions() { const manualPositions = [ { assetId: - 452312848583266388373324160190187140051835877600158453279131187532664323456n, - maturity: 1753660800n, - bondAmount: 93219890613508425843n, // Example: 100 bonds + 452312848583266388373324160190187140051835877600158453279131187532665273856n, + maturity: 1754611200n, + bondAmount: 104902143926345435824n, // Example: 100 bonds }, // Add more positions as needed ]; @@ -276,8 +276,8 @@ async function closeAllPositions() { async function main() { // Uncomment the function call you need - await openLongPosition(); - // await closeAllPositions(); + // await openLongPosition(); + await closeAllPositions(); } main().catch(console.error); diff --git a/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts b/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts index 3cb3b48ad..4d11b41ee 100644 --- a/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts +++ b/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts @@ -16,7 +16,6 @@ import { MAX_UINT256, NULL_BYTES, SECONDS_PER_YEAR } from "src/base/constants"; import { ReadContractClientOptions } from "src/drift/ContractClient"; import { ReadClient } from "src/drift/ReadClient"; import { getBlockOrThrow } from "src/drift/getBlockOrThrow"; -import { zapAbi, ZapAbi } from "src/exports"; import { fixed } from "src/fixed-point"; import { HyperdriveAbi, hyperdriveAbi } from "src/hyperdrive/abi"; import { decodeAssetFromTransferSingleEventData } from "src/hyperdrive/assetId/decodeAssetFromTransferSingleEventData"; @@ -45,7 +44,7 @@ import { ReadErc20 } from "src/token/erc20/ReadErc20"; import { ReadEth } from "src/token/eth/ReadEth"; export interface ReadHyperdriveOptions extends ReadContractClientOptions { - zapAddress?: Address; + auxiliaryContractAddress?: Address; } export class ReadHyperdrive extends ReadClient { @@ -53,10 +52,9 @@ export class ReadHyperdrive extends ReadClient { readonly contract: ReadContract; /** - * The optional address of the zap contract. + * The optional address of an auxiliary contract such as a zap contract. */ - readonly zapAddress?: Address; - readonly zapContract?: ReadContract; + readonly auxiliaryContractAddress?: Address; /** * @hidden @@ -67,26 +65,18 @@ export class ReadHyperdrive extends ReadClient { cache, cacheNamespace, drift, - zapAddress, + auxiliaryContractAddress, ...rest }: ReadHyperdriveOptions) { super({ debugName, drift, ...rest }); this.address = address; - this.zapAddress = zapAddress; + this.auxiliaryContractAddress = auxiliaryContractAddress; this.contract = this.drift.contract({ abi: hyperdriveAbi, address, cache, cacheNamespace, }); - if (zapAddress) { - this.zapContract = this.drift.contract({ - abi: zapAbi, - address: zapAddress, - cache, - cacheNamespace, - }); - } } async getKind(): Promise { @@ -789,7 +779,7 @@ export class ReadHyperdrive extends ReadClient { } }); - return Object.values(openLongs).filter((long) => long.bondAmount > 0n); + return Object.values(openLongs).filter((long) => long.bondAmount); } async getOpenLongPositions({ @@ -819,6 +809,8 @@ export class ReadHyperdrive extends ReadClient { const longsReceived = transfersReceived.filter(isLongEvent); const longsSent = transfersSent.filter(isLongEvent); + // 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 orderedEvents = [...longsReceived, ...longsSent].sort( (a, b) => Number(a.blockNumber) - Number(b.blockNumber), ); @@ -827,14 +819,11 @@ export class ReadHyperdrive extends ReadClient { {}; for (const event of orderedEvents) { - const assetIdStr = event.args.id.toString(); - const assetData = decodeAssetFromTransferSingleEventData( - event.data as `0x${string}`, - ); - - const position = openLongs[assetIdStr] ?? { + const position = openLongs[event.args.id.toString()] ?? { assetId: event.args.id, - maturity: assetData.timestamp, + maturity: decodeAssetFromTransferSingleEventData( + event.data as `0x${string}`, + ).timestamp, value: 0n, }; @@ -843,14 +832,14 @@ export class ReadHyperdrive extends ReadClient { } else if (event.args.from === account) { position.value -= event.args.value; if (position.value === 0n) { - delete openLongs[assetIdStr]; + delete openLongs[event.args.id.toString()]; continue; } } - openLongs[assetIdStr] = position; + openLongs[event.args.id.toString()] = position; } - return Object.values(openLongs).filter((long) => long.value > 0n); + return Object.values(openLongs).filter((long) => long.value); } async getOpenLongDetails({ assetId, @@ -878,9 +867,9 @@ export class ReadHyperdrive extends ReadClient { filter: { trader: account, assetId }, }); - // Handle transfers sent to the auxiliary contract. + // Handle transfers sent to the contract. const transfersSentToAux = await this.contract.getEvents("TransferSingle", { - filter: { from: account, to: this.zapAddress }, + filter: { from: account, to: this.auxiliaryContractAddress }, toBlock: options?.block, }); @@ -890,7 +879,7 @@ export class ReadHyperdrive extends ReadClient { ); // Fetch CloseLong events emitted by the auxiliary contract in the relevant block range. const allAuxCloses = await this.contract.getEvents("CloseLong", { - filter: { trader: this.zapAddress, assetId }, + filter: { trader: this.auxiliaryContractAddress, assetId }, fromBlock: transfersSentToAux[0].blockNumber, toBlock: transfersSentToAux.at(-1)?.blockNumber, }); From 9aff3a3fa8721d9f028c064c03a09852508a7be3 Mon Sep 17 00:00:00 2001 From: jackburrus Date: Fri, 7 Feb 2025 11:47:25 -0700 Subject: [PATCH 4/6] update packages --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index cb047cb77..a0a0893c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20888,7 +20888,7 @@ vfile@^6.0.0, vfile@^6.0.1: unist-util-stringify-position "^4.0.0" vfile-message "^4.0.0" -viem@2.9.2, viem@^2.1.1, viem@^2.22.19, viem@^2.22.21, viem@^2.7.8, viem@^2.9.2: +viem@2.9.2, viem@^2.1.1, viem@^2.22.19, viem@^2.22.21, viem@^2.7.8: version "2.9.2" resolved "https://registry.yarnpkg.com/viem/-/viem-2.9.2.tgz#29bd2425222be136bf969a90dd3a8d6032221904" integrity sha512-GRakUTNiYE9W+vL+Be9JkQfzWnkczerHtSpEe2JR/jEGTYZAs4shrA4WLgiaVCI9JxpnduZhQfRWNvy2dlyP2g== From 3218705ab8d820cb384bd4424c9d14380983d904 Mon Sep 17 00:00:00 2001 From: jackburrus Date: Fri, 7 Feb 2025 11:52:39 -0700 Subject: [PATCH 5/6] update yarn lock --- yarn.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index a0a0893c7..cb047cb77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20888,7 +20888,7 @@ vfile@^6.0.0, vfile@^6.0.1: unist-util-stringify-position "^4.0.0" vfile-message "^4.0.0" -viem@2.9.2, viem@^2.1.1, viem@^2.22.19, viem@^2.22.21, viem@^2.7.8: +viem@2.9.2, viem@^2.1.1, viem@^2.22.19, viem@^2.22.21, viem@^2.7.8, viem@^2.9.2: version "2.9.2" resolved "https://registry.yarnpkg.com/viem/-/viem-2.9.2.tgz#29bd2425222be136bf969a90dd3a8d6032221904" integrity sha512-GRakUTNiYE9W+vL+Be9JkQfzWnkczerHtSpEe2JR/jEGTYZAs4shrA4WLgiaVCI9JxpnduZhQfRWNvy2dlyP2g== From a68de2554a79fc14efd21a5471d54986d2c84a05 Mon Sep 17 00:00:00 2001 From: jackburrus Date: Fri, 7 Feb 2025 13:56:41 -0700 Subject: [PATCH 6/6] address PR comments and add changeset --- .changeset/wet-tools-suffer.md | 5 ++++ .../src/hyperdrive/ReadHyperdrive.ts | 29 ++++++++++--------- 2 files changed, 20 insertions(+), 14 deletions(-) create mode 100644 .changeset/wet-tools-suffer.md diff --git a/.changeset/wet-tools-suffer.md b/.changeset/wet-tools-suffer.md new file mode 100644 index 000000000..3d12dad19 --- /dev/null +++ b/.changeset/wet-tools-suffer.md @@ -0,0 +1,5 @@ +--- +"@delvtech/hyperdrive-js": patch +--- + +Add zap logic to ReadHyperdrive diff --git a/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts b/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts index 4d11b41ee..f3dd225eb 100644 --- a/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts +++ b/packages/hyperdrive-js/src/hyperdrive/ReadHyperdrive.ts @@ -44,7 +44,7 @@ import { ReadErc20 } from "src/token/erc20/ReadErc20"; import { ReadEth } from "src/token/eth/ReadEth"; export interface ReadHyperdriveOptions extends ReadContractClientOptions { - auxiliaryContractAddress?: Address; + zapContractAddress?: Address; } export class ReadHyperdrive extends ReadClient { @@ -54,7 +54,7 @@ export class ReadHyperdrive extends ReadClient { /** * The optional address of an auxiliary contract such as a zap contract. */ - readonly auxiliaryContractAddress?: Address; + readonly zapContractAddress?: Address; /** * @hidden @@ -65,12 +65,12 @@ export class ReadHyperdrive extends ReadClient { cache, cacheNamespace, drift, - auxiliaryContractAddress, + zapContractAddress, ...rest }: ReadHyperdriveOptions) { super({ debugName, drift, ...rest }); this.address = address; - this.auxiliaryContractAddress = auxiliaryContractAddress; + this.zapContractAddress = zapContractAddress; this.contract = this.drift.contract({ abi: hyperdriveAbi, address, @@ -798,14 +798,6 @@ export class ReadHyperdrive extends ReadClient { toBlock: options?.block, }); - const isLongEvent = ( - event: ContractEvent, - ) => { - const { assetType } = decodeAssetFromTransferSingleEventData( - event.data as `0x${string}`, - ); - return assetType === "LONG"; - }; const longsReceived = transfersReceived.filter(isLongEvent); const longsSent = transfersSent.filter(isLongEvent); @@ -869,7 +861,7 @@ export class ReadHyperdrive extends ReadClient { // Handle transfers sent to the contract. const transfersSentToAux = await this.contract.getEvents("TransferSingle", { - filter: { from: account, to: this.auxiliaryContractAddress }, + filter: { from: account, to: this.zapContractAddress }, toBlock: options?.block, }); @@ -879,7 +871,7 @@ export class ReadHyperdrive extends ReadClient { ); // Fetch CloseLong events emitted by the auxiliary contract in the relevant block range. const allAuxCloses = await this.contract.getEvents("CloseLong", { - filter: { trader: this.auxiliaryContractAddress, assetId }, + filter: { trader: this.zapContractAddress, assetId }, fromBlock: transfersSentToAux[0].blockNumber, toBlock: transfersSentToAux.at(-1)?.blockNumber, }); @@ -2069,3 +2061,12 @@ function calculateApyFromPrice({ const yearFraction = fixed(timeFrame).div(SECONDS_PER_YEAR); return priceRatio.pow(fixed(1e18).div(yearFraction)).sub(1e18).bigint; } + +function isLongEvent( + event: ContractEvent, +): boolean { + const { assetType } = decodeAssetFromTransferSingleEventData( + event.data as `0x${string}`, + ); + return assetType === "LONG"; +}