Last active
January 9, 2024 23:54
-
-
Save imthatcarlos/379cb5c4b3b3851dc714800dc222d378 to your computer and use it in GitHub Desktop.
React hook for MadFi onchain points + redemption (see: https://docs.madfi.xyz/protocol-overview/onchain-points-and-redemption)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { useState, useEffect } from 'react'; | |
import { TransactionReceipt } from 'viem'; | |
import { useAccount, useWalletClient } from 'wagmi'; | |
import { getPublicClient } from '@wagmi/core'; | |
import request, { gql } from 'graphql-request'; | |
const CHAIN_ID = 137; | |
const MADFI_SUBGRAPH_URL = "https://api.thegraph.com/subgraphs/name/mad-finance/madfi-subgraph"; | |
const SBT_REDEMPTION_CONTRACT_ADDRESS = ""; // see: https://docs.madfi.xyz | |
const SBT_REDEMPTION_ABI = [ | |
{ | |
"anonymous": false, | |
"inputs": [ | |
{ | |
"indexed": true, | |
"internalType": "uint256", | |
"name": "collectionId", | |
"type": "uint256" | |
}, | |
{ | |
"indexed": false, | |
"internalType": "uint256", | |
"name": "tokenId", | |
"type": "uint256" | |
}, | |
{ | |
"indexed": false, | |
"internalType": "string", | |
"name": "provider", | |
"type": "string" | |
}, | |
{ | |
"indexed": false, | |
"internalType": "uint128", | |
"name": "units", | |
"type": "uint128" | |
} | |
], | |
"name": "Redemption", | |
"type": "event" | |
}, | |
{ | |
"inputs": [ | |
{ | |
"internalType": "uint256", | |
"name": "tokenId", | |
"type": "uint256" | |
} | |
], | |
"name": "getRewardUnitsRedeemable", | |
"outputs": [ | |
{ | |
"internalType": "uint128", | |
"name": "", | |
"type": "uint128" | |
} | |
], | |
"stateMutability": "view", | |
"type": "function" | |
}, | |
{ | |
"inputs": [ | |
{ | |
"components": [ | |
{ | |
"internalType": "uint256", | |
"name": "tokenId", | |
"type": "uint256" | |
}, | |
{ | |
"internalType": "string", | |
"name": "provider", | |
"type": "string" | |
}, | |
{ | |
"internalType": "uint128", | |
"name": "units", | |
"type": "uint128" | |
} | |
], | |
"internalType": "struct ISBTRedemption.RedemptionParams", | |
"name": "params", | |
"type": "tuple" | |
}, | |
{ | |
"internalType": "bytes", | |
"name": "signature", | |
"type": "bytes" | |
} | |
], | |
"name": "redeemRewardUnits", | |
"outputs": [], | |
"stateMutability": "nonpayable", | |
"type": "function" | |
} | |
]; | |
const _buildRedemptionParamsTypedData = (chainId: number, verifyingContract: `0x${string}`) => ({ | |
types: { | |
RedemptionParams: [ | |
{ name: 'tokenId', type: 'uint256' }, | |
{ name: 'provider', type: 'string' }, | |
{ name: 'units', type: 'uint128' } | |
], | |
}, | |
domain: { | |
name: 'MadFi Redemptions', | |
version: '1', | |
chainId, | |
verifyingContract, | |
} | |
}); | |
const getSignedRedemptionParams = async ( | |
walletClient: any, | |
params: any, | |
chainId: number, | |
verifyingContract: `0x${string}` | |
) => { | |
const { domain, types } = _buildRedemptionParamsTypedData(chainId, verifyingContract); | |
const [account] = await walletClient.getAddresses(); | |
return await walletClient.signTypedData({ | |
account, | |
domain, | |
types, | |
primaryType: "RedemptionParams", | |
message: params | |
}); | |
}; | |
export default (collectionId?: string, _tokenId?: string) => { | |
const { address } = useAccount(); | |
const { data: walletClient } = useWalletClient(); | |
const [isRedeeming, setIsRedeeming] = useState<boolean>(false); | |
const [isLoading, setIsLoading] = useState<boolean>(false); | |
const [redeemablePoints, setRedeemablePoints] = useState<BigInt>(BigInt(0)); | |
const [tokenId, setTokenId] = useState<string | undefined>(_tokenId); | |
const MAD_SBT_TOKEN_OWNED = gql` | |
query ($collectionId: String!, $addressLowerCase: String!) { | |
madSbtTokens(where:{collection_:{collectionId:$collectionId}, owner_:{id:$addressLowerCase}}){ | |
tokenId | |
rewardPoints | |
} | |
} | |
`; | |
const fetchTokenId = async (): Promise<string | undefined> => { | |
if (!(collectionId && address)) return; | |
// @ts-expect-error: unknown response | |
const { madSbtTokens } = await request({ | |
url: MADFI_SUBGRAPH_URL, | |
document: MAD_SBT_TOKEN_OWNED, | |
variables: { collectionId, addressLowerCase: address.toLowerCase() } | |
}); | |
const tokenId = madSbtTokens?.length ? madSbtTokens[0].tokenId : undefined; | |
setTokenId(tokenId) | |
return tokenId; | |
}; | |
const fetchRedeemablePoints = async () => { | |
let _points = BigInt(0); | |
const _tokenId = tokenId || await fetchTokenId(); | |
if (_tokenId) { | |
const publicClient = getPublicClient(); | |
const data = await publicClient.readContract({ | |
address: SBT_REDEMPTION_CONTRACT_ADDRESS!, | |
abi: SBT_REDEMPTION_ABI, | |
functionName: "getRewardUnitsRedeemable", | |
args: [_tokenId], | |
}); | |
_points = data as bigint; | |
} | |
setRedeemablePoints(_points); | |
setIsLoading(false); | |
}; | |
/** | |
* Request a signature from the connected wallet client to send to the SBTRedemption contract for redemption. Only | |
* token holders _or_ verified addresses can call #redeemRewardUnits | |
* @param provider The string for the third-party provider fulfilling the redemption (ex: 'https://perk.shop') | |
* @param units The units to redeem | |
* @returns tx receipt of the redemption tx, or undefined on failure | |
*/ | |
const redeemPoints = async (provider: string, units: number): Promise<TransactionReceipt | undefined> => { | |
if (redeemablePoints === 0n) return; | |
setIsRedeeming(true); | |
try { | |
const publicClient = getPublicClient(); | |
const params = { provider, units: units.toString(), tokenId }; | |
const signature = await getSignedRedemptionParams( | |
walletClient, | |
params, | |
CHAIN_ID, | |
SBT_REDEMPTION_CONTRACT_ADDRESS | |
); | |
const hash = await walletClient!.writeContract({ | |
address: SBT_REDEMPTION_CONTRACT_ADDRESS, | |
abi: SBT_REDEMPTION_ABI, | |
functionName: "redeemRewardUnits", | |
args: [params, signature], | |
}); | |
console.log(`hash: ${hash}`); | |
return await publicClient.waitForTransactionReceipt({ hash }); | |
} catch (error) { | |
console.log(error); | |
} | |
setIsRedeeming(false); | |
}; | |
useEffect(() => { | |
if ((collectionId && walletClient && address)) { | |
setIsLoading(true); | |
fetchRedeemablePoints(); | |
} | |
}, [collectionId, walletClient, address]); | |
return { | |
isLoading, | |
redeemablePoints, | |
isRedeeming, | |
redeemPoints, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment