diff --git a/docs/docs/getting-started.mdx b/docs/docs/getting-started.mdx index 668b6b9..702f434 100644 --- a/docs/docs/getting-started.mdx +++ b/docs/docs/getting-started.mdx @@ -78,7 +78,7 @@ const [walletAddress] = await pufferClient.requestAddresses(); Mint pufETH to your `walletAddress` or set a target recipient: ```ts -const { transact, estimate } = pufferClient.depositETH(walletAddress); +const { transact, estimate } = pufferClient.vault.depositETH(walletAddress); // Returns gas estimate of the transaction. const gasEstimate = await estimate(); diff --git a/docs/docs/guides/depositing-eth.md b/docs/docs/guides/depositing-eth.md index 3227755..11ff880 100644 --- a/docs/docs/guides/depositing-eth.md +++ b/docs/docs/guides/depositing-eth.md @@ -29,7 +29,7 @@ const [walletAddress] = await pufferClient.requestAddresses(); With your address at hand, make the transaction to deposit ETH to mint pufETH. ```ts -const { transact, estimate } = pufferClient.depositETH(walletAddress); +const { transact, estimate } = pufferClient.vault.depositETH(walletAddress); const weiAmount = new BigInt(1e18); @@ -42,7 +42,7 @@ const txHash = await transact(weiAmount); Alternatively, you can set the pufETH recipient to a different address. ```ts -const { transact, estimate } = pufferClient.depositETH(recipientAddress); +const { transact, estimate } = pufferClient.vault.depositETH(recipientAddress); const weiAmount = new BigInt(1e18); diff --git a/lib/api/puffer-client.test.ts b/lib/api/puffer-client.test.ts index 4617de7..3913fa4 100644 --- a/lib/api/puffer-client.test.ts +++ b/lib/api/puffer-client.test.ts @@ -38,7 +38,7 @@ describe('PufferClient', () => { walletClient, publicClient, ); - const { transact, estimate } = pufferClient.depositETH(mockAddress); + const { transact, estimate } = pufferClient.vault.depositETH(mockAddress); expect(await transact(BigInt(1))).toBe(mockAddress); expect(await estimate()).toBe(mockGas); diff --git a/lib/api/puffer-client.ts b/lib/api/puffer-client.ts index 89e2549..f5688fc 100644 --- a/lib/api/puffer-client.ts +++ b/lib/api/puffer-client.ts @@ -1,55 +1,57 @@ import { - Address, PublicClient, WalletClient, createPublicClient, createWalletClient, - getContract, http, } from 'viem'; -import { Chain, VIEM_CHAINS, ViemChain } from '../chains/constants'; -import { CHAIN_ADDRESSES } from '../contracts/addresses'; -import { ValueOf } from '../utils/types'; -import { CHAIN_ABIS } from '../contracts/abis/abis'; +import { Chain, VIEM_CHAINS } from '../chains/constants'; +import { PufferVaultHandler } from '../contracts/handlers/puffer-vault-handler'; /** * The core class and the main entry point of the Puffer SDK. */ export class PufferClient { - private chainAddresses: ValueOf; - private chainAbis: ValueOf; - - private viemChain: ViemChain; private walletClient: WalletClient; private publicClient: PublicClient; + // Contract Handlers + public vault: PufferVaultHandler; + /** * Create the Puffer Client. + * * @param chain Chain to use for the client. - * @param walletClient The wallet client to use for wallet interactions. - * @param publicClient The public client to use for public interactions. + * @param walletClient The wallet client to use for wallet + * interactions. + * @param publicClient The public client to use for public + * interactions. */ constructor( chain: Chain, walletClient?: WalletClient, publicClient?: PublicClient, ) { - this.chainAddresses = CHAIN_ADDRESSES[chain]; - this.chainAbis = CHAIN_ABIS[chain]; - this.viemChain = VIEM_CHAINS[chain]; + const viemChain = VIEM_CHAINS[chain]; this.walletClient = walletClient ?? createWalletClient({ - chain: this.viemChain, + chain: viemChain, transport: http(), }); this.publicClient = publicClient ?? createPublicClient({ - chain: this.viemChain, + chain: viemChain, transport: http(), }); + + this.vault = new PufferVaultHandler( + chain, + this.walletClient, + this.publicClient, + ); } /** @@ -60,42 +62,4 @@ export class PufferClient { public async requestAddresses() { return await this.walletClient.requestAddresses(); } - - /** - * Deposit ETH to the given wallet address. This doesn't make the - * transaction but returns two methods namely `transact` and - * `estimate`. - * - * @param walletAddress Wallet address to get the ETH from. - * @returns `transact: (value: bigint) => Promise
` - Used to - * make the transaction with the given value. - * - * `estimate: () => Promise` - Gas estimate of the - * transaction. - */ - public depositETH(walletAddress: Address) { - const contract = getContract({ - address: this.chainAddresses.PufferVault as Address, - abi: this.chainAbis.PufferVaultV2, - client: { - wallet: this.walletClient, - // Public client is needed for simulation. - public: this.publicClient, - }, - }); - - const transact = async (value: bigint) => - await contract.write.depositETH([walletAddress], { - account: walletAddress, - chain: this.viemChain, - value, - }); - - const estimate = async () => - await contract.estimateGas.depositETH([walletAddress], { - account: walletAddress, - }); - - return { transact, estimate }; - } } diff --git a/lib/contracts/addresses.ts b/lib/contracts/addresses.ts index 5649d9e..1b522e7 100644 --- a/lib/contracts/addresses.ts +++ b/lib/contracts/addresses.ts @@ -7,7 +7,7 @@ export const CHAIN_ADDRESSES = { PufferVault: '0xD9A442856C234a39a81a089C06451EBAa4306a72', }, [Chain.Holesky]: { - PufferVault: '0x98408eadD0C7cC9AebbFB2AD2787c7473Db7A1fa', + PufferVault: '0x9196830bB4c05504E0A8475A0aD566AceEB6BeC9', }, [Chain.Anvil]: { PufferVault: '0xf770bF9384c5aaD8b8a6EFAb5951CF089656A371', diff --git a/lib/contracts/handlers/puffer-vault-handler.test.ts b/lib/contracts/handlers/puffer-vault-handler.test.ts new file mode 100644 index 0000000..4fc5248 --- /dev/null +++ b/lib/contracts/handlers/puffer-vault-handler.test.ts @@ -0,0 +1,113 @@ +import { toHex } from 'viem'; +import { mockRpcRequest } from '../../../test/mocks/mock-request'; +import { + setupMockWalletClient, + setupMockPublicClient, +} from '../../../test/mocks/setup-mock-clients'; +import { Chain } from '../../chains/constants'; +import { PufferVaultHandler } from './puffer-vault-handler'; + +describe('PufferVaultHandler', () => { + it('should deposit ETH', async () => { + const mockAddress = '0xEB77D02f8122B32273444a1b544C147Fb559CB41'; + const mockGas = BigInt(1); + + const walletRequest = mockRpcRequest({ + eth_sendTransaction: mockAddress, + }); + const walletClient = setupMockWalletClient(walletRequest); + const publicRequest = mockRpcRequest({ eth_estimateGas: mockGas }); + const publicClient = setupMockPublicClient(publicRequest); + + const handler = new PufferVaultHandler( + Chain.Anvil, + walletClient, + publicClient, + ); + const { transact, estimate } = handler.depositETH(mockAddress); + + expect(await transact(BigInt(1))).toBe(mockAddress); + expect(await estimate()).toBe(mockGas); + }); + + it('should deposit stETH', async () => { + const mockAddress = '0xEB77D02f8122B32273444a1b544C147Fb559CB41'; + const mockGas = BigInt(1); + const mockValue = BigInt(1); + + const walletRequest = mockRpcRequest({ + eth_sendTransaction: mockAddress, + }); + const walletClient = setupMockWalletClient(walletRequest); + const publicRequest = mockRpcRequest({ eth_estimateGas: mockGas }); + const publicClient = setupMockPublicClient(publicRequest); + + const handler = new PufferVaultHandler( + Chain.Anvil, + walletClient, + publicClient, + ); + const { transact, estimate } = handler.depositStETH(mockAddress, mockValue); + + expect(await transact()).toBe(mockAddress); + expect(await estimate()).toBe(mockGas); + }); + + it('should check pufETH balance', async () => { + const mockAddress = '0xEB77D02f8122B32273444a1b544C147Fb559CB41'; + const mockBalance = BigInt(1); + const mockCallHex = toHex(mockBalance, { size: 32 }); + + const walletClient = setupMockWalletClient(); + const publicRequest = mockRpcRequest({ eth_call: mockCallHex }); + const publicClient = setupMockPublicClient(publicRequest); + + const handler = new PufferVaultHandler( + Chain.Anvil, + walletClient, + publicClient, + ); + const balance = await handler.balanceOf(mockAddress); + + expect(balance).toBe(mockBalance); + }); + + it('should check pufETH rate', async () => { + const mockRate = BigInt(1e18); + const mockCallHex = toHex(mockRate, { size: 32 }); + + const walletClient = setupMockWalletClient(); + const publicRequest = mockRpcRequest({ eth_call: mockCallHex }); + const publicClient = setupMockPublicClient(publicRequest); + + const handler = new PufferVaultHandler( + Chain.Anvil, + walletClient, + publicClient, + ); + const rate = await handler.getPufETHRate(); + + expect(rate).toBe(mockRate); + }); + + it('should get allowance', async () => { + const mockAllowance = BigInt(1); + const mockCallHex = toHex(mockAllowance, { size: 32 }); + + const walletClient = setupMockWalletClient(); + const publicRequest = mockRpcRequest({ eth_call: mockCallHex }); + const publicClient = setupMockPublicClient(publicRequest); + + const handler = new PufferVaultHandler( + Chain.Anvil, + walletClient, + publicClient, + ); + const rate = await handler.getAllowance( + '0xEB77D02f8122B32273444a1b544C147Fb559CB41', + '0x92e01fbccae21eed10ab2f278f47905d482df202', + ); + + expect(rate).toBe(mockAllowance); + }); +}); diff --git a/lib/contracts/handlers/puffer-vault-handler.ts b/lib/contracts/handlers/puffer-vault-handler.ts new file mode 100644 index 0000000..5ec17b1 --- /dev/null +++ b/lib/contracts/handlers/puffer-vault-handler.ts @@ -0,0 +1,142 @@ +import { + Address, + Chain as ViemChain, + PublicClient, + WalletClient, + getContract, +} from 'viem'; +import { CHAIN_ABIS } from '../abis/abis'; +import { Chain, VIEM_CHAINS } from '../../chains/constants'; +import { CHAIN_ADDRESSES } from '../addresses'; + +/** + * Handler for the `PufferVaultV2` contract exposing methods to interact + * with the contract. + */ +export class PufferVaultHandler { + private viemChain: ViemChain; + + /** + * Create the handler for the `PufferVaultV2` contract exposing + * methods to interact with the contract. + * + * @param chain Chain to use for the client. + * @param walletClient The wallet client to use for wallet + * interactions. + * @param publicClient The public client to use for public + * interactions. + */ + constructor( + private chain: Chain, + private walletClient: WalletClient, + private publicClient: PublicClient, + ) { + this.viemChain = VIEM_CHAINS[chain]; + } + + // This is a method because the typings are complex and lost when + // trying to make it a member. + private getContract() { + return getContract({ + address: CHAIN_ADDRESSES[this.chain].PufferVault as Address, + abi: CHAIN_ABIS[this.chain].PufferVaultV2, + client: { + wallet: this.walletClient, + public: this.publicClient, + }, + }); + } + + /** + * Deposit ETH to the given wallet address. This doesn't make the + * transaction but returns two methods namely `transact` and + * `estimate`. + * + * @param walletAddress Wallet address to get the ETH from. + * @returns `transact: (value: bigint) => Promise
` - Used to + * make the transaction with the given value. + * + * `estimate: () => Promise` - Gas estimate of the + * transaction. + */ + public depositETH(walletAddress: Address) { + const transact = async (value: bigint) => + await this.getContract().write.depositETH([walletAddress], { + account: walletAddress, + chain: this.viemChain, + value, + }); + + const estimate = async () => + await this.getContract().estimateGas.depositETH([walletAddress], { + account: walletAddress, + }); + + return { transact, estimate }; + } + + /** + * Deposit stETH to the given wallet address. This doesn't make the + * transaction but returns two methods namely `transact` and + * `estimate`. + * + * @param walletAddress Wallet address to get the ETH from. + * @param value Value in wei of the stETH to deposit. + * @returns `transact: () => Promise
` - Used to make the + * transaction with the given value. + * + * `estimate: () => Promise` - Gas estimate of the + * transaction. + */ + public depositStETH(walletAddress: Address, value: bigint) { + const transact = async () => + await this.getContract().write.depositStETH([value, walletAddress], { + account: walletAddress, + chain: this.viemChain, + }); + + const estimate = async () => + await this.getContract().estimateGas.depositStETH( + [value, walletAddress], + { + account: walletAddress, + }, + ); + + return { transact, estimate }; + } + + /** + * Check the pufETH balance of the wallet. + * + * @param walletAddress Wallet address to check the balance of. + * @returns pufETH balance in wei. + */ + public async balanceOf(walletAddress: Address) { + return await this.getContract().read.balanceOf([walletAddress]); + } + + /** + * Get the rate of pufETH compared to ETH. + * + * @returns Rate of pufETH compared to 1 ETH. + */ + public async getPufETHRate() { + const oneWei = BigInt(1e18); + return await this.getContract().read.previewDeposit([oneWei]); + } + + /** + * Get the allowance for the given owner and spender. + * + * @param ownerAddress Address of the owner. + * @param spenderAddress Address of the spender. + * @returns Allowance for the given owner and spender. + */ + public async getAllowance(ownerAddress: Address, spenderAddress: Address) { + return await this.getContract().read.allowance([ + ownerAddress, + spenderAddress, + ]); + } +}