From dafdc5016d706737ac73145f48907c39abde9f28 Mon Sep 17 00:00:00 2001 From: motechFR Date: Fri, 1 Nov 2024 22:05:07 +0200 Subject: [PATCH] Add code for merkle root --- .ebstalk.apps.env/scoutgameadmin.env | 3 +- .../app/(general)/contract/page.tsx | 6 +- .../components/contract/ContractsHome.tsx | 5 +- .../contract/ProtocolContractDashboard.tsx | 69 +-- .../lib/contract/aggregateProtocolData.ts | 49 ++ .../clients/ProtocolImplementationClient.ts | 426 ++++++++++++++++++ .../protocol/clients/ProtocolProxyClient.ts | 388 ++++++++++++++++ .../clients/getProxyClaimsManagerWallet.ts | 12 + .../protocol/clients/protocolReadClients.ts | 18 + .../protocol/clients/protocolWriteClients.ts | 17 + 10 files changed, 933 insertions(+), 60 deletions(-) create mode 100644 apps/scoutgameadmin/lib/contract/aggregateProtocolData.ts create mode 100644 packages/scoutgame/src/protocol/clients/ProtocolImplementationClient.ts create mode 100644 packages/scoutgame/src/protocol/clients/ProtocolProxyClient.ts create mode 100644 packages/scoutgame/src/protocol/clients/getProxyClaimsManagerWallet.ts create mode 100644 packages/scoutgame/src/protocol/clients/protocolReadClients.ts create mode 100644 packages/scoutgame/src/protocol/clients/protocolWriteClients.ts diff --git a/.ebstalk.apps.env/scoutgameadmin.env b/.ebstalk.apps.env/scoutgameadmin.env index 991e90b9a9..4046e63cf0 100644 --- a/.ebstalk.apps.env/scoutgameadmin.env +++ b/.ebstalk.apps.env/scoutgameadmin.env @@ -13,4 +13,5 @@ AIRSTACK_API_KEY="{{pull:secretsmanager:/io.cv.app/prd/farcaster:SecretString:ai REACT_APP_BUILDER_NFT_CONTRACT_ADDRESS="{{pull:secretsmanager:/io.cv.app/prd/buildernft:SecretString:builder_smart_contract_address}}" -REACT_APP_SCOUTPROTOCOL_CONTRACT_ADDRESS="{{pull:secretsmanager:/io.cv.app/prd/buildernft:SecretString:builder_smart_contract_address}}" \ No newline at end of file +REACT_APP_SCOUTPROTOCOL_CONTRACT_ADDRESS="{{pull:secretsmanager:/io.cv.app/prd/buildernft:SecretString:scoutprotocol_contract_address}}" +SCOUTPROTOCOL_CLAIMS_MANAGER_KEY="{{pull:secretsmanager:/io.cv.app/prd/buildernft:SecretString:scoutprotocol_claims_manager_key}}" \ No newline at end of file diff --git a/apps/scoutgameadmin/app/(general)/contract/page.tsx b/apps/scoutgameadmin/app/(general)/contract/page.tsx index 5faa032e43..3081ed526f 100644 --- a/apps/scoutgameadmin/app/(general)/contract/page.tsx +++ b/apps/scoutgameadmin/app/(general)/contract/page.tsx @@ -1,9 +1,11 @@ import { ContractHome } from 'components/contract/ContractsHome'; +import { aggregateProtocolData } from 'lib/contract/aggregateProtocolData'; import { getContractData } from 'lib/contract/getContractData'; export const dynamic = 'force-dynamic'; export default async function Dashboard() { - const data = await getContractData(); - return ; + const seasonOneData = await getContractData(); + const protocolData = await aggregateProtocolData(); + return ; } diff --git a/apps/scoutgameadmin/components/contract/ContractsHome.tsx b/apps/scoutgameadmin/components/contract/ContractsHome.tsx index 4ba6b1d76c..9adf43d460 100644 --- a/apps/scoutgameadmin/components/contract/ContractsHome.tsx +++ b/apps/scoutgameadmin/components/contract/ContractsHome.tsx @@ -3,12 +3,13 @@ import { Tabs, Tab, Box } from '@mui/material'; import { useState } from 'react'; +import type { ProtocolData } from 'lib/contract/aggregateProtocolData'; import type { BuilderNFTContractData } from 'lib/contract/getContractData'; import { ProtocolContractDashboard } from './ProtocolContractDashboard'; import { SeasonOneDashboard } from './SeasonOneDashboard'; -export function ContractHome({ seasonOne }: { seasonOne: BuilderNFTContractData }) { +export function ContractHome({ seasonOne, protocol }: { seasonOne: BuilderNFTContractData; protocol: ProtocolData }) { const [selectedTab, setSelectedTab] = useState(0); const handleChange = (event: React.SyntheticEvent, newValue: number) => { @@ -23,7 +24,7 @@ export function ContractHome({ seasonOne }: { seasonOne: BuilderNFTContractData {selectedTab === 0 && } - {selectedTab === 1 && } + {selectedTab === 1 && } ); diff --git a/apps/scoutgameadmin/components/contract/ProtocolContractDashboard.tsx b/apps/scoutgameadmin/components/contract/ProtocolContractDashboard.tsx index fdc3a3625b..1566ef9100 100644 --- a/apps/scoutgameadmin/components/contract/ProtocolContractDashboard.tsx +++ b/apps/scoutgameadmin/components/contract/ProtocolContractDashboard.tsx @@ -2,6 +2,7 @@ import { Box, Divider, Grid2, IconButton, Typography } from '@mui/material'; import Link from 'next/link'; import { MdLaunch } from 'react-icons/md'; +import type { ProtocolData } from 'lib/contract/aggregateProtocolData'; import type { BuilderNFTContractData } from 'lib/contract/getContractData'; function ContractLink({ @@ -45,7 +46,7 @@ function GridDivider() { ); } -export function ProtocolContractDashboard(data: BuilderNFTContractData) { +export function ProtocolContractDashboard(data: ProtocolData) { const itemSizeTwoColumnMd = { xs: 12, md: 6 }; const itemSizeThreeColumnMd = { xs: 12, md: 4 }; @@ -56,7 +57,7 @@ export function ProtocolContractDashboard(data: BuilderNFTContractData) { @@ -73,70 +74,28 @@ export function ProtocolContractDashboard(data: BuilderNFTContractData) { - - Registered builder NFTs - - {data.totalSupply.toString()} - - - - {/* Currently, this is the balance of the proceeds receiver wallet. Once we start moving funds, we should look at logs instead */} - Sales - - {Number(data.receiverUsdcBalance).toLocaleString('en-US')} USD - - - - {/* Currently, this is the balance of the proceeds receiver wallet. Once we start moving funds, we should look at logs instead */} - Unique NFT holders - - {Number(data.nftSalesData.uniqueHolders).toLocaleString('en-US')} - - - - {/* Currently, this is the balance of the proceeds receiver wallet. Once we start moving funds, we should look at logs instead */} - Total NFTs minted - - {Number(data.nftSalesData.totalNftsSold).toLocaleString('en-US')} - - - - {/* Currently, this is the balance of the proceeds receiver wallet. Once we start moving funds, we should look at logs instead */} - NFTs paid with points - - {Number(data.nftSalesData.nftsPaidWithPoints).toLocaleString('en-US')} - - - - {/* Currently, this is the balance of the proceeds receiver wallet. Once we start moving funds, we should look at logs instead */} - NFTs paid with crypto - - {Number(data.nftSalesData.nftsPaidWithCrypto).toLocaleString('en-US')} - - + {data.merkleRoots.map((root) => ( + + Merkle Root for week {root.week} + {root.root} + + ))} - - - diff --git a/apps/scoutgameadmin/lib/contract/aggregateProtocolData.ts b/apps/scoutgameadmin/lib/contract/aggregateProtocolData.ts new file mode 100644 index 0000000000..5b466dee2f --- /dev/null +++ b/apps/scoutgameadmin/lib/contract/aggregateProtocolData.ts @@ -0,0 +1,49 @@ +import { getAllISOWeeksFromSeasonStart } from '@packages/scoutgame/dates'; +import { + protocolImplementationReadonlyApiClient, + protocolProxyReadonlyApiClient +} from '@packages/scoutgame/protocol/clients/protocolReadClients'; +import { getScoutProtocolAddress } from '@packages/scoutgame/protocol/constants'; +import type { Address } from 'viem'; + +type MerkleRoot = { + week: string; + root: string; +}; + +export type ProtocolData = { + admin: Address; + proxy: Address; + implementation: Address; + claimsManager: Address; + merkleRoots: MerkleRoot[]; +}; + +export async function aggregateProtocolData(): Promise { + const [implementation, admin, claimsManager] = await Promise.all([ + protocolProxyReadonlyApiClient.implementation(), + protocolProxyReadonlyApiClient.admin(), + protocolProxyReadonlyApiClient.claimsManager() + ]); + + const weeks = getAllISOWeeksFromSeasonStart(); + + const merkleRoots = await Promise.all( + weeks.map((week) => + protocolImplementationReadonlyApiClient + .getMerkleRoot({ args: { week } }) + .then((root) => ({ week, root })) + .catch(() => { + return { week, root: 'No root found' }; + }) + ) + ); + + return { + merkleRoots, + admin: admin as Address, + proxy: getScoutProtocolAddress(), + implementation: implementation as Address, + claimsManager: claimsManager as Address + }; +} diff --git a/packages/scoutgame/src/protocol/clients/ProtocolImplementationClient.ts b/packages/scoutgame/src/protocol/clients/ProtocolImplementationClient.ts new file mode 100644 index 0000000000..1909a05c62 --- /dev/null +++ b/packages/scoutgame/src/protocol/clients/ProtocolImplementationClient.ts @@ -0,0 +1,426 @@ +import type { + Abi, + Account, + Address, + Chain, + Client, + PublicActions, + PublicClient, + RpcSchema, + TransactionReceipt, + Transport, + WalletActions, + WalletClient +} from 'viem'; +import { encodeFunctionData, decodeFunctionResult, getAddress } from 'viem'; + +// ReadWriteWalletClient reflects a wallet client that has been extended with PublicActions +// https://github.com/wevm/viem/discussions/1463#discussioncomment-7504732 +type ReadWriteWalletClient< + transport extends Transport = Transport, + chain extends Chain | undefined = Chain | undefined, + account extends Account | undefined = Account | undefined +> = Client< + transport, + chain, + account, + RpcSchema, + PublicActions & WalletActions +>; + +export class ProtocolImplementationClient { + private contractAddress: Address; + + private publicClient: PublicClient; + + private walletClient?: ReadWriteWalletClient; + + private chain: Chain; + + public abi: Abi = [ + { + inputs: [], + name: 'admin', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'string', + name: 'week', + type: 'string' + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256' + }, + { + internalType: 'bytes32[]', + name: 'proofs', + type: 'bytes32[]' + } + ], + name: 'claim', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool' + } + ], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [], + name: 'claimsManager', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'string', + name: 'week', + type: 'string' + } + ], + name: 'getMerkleRoot', + outputs: [ + { + internalType: 'bytes32', + name: '', + type: 'bytes32' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'string', + name: 'week', + type: 'string' + }, + { + internalType: 'address', + name: 'account', + type: 'address' + } + ], + name: 'hasClaimed', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: '_newAdmin', + type: 'address' + } + ], + name: 'setAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address' + } + ], + name: 'setClaimsManager', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'string', + name: 'week', + type: 'string' + }, + { + internalType: 'bytes32', + name: 'merkleRoot', + type: 'bytes32' + } + ], + name: 'setMerkleRoot', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + } + ]; + + constructor({ + contractAddress, + publicClient, + walletClient, + chain + }: { + contractAddress: Address; + chain: Chain; + publicClient?: PublicClient; + walletClient?: ReadWriteWalletClient; + }) { + if (!publicClient && !walletClient) { + throw new Error('At least one client is required.'); + } else if (publicClient && walletClient) { + throw new Error('Provide only a public client or wallet clients'); + } + + this.chain = chain; + this.contractAddress = contractAddress; + + const client = publicClient || walletClient; + + if (client!.chain!.id !== chain.id) { + throw new Error('Client must be on the same chain as the contract. Make sure to add a chain to your client'); + } + + if (publicClient) { + this.publicClient = publicClient; + } else { + this.walletClient = walletClient; + this.publicClient = walletClient as PublicClient; + } + } + + async admin(): Promise { + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'admin', + args: [] + }); + + const { data } = await this.publicClient.call({ + to: this.contractAddress, + data: txData + }); + + // Decode the result based on the expected return type + const result = decodeFunctionResult({ + abi: this.abi, + functionName: 'admin', + data: data as `0x${string}` + }); + + // Parse the result based on the return type + return result as string; + } + + async claim(params: { + args: { week: string; amount: bigint; proofs: any }; + value?: bigint; + gasPrice?: bigint; + }): Promise { + if (!this.walletClient) { + throw new Error('Wallet client is required for write operations.'); + } + + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'claim', + args: [params.args.week, params.args.amount, params.args.proofs] + }); + + const txInput: Omit[0], 'account' | 'chain'> = { + to: getAddress(this.contractAddress), + data: txData, + value: params.value ?? BigInt(0), // Optional value for payable methods + gasPrice: params.gasPrice // Optional gasPrice + }; + + // This is necessary because the wallet client requires account and chain, which actually cause writes to throw + const tx = await this.walletClient.sendTransaction(txInput as any); + + // Return the transaction receipt + return this.walletClient.waitForTransactionReceipt({ hash: tx }); + } + + async claimsManager(): Promise { + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'claimsManager', + args: [] + }); + + const { data } = await this.publicClient.call({ + to: this.contractAddress, + data: txData + }); + + // Decode the result based on the expected return type + const result = decodeFunctionResult({ + abi: this.abi, + functionName: 'claimsManager', + data: data as `0x${string}` + }); + + // Parse the result based on the return type + return result as string; + } + + async getMerkleRoot(params: { args: { week: string } }): Promise { + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'getMerkleRoot', + args: [params.args.week] + }); + + const { data } = await this.publicClient.call({ + to: this.contractAddress, + data: txData + }); + + // Decode the result based on the expected return type + const result = decodeFunctionResult({ + abi: this.abi, + functionName: 'getMerkleRoot', + data: data as `0x${string}` + }); + + // Parse the result based on the return type + return result as any; + } + + async hasClaimed(params: { args: { week: string; account: string } }): Promise { + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'hasClaimed', + args: [params.args.week, params.args.account] + }); + + const { data } = await this.publicClient.call({ + to: this.contractAddress, + data: txData + }); + + // Decode the result based on the expected return type + const result = decodeFunctionResult({ + abi: this.abi, + functionName: 'hasClaimed', + data: data as `0x${string}` + }); + + // Parse the result based on the return type + return result as boolean; + } + + async setAdmin(params: { + args: { _newAdmin: string }; + value?: bigint; + gasPrice?: bigint; + }): Promise { + if (!this.walletClient) { + throw new Error('Wallet client is required for write operations.'); + } + + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'setAdmin', + args: [params.args._newAdmin] + }); + + const txInput: Omit[0], 'account' | 'chain'> = { + to: getAddress(this.contractAddress), + data: txData, + value: params.value ?? BigInt(0), // Optional value for payable methods + gasPrice: params.gasPrice // Optional gasPrice + }; + + // This is necessary because the wallet client requires account and chain, which actually cause writes to throw + const tx = await this.walletClient.sendTransaction(txInput as any); + + // Return the transaction receipt + return this.walletClient.waitForTransactionReceipt({ hash: tx }); + } + + async setClaimsManager(params: { + args: { account: string }; + value?: bigint; + gasPrice?: bigint; + }): Promise { + if (!this.walletClient) { + throw new Error('Wallet client is required for write operations.'); + } + + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'setClaimsManager', + args: [params.args.account] + }); + + const txInput: Omit[0], 'account' | 'chain'> = { + to: getAddress(this.contractAddress), + data: txData, + value: params.value ?? BigInt(0), // Optional value for payable methods + gasPrice: params.gasPrice // Optional gasPrice + }; + + // This is necessary because the wallet client requires account and chain, which actually cause writes to throw + const tx = await this.walletClient.sendTransaction(txInput as any); + + // Return the transaction receipt + return this.walletClient.waitForTransactionReceipt({ hash: tx }); + } + + async setMerkleRoot(params: { + args: { week: string; merkleRoot: any }; + value?: bigint; + gasPrice?: bigint; + }): Promise { + if (!this.walletClient) { + throw new Error('Wallet client is required for write operations.'); + } + + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'setMerkleRoot', + args: [params.args.week, params.args.merkleRoot] + }); + + const txInput: Omit[0], 'account' | 'chain'> = { + to: getAddress(this.contractAddress), + data: txData, + value: params.value ?? BigInt(0), // Optional value for payable methods + gasPrice: params.gasPrice // Optional gasPrice + }; + + // This is necessary because the wallet client requires account and chain, which actually cause writes to throw + const tx = await this.walletClient.sendTransaction(txInput as any); + + // Return the transaction receipt + return this.walletClient.waitForTransactionReceipt({ hash: tx }); + } +} diff --git a/packages/scoutgame/src/protocol/clients/ProtocolProxyClient.ts b/packages/scoutgame/src/protocol/clients/ProtocolProxyClient.ts new file mode 100644 index 0000000000..71ab8e6316 --- /dev/null +++ b/packages/scoutgame/src/protocol/clients/ProtocolProxyClient.ts @@ -0,0 +1,388 @@ +import type { + Abi, + Account, + Address, + Chain, + Client, + PublicActions, + PublicClient, + RpcSchema, + TransactionReceipt, + Transport, + WalletActions, + WalletClient +} from 'viem'; +import { encodeFunctionData, decodeFunctionResult, getAddress } from 'viem'; + +// ReadWriteWalletClient reflects a wallet client that has been extended with PublicActions +// https://github.com/wevm/viem/discussions/1463#discussioncomment-7504732 +type ReadWriteWalletClient< + transport extends Transport = Transport, + chain extends Chain | undefined = Chain | undefined, + account extends Account | undefined = Account | undefined +> = Client< + transport, + chain, + account, + RpcSchema, + PublicActions & WalletActions +>; + +export class ProtocolProxyClient { + private contractAddress: Address; + + private publicClient: PublicClient; + + private walletClient?: ReadWriteWalletClient; + + private chain: Chain; + + public abi: Abi = [ + { + inputs: [], + name: 'admin', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'claimsManager', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'claimsToken', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [], + name: 'implementation', + outputs: [ + { + internalType: 'address', + name: '', + type: 'address' + } + ], + stateMutability: 'view', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: '_newAdmin', + type: 'address' + } + ], + name: 'setAdmin', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address' + } + ], + name: 'setClaimsManager', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: '_claimsToken', + type: 'address' + } + ], + name: 'setClaimsToken', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + }, + { + inputs: [ + { + internalType: 'address', + name: '_newImplementation', + type: 'address' + } + ], + name: 'setImplementation', + outputs: [], + stateMutability: 'nonpayable', + type: 'function' + } + ]; + + constructor({ + contractAddress, + publicClient, + walletClient, + chain + }: { + contractAddress: Address; + chain: Chain; + publicClient?: PublicClient; + walletClient?: ReadWriteWalletClient; + }) { + if (!publicClient && !walletClient) { + throw new Error('At least one client is required.'); + } else if (publicClient && walletClient) { + throw new Error('Provide only a public client or wallet clients'); + } + + this.chain = chain; + this.contractAddress = contractAddress; + + const client = publicClient || walletClient; + + if (client!.chain!.id !== chain.id) { + throw new Error('Client must be on the same chain as the contract. Make sure to add a chain to your client'); + } + + if (publicClient) { + this.publicClient = publicClient; + } else { + this.walletClient = walletClient; + this.publicClient = walletClient as PublicClient; + } + } + + async admin(): Promise { + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'admin', + args: [] + }); + + const { data } = await this.publicClient.call({ + to: this.contractAddress, + data: txData + }); + + // Decode the result based on the expected return type + const result = decodeFunctionResult({ + abi: this.abi, + functionName: 'admin', + data: data as `0x${string}` + }); + + // Parse the result based on the return type + return result as string; + } + + async claimsManager(): Promise { + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'claimsManager', + args: [] + }); + + const { data } = await this.publicClient.call({ + to: this.contractAddress, + data: txData + }); + + // Decode the result based on the expected return type + const result = decodeFunctionResult({ + abi: this.abi, + functionName: 'claimsManager', + data: data as `0x${string}` + }); + + // Parse the result based on the return type + return result as string; + } + + async claimsToken(): Promise { + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'claimsToken', + args: [] + }); + + const { data } = await this.publicClient.call({ + to: this.contractAddress, + data: txData + }); + + // Decode the result based on the expected return type + const result = decodeFunctionResult({ + abi: this.abi, + functionName: 'claimsToken', + data: data as `0x${string}` + }); + + // Parse the result based on the return type + return result as string; + } + + async implementation(): Promise { + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'implementation', + args: [] + }); + + const { data } = await this.publicClient.call({ + to: this.contractAddress, + data: txData + }); + + // Decode the result based on the expected return type + const result = decodeFunctionResult({ + abi: this.abi, + functionName: 'implementation', + data: data as `0x${string}` + }); + + // Parse the result based on the return type + return result as string; + } + + async setAdmin(params: { + args: { _newAdmin: string }; + value?: bigint; + gasPrice?: bigint; + }): Promise { + if (!this.walletClient) { + throw new Error('Wallet client is required for write operations.'); + } + + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'setAdmin', + args: [params.args._newAdmin] + }); + + const txInput: Omit[0], 'account' | 'chain'> = { + to: getAddress(this.contractAddress), + data: txData, + value: params.value ?? BigInt(0), // Optional value for payable methods + gasPrice: params.gasPrice // Optional gasPrice + }; + + // This is necessary because the wallet client requires account and chain, which actually cause writes to throw + const tx = await this.walletClient.sendTransaction(txInput as any); + + // Return the transaction receipt + return this.walletClient.waitForTransactionReceipt({ hash: tx }); + } + + async setClaimsManager(params: { + args: { account: string }; + value?: bigint; + gasPrice?: bigint; + }): Promise { + if (!this.walletClient) { + throw new Error('Wallet client is required for write operations.'); + } + + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'setClaimsManager', + args: [params.args.account] + }); + + const txInput: Omit[0], 'account' | 'chain'> = { + to: getAddress(this.contractAddress), + data: txData, + value: params.value ?? BigInt(0), // Optional value for payable methods + gasPrice: params.gasPrice // Optional gasPrice + }; + + // This is necessary because the wallet client requires account and chain, which actually cause writes to throw + const tx = await this.walletClient.sendTransaction(txInput as any); + + // Return the transaction receipt + return this.walletClient.waitForTransactionReceipt({ hash: tx }); + } + + async setClaimsToken(params: { + args: { _claimsToken: string }; + value?: bigint; + gasPrice?: bigint; + }): Promise { + if (!this.walletClient) { + throw new Error('Wallet client is required for write operations.'); + } + + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'setClaimsToken', + args: [params.args._claimsToken] + }); + + const txInput: Omit[0], 'account' | 'chain'> = { + to: getAddress(this.contractAddress), + data: txData, + value: params.value ?? BigInt(0), // Optional value for payable methods + gasPrice: params.gasPrice // Optional gasPrice + }; + + // This is necessary because the wallet client requires account and chain, which actually cause writes to throw + const tx = await this.walletClient.sendTransaction(txInput as any); + + // Return the transaction receipt + return this.walletClient.waitForTransactionReceipt({ hash: tx }); + } + + async setImplementation(params: { + args: { _newImplementation: string }; + value?: bigint; + gasPrice?: bigint; + }): Promise { + if (!this.walletClient) { + throw new Error('Wallet client is required for write operations.'); + } + + const txData = encodeFunctionData({ + abi: this.abi, + functionName: 'setImplementation', + args: [params.args._newImplementation] + }); + + const txInput: Omit[0], 'account' | 'chain'> = { + to: getAddress(this.contractAddress), + data: txData, + value: params.value ?? BigInt(0), // Optional value for payable methods + gasPrice: params.gasPrice // Optional gasPrice + }; + + // This is necessary because the wallet client requires account and chain, which actually cause writes to throw + const tx = await this.walletClient.sendTransaction(txInput as any); + + // Return the transaction receipt + return this.walletClient.waitForTransactionReceipt({ hash: tx }); + } +} diff --git a/packages/scoutgame/src/protocol/clients/getProxyClaimsManagerWallet.ts b/packages/scoutgame/src/protocol/clients/getProxyClaimsManagerWallet.ts new file mode 100644 index 0000000000..2d54eac96e --- /dev/null +++ b/packages/scoutgame/src/protocol/clients/getProxyClaimsManagerWallet.ts @@ -0,0 +1,12 @@ +import { getWalletClient } from '@packages/blockchain/getWalletClient'; + +import { scoutProtocolChainId } from '../constants'; + +export function getProxyClaimsManagerWallet() { + const scoutProtocolClaimsManagerKey = process.env.SCOUTPROTOCOL_CLAIMS_MANAGER_KEY as string; + + return getWalletClient({ + chainId: scoutProtocolChainId, + privateKey: scoutProtocolClaimsManagerKey + }); +} diff --git a/packages/scoutgame/src/protocol/clients/protocolReadClients.ts b/packages/scoutgame/src/protocol/clients/protocolReadClients.ts new file mode 100644 index 0000000000..c7603bb5be --- /dev/null +++ b/packages/scoutgame/src/protocol/clients/protocolReadClients.ts @@ -0,0 +1,18 @@ +import { getPublicClient } from '@packages/blockchain/getPublicClient'; + +import { scoutProtocolChain, getScoutProtocolAddress, scoutProtocolChainId } from '../constants'; + +import { ProtocolImplementationClient } from './ProtocolImplementationClient'; +import { ProtocolProxyClient } from './ProtocolProxyClient'; + +export const protocolProxyReadonlyApiClient = new ProtocolProxyClient({ + chain: scoutProtocolChain, + contractAddress: getScoutProtocolAddress(), + publicClient: getPublicClient(scoutProtocolChainId) +}); + +export const protocolImplementationReadonlyApiClient = new ProtocolImplementationClient({ + chain: scoutProtocolChain, + contractAddress: getScoutProtocolAddress(), + publicClient: getPublicClient(scoutProtocolChainId) +}); diff --git a/packages/scoutgame/src/protocol/clients/protocolWriteClients.ts b/packages/scoutgame/src/protocol/clients/protocolWriteClients.ts new file mode 100644 index 0000000000..3b973894d8 --- /dev/null +++ b/packages/scoutgame/src/protocol/clients/protocolWriteClients.ts @@ -0,0 +1,17 @@ +import { getScoutProtocolAddress, scoutProtocolChain } from '../constants'; + +import { getProxyClaimsManagerWallet } from './getProxyClaimsManagerWallet'; +import { ProtocolImplementationClient } from './ProtocolImplementationClient'; +import { ProtocolProxyClient } from './ProtocolProxyClient'; + +export const protocolProxyWriteClient = new ProtocolProxyClient({ + chain: scoutProtocolChain, + contractAddress: getScoutProtocolAddress(), + walletClient: getProxyClaimsManagerWallet() +}); + +export const protocolImplementationWriteClient = new ProtocolImplementationClient({ + chain: scoutProtocolChain, + contractAddress: getScoutProtocolAddress(), + walletClient: getProxyClaimsManagerWallet() +});