From 665b55756232846ea5cbace2e410785f16ab918c Mon Sep 17 00:00:00 2001 From: Edward Date: Thu, 15 May 2025 10:25:38 +0800 Subject: [PATCH 1/5] feat: use zpl sdk in zpl client --- cspell.json | 3 +- package-lock.json | 128 +++++- package.json | 10 +- src/bitcoin/index.ts | 1 - src/components/CryptoInput/Dropdown.tsx | 1 - src/components/Mint/Modals/ConfirmDeposit.tsx | 6 +- .../Mint/Modals/ConfirmWithdraw.tsx | 88 ++-- .../MobileMenu/MobileMenuButton.tsx | 1 - src/components/PortfolioV2/Modals/Redeem.tsx | 17 +- .../PortfolioV2/Overview/PortfolioDetails.tsx | 2 +- .../PortfolioTransactionsDeposits.tsx | 2 +- .../Widgets/MintWidget/Withdraw.tsx | 2 +- src/hooks/zpl/useColdReserveBuckets.ts | 2 +- src/hooks/zpl/useHotReserveBucketActions.ts | 50 ++- src/hooks/zpl/useHotReserveBucketsByOwner.ts | 4 +- src/hooks/zpl/usePositions.ts | 13 +- src/hooks/zpl/useTwoWayPegConfiguration.ts | 2 +- src/types/zplClient.ts | 189 -------- src/zplClient/account.ts | 409 ------------------ src/zplClient/index.ts | 210 ++------- src/zplClient/instruction.ts | 349 --------------- src/zplClient/rpcClient.ts | 63 --- 22 files changed, 276 insertions(+), 1276 deletions(-) delete mode 100644 src/types/zplClient.ts delete mode 100644 src/zplClient/account.ts delete mode 100644 src/zplClient/instruction.ts delete mode 100644 src/zplClient/rpcClient.ts diff --git a/cspell.json b/cspell.json index f928b91a..aa000416 100644 --- a/cspell.json +++ b/cspell.json @@ -95,7 +95,8 @@ "Schnorr", "nums", "cobo", - "Xonly" + "Xonly", + "pdas" ], // flagWords - list of words to be always considered incorrect // This is useful for offensive words and common spelling errors. diff --git a/package-lock.json b/package-lock.json index 2e66599c..46e54d69 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "@bitcoinerlab/secp256k1": "^1.1.1", "@cloudflare/next-on-pages": "^1.13.10", - "@coral-xyz/borsh": "^0.30.1", + "@coral-xyz/borsh": "^0.31.1", "@mdx-js/loader": "^3.0.1", "@mdx-js/react": "^3.0.1", "@next/mdx": "^14.2.3", @@ -19,20 +19,18 @@ "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-tooltip": "^1.1.4", "@sentry/nextjs": "^8.26.0", - "@solana/buffer-layout": "^4.0.1", - "@solana/spl-token": "^0.4.6", + "@solana/spl-token": "^0.4.13", "@solana/wallet-adapter-base": "^0.9.23", "@solana/wallet-adapter-react": "^0.15.35", "@solana/wallet-adapter-react-ui": "^0.9.35", "@solana/wallet-adapter-wallets": "^0.19.32", - "@solana/web3.js": "^1.91.8", + "@solana/web3.js": "^1.98.2", "axios": "^1.7.2", "bech32": "^2.0.0", "bignumber.js": "^9.1.2", "bip32": "^4.0.0", "bip65": "^1.0.3", "bitcoinjs-lib": "^6.1.5", - "bs58": "^5.0.0", "class-variance-authority": "^0.7.0", "classnames": "^2.5.1", "clsx": "^2.1.1", @@ -43,7 +41,6 @@ "ecpair": "^2.1.0", "framer-motion": "^11.1.7", "fs": "^0.0.1-security", - "js-sha256": "^0.11.0", "lucide-react": "^0.456.0", "motion": "^12.0.11", "next": "^14.2.3", @@ -63,6 +60,7 @@ "uint8array-tools": "^0.0.9", "usehooks-ts": "^3.1.0", "zod": "^3.23.8", + "zpl-sdk-js": "file:../zpl-sdk-js", "zustand": "^4.5.2" }, "devDependencies": { @@ -96,6 +94,39 @@ "node": "20.18.3" } }, + "../zpl-sdk-js": { + "version": "0.1.0", + "license": "ISC", + "dependencies": { + "@coral-xyz/borsh": "^0.31.1", + "@solana/buffer-layout": "^4.0.1", + "bn.js": "^5.2.2", + "bs58": "^6.0.0", + "js-sha256": "^0.11.0" + }, + "devDependencies": { + "@commitlint/cli": "^19.8.0", + "@commitlint/config-conventional": "^19.8.0", + "@eslint/js": "^9.26.0", + "@types/bn.js": "^5.1.6", + "@types/node": "^22.14.1", + "cspell": "^9.0.0", + "eslint": "^9.26.0", + "eslint-config-prettier": "^10.1.3", + "eslint-plugin-prettier": "^5.4.0", + "eslint-plugin-simple-import-sort": "^12.1.1", + "husky": "^9.1.7", + "lint-staged": "^15.5.2", + "prettier": "^3.5.3", + "rolldown": "1.0.0-beta.7", + "typescript": "^5.8.3", + "typescript-eslint": "^8.32.0" + }, + "peerDependencies": { + "@solana/spl-token": "^0.4.13", + "@solana/web3.js": "^1.98.2" + } + }, "node_modules/@adraffy/ens-normalize": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz", @@ -2698,9 +2729,9 @@ } }, "node_modules/@coral-xyz/borsh": { - "version": "0.30.1", - "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.30.1.tgz", - "integrity": "sha512-aaxswpPrCFKl8vZTbxLssA2RvwX2zmKLlRCIktJOwW+VpVwYtXRtlWiIP+c2pPRKneiTiWCN2GEMSH9j1zTlWQ==", + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/@coral-xyz/borsh/-/borsh-0.31.1.tgz", + "integrity": "sha512-9N8AU9F0ubriKfNE3g1WF0/4dtlGXoBN/hd1PvbNBamBNwRgHxH4P+o3Zt7rSEloW1HUs6LfZEchlx9fW7POYw==", "license": "Apache-2.0", "dependencies": { "bn.js": "^5.1.2", @@ -2710,7 +2741,7 @@ "node": ">=10" }, "peerDependencies": { - "@solana/web3.js": "^1.68.0" + "@solana/web3.js": "^1.69.0" } }, "node_modules/@cspell/cspell-bundled-dicts": { @@ -12480,17 +12511,17 @@ } }, "node_modules/@solana/web3.js": { - "version": "1.98.0", - "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.0.tgz", - "integrity": "sha512-nz3Q5OeyGFpFCR+erX2f6JPt3sKhzhYcSycBCSPkWjzSVDh/Rr1FqTVMRe58FKO16/ivTUcuJjeS5MyBvpkbzA==", + "version": "1.98.2", + "resolved": "https://registry.npmjs.org/@solana/web3.js/-/web3.js-1.98.2.tgz", + "integrity": "sha512-BqVwEG+TaG2yCkBMbD3C4hdpustR4FpuUFRPUmqRZYYlPI9Hg4XMWxHWOWRzHE9Lkc9NDjzXFX7lDXSgzC7R1A==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.0", "@noble/curves": "^1.4.2", "@noble/hashes": "^1.4.0", "@solana/buffer-layout": "^4.0.1", + "@solana/codecs-numbers": "^2.1.0", "agentkeepalive": "^4.5.0", - "bigint-buffer": "^1.1.5", "bn.js": "^5.2.1", "borsh": "^0.7.0", "bs58": "^4.0.1", @@ -12502,6 +12533,56 @@ "superstruct": "^2.0.2" } }, + "node_modules/@solana/web3.js/node_modules/@solana/codecs-core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.1.0.tgz", + "integrity": "sha512-SR7pKtmJBg2mhmkel2NeHA1pz06QeQXdMv8WJoIR9m8F/hw80K/612uaYbwTt2nkK0jg/Qn/rNSd7EcJ4SBGjw==", + "license": "MIT", + "dependencies": { + "@solana/errors": "2.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/web3.js/node_modules/@solana/codecs-numbers": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.1.0.tgz", + "integrity": "sha512-XMu4yw5iCgQnMKsxSWPPOrGgtaohmupN3eyAtYv3K3C/MJEc5V90h74k5B1GUCiHvcrdUDO9RclNjD9lgbjFag==", + "license": "MIT", + "dependencies": { + "@solana/codecs-core": "2.1.0", + "@solana/errors": "2.1.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, + "node_modules/@solana/web3.js/node_modules/@solana/errors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.1.0.tgz", + "integrity": "sha512-l+GxAv0Ar4d3c3PlZdA9G++wFYZREEbbRyAFP8+n8HSg0vudCuzogh/13io6hYuUhG/9Ve8ARZNamhV7UScKNw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.3.0", + "commander": "^13.1.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5" + } + }, "node_modules/@solana/web3.js/node_modules/bs58": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", @@ -12511,6 +12592,15 @@ "base-x": "^3.0.2" } }, + "node_modules/@solana/web3.js/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@solflare-wallet/metamask-sdk": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@solflare-wallet/metamask-sdk/-/metamask-sdk-1.0.3.tgz", @@ -25427,12 +25517,6 @@ "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", "license": "BSD-3-Clause" }, - "node_modules/js-sha256": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz", - "integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==", - "license": "MIT" - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -35572,6 +35656,10 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zpl-sdk-js": { + "resolved": "../zpl-sdk-js", + "link": true + }, "node_modules/zustand": { "version": "4.5.6", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", diff --git a/package.json b/package.json index 85503858..00c4a88d 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "dependencies": { "@bitcoinerlab/secp256k1": "^1.1.1", "@cloudflare/next-on-pages": "^1.13.10", - "@coral-xyz/borsh": "^0.30.1", + "@coral-xyz/borsh": "^0.31.1", "@mdx-js/loader": "^3.0.1", "@mdx-js/react": "^3.0.1", "@next/mdx": "^14.2.3", @@ -38,20 +38,18 @@ "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-tooltip": "^1.1.4", "@sentry/nextjs": "^8.26.0", - "@solana/buffer-layout": "^4.0.1", - "@solana/spl-token": "^0.4.6", + "@solana/spl-token": "^0.4.13", "@solana/wallet-adapter-base": "^0.9.23", "@solana/wallet-adapter-react": "^0.15.35", "@solana/wallet-adapter-react-ui": "^0.9.35", "@solana/wallet-adapter-wallets": "^0.19.32", - "@solana/web3.js": "^1.91.8", + "@solana/web3.js": "^1.98.2", "axios": "^1.7.2", "bech32": "^2.0.0", "bignumber.js": "^9.1.2", "bip32": "^4.0.0", "bip65": "^1.0.3", "bitcoinjs-lib": "^6.1.5", - "bs58": "^5.0.0", "class-variance-authority": "^0.7.0", "classnames": "^2.5.1", "clsx": "^2.1.1", @@ -62,7 +60,6 @@ "ecpair": "^2.1.0", "framer-motion": "^11.1.7", "fs": "^0.0.1-security", - "js-sha256": "^0.11.0", "lucide-react": "^0.456.0", "motion": "^12.0.11", "next": "^14.2.3", @@ -82,6 +79,7 @@ "uint8array-tools": "^0.0.9", "usehooks-ts": "^3.1.0", "zod": "^3.23.8", + "zpl-sdk-js": "file:../zpl-sdk-js", "zustand": "^4.5.2" }, "devDependencies": { diff --git a/src/bitcoin/index.ts b/src/bitcoin/index.ts index 88eb3e1e..4ffcff0a 100644 --- a/src/bitcoin/index.ts +++ b/src/bitcoin/index.ts @@ -10,7 +10,6 @@ import { BitcoinXOnlyPublicKey } from "@/types/wallet"; export const UNLOCK_BLOCK_HEIGHT = 4320; // 144 blocks (1 day) * 30 const SATOSHIS_PER_BTC = new BN(1e8); - const TX_INPUT_VBYTE: number = 58; const TX_BASIC_VBYTE: number = 10; const TX_OUTPUT_VBYTE: number = 44; diff --git a/src/components/CryptoInput/Dropdown.tsx b/src/components/CryptoInput/Dropdown.tsx index 91dd5ff6..b88f53ef 100644 --- a/src/components/CryptoInput/Dropdown.tsx +++ b/src/components/CryptoInput/Dropdown.tsx @@ -36,7 +36,6 @@ export default function InputDropdown({ >
{dropdownOptions.map((option) => { - console.log(option); return (
- bucket.guardianSetting.toBase58() === networkConfig.guardianSetting + bucket.reserveSetting.toBase58() === networkConfig.guardianSetting ); if (!targetHotReserveBucket) throw new Error("Wrong guardian setting"); @@ -172,7 +172,7 @@ export default function ConfirmDepositModal({ const transaction: Interaction = { status: InteractionStatus.BitcoinDepositToHotReserve, - interaction_id: zplClient + interaction_id: zplClient.twoWayPeg.pdas .deriveInteraction(Buffer.from(txId, "hex"), new BN(0)) .toBase58(), interaction_type: InteractionType.Deposit, diff --git a/src/components/Mint/Modals/ConfirmWithdraw.tsx b/src/components/Mint/Modals/ConfirmWithdraw.tsx index f3c6b366..8ea44d16 100644 --- a/src/components/Mint/Modals/ConfirmWithdraw.tsx +++ b/src/components/Mint/Modals/ConfirmWithdraw.tsx @@ -8,6 +8,7 @@ import { BN } from "bn.js"; import classNames from "classnames"; import { AnimatePresence, motion } from "framer-motion"; import { useState } from "react"; +import { Position } from "zpl-sdk-js/liquidity-management/types"; import { convertP2trToTweakedXOnlyPubkey } from "@/bitcoin"; import Button from "@/components/Button/Button"; @@ -25,7 +26,6 @@ import { useZplClient } from "@/contexts/ZplClientProvider"; import useTwoWayPegGuardianSettings from "@/hooks/hermes/useTwoWayPegGuardianSettings"; import { InteractionType } from "@/types/api"; import { Chain } from "@/types/network"; -import { Position } from "@/types/zplClient"; import { BTC_DECIMALS } from "@/utils/constant"; import { shortenString } from "@/utils/format"; import { notifyError, notifyTx } from "@/utils/notification"; @@ -94,7 +94,7 @@ export default function ConfirmWithdraw({ ); const twoWayPegConfiguration = - await zplClient.getTwoWayPegConfiguration(); + await zplClient.twoWayPeg.accounts.getConfiguration(); const ixs: TransactionInstruction[] = []; @@ -119,22 +119,36 @@ export default function ConfirmWithdraw({ const twoWayPegGuardianSetting = twoWayPegGuardianSettings.find( (setting) => - zplClient - .deriveLiquidityManagementGuardianSettingAddress( - new PublicKey(setting.address) - ) - .toBase58() === position.guardianSetting.toBase58() + zplClient.liquidityManagement.pdas + .deriveVaultSettingAddress(new PublicKey(setting.address)) + .toBase58() === position.vaultSetting.toBase58() ); if (!twoWayPegGuardianSetting) return; - const withdrawalRequestIx = zplClient.constructAddWithdrawalRequestIx( - solanaPubkey, - amountToWithdraw, - convertP2trToTweakedXOnlyPubkey(selectedWallet), - new PublicKey(twoWayPegGuardianSetting.address), - twoWayPegConfiguration.layerFeeCollector - ); + const vaultSettingPda = + zplClient.liquidityManagement.pdas.deriveVaultSettingAddress( + new PublicKey(twoWayPegGuardianSetting.address) + ); + + const currentTimestamp = new BN(Date.now() / 1000); + + const withdrawalRequestIx = + zplClient.twoWayPeg.instructions.buildAddWithdrawalRequestIx( + amountToWithdraw, + currentTimestamp, + convertP2trToTweakedXOnlyPubkey(selectedWallet), + solanaPubkey, + twoWayPegConfiguration.layerFeeCollector, + new PublicKey(twoWayPegGuardianSetting.address), + zplClient.liquidityManagementProgramId, + zplClient.liquidityManagement.pdas.deriveConfigurationAddress(), + vaultSettingPda, + zplClient.liquidityManagement.pdas.derivePositionAddress( + vaultSettingPda, + solanaPubkey + ) + ); ixs.push(withdrawalRequestIx); remainingAmount = remainingAmount.sub(amountToWithdraw); @@ -150,7 +164,7 @@ export default function ConfirmWithdraw({ ); const splTokenVaultAuthority = - zplClient.deriveSplTokenVaultAuthorityAddress( + zplClient.liquidityManagement.pdas.deriveSplTokenVaultAuthorityAddress( new PublicKey(twoWayPegGuardianSetting.address) ); @@ -175,7 +189,7 @@ export default function ConfirmWithdraw({ address: twoWayPegGuardianSetting.address, remainingStoreQuota, liquidityManagementGuardianSetting: - zplClient.deriveLiquidityManagementGuardianSettingAddress( + zplClient.liquidityManagement.pdas.deriveVaultSettingAddress( new PublicKey(twoWayPegGuardianSetting.address) ), }; @@ -194,18 +208,37 @@ export default function ConfirmWithdraw({ remainingAmount ); - const storeIx = zplClient.constructStoreIx( - withdrawAmountBN, - new PublicKey(twoWayPegGuardian.address) - ); + const storeIx = + zplClient.liquidityManagement.instructions.buildStoreIx( + withdrawAmountBN, + solanaPubkey, + zplClient.assetMint, + new PublicKey(twoWayPegGuardian.address) + ); - const withdrawalRequestIx = zplClient.constructAddWithdrawalRequestIx( - solanaPubkey, - amountToWithdraw, - convertP2trToTweakedXOnlyPubkey(selectedWallet), - new PublicKey(twoWayPegGuardian.address), - twoWayPegConfiguration.layerFeeCollector - ); + const vaultSettingPda = + zplClient.liquidityManagement.pdas.deriveVaultSettingAddress( + new PublicKey(twoWayPegGuardian.address) + ); + + const currentTimestamp = new BN(Date.now() / 1000); + + const withdrawalRequestIx = + zplClient.twoWayPeg.instructions.buildAddWithdrawalRequestIx( + amountToWithdraw, + currentTimestamp, + convertP2trToTweakedXOnlyPubkey(selectedWallet), + solanaPubkey, + twoWayPegConfiguration.layerFeeCollector, + new PublicKey(twoWayPegGuardian.address), + zplClient.liquidityManagementProgramId, + zplClient.liquidityManagement.pdas.deriveConfigurationAddress(), + vaultSettingPda, + zplClient.liquidityManagement.pdas.derivePositionAddress( + vaultSettingPda, + solanaPubkey + ) + ); ixs.push(storeIx); ixs.push(withdrawalRequestIx); @@ -221,7 +254,6 @@ export default function ConfirmWithdraw({ return sig; }; - setIsWithdrawing(true); action() .catch((e) => { if (e instanceof WalletSignTransactionError) { diff --git a/src/components/MobileMenu/MobileMenuButton.tsx b/src/components/MobileMenu/MobileMenuButton.tsx index 7a57cf0d..fde99e33 100644 --- a/src/components/MobileMenu/MobileMenuButton.tsx +++ b/src/components/MobileMenu/MobileMenuButton.tsx @@ -13,7 +13,6 @@ export default function MobileMenuButton() {
{ - console.log(currentModal); if (currentModal === MODAL_NAMES.MOBILE_MENU) { closeModal(); } else { diff --git a/src/components/PortfolioV2/Modals/Redeem.tsx b/src/components/PortfolioV2/Modals/Redeem.tsx index 4f37fb19..8ec8a880 100644 --- a/src/components/PortfolioV2/Modals/Redeem.tsx +++ b/src/components/PortfolioV2/Modals/Redeem.tsx @@ -10,6 +10,7 @@ import { PublicKey, TransactionInstruction } from "@solana/web3.js"; import BigNumber from "bignumber.js"; import { BN } from "bn.js"; import { useState } from "react"; +import { Position } from "zpl-sdk-js/liquidity-management/types"; import Button from "@/components/Button/Button"; import Icon from "@/components/Icons"; @@ -22,7 +23,6 @@ import { useNetworkConfig } from "@/hooks/misc/useNetworkConfig"; import usePositions from "@/hooks/zpl/usePositions"; import usePersistentStore from "@/stores/persistentStore"; import { Chain } from "@/types/network"; -import { Position } from "@/types/zplClient"; import { BTC_DECIMALS } from "@/utils/constant"; import { formatValue } from "@/utils/format"; import { notifyError, notifyTx } from "@/utils/notification"; @@ -149,16 +149,19 @@ export default function RedeemModal({ throw new Error("Two way peg guardian setting not found"); const receiverAta = getAssociatedTokenAddressSync( - new PublicKey(config.assetMint), + zplClient.assetMint, solanaPubkey, true ); - const retrieveIx = zplClient.constructRetrieveIx( - amountToRedeem, - new PublicKey(twoWayPegGuardianSetting), - receiverAta - ); + const retrieveIx = + zplClient.liquidityManagement.instructions.buildRetrieveIx( + amountToRedeem, + solanaPubkey, + zplClient.assetMint, + new PublicKey(twoWayPegGuardianSetting), + receiverAta + ); ixs.push(retrieveIx); // TODO: You can customize the retrieve address here diff --git a/src/components/PortfolioV2/Overview/PortfolioDetails.tsx b/src/components/PortfolioV2/Overview/PortfolioDetails.tsx index ed7c6119..9a904754 100644 --- a/src/components/PortfolioV2/Overview/PortfolioDetails.tsx +++ b/src/components/PortfolioV2/Overview/PortfolioDetails.tsx @@ -1,9 +1,9 @@ import BigNumber from "bignumber.js"; import { useRef, useState } from "react"; +import { Position } from "zpl-sdk-js/liquidity-management/types"; import Button from "@/components/Button/Button"; import Icon from "@/components/Icons"; -import { Position } from "@/types/zplClient"; import { BTC_DECIMALS } from "@/utils/constant"; import { formatValue } from "@/utils/format"; diff --git a/src/components/PortfolioV2/Transactions/PortfolioTransactionsDeposits.tsx b/src/components/PortfolioV2/Transactions/PortfolioTransactionsDeposits.tsx index d33b08b3..f06ff74b 100644 --- a/src/components/PortfolioV2/Transactions/PortfolioTransactionsDeposits.tsx +++ b/src/components/PortfolioV2/Transactions/PortfolioTransactionsDeposits.tsx @@ -79,7 +79,7 @@ const PortfolioTransactionsDeposits = ({ `/api/v1/raw/layer/interactions/${transaction.interaction_id}/steps`, interactionSchema ); - console.log("interactionDetail", interactionDetail); + setSelectedTransaction(interactionDetail); let depositToHotReserveTxConfirmations = 0; diff --git a/src/components/Widgets/MintWidget/Withdraw.tsx b/src/components/Widgets/MintWidget/Withdraw.tsx index 7d0ea2f9..f19aba75 100644 --- a/src/components/Widgets/MintWidget/Withdraw.tsx +++ b/src/components/Widgets/MintWidget/Withdraw.tsx @@ -1,6 +1,7 @@ import { PublicKey } from "@solana/web3.js"; import BigNumber from "bignumber.js"; import { useState } from "react"; +import { Position } from "zpl-sdk-js/liquidity-management/types"; import { xOnlyPubkeyHexToP2tr } from "@/bitcoin"; import Icon from "@/components/Icons"; @@ -12,7 +13,6 @@ import useTwoWayPegConfiguration from "@/hooks/zpl/useTwoWayPegConfiguration"; import usePersistentStore from "@/stores/persistentStore"; import useStore from "@/stores/store"; import { CryptoInputOption } from "@/types/misc"; -import { Position } from "@/types/zplClient"; import { DEFAULT_SERVICE_FEE_BASIS_POINT_PERCENT, BTC_DECIMALS, diff --git a/src/hooks/zpl/useColdReserveBuckets.ts b/src/hooks/zpl/useColdReserveBuckets.ts index 876df282..dce357a1 100644 --- a/src/hooks/zpl/useColdReserveBuckets.ts +++ b/src/hooks/zpl/useColdReserveBuckets.ts @@ -6,7 +6,7 @@ function useColdReserveBuckets() { const client = useZplClient(); const { data, mutate, isLoading } = useSWR( client ? [client, "getColdReserveBuckets"] : null, - ([client]) => client.getColdReserveBuckets(), + ([client]) => client.twoWayPeg.accounts.getColdReserveBuckets(), { dedupingInterval: 3600000, } diff --git a/src/hooks/zpl/useHotReserveBucketActions.ts b/src/hooks/zpl/useHotReserveBucketActions.ts index 9dd993ce..6cdc1437 100644 --- a/src/hooks/zpl/useHotReserveBucketActions.ts +++ b/src/hooks/zpl/useHotReserveBucketActions.ts @@ -1,6 +1,7 @@ import { useWallet } from "@solana/wallet-adapter-react"; import { PublicKey } from "@solana/web3.js"; import { useCallback } from "react"; +import { HotReserveBucketStatus } from "zpl-sdk-js/two-way-peg/types"; import { convertBitcoinNetwork, UNLOCK_BLOCK_HEIGHT } from "@/bitcoin"; import { deriveHotReserveAddress } from "@/bitcoin"; @@ -11,8 +12,8 @@ import usePersistentStore from "@/stores/persistentStore"; import { CheckBucketResult } from "@/types/misc"; import { Chain } from "@/types/network"; import { BitcoinWallet } from "@/types/wallet"; -import { HotReserveBucketStatus } from "@/types/zplClient"; import { createAxiosInstances } from "@/utils/axios"; +import { HOT_RESERVE_BUCKET_VALIDITY_PERIOD } from "@/utils/constant"; import { notifyTx } from "@/utils/notification"; import useTwoWayPegGuardianSettings from "../hermes/useTwoWayPegGuardianSettings"; @@ -35,7 +36,7 @@ const useHotReserveBucketActions = (bitcoinWallet: BitcoinWallet | null) => { const selectedGuardian = twoWayPegGuardianSettings[0]; const coldReserveBucket = coldReserveBuckets.find( - (bucket) => bucket.guardianSetting.toBase58() === selectedGuardian.address + (bucket) => bucket.reserveSetting.toBase58() === selectedGuardian.address ); if (!coldReserveBucket) @@ -61,18 +62,21 @@ const useHotReserveBucketActions = (bitcoinWallet: BitcoinWallet | null) => { if (!hotReserveBitcoinXOnlyPublicKey) throw new Error("Can't get hot reserve x-only publickey"); - const twoWayPegConfiguration = await zplClient.getTwoWayPegConfiguration(); + const twoWayPegConfiguration = + await zplClient.twoWayPeg.accounts.getConfiguration(); - const ix = zplClient.constructCreateHotReserveBucketIx( + const ix = zplClient.twoWayPeg.instructions.buildCreateHotReserveBucketIx( + UNLOCK_BLOCK_HEIGHT, + HOT_RESERVE_BUCKET_VALIDITY_PERIOD, solanaPubkey, - hotReserveBitcoinXOnlyPublicKey, userBitcoinXOnlyPublicKey, - UNLOCK_BLOCK_HEIGHT, + hotReserveBitcoinXOnlyPublicKey, new PublicKey(selectedGuardian.address), new PublicKey(selectedGuardian.guardian_certificate), coldReserveBucket.publicKey, twoWayPegConfiguration.layerFeeCollector ); + const sig = await zplClient.signAndSendTransactionWithInstructions([ix]); notifyTx(true, { @@ -88,7 +92,7 @@ const useHotReserveBucketActions = (bitcoinWallet: BitcoinWallet | null) => { `/api/v1/cobo-address`, { type: "hotReserveBucket", - hotReserveBucketPda: zplClient + hotReserveBucketPda: zplClient.twoWayPeg.pdas .deriveHotReserveBucketAddress(hotReserveBitcoinXOnlyPublicKey) .toBase58(), coldReserveBucketPda: coldReserveBucket.publicKey.toBase58(), @@ -111,7 +115,7 @@ const useHotReserveBucketActions = (bitcoinWallet: BitcoinWallet | null) => { ]); const reactivateHotReserveBucket = useCallback(async () => { - if (!zplClient) return; + if (!zplClient || !solanaPubkey) return; const userBitcoinXOnlyPublicKey = getInternalXOnlyPubkeyFromUserWallet(bitcoinWallet); @@ -119,7 +123,7 @@ const useHotReserveBucketActions = (bitcoinWallet: BitcoinWallet | null) => { if (!userBitcoinXOnlyPublicKey) return; const hotReserveBuckets = - await zplClient.getHotReserveBucketsByBitcoinXOnlyPubkey( + await zplClient.twoWayPeg.accounts.getHotReserveBucketsByBitcoinXOnlyPubkey( userBitcoinXOnlyPublicKey ); @@ -127,16 +131,20 @@ const useHotReserveBucketActions = (bitcoinWallet: BitcoinWallet | null) => { const targetHotReserveBucket = hotReserveBuckets.find( (bucket) => - bucket.guardianSetting.toBase58() === networkConfig.guardianSetting + bucket.reserveSetting.toBase58() === networkConfig.guardianSetting ); if (!targetHotReserveBucket) throw new Error("Wrong guardian setting"); - const twoWayPegConfiguration = await zplClient.getTwoWayPegConfiguration(); + const twoWayPegConfiguration = + await zplClient.twoWayPeg.accounts.getConfiguration(); - const ix = zplClient.constructReactivateHotReserveBucketIx( - targetHotReserveBucket.publicKey, - twoWayPegConfiguration.layerFeeCollector - ); + const ix = + zplClient.twoWayPeg.instructions.buildReactivateHotReserveBucketIx( + HOT_RESERVE_BUCKET_VALIDITY_PERIOD, + solanaPubkey, + targetHotReserveBucket.publicKey, + twoWayPegConfiguration.layerFeeCollector + ); const sig = await zplClient.signAndSendTransactionWithInstructions([ix]); notifyTx(true, { @@ -144,7 +152,13 @@ const useHotReserveBucketActions = (bitcoinWallet: BitcoinWallet | null) => { txId: sig, solanaNetwork: solanaNetwork, }); - }, [zplClient, bitcoinWallet, solanaNetwork, networkConfig.guardianSetting]); + }, [ + zplClient, + solanaPubkey, + bitcoinWallet, + solanaNetwork, + networkConfig.guardianSetting, + ]); const checkHotReserveBucketStatus = useCallback(async () => { if (!zplClient || !solanaPubkey) return; @@ -155,7 +169,7 @@ const useHotReserveBucketActions = (bitcoinWallet: BitcoinWallet | null) => { if (!userBitcoinXOnlyPublicKey) return; const hotReserveBuckets = - await zplClient.getHotReserveBucketsByBitcoinXOnlyPubkey( + await zplClient.twoWayPeg.accounts.getHotReserveBucketsByBitcoinXOnlyPubkey( userBitcoinXOnlyPublicKey ); @@ -165,7 +179,7 @@ const useHotReserveBucketActions = (bitcoinWallet: BitcoinWallet | null) => { // NOTE: Regtest and Testnet use the same ZPL with different guardian settings, so we need to set guardian setting in env, and our mechanism only create 1 hot reserve bucket for each bitcoin public key in mainnet. const targetHotReserveBucket = hotReserveBuckets.find( (bucket) => - bucket.guardianSetting.toBase58() === networkConfig.guardianSetting + bucket.reserveSetting.toBase58() === networkConfig.guardianSetting ); if (!targetHotReserveBucket) throw new Error("Wrong guardian setting"); diff --git a/src/hooks/zpl/useHotReserveBucketsByOwner.ts b/src/hooks/zpl/useHotReserveBucketsByOwner.ts index d7dfae9f..e3ebe0f8 100644 --- a/src/hooks/zpl/useHotReserveBucketsByOwner.ts +++ b/src/hooks/zpl/useHotReserveBucketsByOwner.ts @@ -10,7 +10,9 @@ function useHotReserveBucketsByOwner(solanaPubkey: PublicKey | null) { ? [client, solanaPubkey, "getHotReserveBucketsByOwner"] : null, ([client, solanaPubkey]) => - client.getHotReserveBucketsBySolanaPubkey(solanaPubkey), + client.twoWayPeg.accounts.getHotReserveBucketsBySolanaPubkey( + solanaPubkey + ), { refreshInterval: 60000, dedupingInterval: 60000, diff --git a/src/hooks/zpl/usePositions.ts b/src/hooks/zpl/usePositions.ts index 7bda23f0..07ffb462 100644 --- a/src/hooks/zpl/usePositions.ts +++ b/src/hooks/zpl/usePositions.ts @@ -12,15 +12,16 @@ function usePositions(solanaPubkey: PublicKey | null) { ? [client, solanaPubkey, "getPositionsByWallet"] : null, async ([client, solanaPubkey]) => { - const positions = await client?.getPositionsByWallet(solanaPubkey); + const positions = + await client?.liquidityManagement.accounts.getPositionsByWallet( + solanaPubkey + ); const targetPositions = positions.filter( (position) => - position.guardianSetting.toBase58() === - client - .deriveLiquidityManagementGuardianSettingAddress( - new PublicKey(config.guardianSetting) - ) + position.vaultSetting.toBase58() === + client.liquidityManagement.pdas + .deriveVaultSettingAddress(new PublicKey(config.guardianSetting)) .toBase58() ); diff --git a/src/hooks/zpl/useTwoWayPegConfiguration.ts b/src/hooks/zpl/useTwoWayPegConfiguration.ts index b88aa073..f3608f1d 100644 --- a/src/hooks/zpl/useTwoWayPegConfiguration.ts +++ b/src/hooks/zpl/useTwoWayPegConfiguration.ts @@ -6,7 +6,7 @@ function useTwoWayPegConfiguration() { const client = useZplClient(); const { data, mutate } = useSWR( client ? [client, "getTwoWayPegConfiguration"] : null, - () => client?.getTwoWayPegConfiguration(), + () => client?.twoWayPeg.accounts.getConfiguration(), { dedupingInterval: 600000, } diff --git a/src/types/zplClient.ts b/src/types/zplClient.ts deleted file mode 100644 index 25d9a1c9..00000000 --- a/src/types/zplClient.ts +++ /dev/null @@ -1,189 +0,0 @@ -import * as borsh from "@coral-xyz/borsh"; -import { Structure } from "@solana/buffer-layout"; -import { PublicKey } from "@solana/web3.js"; -import BN from "bn.js"; - -// Account Schema for Deserialization -export interface TwoWayPegConfiguration { - publicKey: PublicKey; - superOperatorCertificate: PublicKey; - zeusColdReserveRecoveryPublicKey: Uint8Array; - zeusColdReserveRecoveryLockTime: number; - layerFeeCollector: PublicKey; - chadbufferAuthority: PublicKey; - cpiIdentity: PublicKey; - layerCaProgramId: PublicKey; - bitcoinSpvProgramId: PublicKey; - liquidityManagementProgramId: PublicKey; - bucketOpenFeeAmount: BN; - bucketReactivationFeeAmount: BN; - withdrawalFeeAmount: BN; - minerFeeRate: number; -} - -export const twoWayPegConfigurationSchema: Structure = - borsh.struct([ - borsh.publicKey("superOperatorCertificate"), - borsh.array(borsh.u8(), 32, "zeusColdReserveRecoveryPublicKey"), - borsh.u16("zeusColdReserveRecoveryLockTime"), - borsh.publicKey("layerFeeCollector"), - borsh.publicKey("chadbufferAuthority"), - borsh.publicKey("cpiIdentity"), - borsh.publicKey("layerCaProgramId"), - borsh.publicKey("bitcoinSpvProgramId"), - borsh.publicKey("liquidityManagementProgramId"), - borsh.u64("bucketOpenFeeAmount"), - borsh.u64("bucketReactivationFeeAmount"), - borsh.u64("withdrawalFeeAmount"), - borsh.u32("minerFeeRate"), - ]); - -export interface ColdReserveBucket { - publicKey: PublicKey; - guardianSetting: PublicKey; - owner: PublicKey; - // Bitcoin Cold Reserve X-Only Public Key - taprootXOnlyPublicKey: Uint8Array; - tapTweakHash: Uint8Array; - createdAt: BN; - // Guardian X-Only PublicKey - keyPathSpendPublicKey: Uint8Array; - recoveryParameters: ColdReserveRecoveryParameter[]; -} - -export interface ColdReserveRecoveryParameter { - scriptPathSpendPublicKey: Uint8Array; - lockTime: BN; -} - -export const coldReserveBucketSchema: Structure = - borsh.struct([ - borsh.publicKey("guardianSetting"), - borsh.publicKey("owner"), - borsh.array(borsh.u8(), 32, "taprootXOnlyPublicKey"), - borsh.array(borsh.u8(), 32, "tapTweakHash"), - borsh.i64("createdAt"), - borsh.array(borsh.u8(), 32, "keyPathSpendPublicKey"), - borsh.vec( - borsh.struct([ - borsh.array(borsh.u8(), 32, "scriptPathSpendPublicKey"), - borsh.i64("lockTime"), - ]), - "recoveryParameters" - ), - ]); - -export interface HotReserveBucket { - publicKey: PublicKey; - owner: PublicKey; - guardianSetting: PublicKey; - status: number; - // Bitcoin Hot Reserve X-Only Public Key - taprootXOnlyPublicKey: Uint8Array; - tapTweakHash: Uint8Array; - // Guardian X-Only PublicKey - keyPathSpendPublicKey: Uint8Array; - // User X-Only PublicKey - scriptPathSpendPublicKey: Uint8Array; - lockTime: BN; - createdAt: BN; - expiredAt: BN; -} - -export const hotReserveBucketSchema: Structure = borsh.struct( - [ - borsh.publicKey("owner"), - borsh.publicKey("guardianSetting"), - borsh.u8("status"), - borsh.array(borsh.u8(), 32, "taprootXOnlyPublicKey"), - borsh.array(borsh.u8(), 32, "tapTweakHash"), - borsh.array(borsh.u8(), 32, "keyPathSpendPublicKey"), - borsh.array(borsh.u8(), 32, "scriptPathSpendPublicKey"), - borsh.u64("lockTime"), - borsh.i64("createdAt"), - borsh.i64("expiredAt"), - ] -); - -export enum HotReserveBucketStatus { - Activated = 0, - Deactivated = 1, -} - -export interface Position { - publicKey: PublicKey; - owner: PublicKey; - guardianSetting: PublicKey; - storedAmount: BN; - frozenAmount: BN; - createdAt: BN; - updatedAt: BN; -} - -export const positionSchema: Structure = borsh.struct([ - borsh.publicKey("owner"), - borsh.publicKey("guardianSetting"), - borsh.u64("storedAmount"), - borsh.u64("frozenAmount"), - borsh.i64("createdAt"), - borsh.i64("updatedAt"), -]); - -// Instruction Schema for Serialization -export interface CreateHotReserveBucket { - discriminator: number; - scriptPathSpendPublicKey: Uint8Array; - lockTime: BN; - validityPeriod: number; -} - -export const createHotReserveBucketSchema: Structure = - borsh.struct([ - borsh.u8("discriminator"), - borsh.array(borsh.u8(), 32, "scriptPathSpendPublicKey"), - borsh.u64("lockTime"), - borsh.u32("validityPeriod"), - ]); - -export interface ReactivateHotReserveBucket { - discriminator: number; - validityPeriod: number; -} - -export const reactivateHotReserveBucketSchema: Structure = - borsh.struct([borsh.u8("discriminator"), borsh.u32("validityPeriod")]); - -export interface AddWithdrawalRequest { - discriminator: number; - receiverAddress: Uint8Array; - currentSlot: BN; - withdrawalAmount: BN; -} - -export const addWithdrawalRequestSchema: Structure = - borsh.struct([ - borsh.u8("discriminator"), - borsh.array(borsh.u8(), 32, "receiverAddress"), - borsh.u64("currentSlot"), - borsh.u64("withdrawalAmount"), - ]); - -export interface Retrieve { - discriminator: number; - amount: BN; -} - -export const retrieveSchema: Structure = borsh.struct([ - borsh.u8("discriminator"), - borsh.u64("amount"), -]); - -export interface Store { - discriminator: number; - amount: BN; -} - -export const storeSchema: Structure = borsh.struct([ - borsh.u8("discriminator"), - borsh.u64("amount"), -]); diff --git a/src/zplClient/account.ts b/src/zplClient/account.ts deleted file mode 100644 index ba4781d2..00000000 --- a/src/zplClient/account.ts +++ /dev/null @@ -1,409 +0,0 @@ -import { Connection, PublicKey } from "@solana/web3.js"; -import BN from "bn.js"; -import bs58 from "bs58"; -import { sha256 } from "js-sha256"; - -import { BitcoinXOnlyPublicKey } from "@/types/wallet"; -import { - HotReserveBucket, - hotReserveBucketSchema, - positionSchema, - Position, - TwoWayPegConfiguration, - twoWayPegConfigurationSchema, - ColdReserveBucket, - coldReserveBucketSchema, -} from "@/types/zplClient"; - -// Helper functions -function generateAccountDiscriminator(input: string): Buffer { - const preImage = Buffer.from(input); - return Buffer.from(sha256(preImage), "hex").subarray(0, 8); -} - -export function deserializeColdReserveBucket( - publicKey: PublicKey, - data?: Buffer -): ColdReserveBucket { - if (!data) throw new Error("Data is undefined"); - - const { - guardianSetting, - owner, - taprootXOnlyPublicKey, - tapTweakHash, - createdAt, - keyPathSpendPublicKey, - recoveryParameters, - } = coldReserveBucketSchema.decode(data); - return { - publicKey, - guardianSetting, - owner, - taprootXOnlyPublicKey, - tapTweakHash, - createdAt, - keyPathSpendPublicKey, - recoveryParameters, - }; -} - -function deserializeHotReserveBucket( - publicKey: PublicKey, - data: Buffer | undefined -): HotReserveBucket { - if (!data) throw new Error("Data is undefined"); - - const { - owner, - guardianSetting, - status, - taprootXOnlyPublicKey, - tapTweakHash, - keyPathSpendPublicKey, - scriptPathSpendPublicKey, - lockTime, - createdAt, - expiredAt, - } = hotReserveBucketSchema.decode(data); - - return { - publicKey, - owner, - guardianSetting, - status, - taprootXOnlyPublicKey, - tapTweakHash, - keyPathSpendPublicKey, - scriptPathSpendPublicKey, - lockTime, - createdAt, - expiredAt, - }; -} - -function deserializePosition( - publicKey: PublicKey, - data: Buffer | undefined -): Position { - if (!data) throw new Error("Data is undefined"); - - const { - owner, - guardianSetting, - storedAmount, - frozenAmount, - createdAt, - updatedAt, - } = positionSchema.decode(data); - - return { - publicKey, - owner, - guardianSetting, - storedAmount, - frozenAmount, - createdAt, - updatedAt, - }; -} - -export function deserializeTwoWayPegConfiguration( - publicKey: PublicKey, - data: Buffer | undefined -): TwoWayPegConfiguration { - if (!data) throw new Error("Data is undefined"); - - const { - superOperatorCertificate, - zeusColdReserveRecoveryPublicKey, - zeusColdReserveRecoveryLockTime, - layerFeeCollector, - chadbufferAuthority, - cpiIdentity, - layerCaProgramId, - bitcoinSpvProgramId, - liquidityManagementProgramId, - bucketOpenFeeAmount, - bucketReactivationFeeAmount, - withdrawalFeeAmount, - minerFeeRate, - } = twoWayPegConfigurationSchema.decode(data); - - return { - publicKey, - superOperatorCertificate, - zeusColdReserveRecoveryPublicKey, - zeusColdReserveRecoveryLockTime, - layerFeeCollector, - chadbufferAuthority, - cpiIdentity, - layerCaProgramId, - bitcoinSpvProgramId, - liquidityManagementProgramId, - bucketOpenFeeAmount, - bucketReactivationFeeAmount, - withdrawalFeeAmount, - minerFeeRate, - }; -} - -export class AccountService { - constructor( - private connection: Connection, - private twoWayPegProgramId: PublicKey, - private liquidityManagementProgramId: PublicKey - ) {} - - // Address derivation methods - deriveConfigurationAddress() { - const [configurationAddress] = PublicKey.findProgramAddressSync( - [Buffer.from("configuration")], - this.twoWayPegProgramId - ); - return configurationAddress; - } - - deriveCpiIdentityAddress() { - return PublicKey.findProgramAddressSync( - [Buffer.from("cpi-identity")], - this.twoWayPegProgramId - )[0]; - } - - deriveHotReserveBucketAddress( - hotReserveBitcoinXOnlyPublicKey: BitcoinXOnlyPublicKey - ): PublicKey { - const bucketPda = PublicKey.findProgramAddressSync( - [Buffer.from("hot-reserve-bucket"), hotReserveBitcoinXOnlyPublicKey], - this.twoWayPegProgramId - )[0]; - return bucketPda; - } - - deriveInteraction(seed1: Buffer, seed2: BN) { - const interactionPda = PublicKey.findProgramAddressSync( - [ - Buffer.from("interaction"), - // Deposit: transaction_id, Withdrawal: receiver_address - seed1, - // Deposit: v_out, Withdrawal: slot (u64 trimmed to u32) - seed2.toArrayLike(Buffer, "le", 4), - ], - this.twoWayPegProgramId - )[0]; - - return interactionPda; - } - - deriveLiquidityManagementConfigurationAddress() { - const [configurationAddress] = PublicKey.findProgramAddressSync( - [Buffer.from("configuration")], - this.liquidityManagementProgramId - ); - - return configurationAddress; - } - - deriveLiquidityManagementGuardianSettingAddress( - twoWayPegGuardianSetting: PublicKey - ) { - const [guardianSettingAddress] = PublicKey.findProgramAddressSync( - [Buffer.from("guardian-setting"), twoWayPegGuardianSetting.toBuffer()], - this.liquidityManagementProgramId - ); - - return guardianSettingAddress; - } - - deriveSplTokenVaultAuthorityAddress(twoWayPegGuardianSetting: PublicKey) { - const [guardianSettingAddress] = PublicKey.findProgramAddressSync( - [ - Buffer.from("spl-token-vault-authority"), - twoWayPegGuardianSetting.toBuffer(), - ], - this.liquidityManagementProgramId - ); - - return guardianSettingAddress; - } - - derivePositionAddress( - lmGuardianSetting: PublicKey, - userAddress: PublicKey | null - ): PublicKey { - return PublicKey.findProgramAddressSync( - [ - Buffer.from("position"), - lmGuardianSetting.toBuffer(), - userAddress?.toBuffer() ?? Buffer.from([]), - ], - this.liquidityManagementProgramId - )[0]; - } - - // Query methods - async getTwoWayPegConfiguration() { - const filters = [ - { - memcmp: { - offset: 0, - bytes: bs58.encode( - generateAccountDiscriminator("two-way-peg:configuration") - ), - }, - }, - ]; - - const twoWayPegConfiguration = await this.connection.getProgramAccounts( - this.twoWayPegProgramId, - { filters } - ); - - const twoWayPegConfigurationData = twoWayPegConfiguration.map( - (twoWayPegConfiguration) => - deserializeTwoWayPegConfiguration( - twoWayPegConfiguration.pubkey, - twoWayPegConfiguration.account.data.subarray(8) - ) - ); - - return twoWayPegConfigurationData[0]; - } - - async getColdReserveBuckets() { - const filters = [ - { - memcmp: { - offset: 0, - bytes: bs58.encode( - generateAccountDiscriminator("two-way-peg:cold-reserve-bucket") - ), - }, - }, - ]; - - const accounts = await this.connection.getProgramAccounts( - this.twoWayPegProgramId, - { filters } - ); - - const accountsData = accounts - .map((account) => { - const { data } = account.account; - return deserializeColdReserveBucket(account.pubkey, data.subarray(8)); - }) - .toSorted((a, b) => b.createdAt.cmp(a.createdAt)) - .reduce((acc, current) => { - if ( - !acc.some( - (item) => - item.guardianSetting.toBase58() === - current.guardianSetting.toBase58() - ) - ) { - acc.push(current); - } - return acc; - }, [] as ColdReserveBucket[]); - - return accountsData; - } - - async getHotReserveBucketsByBitcoinXOnlyPubkey( - bitcoinXOnlyPubkey: BitcoinXOnlyPublicKey - ) { - const filters = [ - { - memcmp: { - offset: 0, - bytes: bs58.encode( - generateAccountDiscriminator("two-way-peg:hot-reserve-bucket") - ), - }, - }, - { - memcmp: { - offset: 169, - bytes: bs58.encode(bitcoinXOnlyPubkey), - }, - }, - ]; - - const hotReserveBuckets = await this.connection.getProgramAccounts( - this.twoWayPegProgramId, - { filters } - ); - - const hotReserveBucketsData = hotReserveBuckets.map((hotReserveBucket) => - deserializeHotReserveBucket( - hotReserveBucket.pubkey, - hotReserveBucket.account.data.subarray(8) - ) - ); - - return hotReserveBucketsData; - } - - async getHotReserveBucketsBySolanaPubkey(solanaPubkey: PublicKey) { - const filters = [ - { - memcmp: { - offset: 0, - bytes: bs58.encode( - generateAccountDiscriminator("two-way-peg:hot-reserve-bucket") - ), - }, - }, - { - memcmp: { - offset: 8, - bytes: solanaPubkey.toBase58(), - }, - }, - ]; - - const hotReserveBuckets = await this.connection.getProgramAccounts( - this.twoWayPegProgramId, - { filters } - ); - - const hotReserveBucketsData = hotReserveBuckets.map((hotReserveBucket) => - deserializeHotReserveBucket( - hotReserveBucket.pubkey, - hotReserveBucket.account.data.subarray(8) - ) - ); - - return hotReserveBucketsData; - } - - async getPositionsByWallet(solanaPubkey: PublicKey) { - const filters = [ - { - memcmp: { - offset: 0, - bytes: bs58.encode( - generateAccountDiscriminator("liquidity-management:position") - ), - }, - }, - { - memcmp: { - offset: 8, - bytes: solanaPubkey.toBase58(), - }, - }, - ]; - - const positions = await this.connection.getProgramAccounts( - this.liquidityManagementProgramId, - { filters } - ); - - return positions.map((position) => { - const { data } = position.account; - return deserializePosition(position.pubkey, data.subarray(8)); - }); - } -} diff --git a/src/zplClient/index.ts b/src/zplClient/index.ts index d3e168d1..e3a39862 100644 --- a/src/zplClient/index.ts +++ b/src/zplClient/index.ts @@ -4,28 +4,22 @@ import { PublicKey, Transaction, TransactionInstruction, + TransactionMessage, VersionedTransaction, } from "@solana/web3.js"; -import BN from "bn.js"; - -import { BitcoinAddress, BitcoinXOnlyPublicKey } from "@/types/wallet"; - -import { AccountService } from "./account"; -import { InstructionService } from "./instruction"; -import { RpcClient } from "./rpcClient"; +import { LiquidityManagementClient, TwoWayPegClient } from "zpl-sdk-js"; export class ZplClient { - private accountService: AccountService; - private instructionService: InstructionService; - private rpcClient: RpcClient; - private twoWayPegProgramId: PublicKey; - private liquidityManagementProgramId: PublicKey; - private assetMint: PublicKey; + public twoWayPegProgramId: PublicKey; + public liquidityManagementProgramId: PublicKey; + public assetMint: PublicKey; + public twoWayPeg: TwoWayPegClient; + public liquidityManagement: LiquidityManagementClient; constructor( - connection: Connection, - walletPublicKey: PublicKey | null, - signTransaction: + private connection: Connection, + private walletPublicKey: PublicKey | null, + private signTransaction: | (( transaction: T ) => Promise) @@ -40,173 +34,53 @@ export class ZplClient { ); this.assetMint = new PublicKey(assetMint); - this.accountService = new AccountService( - connection, - this.twoWayPegProgramId, - this.liquidityManagementProgramId - ); - - this.instructionService = new InstructionService( - walletPublicKey, - this.twoWayPegProgramId, - this.liquidityManagementProgramId, - this.assetMint, - this.accountService - ); + this.twoWayPeg = new TwoWayPegClient(connection, this.twoWayPegProgramId); - this.rpcClient = new RpcClient( + this.liquidityManagement = new LiquidityManagementClient( connection, - walletPublicKey, - signTransaction - ); - } - - // Account service methods - deriveConfigurationAddress() { - return this.accountService.deriveConfigurationAddress(); - } - - deriveCpiIdentityAddress() { - return this.accountService.deriveCpiIdentityAddress(); - } - - deriveHotReserveBucketAddress( - hotReserveBitcoinXOnlyPublicKey: BitcoinXOnlyPublicKey - ): PublicKey { - return this.accountService.deriveHotReserveBucketAddress( - hotReserveBitcoinXOnlyPublicKey + this.liquidityManagementProgramId ); } - deriveInteraction(seed1: Buffer, seed2: BN) { - return this.accountService.deriveInteraction(seed1, seed2); - } - - deriveLiquidityManagementConfigurationAddress() { - return this.accountService.deriveLiquidityManagementConfigurationAddress(); - } - - deriveLiquidityManagementGuardianSettingAddress( - twoWayPegGuardianSetting: PublicKey + async signAndSendTransactionWithInstructions( + ixs: TransactionInstruction[], + lookupTableAccounts?: AddressLookupTableAccount[] ) { - return this.accountService.deriveLiquidityManagementGuardianSettingAddress( - twoWayPegGuardianSetting - ); - } - - deriveSplTokenVaultAuthorityAddress(twoWayPegGuardianSetting: PublicKey) { - return this.accountService.deriveSplTokenVaultAuthorityAddress( - twoWayPegGuardianSetting - ); - } - - derivePositionAddress( - lmGuardianSetting: PublicKey, - userAddress: PublicKey | null - ): PublicKey { - return this.accountService.derivePositionAddress( - lmGuardianSetting, - userAddress - ); - } - - async getTwoWayPegConfiguration() { - return this.accountService.getTwoWayPegConfiguration(); - } + const solanaPubkey = this.walletPublicKey; + const { signTransaction } = this; - async getColdReserveBuckets() { - return this.accountService.getColdReserveBuckets(); - } - - async getHotReserveBucketsByBitcoinXOnlyPubkey( - bitcoinXOnlyPubkey: BitcoinXOnlyPublicKey - ) { - return this.accountService.getHotReserveBucketsByBitcoinXOnlyPubkey( - bitcoinXOnlyPubkey - ); - } + if (!solanaPubkey || !signTransaction) + throw new Error("Wallet is not connected"); - async getHotReserveBucketsBySolanaPubkey(solanaPubkey: PublicKey) { - return this.accountService.getHotReserveBucketsBySolanaPubkey(solanaPubkey); - } + const { blockhash, lastValidBlockHeight } = + await this.connection.getLatestBlockhash(); - async getPositionsByWallet(solanaPubkey: PublicKey) { - return this.accountService.getPositionsByWallet(solanaPubkey); - } + const msg = new TransactionMessage({ + payerKey: solanaPubkey, + recentBlockhash: blockhash, + instructions: ixs, + }).compileToV0Message(lookupTableAccounts); - // Instruction service methods - constructCreateHotReserveBucketIx( - solanaPubkey: PublicKey, - hotReserveBitcoinXOnlyPublicKey: BitcoinXOnlyPublicKey, - userBitcoinXOnlyPublicKey: BitcoinXOnlyPublicKey, - unlockBlockHeight: number, - guardianSetting: PublicKey, - guardianCertificate: PublicKey, - coldReserveBucket: PublicKey, - layerFeeCollector: PublicKey - ) { - return this.instructionService.constructCreateHotReserveBucketIx( - solanaPubkey, - hotReserveBitcoinXOnlyPublicKey, - userBitcoinXOnlyPublicKey, - unlockBlockHeight, - guardianSetting, - guardianCertificate, - coldReserveBucket, - layerFeeCollector - ); - } + const tx = new VersionedTransaction(msg); - constructReactivateHotReserveBucketIx( - hotReserveBucketPda: PublicKey, - layerFeeCollector: PublicKey - ) { - return this.instructionService.constructReactivateHotReserveBucketIx( - hotReserveBucketPda, - layerFeeCollector - ); - } + const signedTx = await signTransaction(tx); - constructAddWithdrawalRequestIx( - solanaPubkey: PublicKey, - amount: BN, - receiverAddress: BitcoinAddress, - guardianSetting: PublicKey, - layerFeeCollector: PublicKey - ) { - return this.instructionService.constructAddWithdrawalRequestIx( - solanaPubkey, - amount, - receiverAddress, - guardianSetting, - layerFeeCollector + const signature = await this.connection.sendRawTransaction( + signedTx.serialize(), + { + preflightCommitment: "confirmed", + } ); - } - constructRetrieveIx( - amount: BN, - guardianSetting: PublicKey, - receiverAta: PublicKey - ) { - return this.instructionService.constructRetrieveIx( - amount, - guardianSetting, - receiverAta + await this.connection.confirmTransaction( + { + signature, + lastValidBlockHeight, + blockhash, + }, + "confirmed" ); - } - constructStoreIx(amount: BN, guardianSetting: PublicKey) { - return this.instructionService.constructStoreIx(amount, guardianSetting); - } - - // RPC client methods - async signAndSendTransactionWithInstructions( - ixs: TransactionInstruction[], - lookupTableAccounts?: AddressLookupTableAccount[] - ) { - return this.rpcClient.signAndSendTransactionWithInstructions( - ixs, - lookupTableAccounts - ); + return signature; } } diff --git a/src/zplClient/instruction.ts b/src/zplClient/instruction.ts deleted file mode 100644 index 11c1f52b..00000000 --- a/src/zplClient/instruction.ts +++ /dev/null @@ -1,349 +0,0 @@ -import { - ASSOCIATED_TOKEN_PROGRAM_ID, - getAssociatedTokenAddressSync, - TOKEN_PROGRAM_ID, -} from "@solana/spl-token"; -import { - PublicKey, - SystemProgram, - TransactionInstruction, -} from "@solana/web3.js"; -import BN from "bn.js"; - -import { BitcoinAddress, BitcoinXOnlyPublicKey } from "@/types/wallet"; -import { - createHotReserveBucketSchema, - retrieveSchema, - storeSchema, - addWithdrawalRequestSchema, - reactivateHotReserveBucketSchema, -} from "@/types/zplClient"; -import { HOT_RESERVE_BUCKET_VALIDITY_PERIOD } from "@/utils/constant"; - -import { AccountService } from "./account"; - -export class InstructionService { - constructor( - private walletPublicKey: PublicKey | null, - private twoWayPegProgramId: PublicKey, - private liquidityManagementProgramId: PublicKey, - private assetMint: PublicKey, - private accountService: AccountService - ) {} - - constructCreateHotReserveBucketIx( - solanaPubkey: PublicKey, - hotReserveBitcoinXOnlyPublicKey: BitcoinXOnlyPublicKey, - userBitcoinXOnlyPublicKey: BitcoinXOnlyPublicKey, - unlockBlockHeight: number, - guardianSetting: PublicKey, - guardianCertificate: PublicKey, - coldReserveBucket: PublicKey, - layerFeeCollector: PublicKey - ) { - const instructionData = Buffer.alloc(createHotReserveBucketSchema.span); - createHotReserveBucketSchema.encode( - { - discriminator: 11, - scriptPathSpendPublicKey: Uint8Array.from(userBitcoinXOnlyPublicKey), - lockTime: new BN(unlockBlockHeight), - validityPeriod: HOT_RESERVE_BUCKET_VALIDITY_PERIOD, - }, - instructionData - ); - - const configurationPda = this.accountService.deriveConfigurationAddress(); - - const hotReserveBucketPda = - this.accountService.deriveHotReserveBucketAddress( - hotReserveBitcoinXOnlyPublicKey - ); - - const ix = new TransactionInstruction({ - keys: [ - { pubkey: solanaPubkey, isSigner: true, isWritable: true }, - { pubkey: configurationPda, isSigner: false, isWritable: false }, - { pubkey: guardianSetting, isSigner: false, isWritable: false }, - { pubkey: guardianCertificate, isSigner: false, isWritable: false }, - { pubkey: coldReserveBucket, isSigner: false, isWritable: false }, - { pubkey: hotReserveBucketPda, isSigner: false, isWritable: true }, - { - pubkey: layerFeeCollector, - isSigner: false, - isWritable: true, - }, - { pubkey: SystemProgram.programId, isSigner: false, isWritable: true }, - ], - programId: this.twoWayPegProgramId, - data: instructionData, - }); - - return ix; - } - - constructReactivateHotReserveBucketIx( - hotReserveBucketPda: PublicKey, - layerFeeCollector: PublicKey - ) { - if (!this.walletPublicKey) throw new Error("Wallet is not connected"); - - const instructionData = Buffer.alloc(reactivateHotReserveBucketSchema.span); - - reactivateHotReserveBucketSchema.encode( - { - discriminator: 13, - validityPeriod: HOT_RESERVE_BUCKET_VALIDITY_PERIOD, - }, - instructionData - ); - - const configurationPda = this.accountService.deriveConfigurationAddress(); - - const ix = new TransactionInstruction({ - keys: [ - { - pubkey: this.walletPublicKey, - isSigner: true, - isWritable: true, - }, - { pubkey: configurationPda, isSigner: false, isWritable: false }, - { pubkey: hotReserveBucketPda, isSigner: false, isWritable: true }, - { - pubkey: layerFeeCollector, - isSigner: false, - isWritable: true, - }, - { - pubkey: SystemProgram.programId, - isSigner: false, - isWritable: false, - }, - ], - programId: this.twoWayPegProgramId, - data: instructionData, - }); - - return ix; - } - - constructAddWithdrawalRequestIx( - solanaPubkey: PublicKey, - amount: BN, - receiverAddress: BitcoinAddress, - guardianSetting: PublicKey, - layerFeeCollector: PublicKey - ) { - const withdrawalRequestSeed = new BN(Date.now() / 1000); // current slot as Unix timestamp - const withdrawalRequestPda = PublicKey.findProgramAddressSync( - [ - Buffer.from("withdrawal-request"), - receiverAddress, - withdrawalRequestSeed.toArrayLike(Buffer, "le", 4), - ], - this.twoWayPegProgramId - )[0]; - - const interactionPda = this.accountService.deriveInteraction( - receiverAddress, - withdrawalRequestSeed - ); - - const instructionData = Buffer.alloc(addWithdrawalRequestSchema.span); - addWithdrawalRequestSchema.encode( - { - discriminator: 131, - receiverAddress: Uint8Array.from(receiverAddress), - currentSlot: new BN(Date.now() / 1000), - withdrawalAmount: amount, - }, - instructionData - ); - - const twoWayPegProgramCPIIdentity = - this.accountService.deriveCpiIdentityAddress(); - - const configurationPda = this.accountService.deriveConfigurationAddress(); - - const lmGuardianSetting = - this.accountService.deriveLiquidityManagementGuardianSettingAddress( - guardianSetting - ); - - const positionPda = this.accountService.derivePositionAddress( - lmGuardianSetting, - solanaPubkey - ); - - const liquidityManagementConfiguration = - this.accountService.deriveLiquidityManagementConfigurationAddress(); - - const ix = new TransactionInstruction({ - keys: [ - { pubkey: solanaPubkey, isSigner: true, isWritable: true }, - { - pubkey: twoWayPegProgramCPIIdentity, - isSigner: false, - isWritable: false, - }, - { pubkey: configurationPda, isSigner: false, isWritable: false }, - { pubkey: guardianSetting, isSigner: false, isWritable: false }, - { pubkey: lmGuardianSetting, isSigner: false, isWritable: false }, - { pubkey: withdrawalRequestPda, isSigner: false, isWritable: true }, - { pubkey: interactionPda, isSigner: false, isWritable: true }, - { pubkey: positionPda, isSigner: false, isWritable: true }, - { - pubkey: layerFeeCollector, - isSigner: false, - isWritable: true, - }, - { - pubkey: liquidityManagementConfiguration, - isSigner: false, - isWritable: false, - }, - { - pubkey: this.liquidityManagementProgramId, - isSigner: false, - isWritable: false, - }, - { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, - ], - programId: this.twoWayPegProgramId, - data: instructionData, - }); - - return ix; - } - - constructRetrieveIx( - amount: BN, - guardianSetting: PublicKey, - receiverAta: PublicKey - ) { - if (!this.walletPublicKey) throw new Error("Wallet is not connected"); - - const lmGuardianSetting = - this.accountService.deriveLiquidityManagementGuardianSettingAddress( - guardianSetting - ); - - const splTokenVaultAuthority = - this.accountService.deriveSplTokenVaultAuthorityAddress(guardianSetting); - - const vaultAta = getAssociatedTokenAddressSync( - this.assetMint, - splTokenVaultAuthority, - true - ); - - const positionPda = this.accountService.derivePositionAddress( - lmGuardianSetting, - this.walletPublicKey - ); - - const instructionData = Buffer.alloc(retrieveSchema.span); - retrieveSchema.encode( - { - discriminator: 21, - amount, - }, - instructionData - ); - - const ix = new TransactionInstruction({ - keys: [ - { - pubkey: this.walletPublicKey, - isSigner: true, - isWritable: true, - }, - { pubkey: receiverAta, isSigner: false, isWritable: true }, - { pubkey: positionPda, isSigner: false, isWritable: true }, - { pubkey: lmGuardianSetting, isSigner: false, isWritable: false }, - { pubkey: splTokenVaultAuthority, isSigner: false, isWritable: false }, - { pubkey: vaultAta, isSigner: false, isWritable: true }, - { pubkey: this.assetMint, isSigner: false, isWritable: false }, - { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - { - pubkey: ASSOCIATED_TOKEN_PROGRAM_ID, - isSigner: false, - isWritable: false, - }, - ], - programId: this.liquidityManagementProgramId, - data: instructionData, - }); - - return ix; - } - - constructStoreIx(amount: BN, guardianSetting: PublicKey) { - if (!this.walletPublicKey) throw new Error("Wallet is not connected"); - - const userAta = getAssociatedTokenAddressSync( - this.assetMint, - this.walletPublicKey, - true - ); - - const lmGuardianSetting = - this.accountService.deriveLiquidityManagementGuardianSettingAddress( - guardianSetting - ); - - const splTokenVaultAuthority = - this.accountService.deriveSplTokenVaultAuthorityAddress(guardianSetting); - - const vaultAta = getAssociatedTokenAddressSync( - this.assetMint, - splTokenVaultAuthority, - true - ); - - const positionPda = this.accountService.derivePositionAddress( - lmGuardianSetting, - this.walletPublicKey - ); - - const lmConfiguration = - this.accountService.deriveLiquidityManagementConfigurationAddress(); - - const instructionData = Buffer.alloc(storeSchema.span); - storeSchema.encode( - { - discriminator: 22, - amount, - }, - instructionData - ); - - const ix = new TransactionInstruction({ - keys: [ - { - pubkey: this.walletPublicKey, - isSigner: true, - isWritable: true, - }, - { pubkey: userAta, isSigner: false, isWritable: true }, - { pubkey: positionPda, isSigner: false, isWritable: true }, - { - pubkey: lmConfiguration, - isSigner: false, - isWritable: false, - }, - { pubkey: lmGuardianSetting, isSigner: false, isWritable: false }, - { pubkey: guardianSetting, isSigner: false, isWritable: false }, - { pubkey: splTokenVaultAuthority, isSigner: false, isWritable: false }, - { pubkey: vaultAta, isSigner: false, isWritable: true }, - { pubkey: this.assetMint, isSigner: false, isWritable: false }, - { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, - { pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false }, - ], - programId: this.liquidityManagementProgramId, - data: instructionData, - }); - - return ix; - } -} diff --git a/src/zplClient/rpcClient.ts b/src/zplClient/rpcClient.ts deleted file mode 100644 index 979aefc3..00000000 --- a/src/zplClient/rpcClient.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { - AddressLookupTableAccount, - Connection, - PublicKey, - Transaction, - TransactionInstruction, - TransactionMessage, - VersionedTransaction, -} from "@solana/web3.js"; - -export class RpcClient { - constructor( - private connection: Connection, - private walletPublicKey: PublicKey | null, - private signTransaction: - | (( - transaction: T - ) => Promise) - | undefined - ) {} - - async signAndSendTransactionWithInstructions( - ixs: TransactionInstruction[], - lookupTableAccounts?: AddressLookupTableAccount[] - ) { - const solanaPubkey = this.walletPublicKey; - const { signTransaction } = this; - - if (!solanaPubkey || !signTransaction) - throw new Error("Wallet is not connected"); - - const { blockhash, lastValidBlockHeight } = - await this.connection.getLatestBlockhash(); - - const msg = new TransactionMessage({ - payerKey: solanaPubkey, - recentBlockhash: blockhash, - instructions: ixs, - }).compileToV0Message(lookupTableAccounts); - - const tx = new VersionedTransaction(msg); - - const signedTx = await signTransaction(tx); - - const signature = await this.connection.sendRawTransaction( - signedTx.serialize(), - { - preflightCommitment: "confirmed", - } - ); - - await this.connection.confirmTransaction( - { - signature, - lastValidBlockHeight, - blockhash, - }, - "confirmed" - ); - - return signature; - } -} From 00b175359f995df441c20c09b823dac63f1fdaa6 Mon Sep 17 00:00:00 2001 From: Edward Date: Thu, 15 May 2025 14:26:09 +0800 Subject: [PATCH 2/5] feat: use hot reserve related fn in zpl sdk --- package-lock.json | 29 +-- package.json | 2 - src/bitcoin/index.ts | 242 ------------------ src/components/Mint/Modals/ConfirmDeposit.tsx | 9 +- src/components/Widgets/MintWidget/Deposit.tsx | 5 +- src/hooks/zpl/useHotReserveBucketActions.ts | 2 +- 6 files changed, 11 insertions(+), 278 deletions(-) diff --git a/package-lock.json b/package-lock.json index 46e54d69..97d8db79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,7 +57,6 @@ "swr": "^2.2.5", "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", - "uint8array-tools": "^0.0.9", "usehooks-ts": "^3.1.0", "zod": "^3.23.8", "zpl-sdk-js": "file:../zpl-sdk-js", @@ -70,7 +69,6 @@ "@next/eslint-plugin-next": "^14.2.3", "@tailwindcss/typography": "^0.5.13", "@types/bn.js": "^5.1.5", - "@types/bs58": "^4.0.4", "@types/d3": "^7.4.3", "@types/mdx": "^2.0.13", "@types/node": "^20", @@ -100,15 +98,18 @@ "dependencies": { "@coral-xyz/borsh": "^0.31.1", "@solana/buffer-layout": "^4.0.1", + "bitcoinjs-lib": "^6.1.7", "bn.js": "^5.2.2", - "bs58": "^6.0.0", - "js-sha256": "^0.11.0" + "bs58": "^5.0.0", + "js-sha256": "^0.11.0", + "uint8array-tools": "^0.0.9" }, "devDependencies": { "@commitlint/cli": "^19.8.0", "@commitlint/config-conventional": "^19.8.0", "@eslint/js": "^9.26.0", "@types/bn.js": "^5.1.6", + "@types/bs58": "^4.0.4", "@types/node": "^22.14.1", "cspell": "^9.0.0", "eslint": "^9.26.0", @@ -13454,17 +13455,6 @@ "@types/node": "*" } }, - "node_modules/@types/bs58": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.4.tgz", - "integrity": "sha512-0IEpMFXXQi2zXaXl9GJ3sRwQo0uEkD+yFOv+FnAU5lkPtcu6h61xb7jc2CFPEZ5BUOaiP13ThuGc9HD4R8lR5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "base-x": "^3.0.6" - } - }, "node_modules/@types/connect": { "version": "3.4.36", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", @@ -33509,15 +33499,6 @@ "license": "MIT", "peer": true }, - "node_modules/uint8array-tools": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.9.tgz", - "integrity": "sha512-9vqDWmoSXOoi+K14zNaf6LBV51Q8MayF0/IiQs3GlygIKUYtog603e6virExkjjFosfJUBI4LhbQK1iq8IG11A==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/uint8arrays": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", diff --git a/package.json b/package.json index 00c4a88d..9940680f 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,6 @@ "swr": "^2.2.5", "tailwind-merge": "^2.5.4", "tailwindcss-animate": "^1.0.7", - "uint8array-tools": "^0.0.9", "usehooks-ts": "^3.1.0", "zod": "^3.23.8", "zpl-sdk-js": "file:../zpl-sdk-js", @@ -89,7 +88,6 @@ "@next/eslint-plugin-next": "^14.2.3", "@tailwindcss/typography": "^0.5.13", "@types/bn.js": "^5.1.5", - "@types/bs58": "^4.0.4", "@types/d3": "^7.4.3", "@types/mdx": "^2.0.13", "@types/node": "^20", diff --git a/src/bitcoin/index.ts b/src/bitcoin/index.ts index 4ffcff0a..d4a475a5 100644 --- a/src/bitcoin/index.ts +++ b/src/bitcoin/index.ts @@ -1,21 +1,12 @@ import * as bitcoin from "bitcoinjs-lib"; -import { toXOnly } from "bitcoinjs-lib/src/psbt/bip371"; import BN from "bn.js"; -import * as tools from "uint8array-tools"; -import { UTXO, UTXOs } from "@/types/api"; import { BitcoinNetwork } from "@/types/store"; import { BitcoinXOnlyPublicKey } from "@/types/wallet"; export const UNLOCK_BLOCK_HEIGHT = 4320; // 144 blocks (1 day) * 30 const SATOSHIS_PER_BTC = new BN(1e8); -const TX_INPUT_VBYTE: number = 58; -const TX_BASIC_VBYTE: number = 10; -const TX_OUTPUT_VBYTE: number = 44; - -// FIXME: need to figure out the dust amount -const DUST_AMOUNT: number = 546; // FIXME: move to a more appropriate location /** @@ -132,237 +123,4 @@ export const convertBitcoinNetwork = (bitcoinNetwork: BitcoinNetwork) => { throw new Error("Invalid network type"); }; -const isSpendable = (utxo: UTXO, satoshisPerVBytes: number): boolean => { - return ( - BigInt(Math.round(utxo.satoshis)) > - BigInt(Math.ceil(satoshisPerVBytes * TX_INPUT_VBYTE)) - ); -}; - -/** - * - * @param utxos available utxos - * @param hotReserveAddress hot reserve address in p2tr format - * @param amount amount to deposit (satoshis) - * @param userXOnlyPubKey userXonlyPubKey - * @param feeRate fee rate in satoshis per vbyte - * @param network network - * @returns - */ -export const constructDepositToHotReserveTx = ( - utxos: UTXOs, - hotReserveAddress: string, - amount: number, - userXOnlyPubKey: BitcoinXOnlyPublicKey, - feeRate: number, - network: bitcoin.networks.Network, - isDepositAll: boolean = false -): { - psbt: bitcoin.Psbt; - amountToSend: number; - returnAmount: number; - usedUTXOs: UTXOs; -} => { - if (utxos.length === 0) { - throw new Error("No UTXOs available"); - } - - if (feeRate < 1) { - throw new Error("Invalid satoshisPerVBytes"); - } - - // FIXME: sort utxos by satoshis from low to high - utxos.sort((a, b) => a.satoshis - b.satoshis); - - // if the fee is higher than the amount to deposit - const spendableUTXOs = utxos.filter((utxo) => isSpendable(utxo, feeRate)); - - if (spendableUTXOs.length === 0) { - throw new Error("No spendable UTXOs available"); - } - - // available amount - const totalAvailableAmount = spendableUTXOs.reduce( - (acc, utxo) => acc + utxo.satoshis, - 0 - ); - - if (totalAvailableAmount < amount) { - throw new Error("Insufficient balance"); - } - - const amountToSend = amount; - - const { output, address } = bitcoin.payments.p2tr({ - internalPubkey: userXOnlyPubKey, - network: network, - }); - - if (!output) { - throw new Error("Invalid output"); - } - - if (!address) { - throw new Error("Invalid address"); - } - - const psbt = new bitcoin.Psbt({ network }).setVersion(2); - - let TOTAL_VBYTE = TX_BASIC_VBYTE; - let preparedAmount = BigInt(0); - - const pickedUTXOs: UTXOs = []; - - for (const utxo of spendableUTXOs) { - psbt.addInput({ - hash: utxo.transaction_id, - index: utxo.transaction_index, - witnessUtxo: { - script: output!, - value: utxo.satoshis, - }, - tapInternalKey: userXOnlyPubKey, - }); - preparedAmount += BigInt(utxo.satoshis); - TOTAL_VBYTE += TX_INPUT_VBYTE; - pickedUTXOs.push(utxo); - if ( - preparedAmount >= - BigInt(amountToSend) + BigInt(feeRate) * BigInt(Math.ceil(TOTAL_VBYTE)) - ) { - break; - } - } - - // basic 1 output - TOTAL_VBYTE += TX_OUTPUT_VBYTE; - - // if not deposit all, add 1 more output - if (!isDepositAll) { - TOTAL_VBYTE += TX_OUTPUT_VBYTE; - } - - const returnAmount = Number( - preparedAmount - - BigInt(amountToSend) - - BigInt(feeRate) * BigInt(Math.ceil(TOTAL_VBYTE)) - ); - - if (returnAmount < 0) { - throw new Error("Insufficient balance for fee"); - } - - psbt.addOutput({ - address: hotReserveAddress, - value: amountToSend, - }); - - // FIXME: there is a return amount, might need to handle minimum dust amount - if (returnAmount != 0 && returnAmount > DUST_AMOUNT) { - psbt.addOutput({ - address: address, - value: returnAmount, - }); - } - - psbt.setMaximumFeeRate(feeRate + 1); - - return { - psbt, - amountToSend: Number(amountToSend), - returnAmount: Number(returnAmount), - usedUTXOs: pickedUTXOs, - }; -}; - -/** - * - * @param utxos available utxos - * @param feeRate fee rate in satoshis per vbyte - * @returns spendable amount in satoshis - */ -export const estimateMaxSpendableAmount = ( - utxos: UTXOs, - feeRate: number -): number => { - if (utxos.length === 0) { - return 0; - } - - // FIXME: if need? - const spendableUTXOs = utxos.filter((utxo) => isSpendable(utxo, feeRate)); - - if (spendableUTXOs.length === 0) { - return 0; - } - - let preparedAmount = BigInt(0); - let TOTAL_VBYTE = TX_BASIC_VBYTE; - - for (const utxo of spendableUTXOs) { - TOTAL_VBYTE += TX_INPUT_VBYTE; - preparedAmount += BigInt(utxo.satoshis); - } - - // spend all means only 1 output - TOTAL_VBYTE += TX_OUTPUT_VBYTE; - - const toSendAmount = - preparedAmount - BigInt(feeRate) * BigInt(Math.ceil(TOTAL_VBYTE)); - - return Number(toSendAmount); -}; - -/** - * Calculate the hot reserve bucket address based on the cold reserve address's internal key and the user's unlocking script. - * * the key_path_spend_public_key and script_path_spend_public_key must be tweaked public keys. - * @param {Buffer} key_path_spend_public_key - cold reserve address's internal key (must be tweaked public key) - * @param {Buffer} script_path_spend_public_key - user's unlocking script (must be tweaked public key) - * @param {number} lockTime - the lock time of the hot reserve address - * @param {bitcoin.Network=} network - the network to use - * @return - the hot reserve address and the script - */ -export function deriveHotReserveAddress( - // tweaked pubkey that could directly spend the UTXO, usually the address of zeus node operator - key_path_spend_public_key: Buffer, - // user's unlocking script - script_path_spend_public_key: Buffer, - lockTime: number, - network?: bitcoin.Network -): { - address: string; - script: Buffer; - hash: Buffer | undefined; - output: Buffer | undefined; - pubkey: Buffer | undefined; -} { - network = network ?? bitcoin.networks.regtest; - - // bitcoin csv encoding sample - // * ref: https://github.com/bitcoinjs/bitcoinjs-lib/blob/151173f05e26a9af7c98d8d1e3f90e97185955f1/test/integration/csv.spec.ts#L61 - const targetScript = `${tools.toHex(bitcoin.script.number.encode(lockTime))} OP_CHECKSEQUENCEVERIFY OP_DROP ${toXOnly(script_path_spend_public_key).toString("hex")} OP_CHECKSIG`; - - const tap = bitcoin.script.fromASM(targetScript); - - const script_p2tr = bitcoin.payments.p2tr({ - internalPubkey: toXOnly(key_path_spend_public_key), - scriptTree: { - output: tap, - }, - network, - }); - - if (script_p2tr.address === undefined) { - throw new Error("Failed to calculate the address"); - } - - return { - address: script_p2tr.address!, - script: tap, - hash: script_p2tr.hash, - output: script_p2tr.output, - pubkey: script_p2tr.pubkey, - }; -} - export default bitcoin; diff --git a/src/components/Mint/Modals/ConfirmDeposit.tsx b/src/components/Mint/Modals/ConfirmDeposit.tsx index 318003cd..6280019e 100644 --- a/src/components/Mint/Modals/ConfirmDeposit.tsx +++ b/src/components/Mint/Modals/ConfirmDeposit.tsx @@ -8,9 +8,9 @@ import { toXOnly } from "bitcoinjs-lib/src/psbt/bip371"; import { BN } from "bn.js"; import classNames from "classnames"; import { useState } from "react"; +import { buildDepositToHotReserveTx } from "zpl-sdk-js/bitcoin"; import { btcToSatoshi, convertBitcoinNetwork } from "@/bitcoin"; -import { constructDepositToHotReserveTx } from "@/bitcoin"; import { sendTransaction } from "@/bitcoin/rpcClient"; import { getInternalXOnlyPubkeyFromUserWallet } from "@/bitcoin/wallet"; import Button from "@/components/Button/Button"; @@ -59,7 +59,6 @@ export interface ConfirmDepositModalProps { amount: string; isLocked: boolean; }; - isDepositAll: boolean; signPsbt: (psbt: Psbt, tweaked?: boolean) => Promise; updateTransactions: () => Promise; resetProvideAmountValue: () => void; @@ -75,7 +74,6 @@ export default function ConfirmDepositModal({ minerFee, assetFrom, assetTo, - isDepositAll, signPsbt, updateTransactions, resetProvideAmountValue, @@ -139,14 +137,13 @@ export default function ConfirmDepositModal({ let depositPsbt; let usedBitcoinUTXOs; try { - const { psbt, usedUTXOs } = constructDepositToHotReserveTx( + const { psbt, usedUTXOs } = buildDepositToHotReserveTx( bitcoinUTXOs, targetHotReserveAddress, btcToSatoshi(depositAmount), userXOnlyPublicKey, feeRate, - convertBitcoinNetwork(bitcoinNetwork), - isDepositAll + convertBitcoinNetwork(bitcoinNetwork) ); depositPsbt = psbt; usedBitcoinUTXOs = usedUTXOs; diff --git a/src/components/Widgets/MintWidget/Deposit.tsx b/src/components/Widgets/MintWidget/Deposit.tsx index f440b301..fb3c41dc 100644 --- a/src/components/Widgets/MintWidget/Deposit.tsx +++ b/src/components/Widgets/MintWidget/Deposit.tsx @@ -1,9 +1,9 @@ import { PublicKey } from "@solana/web3.js"; import { Psbt } from "bitcoinjs-lib"; import { useState } from "react"; +import { estimateMaxSpendableAmount } from "zpl-sdk-js/bitcoin"; -import { btcToSatoshi, satoshiToBtc } from "@/bitcoin"; -import { estimateMaxSpendableAmount } from "@/bitcoin"; +import { satoshiToBtc } from "@/bitcoin"; import Icon from "@/components/Icons"; import { DepositTooltip } from "@/components/Mint/DepositTooltip/DepositTooltip"; import AccountProcess from "@/components/Mint/Modals/AccountProcess"; @@ -270,7 +270,6 @@ export default function Deposit({ name: "zBTC", isLocked: true, }} - isDepositAll={btcToSatoshi(provideAmount) === maxSpendableSatoshis} signPsbt={signPsbt} updateTransactions={updateDepositTransactions} resetProvideAmountValue={resetProvideAmountValue} diff --git a/src/hooks/zpl/useHotReserveBucketActions.ts b/src/hooks/zpl/useHotReserveBucketActions.ts index 6cdc1437..3e6ee614 100644 --- a/src/hooks/zpl/useHotReserveBucketActions.ts +++ b/src/hooks/zpl/useHotReserveBucketActions.ts @@ -1,10 +1,10 @@ import { useWallet } from "@solana/wallet-adapter-react"; import { PublicKey } from "@solana/web3.js"; import { useCallback } from "react"; +import { deriveHotReserveAddress } from "zpl-sdk-js/bitcoin"; import { HotReserveBucketStatus } from "zpl-sdk-js/two-way-peg/types"; import { convertBitcoinNetwork, UNLOCK_BLOCK_HEIGHT } from "@/bitcoin"; -import { deriveHotReserveAddress } from "@/bitcoin"; import { getInternalXOnlyPubkeyFromUserWallet } from "@/bitcoin/wallet"; import { useZplClient } from "@/contexts/ZplClientProvider"; import { useNetworkConfig } from "@/hooks/misc/useNetworkConfig"; From 8c69e959fbb8739d3af75974521693a566655566 Mon Sep 17 00:00:00 2001 From: Edward Date: Fri, 16 May 2025 21:27:06 +0800 Subject: [PATCH 3/5] feat: use zpl sdk from npm --- .gitignore | 3 -- package-lock.json | 54 +++++++++++++++---- package.json | 2 +- src/components/Mint/Modals/ConfirmDeposit.tsx | 2 +- .../Mint/Modals/ConfirmWithdraw.tsx | 2 +- src/components/PortfolioV2/Modals/Redeem.tsx | 2 +- .../PortfolioV2/Overview/PortfolioDetails.tsx | 2 +- src/components/Widgets/MintWidget/Deposit.tsx | 2 +- .../Widgets/MintWidget/Withdraw.tsx | 2 +- src/hooks/zpl/useHotReserveBucketActions.ts | 4 +- src/zplClient/index.ts | 5 +- 11 files changed, 57 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 7728c84f..337f041c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. -# npm -.npmrc - # dependencies /node_modules /.pnp diff --git a/package-lock.json b/package-lock.json index 97d8db79..151a01d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@solana/wallet-adapter-react-ui": "^0.9.35", "@solana/wallet-adapter-wallets": "^0.19.32", "@solana/web3.js": "^1.98.2", + "@zeus-network/zpl-sdk": "^0.11.1", "axios": "^1.7.2", "bech32": "^2.0.0", "bignumber.js": "^9.1.2", @@ -59,7 +60,6 @@ "tailwindcss-animate": "^1.0.7", "usehooks-ts": "^3.1.0", "zod": "^3.23.8", - "zpl-sdk-js": "file:../zpl-sdk-js", "zustand": "^4.5.2" }, "devDependencies": { @@ -93,9 +93,12 @@ } }, "../zpl-sdk-js": { - "version": "0.1.0", - "license": "ISC", + "name": "@zeus-network/zpl-sdk", + "version": "0.11.1", + "extraneous": true, + "license": "MIT", "dependencies": { + "@bitcoinerlab/secp256k1": "^1.2.0", "@coral-xyz/borsh": "^0.31.1", "@solana/buffer-layout": "^4.0.1", "bitcoinjs-lib": "^6.1.7", @@ -17130,6 +17133,26 @@ "license": "Apache-2.0", "peer": true }, + "node_modules/@zeus-network/zpl-sdk": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@zeus-network/zpl-sdk/-/zpl-sdk-0.11.1.tgz", + "integrity": "sha512-BIjEAiVOkFOa1QEU0AejG+Ra51mhDL1XicF8rVuKs9KKmfVmlY/+FTwyztF0QO8bMjkh/2uH4QKOtX3RjG+3Ag==", + "license": "MIT", + "dependencies": { + "@bitcoinerlab/secp256k1": "^1.2.0", + "@coral-xyz/borsh": "^0.31.1", + "@solana/buffer-layout": "^4.0.1", + "bitcoinjs-lib": "^6.1.7", + "bn.js": "^5.2.2", + "bs58": "^5.0.0", + "js-sha256": "^0.11.0", + "uint8array-tools": "^0.0.9" + }, + "peerDependencies": { + "@solana/spl-token": "^0.4.13", + "@solana/web3.js": "^1.98.2" + } + }, "node_modules/abbrev": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.0.tgz", @@ -18237,9 +18260,9 @@ "license": "MIT" }, "node_modules/bn.js": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", - "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==", "license": "MIT" }, "node_modules/body-scroll-lock": { @@ -25507,6 +25530,12 @@ "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==", "license": "BSD-3-Clause" }, + "node_modules/js-sha256": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz", + "integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -33499,6 +33528,15 @@ "license": "MIT", "peer": true }, + "node_modules/uint8array-tools": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.9.tgz", + "integrity": "sha512-9vqDWmoSXOoi+K14zNaf6LBV51Q8MayF0/IiQs3GlygIKUYtog603e6virExkjjFosfJUBI4LhbQK1iq8IG11A==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/uint8arrays": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", @@ -35637,10 +35675,6 @@ "url": "https://github.com/sponsors/colinhacks" } }, - "node_modules/zpl-sdk-js": { - "resolved": "../zpl-sdk-js", - "link": true - }, "node_modules/zustand": { "version": "4.5.6", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", diff --git a/package.json b/package.json index 9940680f..502c15d2 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@solana/wallet-adapter-react-ui": "^0.9.35", "@solana/wallet-adapter-wallets": "^0.19.32", "@solana/web3.js": "^1.98.2", + "@zeus-network/zpl-sdk": "^0.11.1", "axios": "^1.7.2", "bech32": "^2.0.0", "bignumber.js": "^9.1.2", @@ -78,7 +79,6 @@ "tailwindcss-animate": "^1.0.7", "usehooks-ts": "^3.1.0", "zod": "^3.23.8", - "zpl-sdk-js": "file:../zpl-sdk-js", "zustand": "^4.5.2" }, "devDependencies": { diff --git a/src/components/Mint/Modals/ConfirmDeposit.tsx b/src/components/Mint/Modals/ConfirmDeposit.tsx index 6280019e..78169b18 100644 --- a/src/components/Mint/Modals/ConfirmDeposit.tsx +++ b/src/components/Mint/Modals/ConfirmDeposit.tsx @@ -1,5 +1,6 @@ import { captureException } from "@sentry/nextjs"; import { PublicKey } from "@solana/web3.js"; +import { buildDepositToHotReserveTx } from "@zeus-network/zpl-sdk/bitcoin"; import { AxiosError } from "axios"; import BigNumber from "bignumber.js"; import * as bitcoin from "bitcoinjs-lib"; @@ -8,7 +9,6 @@ import { toXOnly } from "bitcoinjs-lib/src/psbt/bip371"; import { BN } from "bn.js"; import classNames from "classnames"; import { useState } from "react"; -import { buildDepositToHotReserveTx } from "zpl-sdk-js/bitcoin"; import { btcToSatoshi, convertBitcoinNetwork } from "@/bitcoin"; import { sendTransaction } from "@/bitcoin/rpcClient"; diff --git a/src/components/Mint/Modals/ConfirmWithdraw.tsx b/src/components/Mint/Modals/ConfirmWithdraw.tsx index 8ea44d16..7898b2ce 100644 --- a/src/components/Mint/Modals/ConfirmWithdraw.tsx +++ b/src/components/Mint/Modals/ConfirmWithdraw.tsx @@ -3,12 +3,12 @@ import { getAccount, getAssociatedTokenAddressSync } from "@solana/spl-token"; import { WalletSignTransactionError } from "@solana/wallet-adapter-base"; import { useConnection } from "@solana/wallet-adapter-react"; import { PublicKey, TransactionInstruction } from "@solana/web3.js"; +import { Position } from "@zeus-network/zpl-sdk/liquidity-management/types"; import BigNumber from "bignumber.js"; import { BN } from "bn.js"; import classNames from "classnames"; import { AnimatePresence, motion } from "framer-motion"; import { useState } from "react"; -import { Position } from "zpl-sdk-js/liquidity-management/types"; import { convertP2trToTweakedXOnlyPubkey } from "@/bitcoin"; import Button from "@/components/Button/Button"; diff --git a/src/components/PortfolioV2/Modals/Redeem.tsx b/src/components/PortfolioV2/Modals/Redeem.tsx index 8ec8a880..a5d99508 100644 --- a/src/components/PortfolioV2/Modals/Redeem.tsx +++ b/src/components/PortfolioV2/Modals/Redeem.tsx @@ -7,10 +7,10 @@ import { import { WalletSignTransactionError } from "@solana/wallet-adapter-base"; import { useWallet, useConnection } from "@solana/wallet-adapter-react"; import { PublicKey, TransactionInstruction } from "@solana/web3.js"; +import { Position } from "@zeus-network/zpl-sdk/liquidity-management/types"; import BigNumber from "bignumber.js"; import { BN } from "bn.js"; import { useState } from "react"; -import { Position } from "zpl-sdk-js/liquidity-management/types"; import Button from "@/components/Button/Button"; import Icon from "@/components/Icons"; diff --git a/src/components/PortfolioV2/Overview/PortfolioDetails.tsx b/src/components/PortfolioV2/Overview/PortfolioDetails.tsx index 9a904754..ee099465 100644 --- a/src/components/PortfolioV2/Overview/PortfolioDetails.tsx +++ b/src/components/PortfolioV2/Overview/PortfolioDetails.tsx @@ -1,6 +1,6 @@ +import { Position } from "@zeus-network/zpl-sdk/liquidity-management/types"; import BigNumber from "bignumber.js"; import { useRef, useState } from "react"; -import { Position } from "zpl-sdk-js/liquidity-management/types"; import Button from "@/components/Button/Button"; import Icon from "@/components/Icons"; diff --git a/src/components/Widgets/MintWidget/Deposit.tsx b/src/components/Widgets/MintWidget/Deposit.tsx index fb3c41dc..d9d88903 100644 --- a/src/components/Widgets/MintWidget/Deposit.tsx +++ b/src/components/Widgets/MintWidget/Deposit.tsx @@ -1,7 +1,7 @@ import { PublicKey } from "@solana/web3.js"; +import { estimateMaxSpendableAmount } from "@zeus-network/zpl-sdk/bitcoin"; import { Psbt } from "bitcoinjs-lib"; import { useState } from "react"; -import { estimateMaxSpendableAmount } from "zpl-sdk-js/bitcoin"; import { satoshiToBtc } from "@/bitcoin"; import Icon from "@/components/Icons"; diff --git a/src/components/Widgets/MintWidget/Withdraw.tsx b/src/components/Widgets/MintWidget/Withdraw.tsx index f19aba75..61f40034 100644 --- a/src/components/Widgets/MintWidget/Withdraw.tsx +++ b/src/components/Widgets/MintWidget/Withdraw.tsx @@ -1,7 +1,7 @@ import { PublicKey } from "@solana/web3.js"; +import { Position } from "@zeus-network/zpl-sdk/liquidity-management/types"; import BigNumber from "bignumber.js"; import { useState } from "react"; -import { Position } from "zpl-sdk-js/liquidity-management/types"; import { xOnlyPubkeyHexToP2tr } from "@/bitcoin"; import Icon from "@/components/Icons"; diff --git a/src/hooks/zpl/useHotReserveBucketActions.ts b/src/hooks/zpl/useHotReserveBucketActions.ts index 3e6ee614..2c09a14c 100644 --- a/src/hooks/zpl/useHotReserveBucketActions.ts +++ b/src/hooks/zpl/useHotReserveBucketActions.ts @@ -1,8 +1,8 @@ import { useWallet } from "@solana/wallet-adapter-react"; import { PublicKey } from "@solana/web3.js"; +import { deriveHotReserveAddress } from "@zeus-network/zpl-sdk/bitcoin"; +import { HotReserveBucketStatus } from "@zeus-network/zpl-sdk/two-way-peg/types"; import { useCallback } from "react"; -import { deriveHotReserveAddress } from "zpl-sdk-js/bitcoin"; -import { HotReserveBucketStatus } from "zpl-sdk-js/two-way-peg/types"; import { convertBitcoinNetwork, UNLOCK_BLOCK_HEIGHT } from "@/bitcoin"; import { getInternalXOnlyPubkeyFromUserWallet } from "@/bitcoin/wallet"; diff --git a/src/zplClient/index.ts b/src/zplClient/index.ts index e3a39862..7cbae0f8 100644 --- a/src/zplClient/index.ts +++ b/src/zplClient/index.ts @@ -7,7 +7,10 @@ import { TransactionMessage, VersionedTransaction, } from "@solana/web3.js"; -import { LiquidityManagementClient, TwoWayPegClient } from "zpl-sdk-js"; +import { + LiquidityManagementClient, + TwoWayPegClient, +} from "@zeus-network/zpl-sdk"; export class ZplClient { public twoWayPegProgramId: PublicKey; From 9fed1338af65c9b2ad5d1ef6f8b1a16a14514e98 Mon Sep 17 00:00:00 2001 From: Edward Date: Fri, 16 May 2025 22:09:02 +0800 Subject: [PATCH 4/5] docs: update README --- .env.example | 1 + README.md | 102 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 70 insertions(+), 33 deletions(-) diff --git a/.env.example b/.env.example index d50f687c..87fb5bb1 100644 --- a/.env.example +++ b/.env.example @@ -16,4 +16,5 @@ NEXT_PUBLIC_REGTEST_DEVNET_ARES_URL=https://bitcoin-api-gateway-regtest-devnet.z NEXT_PUBLIC_REGTEST_DEVNET_AEGLE_URL=https://api-regtest-devnet.apollobyzeus.space +# TODO: You can customize the redeem address here NEXT_PUBLIC_DEVNET_REDEEM_ADDRESS= diff --git a/README.md b/README.md index 36941dfb..6f64c341 100644 --- a/README.md +++ b/README.md @@ -481,9 +481,9 @@ export default function Home() { // although we have a array of hotReserveBuckets, but the user could only bind one bitcoin address with the protocol, so we only need to get the first one const hotReserveBuckets = - await zplClient.getHotReserveBucketsByBitcoinXOnlyPubkey( - userXOnlyPublicKey - ); + await zplClient.twoWayPeg.accounts.getHotReserveBucketsByBitcoinXOnlyPubkey( + userXOnlyPublicKey + ); if (!hotReserveBuckets || hotReserveBuckets.length === 0) { console.log("No hot reserve address found"); @@ -684,9 +684,12 @@ export default function Home() { if (!twoWayPegGuardianSetting) throw new Error("Two way peg guardian setting not found"); - const retrieveIx = zplClient.constructRetrieveIx( + const retrieveIx = zplClient.liquidityManagement.instructions.buildRetrieveIx( amountToRedeem, - new PublicKey(twoWayPegGuardianSetting) + solanaPubkey, + zplClient.assetMint, + new PublicKey(twoWayPegGuardianSetting), + receiverAta ); ixs.push(retrieveIx); @@ -824,13 +827,13 @@ export default function Home() { ); const twoWayPegConfiguration = - await zplClient.getTwoWayPegConfiguration(); + await zplClient.twoWayPeg.accounts.getConfiguration(); const ixs: TransactionInstruction[] = []; // NOTE: asset is in vault, so use the biggest position guardian first if (assetFrom.isLocked) { - if (!positions) { + if (!positions) { return; } @@ -849,22 +852,36 @@ export default function Home() { const twoWayPegGuardianSetting = twoWayPegGuardianSettings.find( (setting) => - zplClient - .deriveLiquidityManagementGuardianSettingAddress( - new PublicKey(setting.address) - ) - .toBase58() === position.guardianSetting.toBase58() + zplClient.liquidityManagement.pdas + .deriveVaultSettingAddress(new PublicKey(setting.address)) + .toBase58() === position.vaultSetting.toBase58() ); if (!twoWayPegGuardianSetting) return; - const withdrawalRequestIx = zplClient.constructAddWithdrawalRequestIx( - solanaPubkey, - amountToWithdraw, - convertP2trToTweakedXOnlyPubkey(selectedWallet), - new PublicKey(twoWayPegGuardianSetting.address), - twoWayPegConfiguration.layerFeeCollector - ); + const vaultSettingPda = + zplClient.liquidityManagement.pdas.deriveVaultSettingAddress( + new PublicKey(twoWayPegGuardianSetting.address) + ); + + const currentTimestamp = new BN(Date.now() / 1000); + + const withdrawalRequestIx = + zplClient.twoWayPeg.instructions.buildAddWithdrawalRequestIx( + amountToWithdraw, + currentTimestamp, + convertP2trToTweakedXOnlyPubkey(selectedWallet), + solanaPubkey, + twoWayPegConfiguration.layerFeeCollector, + new PublicKey(twoWayPegGuardianSetting.address), + zplClient.liquidityManagementProgramId, + zplClient.liquidityManagement.pdas.deriveConfigurationAddress(), + vaultSettingPda, + zplClient.liquidityManagement.pdas.derivePositionAddress( + vaultSettingPda, + solanaPubkey + ) + ); ixs.push(withdrawalRequestIx); remainingAmount = remainingAmount.sub(amountToWithdraw); @@ -873,14 +890,14 @@ export default function Home() { } // NOTE: asset is in wallet, so need to check all guardians store quota and store to the biggest quota guardian first } else { - const twoWayPegGuardiansWithQuota = await Promise.all( + const twoWayPegGuardiansWithQuota = await Promise.all( twoWayPegGuardianSettings.map(async (twoWayPegGuardianSetting) => { const totalSplTokenMinted = new BN( twoWayPegGuardianSetting.total_amount_pegged ); const splTokenVaultAuthority = - zplClient.deriveSplTokenVaultAuthorityAddress( + zplClient.liquidityManagement.pdas.deriveSplTokenVaultAuthorityAddress( new PublicKey(twoWayPegGuardianSetting.address) ); @@ -905,7 +922,7 @@ export default function Home() { address: twoWayPegGuardianSetting.address, remainingStoreQuota, liquidityManagementGuardianSetting: - zplClient.deriveLiquidityManagementGuardianSettingAddress( + zplClient.liquidityManagement.pdas.deriveVaultSettingAddress( new PublicKey(twoWayPegGuardianSetting.address) ), }; @@ -924,18 +941,37 @@ export default function Home() { remainingAmount ); - const storeIx = zplClient.constructStoreIx( - withdrawAmountBN, - new PublicKey(twoWayPegGuardian.address) - ); + const storeIx = + zplClient.liquidityManagement.instructions.buildStoreIx( + withdrawAmountBN, + solanaPubkey, + zplClient.assetMint, + new PublicKey(twoWayPegGuardian.address) + ); - const withdrawalRequestIx = zplClient.constructAddWithdrawalRequestIx( - solanaPubkey, - amountToWithdraw, - convertP2trToTweakedXOnlyPubkey(selectedWallet), - new PublicKey(twoWayPegGuardian.address), - twoWayPegConfiguration.layerFeeCollector - ); + const vaultSettingPda = + zplClient.liquidityManagement.pdas.deriveVaultSettingAddress( + new PublicKey(twoWayPegGuardian.address) + ); + + const currentTimestamp = new BN(Date.now() / 1000); + + const withdrawalRequestIx = + zplClient.twoWayPeg.instructions.buildAddWithdrawalRequestIx( + amountToWithdraw, + currentTimestamp, + convertP2trToTweakedXOnlyPubkey(selectedWallet), + solanaPubkey, + twoWayPegConfiguration.layerFeeCollector, + new PublicKey(twoWayPegGuardian.address), + zplClient.liquidityManagementProgramId, + zplClient.liquidityManagement.pdas.deriveConfigurationAddress(), + vaultSettingPda, + zplClient.liquidityManagement.pdas.derivePositionAddress( + vaultSettingPda, + solanaPubkey + ) + ); ixs.push(storeIx); ixs.push(withdrawalRequestIx); From d660b30fee5ed896c5d74c580725c39e32a61c2f Mon Sep 17 00:00:00 2001 From: Edward Date: Fri, 16 May 2025 22:22:51 +0800 Subject: [PATCH 5/5] fix: fix redeem tx issue --- README.md | 82 +++++++++++--------- src/components/PortfolioV2/Modals/Redeem.tsx | 76 +++++++++--------- 2 files changed, 83 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 6f64c341..6ec175b1 100644 --- a/README.md +++ b/README.md @@ -670,6 +670,12 @@ export default function Home() { .toString() ); + const receiverAta = getAssociatedTokenAddressSync( + zplClient.assetMint, + solanaPubkey, + true + ); + const ixs: TransactionInstruction[] = []; let remainingAmount = redeemAmountBN.clone(); @@ -684,51 +690,53 @@ export default function Home() { if (!twoWayPegGuardianSetting) throw new Error("Two way peg guardian setting not found"); - const retrieveIx = zplClient.liquidityManagement.instructions.buildRetrieveIx( - amountToRedeem, - solanaPubkey, - zplClient.assetMint, - new PublicKey(twoWayPegGuardianSetting), - receiverAta - ); - - ixs.push(retrieveIx); - - const escrow_address = process.env.NEXT_PUBLIC_ESCROW_ADDRESS; // set your escrow address here or other method you like - if(escrow_address) { - const targetAddress = new PublicKey(escrow_address); - const toATA = getAssociatedTokenAddressSync( - new PublicKey(config.assetMint), - targetAddress, - true // allow off curve to approve PDA - ); - // check if the associated token account of target address initialized - const info = await connection.getAccountInfo(toATA); - if (!info) { - // if not, create one - const createIx = createAssociatedTokenAccountInstruction( - solanaPubkey, - toATA, - targetAddress, - new PublicKey(config.assetMint) - ); - ixs.push(createIx); - } - // add a transfer instruction to transfer the tokens to the receive_address - const transferIx = createTransferInstruction( - receiverAta, - toATA, + const retrieveIx = + zplClient.liquidityManagement.instructions.buildRetrieveIx( + amountToRedeem, solanaPubkey, - BigInt(amountToRedeem.toString()) + zplClient.assetMint, + new PublicKey(twoWayPegGuardianSetting), + receiverAta ); - ixs.push(transferIx); - } + ixs.push(retrieveIx); remainingAmount = remainingAmount.sub(amountToRedeem); if (remainingAmount.eq(new BN(0))) break; } + // TODO: You can customize the retrieve address here + if (process.env.NEXT_PUBLIC_DEVNET_REDEEM_ADDRESS) { + const targetAddress = new PublicKey( + process.env.NEXT_PUBLIC_DEVNET_REDEEM_ADDRESS + ); + const toATA = getAssociatedTokenAddressSync( + new PublicKey(config.assetMint), + targetAddress, + true + ); + // check if the target address has an associated token account + const info = await connection.getAccountInfo(toATA); + if (!info) { + // if not, create one + const createIx = createAssociatedTokenAccountInstruction( + solanaPubkey, + toATA, + targetAddress, + new PublicKey(config.assetMint) + ); + ixs.push(createIx); + } + // add a transfer instruction to transfer the tokens to the receive_address + const transferIx = createTransferInstruction( + receiverAta, + toATA, + solanaPubkey, + BigInt(redeemAmountBN.toString()) + ); + ixs.push(transferIx); + } + const sig = await zplClient.signAndSendTransactionWithInstructions(ixs); console.log(sig); } catch (error) { diff --git a/src/components/PortfolioV2/Modals/Redeem.tsx b/src/components/PortfolioV2/Modals/Redeem.tsx index a5d99508..952c5915 100644 --- a/src/components/PortfolioV2/Modals/Redeem.tsx +++ b/src/components/PortfolioV2/Modals/Redeem.tsx @@ -134,6 +134,12 @@ export default function RedeemModal({ .toString() ); + const receiverAta = getAssociatedTokenAddressSync( + zplClient.assetMint, + solanaPubkey, + true + ); + const ixs: TransactionInstruction[] = []; let remainingAmount = redeemAmountBN.clone(); @@ -148,12 +154,6 @@ export default function RedeemModal({ if (!twoWayPegGuardianSetting) throw new Error("Two way peg guardian setting not found"); - const receiverAta = getAssociatedTokenAddressSync( - zplClient.assetMint, - solanaPubkey, - true - ); - const retrieveIx = zplClient.liquidityManagement.instructions.buildRetrieveIx( amountToRedeem, @@ -164,43 +164,43 @@ export default function RedeemModal({ ); ixs.push(retrieveIx); - // TODO: You can customize the retrieve address here - if (process.env.NEXT_PUBLIC_DEVNET_REDEEM_ADDRESS) { - const targetAddress = new PublicKey( - process.env.NEXT_PUBLIC_DEVNET_REDEEM_ADDRESS - ); - const toATA = getAssociatedTokenAddressSync( - new PublicKey(config.assetMint), - targetAddress, - true - ); - // check if the target address has an associated token account - const info = await connection.getAccountInfo(toATA); - if (!info) { - // if not, create one - const createIx = createAssociatedTokenAccountInstruction( - solanaPubkey, - toATA, - targetAddress, - new PublicKey(config.assetMint) - ); - ixs.push(createIx); - } - // add a transfer instruction to transfer the tokens to the receive_address - const transferIx = createTransferInstruction( - receiverAta, - toATA, - solanaPubkey, - BigInt(amountToRedeem.toString()) - ); - ixs.push(transferIx); - } - remainingAmount = remainingAmount.sub(amountToRedeem); if (remainingAmount.eq(new BN(0))) break; } + // TODO: You can customize the retrieve address here + if (process.env.NEXT_PUBLIC_DEVNET_REDEEM_ADDRESS) { + const targetAddress = new PublicKey( + process.env.NEXT_PUBLIC_DEVNET_REDEEM_ADDRESS + ); + const toATA = getAssociatedTokenAddressSync( + new PublicKey(config.assetMint), + targetAddress, + true + ); + // check if the target address has an associated token account + const info = await connection.getAccountInfo(toATA); + if (!info) { + // if not, create one + const createIx = createAssociatedTokenAccountInstruction( + solanaPubkey, + toATA, + targetAddress, + new PublicKey(config.assetMint) + ); + ixs.push(createIx); + } + // add a transfer instruction to transfer the tokens to the receive_address + const transferIx = createTransferInstruction( + receiverAta, + toATA, + solanaPubkey, + BigInt(redeemAmountBN.toString()) + ); + ixs.push(transferIx); + } + const sig = await zplClient.signAndSendTransactionWithInstructions(ixs); await mutateBalance();