diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 32764ed..669bcae 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,11 +8,15 @@ on: - 'v**' - 'releases/v**' +permissions: + packages: read + contents: read + jobs: build-and-test: runs-on: ubuntu-latest env: - CR_PAT: ${{ secrets.GITHUB_TOKEN }} + GHP_PAT: ${{ secrets.GITHUB_TOKEN }} steps: - name: Checkout repo diff --git a/package.json b/package.json index efa649b..585a20a 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,15 @@ "source": "src/index.ts", "types": "dist/types.d.ts", "scripts": { - "build": "pnpm clean && pnpm codegen && pnpm parcel build --no-cache", + "build": "pnpm clean && pnpm generate && pnpm parcel build --no-cache", "ci:release": "pnpm build && pnpm changeset publish", "clean": "rm -rf ./dist", "codegen": "rm -rf ./src/lib/codegen && wagmi generate && npx buf generate", "format": "prettier --write \"**/*.{ts,tsx,md,json}\"", "gen-docs": "typedoc", + "generate": "rm -rf ./src/lib/codegen && pnpm generate:wagmi && pnpm generate:grpc", + "generate:grpc": "npx buf generate", + "generate:wagmi": "wagmi generate", "lint": "eslint .", "lint:fix": "eslint . --fix", "prepare": "git submodule update --init --recursive && pnpm codegen", @@ -61,7 +64,7 @@ "@connectrpc/connect-query": "0.5.3", "@connectrpc/connect-web": "^1.1.2", "@tanstack/react-query": "^4.36.1", - "@valorem-labs-inc/sdk": "^0.0.4", + "@valorem-labs-inc/sdk": "^0.0.8", "@wagmi/core": "1.4.7", "abitype": "0.8.7", "connectkit": "^1.5.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe0b294..6c206a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -24,8 +24,8 @@ dependencies: specifier: ^4.36.1 version: 4.36.1(react-dom@18.2.0)(react@18.2.0) '@valorem-labs-inc/sdk': - specifier: ^0.0.4 - version: 0.0.4(@types/react@18.2.39)(react@18.2.0)(typescript@5.3.2)(viem@1.19.9)(zod@3.22.4) + specifier: ^0.0.8 + version: 0.0.8(@bufbuild/protobuf@1.4.2)(@connectrpc/connect@1.1.3)(@wagmi/core@1.4.7)(typescript@5.3.2)(viem@1.19.9) typescript: specifier: ^5.2.0 version: 5.3.2 @@ -3398,43 +3398,22 @@ packages: resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} dev: true - /@valorem-labs-inc/sdk@0.0.4(@types/react@18.2.39)(react@18.2.0)(typescript@5.3.2)(viem@1.19.9)(zod@3.22.4): - resolution: {integrity: sha512-jiUah39W/zP7elbZrnJg/IAMMj2YckWXXYf3GUZdBen/YTSrbQKA4YDOIffHY2xSifOECdA5hruT/wp8g0agiQ==, tarball: https://npm.pkg.github.com/download/@valorem-labs-inc/sdk/0.0.4/338a78cbd44a076c19d4dd7e87911c54c12a08e2} + /@valorem-labs-inc/sdk@0.0.8(@bufbuild/protobuf@1.4.2)(@connectrpc/connect@1.1.3)(@wagmi/core@1.4.7)(typescript@5.3.2)(viem@1.19.9): + resolution: {integrity: sha512-UK9uHWhOlaXm95eZV9jp0EOJsMuBSangkNc/KhZ9iuj3csCvMKE69Bdt+73YeOT+5qIwgo1EGsFizNoW2MTwzA==, tarball: https://npm.pkg.github.com/download/@valorem-labs-inc/sdk/0.0.8/04832344bcd5671bd7e3fc4cbf05d79a3f31cd0e} engines: {node: '>=18'} peerDependencies: - typescript: '>=5.2.0' - viem: '>=1.0.0' - peerDependenciesMeta: - typescript: - optional: true + '@bufbuild/protobuf': ^1.4.2 + '@connectrpc/connect': ^1.1.3 + '@wagmi/core': ^1.4.7 + typescript: ^5.2.0 + viem: ^1.19.9 dependencies: - '@bufbuild/buf': 1.28.1 '@bufbuild/protobuf': 1.4.2 '@connectrpc/connect': 1.1.3(@bufbuild/protobuf@1.4.2) '@wagmi/core': 1.4.7(@types/react@18.2.39)(react@18.2.0)(typescript@5.3.2)(viem@1.19.9)(zod@3.22.4) + mathjs: 12.2.0 typescript: 5.3.2 viem: 1.19.9(typescript@5.3.2)(zod@3.22.4) - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@capacitor/preferences' - - '@netlify/blobs' - - '@planetscale/database' - - '@react-native-async-storage/async-storage' - - '@types/react' - - '@upstash/redis' - - '@vercel/kv' - - bufferutil - - encoding - - immer - - react - - supports-color - - utf-8-validate - - zod dev: false /@vercel/style-guide@5.1.0(eslint@8.54.0)(prettier@3.1.0)(typescript@5.3.2): @@ -4808,6 +4787,10 @@ packages: engines: {node: '>= 10'} dev: true + /complex.js@2.1.1: + resolution: {integrity: sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==} + dev: false + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true @@ -5063,7 +5046,6 @@ packages: /decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} - dev: true /decode-uri-component@0.2.2: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} @@ -5549,6 +5531,10 @@ packages: engines: {node: '>=6'} dev: true + /escape-latex@1.2.0: + resolution: {integrity: sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==} + dev: false + /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -6224,6 +6210,10 @@ packages: fetch-blob: 3.2.0 dev: true + /fraction.js@4.3.4: + resolution: {integrity: sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q==} + dev: false + /framer-motion@6.5.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-o1BGqqposwi7cgDrtg0dNONhkmPsUFDaLcKXigzuTFC5x58mE8iyTazxSudFzmT6MEyJKfjjU8ItoMe3W+3fiw==} peerDependencies: @@ -7047,6 +7037,10 @@ packages: set-function-name: 2.0.1 dev: true + /javascript-natural-sort@0.7.1: + resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} + dev: false + /jayson@4.1.0: resolution: {integrity: sha512-R6JlbyLN53Mjku329XoRT2zJAE6ZgOQ8f91ucYdMCD4nkGCF9kZSrcGXpHIU4jeKj58zUZke2p+cdQchU7Ly7A==} engines: {node: '>=8'} @@ -7691,6 +7685,22 @@ packages: hasBin: true dev: true + /mathjs@12.2.0: + resolution: {integrity: sha512-lEoZ3OtOno1dxta5xHnPOwPI+z94/+NX70JwaJynHosKdq10wJ4InYadxXaHM19ALr/UZbmTixFS7F4ufJnazg==} + engines: {node: '>= 18'} + hasBin: true + dependencies: + '@babel/runtime': 7.23.5 + complex.js: 2.1.1 + decimal.js: 10.4.3 + escape-latex: 1.2.0 + fraction.js: 4.3.4 + javascript-natural-sort: 0.7.1 + seedrandom: 3.0.5 + tiny-emitter: 2.1.0 + typed-function: 4.1.1 + dev: false + /mdn-data@2.0.14: resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} dev: true @@ -8913,6 +8923,10 @@ packages: dependencies: loose-envify: 1.4.0 + /seedrandom@3.0.5: + resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} + dev: false + /semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -9417,6 +9431,10 @@ packages: resolution: {integrity: sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==} dev: true + /tiny-emitter@2.1.0: + resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} + dev: false + /tinybench@2.5.1: resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} dev: true @@ -9611,6 +9629,11 @@ packages: is-typed-array: 1.1.12 dev: true + /typed-function@4.1.1: + resolution: {integrity: sha512-Pq1DVubcvibmm8bYcMowjVnnMwPVMeh0DIdA8ad8NZY2sJgapANJmiigSUwlt+EgXxpfIv8MWrQXTIzkfYZLYQ==} + engines: {node: '>= 14'} + dev: false + /typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} dependencies: diff --git a/src/hooks/useSoftQuote.ts b/src/hooks/useSoftQuote.ts new file mode 100644 index 0000000..28790b1 --- /dev/null +++ b/src/hooks/useSoftQuote.ts @@ -0,0 +1,130 @@ +import type { + OptionType, + ParsedSoftQuoteResponse, +} from '@valorem-labs-inc/sdk'; +import { + CLEAR_ADDRESS, + ItemType, + QuoteRequest, + SoftQuote, + SEAPORT_ADDRESS, + parseSoftQuoteResponse, + toH160, + toH256, +} from '@valorem-labs-inc/sdk'; +import { useQueryClient } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import { useAccount, useChainId } from 'wagmi'; +import { hexToBigInt } from 'viem'; +import { useStream } from './useStream'; +import { usePromiseClient } from './usePromiseClient'; + +/** + * Configuration for the useSoftQuote hook. + * quoteRequest - An object or instance containing the details for requesting a quote. + * enabled - Flag to enable the hook. + * timeoutMs - Timeout for the quote request in milliseconds. + * onError - Callback function for handling errors. + */ +export interface UseRFQConfig { + quoteRequest: + | QuoteRequest + | { + tokenId: OptionType['tokenId']; + action: QuoteRequest['action']; + amount: bigint; + } + | undefined; + enabled?: boolean; + timeoutMs?: number; + onError?: (err: Error) => void; +} + +/** + * Return type of the useSoftQuote hook. + * quotes - Array of parsed quote responses. + * responses - Array of raw quote responses. + * openStream - Function to open the stream for receiving quotes. + * resetAndRestartStream - Function to reset and restart the quote stream. + * abortStream - Function to abort the quote stream. + * error - Error object if an error occurred during the RFQ process. + */ +export interface UseRFQReturn { + quotes?: ParsedSoftQuoteResponse[]; + responses?: ParsedSoftQuoteResponse[]; + openStream: () => Promise<() => void>; + resetAndRestartStream: () => void; + abortStream: () => void; + error?: Error; +} + +/** + * Hook to manage the useSoftQuote process in the Valorem trading environment. + * It handles sending quote requests to market makers and receiving their responses. + * @param config - Configuration for the RFQ process. + * @returns An object containing the quotes, response management functions, and any errors. + */ +export const useSoftQuote = ({ + quoteRequest, + enabled, + timeoutMs = 15000, + onError, +}: UseRFQConfig): UseRFQReturn => { + const grpcClient = usePromiseClient(SoftQuote); + const queryClient = useQueryClient(); + const { address } = useAccount(); + const chainId = useChainId(); + + const request = useMemo(() => { + if (quoteRequest === undefined) return undefined; + + // pre-constructed quote request + if (quoteRequest instanceof QuoteRequest) return quoteRequest; + + // construct quote request from quote request config + if (address === undefined) return undefined; + const { tokenId, action, amount } = quoteRequest; + if (tokenId === undefined) return undefined; + + return new QuoteRequest({ + ulid: undefined, + takerAddress: toH160(address), + itemType: ItemType.ERC1155, + tokenAddress: toH160(CLEAR_ADDRESS), + identifierOrCriteria: toH256(tokenId), + amount: toH256(amount), + action, + chainId: toH256(BigInt(chainId)), + seaportAddress: toH160(hexToBigInt(SEAPORT_ADDRESS)), + }); + }, [address, chainId, quoteRequest]); + + const { + data, + responses, + openStream, + resetAndRestartStream, + abortStream, + error, + } = useStream({ + queryClient, + queryKey: ['useSoftQuote'], + grpcClient, + method: 'webTaker', + request, + enabled: enabled && request !== undefined, + keepAlive: true, + timeoutMs, + parseResponse: parseSoftQuoteResponse, + onError, + }); + + return { + quotes: data, + responses, + openStream, + resetAndRestartStream, + abortStream, + error, + }; +}; diff --git a/src/lib/trade-interfaces b/src/lib/trade-interfaces index 246f732..e841c89 160000 --- a/src/lib/trade-interfaces +++ b/src/lib/trade-interfaces @@ -1 +1 @@ -Subproject commit 246f732b140b8b7a78ce686b78d87714189371d4 +Subproject commit e841c8981883ccd0207301717c7c4ef143a76ff0