@@ -24,6 +24,7 @@ export interface PermitNftParams {
2424 tokenId : string
2525 spender : string
2626 deadline ?: number
27+ version ?: 'v3' | 'v4' | 'auto' // specify version or auto-detect
2728}
2829
2930export interface PermitNftResult {
@@ -36,26 +37,49 @@ export interface PermitNftResult {
3637// NFT Position Manager ABI for permit functionality
3738const NFT_PERMIT_ABI = [
3839 'function name() view returns (string)' ,
39- 'function nonces(address owner, uint256 word) view returns (uint256 bitmap)' ,
40+ 'function nonces(address owner, uint256 word) view returns (uint256 bitmap)' , // V4 unordered nonces
41+ 'function positions(uint256 tokenId) view returns (uint96 nonce, address operator, address token0, address token1, uint24 fee, int24 tickLower, int24 tickUpper, uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128, uint128 tokensOwed0, uint128 tokensOwed1)' , // V3 ordered nonces
4042 'function permit(address spender, uint256 tokenId, uint256 deadline, uint256 nonce, bytes signature) payable' ,
4143]
4244
4345// 30 days validity buffer
4446const PERMIT_NFT_VALIDITY_BUFFER = 30 * 24 * 60 * 60
4547
46- export const usePermitNft = ( { contractAddress, tokenId, spender, deadline } : PermitNftParams ) => {
48+ export const usePermitNft = ( { contractAddress, tokenId, spender, deadline, version = 'auto' } : PermitNftParams ) => {
4749 const { account, chainId } = useActiveWeb3React ( )
4850 const { library } = useWeb3React ( )
4951 const notify = useNotify ( )
5052 const [ isSigningInProgress , setIsSigningInProgress ] = useState ( false )
5153 const [ permitData , setPermitData ] = useState < PermitNftResult | null > ( null )
54+ const [ detectedVersion , setDetectedVersion ] = useState < 'v3' | 'v4' | null > ( null )
5255
5356 const nftContract = useReadingContract ( contractAddress , NFT_PERMIT_ABI )
5457
55- // Get nonces bitmap for word 0
58+ // Get nonces bitmap for word 0 (V4 style)
5659 const noncesState = useSingleCallResult ( nftContract , 'nonces' , [ account , 0 ] )
60+ // Get position data (V3 style) - only call if we have a tokenId
61+ const positionsState = useSingleCallResult ( nftContract , 'positions' , tokenId ? [ tokenId ] : undefined )
5762 const nameState = useSingleCallResult ( nftContract , 'name' , [ ] )
5863
64+ // Auto-detect version based on available data
65+ const actualVersion = useMemo ( ( ) => {
66+ if ( version !== 'auto' ) return version
67+
68+ if ( detectedVersion ) return detectedVersion
69+
70+ // Try to detect based on available data
71+ if ( positionsState ?. result && ! positionsState . error ) {
72+ setDetectedVersion ( 'v3' )
73+ return 'v3'
74+ }
75+ if ( noncesState ?. result && ! noncesState . error ) {
76+ setDetectedVersion ( 'v4' )
77+ return 'v4'
78+ }
79+
80+ return 'v4' // Default to v4 if uncertain
81+ } , [ version , detectedVersion , positionsState , noncesState ] )
82+
5983 const permitState = useMemo ( ( ) => {
6084 if ( ! account || ! contractAddress || ! tokenId || ! spender ) {
6185 return PermitNftState . NOT_APPLICABLE
@@ -79,12 +103,38 @@ export const usePermitNft = ({ contractAddress, tokenId, spender, deadline }: Pe
79103 throw new Error ( 'No free nonce in word 0; pick a different word.' )
80104 } , [ ] )
81105
106+ // Get nonce based on version
107+ const getNonce = useCallback ( ( ) : BigNumber | null => {
108+ if ( actualVersion === 'v3' ) {
109+ // Use ordered nonce from positions function
110+ if ( positionsState ?. result ?. [ 0 ] !== undefined ) {
111+ return BigNumber . from ( positionsState . result [ 0 ] ) . add ( 1 ) // Next nonce is current + 1
112+ }
113+ } else {
114+ // Use unordered nonce from bitmap (V4)
115+ if ( noncesState ?. result ?. [ 0 ] ) {
116+ return findFreeNonce ( noncesState . result [ 0 ] , 0 )
117+ }
118+ }
119+ return null
120+ } , [ actualVersion , positionsState ?. result , noncesState ?. result , findFreeNonce ] )
121+
82122 const signPermitNft = useCallback ( async ( ) : Promise < PermitNftResult | null > => {
83- if ( ! library || ! account || ! chainId || ! noncesState ?. result ?. [ 0 ] || ! nameState ?. result ?. [ 0 ] ) {
123+ if ( ! library || ! account || ! chainId || ! nameState ?. result ?. [ 0 ] ) {
84124 console . error ( 'Missing required data for NFT permit' )
85125 return null
86126 }
87127
128+ // Check version-specific requirements
129+ if ( actualVersion === 'v3' && ! positionsState ?. result ) {
130+ console . error ( 'Missing positions data for V3 NFT permit' )
131+ return null
132+ }
133+ if ( actualVersion === 'v4' && ! noncesState ?. result ?. [ 0 ] ) {
134+ console . error ( 'Missing nonces data for V4 NFT permit' )
135+ return null
136+ }
137+
88138 if ( permitState !== PermitNftState . READY_TO_SIGN ) {
89139 console . error ( 'NFT permit not ready to sign' )
90140 return null
@@ -94,8 +144,12 @@ export const usePermitNft = ({ contractAddress, tokenId, spender, deadline }: Pe
94144
95145 try {
96146 const contractName = nameState . result [ 0 ]
97- const bitmap = noncesState . result [ 0 ]
98- const nonce = findFreeNonce ( bitmap , 0 )
147+ const nonce = getNonce ( )
148+
149+ if ( ! nonce ) {
150+ throw new Error ( `Failed to get nonce for ${ actualVersion } ` )
151+ }
152+
99153 const permitDeadline = deadline || Math . floor ( Date . now ( ) / 1000 ) + PERMIT_NFT_VALIDITY_BUFFER
100154
101155 // EIP-712 domain and types for NFT permit
@@ -135,17 +189,18 @@ export const usePermitNft = ({ contractAddress, tokenId, spender, deadline }: Pe
135189 message,
136190 } )
137191
138- console . log ( ' Signing NFT permit with data:' , typedData )
192+ console . log ( ` Signing ${ actualVersion } NFT permit with data:` , typedData )
139193
140194 const signature = await library . send ( 'eth_signTypedData_v4' , [ account . toLowerCase ( ) , typedData ] )
141195
142196 // Encode permit data for contract call
143197 const permitData = defaultAbiCoder . encode ( [ 'uint256' , 'uint256' , 'bytes' ] , [ permitDeadline , nonce , signature ] )
144198
199+ const v = actualVersion . toUpperCase ( )
145200 notify ( {
146201 type : NotificationType . SUCCESS ,
147202 title : t `NFT Permit Signed` ,
148- summary : t `Successfully signed permit for NFT #${ tokenId } ` ,
203+ summary : t `Successfully signed ${ v } permit for NFT #${ tokenId } ` ,
149204 } )
150205
151206 const result = {
@@ -180,16 +235,32 @@ export const usePermitNft = ({ contractAddress, tokenId, spender, deadline }: Pe
180235 spender ,
181236 deadline ,
182237 permitState ,
238+ actualVersion ,
183239 noncesState ?. result ,
240+ positionsState ?. result ,
184241 nameState ?. result ,
185- findFreeNonce ,
242+ getNonce ,
186243 notify ,
187244 ] )
188245
246+ // Check readiness based on version
247+ const isReady = useMemo ( ( ) => {
248+ if ( permitState !== PermitNftState . READY_TO_SIGN || ! nameState ?. result ) {
249+ return false
250+ }
251+
252+ if ( actualVersion === 'v3' ) {
253+ return ! ! positionsState ?. result
254+ } else {
255+ return ! ! noncesState ?. result
256+ }
257+ } , [ permitState , nameState ?. result , actualVersion , positionsState ?. result , noncesState ?. result ] )
258+
189259 return {
190260 permitState,
191261 signPermitNft,
192262 permitData,
193- isReady : permitState === PermitNftState . READY_TO_SIGN && ! ! noncesState ?. result && ! ! nameState ?. result ,
263+ isReady,
264+ version : actualVersion ,
194265 }
195266}
0 commit comments