[Dashboard/SDK] Add more Split extensions and remove Split v4 code from Dashboard (#4033)
<!-- start pr-codex -->
## PR-Codex overview
This PR adds more functionality to the `Split` contract extensions, including deploying the `Split` contract, reading recipient addresses and percentages, and testing these features.
### Detailed summary
- Added more Split contract extensions
- Updated `deploys.ts` with new exports for deploying Split contract
- Updated `split.ts` with new exports for reading recipient addresses and percentages
- Modified `page.tsx` to display contract balance using `getContract`
- Added new files for reading recipient addresses and percentages
- Updated `account-balance.tsx` to use `getContract` and `useSplitBalances`
- Added tests for getting all recipient addresses and percentages
- Added functionality to get recipient split percentage and corresponding tests
> The following files were skipped due to too many changes: `packages/thirdweb/src/extensions/split/__generated__/Split/read/totalShares.ts`, `apps/dashboard/src/contract-ui/tabs/split/components/distribute-button.tsx`, `packages/thirdweb/test/src/test-contracts.ts`, `apps/dashboard/src/@3rdweb-sdk/react/hooks/useSplit.ts`, `packages/thirdweb/src/extensions/prebuilts/deploy-split.ts`, `packages/thirdweb/src/extensions/prebuilts/__generated__/Split/write/initialize.ts`, `apps/dashboard/src/contract-ui/tabs/split/page.tsx`
> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`
<!-- end pr-codex -->
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"thirdweb": patch
|
||||
---
|
||||
|
||||
Add more Split contract extensions
|
||||
@@ -1,29 +1,21 @@
|
||||
import { useToast } from "@chakra-ui/react";
|
||||
import { useSDKChainId } from "@thirdweb-dev/react";
|
||||
import type { Split } from "@thirdweb-dev/sdk";
|
||||
import type {
|
||||
BalanceQueryRequest,
|
||||
BalanceQueryResponse,
|
||||
} from "pages/api/moralis/balances";
|
||||
import { toast } from "sonner";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { distribute, distributeByToken } from "thirdweb/extensions/split";
|
||||
import { useSendAndConfirmTransaction } from "thirdweb/react";
|
||||
import invariant from "tiny-invariant";
|
||||
import { parseErrorToMessage } from "utils/errorParser";
|
||||
import { splitsKeys } from "..";
|
||||
import {
|
||||
useMutationWithInvalidate,
|
||||
useQueryWithNetwork,
|
||||
} from "./query/useQueryWithNetwork";
|
||||
|
||||
export function useSplitData(contract?: Split) {
|
||||
return useQueryWithNetwork(
|
||||
splitsKeys.list(contract?.getAddress()),
|
||||
async () => contract?.getAllRecipients(),
|
||||
{
|
||||
enabled: !!contract,
|
||||
},
|
||||
);
|
||||
}
|
||||
export function useSplitBalances(contractAddress?: string) {
|
||||
const chainId = useSDKChainId();
|
||||
export function useSplitBalances(contract: ThirdwebContract) {
|
||||
const chainId = contract.chain.id;
|
||||
const contractAddress = contract.address;
|
||||
const currencies = useQueryWithNetwork(
|
||||
splitsKeys.currencies(contractAddress),
|
||||
async () => {
|
||||
@@ -48,72 +40,31 @@ export function useSplitBalances(contractAddress?: string) {
|
||||
return currencies;
|
||||
}
|
||||
|
||||
export function useSplitDistributeFunds(contract?: Split) {
|
||||
const contractAddress = contract?.getAddress();
|
||||
const balances = useSplitBalances(contractAddress);
|
||||
const toast = useToast();
|
||||
export function useSplitDistributeFunds(contract: ThirdwebContract) {
|
||||
const contractAddress = contract.address;
|
||||
const balances = useSplitBalances(contract);
|
||||
const { mutateAsync } = useSendAndConfirmTransaction();
|
||||
|
||||
return useMutationWithInvalidate(
|
||||
async () => {
|
||||
invariant(contract, "split contract is not ready");
|
||||
invariant(balances.data, "No balances to distribute");
|
||||
|
||||
const distributions = (balances.data || [])
|
||||
.filter((token) => token.display_balance !== "0.0")
|
||||
.map(async (currency) => {
|
||||
if (currency.name === "Native Token") {
|
||||
await contract
|
||||
.distribute()
|
||||
.then(() => {
|
||||
toast({
|
||||
position: "bottom",
|
||||
variant: "solid",
|
||||
title: "Success",
|
||||
description: `Successfully distributed ${currency.name}`,
|
||||
status: "success",
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
const transaction =
|
||||
currency.name === "Native Token"
|
||||
? distribute({ contract })
|
||||
: distributeByToken({
|
||||
contract,
|
||||
tokenAddress: currency.token_address,
|
||||
});
|
||||
return void 0;
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
toast({
|
||||
position: "bottom",
|
||||
variant: "solid",
|
||||
title: `Error distributing ${currency.name}`,
|
||||
description: parseErrorToMessage(err),
|
||||
status: "error",
|
||||
duration: 9000,
|
||||
isClosable: true,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
await contract
|
||||
.distributeToken(currency.token_address)
|
||||
.then(() => {
|
||||
toast({
|
||||
position: "bottom",
|
||||
variant: "solid",
|
||||
title: "Success",
|
||||
description: `Successfully distributed ${currency.name}`,
|
||||
status: "success",
|
||||
duration: 5000,
|
||||
isClosable: true,
|
||||
});
|
||||
return void 0;
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
toast({
|
||||
position: "bottom",
|
||||
variant: "solid",
|
||||
title: `Error distributing ${currency.name}`,
|
||||
description: parseErrorToMessage(err),
|
||||
status: "error",
|
||||
duration: 9000,
|
||||
isClosable: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
const promise = mutateAsync(transaction);
|
||||
toast.promise(promise, {
|
||||
success: `Successfully distributed ${currency.name}`,
|
||||
error: `Error distributing ${currency.name}`,
|
||||
loading: "Distributing funds",
|
||||
});
|
||||
});
|
||||
|
||||
return await Promise.all(distributions);
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { useSplitBalances } from "@3rdweb-sdk/react/hooks/useSplit";
|
||||
import { SimpleGrid, Stat, StatLabel, StatNumber } from "@chakra-ui/react";
|
||||
import { useV5DashboardChain } from "lib/v5-adapter";
|
||||
import { getContract } from "thirdweb";
|
||||
import { useActiveWalletChain, useWalletBalance } from "thirdweb/react";
|
||||
import { Card } from "tw-components";
|
||||
|
||||
interface AccountBalanceProps {
|
||||
address: string;
|
||||
chainId: number;
|
||||
}
|
||||
|
||||
export const AccountBalance: React.FC<AccountBalanceProps> = ({ address }) => {
|
||||
export const AccountBalance: React.FC<AccountBalanceProps> = ({
|
||||
address,
|
||||
chainId,
|
||||
}) => {
|
||||
const v5Chain = useV5DashboardChain(chainId);
|
||||
const activeChain = useActiveWalletChain();
|
||||
const { data: balance } = useWalletBalance({
|
||||
address,
|
||||
chain: activeChain,
|
||||
client: thirdwebClient,
|
||||
});
|
||||
const balanceQuery = useSplitBalances(address);
|
||||
const contract = getContract({
|
||||
address,
|
||||
chain: v5Chain,
|
||||
client: thirdwebClient,
|
||||
});
|
||||
const balanceQuery = useSplitBalances(contract);
|
||||
|
||||
return (
|
||||
<SimpleGrid spacing={{ base: 3, md: 6 }} columns={{ base: 2, md: 4 }}>
|
||||
|
||||
@@ -50,12 +50,19 @@ export const AccountPage: React.FC<AccountPageProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
const contract = contractQuery.contract;
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap={6}>
|
||||
<Flex direction="row" justify="space-between" align="center">
|
||||
<Heading size="title.sm">Balances</Heading>
|
||||
</Flex>
|
||||
<AccountBalance address={contractAddress || ""} />
|
||||
{contract && (
|
||||
<AccountBalance
|
||||
address={contract.getAddress()}
|
||||
chainId={contract.chainId}
|
||||
/>
|
||||
)}
|
||||
<Flex direction="row" justify="space-between" align="center">
|
||||
<Heading size="title.sm">Deposit {symbol}</Heading>
|
||||
</Flex>
|
||||
|
||||
@@ -1,42 +1,47 @@
|
||||
import { useSplitDistributeFunds } from "@3rdweb-sdk/react/hooks/useSplit";
|
||||
import type { UseContractResult } from "@thirdweb-dev/react";
|
||||
import type { Split } from "@thirdweb-dev/sdk";
|
||||
import { MismatchButton } from "components/buttons/MismatchButton";
|
||||
import { TransactionButton } from "components/buttons/TransactionButton";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
import { useTxNotifications } from "hooks/useTxNotifications";
|
||||
import { useMemo } from "react";
|
||||
import type { ThirdwebContract } from "thirdweb";
|
||||
import { Button } from "tw-components";
|
||||
import type { Balance } from "../page";
|
||||
|
||||
interface DistributeButtonProps {
|
||||
contractQuery?: UseContractResult<Split>;
|
||||
contract: ThirdwebContract;
|
||||
balances: Balance[];
|
||||
balancesIsLoading: boolean;
|
||||
balancesIsError: boolean;
|
||||
}
|
||||
|
||||
export const DistributeButton: React.FC<DistributeButtonProps> = ({
|
||||
contractQuery,
|
||||
contract,
|
||||
balances,
|
||||
balancesIsLoading,
|
||||
balancesIsError,
|
||||
...restButtonProps
|
||||
}) => {
|
||||
const trackEvent = useTrack();
|
||||
const validBalances = balances.filter(
|
||||
(item) => item.balance !== "0" && item.balance !== "0.0",
|
||||
);
|
||||
const numTransactions = useMemo(() => {
|
||||
if (balances.length === 1 && balances[0].name === "Native Token") {
|
||||
if (
|
||||
validBalances.length === 1 &&
|
||||
validBalances[0].name === "Native Token"
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
if (!balances || balancesIsLoading) {
|
||||
if (!validBalances || balancesIsLoading) {
|
||||
return 0;
|
||||
}
|
||||
return balances?.filter((b) => b.display_balance !== "0.0").length;
|
||||
}, [balances, balancesIsLoading]);
|
||||
return validBalances?.filter(
|
||||
(b) => b.display_balance !== "0.0" && b.display_balance !== "0",
|
||||
).length;
|
||||
}, [validBalances, balancesIsLoading]);
|
||||
|
||||
const distibutedFundsMutation = useSplitDistributeFunds(
|
||||
contractQuery?.contract,
|
||||
);
|
||||
const mutation = useSplitDistributeFunds(contract);
|
||||
|
||||
const { onSuccess, onError } = useTxNotifications(
|
||||
"Funds splitted successfully",
|
||||
@@ -44,7 +49,7 @@ export const DistributeButton: React.FC<DistributeButtonProps> = ({
|
||||
);
|
||||
|
||||
const distributeFunds = () => {
|
||||
distibutedFundsMutation.mutate(undefined, {
|
||||
mutation.mutate(undefined, {
|
||||
onSuccess: () => {
|
||||
onSuccess();
|
||||
trackEvent({
|
||||
@@ -70,7 +75,7 @@ export const DistributeButton: React.FC<DistributeButtonProps> = ({
|
||||
// we still want to show the button, so we'll just show the mismatch button
|
||||
return (
|
||||
<MismatchButton
|
||||
isLoading={distibutedFundsMutation.isLoading}
|
||||
isLoading={mutation.isLoading}
|
||||
colorScheme="primary"
|
||||
onClick={distributeFunds}
|
||||
{...restButtonProps}
|
||||
@@ -91,7 +96,7 @@ export const DistributeButton: React.FC<DistributeButtonProps> = ({
|
||||
return (
|
||||
<TransactionButton
|
||||
transactionCount={numTransactions}
|
||||
isLoading={distibutedFundsMutation.isLoading}
|
||||
isLoading={mutation.isLoading}
|
||||
colorScheme="primary"
|
||||
onClick={distributeFunds}
|
||||
{...restButtonProps}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { thirdwebClient } from "@/constants/client";
|
||||
import { useDashboardEVMChainId } from "@3rdweb-sdk/react";
|
||||
import {
|
||||
useSplitBalances,
|
||||
useSplitData,
|
||||
} from "@3rdweb-sdk/react/hooks/useSplit";
|
||||
import { useSplitBalances } from "@3rdweb-sdk/react/hooks/useSplit";
|
||||
import {
|
||||
Center,
|
||||
Flex,
|
||||
@@ -15,11 +11,21 @@ import {
|
||||
StatNumber,
|
||||
} from "@chakra-ui/react";
|
||||
import { useContract } from "@thirdweb-dev/react";
|
||||
import { BigNumber, ethers } from "ethers";
|
||||
import { useSupportedChainsRecord } from "hooks/chains/configureChains";
|
||||
import { useMemo } from "react";
|
||||
import { ZERO_ADDRESS } from "thirdweb";
|
||||
import { useActiveAccount, useWalletBalance } from "thirdweb/react";
|
||||
import {
|
||||
type ThirdwebContract,
|
||||
ZERO_ADDRESS,
|
||||
getContract,
|
||||
toEther,
|
||||
toTokens,
|
||||
} from "thirdweb";
|
||||
import { getAllRecipientsPercentages } from "thirdweb/extensions/split";
|
||||
import {
|
||||
useActiveAccount,
|
||||
useReadContract,
|
||||
useWalletBalance,
|
||||
} from "thirdweb/react";
|
||||
import { Card, Heading, Text } from "tw-components";
|
||||
import { shortenIfAddress } from "utils/usedapp-external";
|
||||
import { useV5DashboardChain } from "../../../lib/v5-adapter";
|
||||
@@ -40,21 +46,45 @@ interface SplitPageProps {
|
||||
export const ContractSplitPage: React.FC<SplitPageProps> = ({
|
||||
contractAddress,
|
||||
}) => {
|
||||
const address = useActiveAccount()?.address;
|
||||
const contractQuery = useContract(contractAddress, "split");
|
||||
const configuredChainsRecord = useSupportedChainsRecord();
|
||||
const chainId = useDashboardEVMChainId();
|
||||
const v4Chain = chainId ? configuredChainsRecord[chainId] : undefined;
|
||||
const splitQuery = useSplitData(contractQuery.contract);
|
||||
const v5Chain = useV5DashboardChain(chainId);
|
||||
const v5Chain = useV5DashboardChain(contractQuery.contract?.chainId);
|
||||
|
||||
const nativeBalanceQuery = useWalletBalance({
|
||||
address: contractAddress,
|
||||
if (contractQuery.isLoading) {
|
||||
// TODO build a skeleton for this
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (!contractQuery?.contract || !v5Chain) {
|
||||
return null;
|
||||
}
|
||||
const contract = getContract({
|
||||
address: contractQuery.contract.getAddress(),
|
||||
client: thirdwebClient,
|
||||
chain: v5Chain,
|
||||
});
|
||||
const balanceQuery = useSplitBalances(contractAddress);
|
||||
return <ContractSplitContent contract={contract} />;
|
||||
};
|
||||
|
||||
type SplitContentProps = {
|
||||
contract: ThirdwebContract;
|
||||
};
|
||||
|
||||
const ContractSplitContent: React.FC<SplitContentProps> = ({ contract }) => {
|
||||
const address = useActiveAccount()?.address;
|
||||
const configuredChainsRecord = useSupportedChainsRecord();
|
||||
const chainId = contract.chain.id;
|
||||
const v4Chain = configuredChainsRecord[chainId];
|
||||
const contractAddress = contract.address;
|
||||
const nativeBalanceQuery = useWalletBalance({
|
||||
address: contractAddress,
|
||||
client: thirdwebClient,
|
||||
chain: contract.chain,
|
||||
});
|
||||
const { data: allRecipientsPercentages } = useReadContract(
|
||||
getAllRecipientsPercentages,
|
||||
{ contract },
|
||||
);
|
||||
const balanceQuery = useSplitBalances(contract);
|
||||
const balances = useMemo(() => {
|
||||
if (!balanceQuery.data && !nativeBalanceQuery.data) {
|
||||
return [];
|
||||
@@ -73,37 +103,30 @@ export const ContractSplitPage: React.FC<SplitPageProps> = ({
|
||||
}, [balanceQuery.data, nativeBalanceQuery.data]);
|
||||
|
||||
const shareOfBalancesForConnectedWallet = useMemo(() => {
|
||||
const activeRecipient = splitQuery.data?.find((r) => r.address === address);
|
||||
const activeRecipient = (allRecipientsPercentages || []).find(
|
||||
(r) => r.address.toLowerCase() === address?.toLowerCase(),
|
||||
);
|
||||
if (!activeRecipient || !balances) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return balances.reduce(
|
||||
(acc, curr) => {
|
||||
// For native token balance, Moralis returns the zero address
|
||||
// this logic will potentially have to change if we decide to replace the service
|
||||
const isNativeToken = curr.token_address === ZERO_ADDRESS;
|
||||
const displayBalance = isNativeToken
|
||||
? toEther(BigInt(curr.balance))
|
||||
: toTokens(BigInt(curr.balance), curr.decimals);
|
||||
return {
|
||||
// biome-ignore lint/performance/noAccumulatingSpread: FIXME
|
||||
...acc,
|
||||
// convert to bps for BigNumber calculations
|
||||
[curr.token_address]: ethers.utils.formatUnits(
|
||||
BigNumber.from(curr.balance)
|
||||
.mul(activeRecipient.splitPercentage * 100)
|
||||
.div(10000),
|
||||
curr.decimals,
|
||||
),
|
||||
[curr.token_address]: displayBalance,
|
||||
};
|
||||
},
|
||||
{} as { [address: string]: string },
|
||||
);
|
||||
}, [splitQuery.data, balances, address]);
|
||||
|
||||
if (contractQuery.isLoading) {
|
||||
// TODO build a skeleton for this
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (!contractQuery?.contract) {
|
||||
return null;
|
||||
}
|
||||
}, [allRecipientsPercentages, balances, address]);
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap={6}>
|
||||
@@ -116,7 +139,7 @@ export const ContractSplitPage: React.FC<SplitPageProps> = ({
|
||||
balanceQuery.isLoading || nativeBalanceQuery.isLoading
|
||||
}
|
||||
balancesIsError={balanceQuery.isError && nativeBalanceQuery.isError}
|
||||
contractQuery={contractQuery}
|
||||
contract={contract}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -188,6 +211,11 @@ export const ContractSplitPage: React.FC<SplitPageProps> = ({
|
||||
The Split can receive funds in the native token or in any ERC20.
|
||||
Balances may take a couple of minutes to display after being
|
||||
received.
|
||||
<br />
|
||||
{/* We currently use Moralis and high chances are they don't recognize all ERC20 tokens in the contract */}
|
||||
If you are looking to distribute an ERC20 token and it's not being
|
||||
recognized on this page, you can manually call the `distribute`
|
||||
method in the Explorer page
|
||||
</Text>
|
||||
</Flex>
|
||||
|
||||
@@ -195,7 +223,7 @@ export const ContractSplitPage: React.FC<SplitPageProps> = ({
|
||||
<Heading size="label.lg" mb="8px">
|
||||
Split Recipients
|
||||
</Heading>
|
||||
{splitQuery.data?.map((split) => (
|
||||
{(allRecipientsPercentages || []).map((split) => (
|
||||
<Card key={split.address}>
|
||||
<Text>
|
||||
<Text as="span" size="label.md">
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[
|
||||
"function initialize(address _defaultAdmin, string memory _contractURI, address[] memory _trustedForwarders, address[] memory _payees, uint256[] memory _shares)"
|
||||
]
|
||||
@@ -6,5 +6,6 @@
|
||||
"function shares(address account) view returns (uint256)",
|
||||
"function totalReleased() view returns (uint256)",
|
||||
"function distribute()",
|
||||
"function release(address account)"
|
||||
"function release(address account)",
|
||||
"function totalShares() view returns (uint256)"
|
||||
]
|
||||
@@ -33,3 +33,8 @@ export {
|
||||
type PrepareDirectDeployTransactionOptions,
|
||||
} from "../contract/deployment/deploy-with-abi.js";
|
||||
export { computePublishedContractAddress } from "../utils/any-evm/compute-published-contract-address.js";
|
||||
export {
|
||||
deploySplitContract,
|
||||
type SplitContractParams,
|
||||
type DeploySplitContractOptions,
|
||||
} from "../extensions/prebuilts/deploy-split.js";
|
||||
|
||||
@@ -34,6 +34,14 @@ export { totalReleased } from "../../extensions/split/__generated__/Split/read/t
|
||||
|
||||
export { totalReleasedByToken } from "../../extensions/split/read/totalReleasedByToken.js";
|
||||
|
||||
export { getAllRecipientsAddresses } from "../../extensions/split/read/getAllRecipientsAddresses.js";
|
||||
|
||||
export { getAllRecipientsPercentages } from "../../extensions/split/read/getAllRecipientsPercentages.js";
|
||||
|
||||
export {
|
||||
getRecipientSplitPercentage,
|
||||
type SplitRecipient,
|
||||
} from "../../extensions/split/read/getRecipientSplitPercentage.js";
|
||||
/**
|
||||
* WRITE
|
||||
*/
|
||||
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
import type { AbiParameterToPrimitiveType } from "abitype";
|
||||
import type {
|
||||
BaseTransactionOptions,
|
||||
WithOverrides,
|
||||
} from "../../../../../transaction/types.js";
|
||||
import { prepareContractCall } from "../../../../../transaction/prepare-contract-call.js";
|
||||
import { encodeAbiParameters } from "../../../../../utils/abi/encodeAbiParameters.js";
|
||||
import { once } from "../../../../../utils/promise/once.js";
|
||||
import type { ThirdwebContract } from "../../../../../contract/contract.js";
|
||||
import { detectMethod } from "../../../../../utils/bytecode/detectExtension.js";
|
||||
|
||||
/**
|
||||
* Represents the parameters for the "initialize" function.
|
||||
*/
|
||||
export type InitializeParams = WithOverrides<{
|
||||
defaultAdmin: AbiParameterToPrimitiveType<{
|
||||
type: "address";
|
||||
name: "_defaultAdmin";
|
||||
}>;
|
||||
contractURI: AbiParameterToPrimitiveType<{
|
||||
type: "string";
|
||||
name: "_contractURI";
|
||||
}>;
|
||||
trustedForwarders: AbiParameterToPrimitiveType<{
|
||||
type: "address[]";
|
||||
name: "_trustedForwarders";
|
||||
}>;
|
||||
payees: AbiParameterToPrimitiveType<{ type: "address[]"; name: "_payees" }>;
|
||||
shares: AbiParameterToPrimitiveType<{ type: "uint256[]"; name: "_shares" }>;
|
||||
}>;
|
||||
|
||||
export const FN_SELECTOR = "0xb1a14437" as const;
|
||||
const FN_INPUTS = [
|
||||
{
|
||||
type: "address",
|
||||
name: "_defaultAdmin",
|
||||
},
|
||||
{
|
||||
type: "string",
|
||||
name: "_contractURI",
|
||||
},
|
||||
{
|
||||
type: "address[]",
|
||||
name: "_trustedForwarders",
|
||||
},
|
||||
{
|
||||
type: "address[]",
|
||||
name: "_payees",
|
||||
},
|
||||
{
|
||||
type: "uint256[]",
|
||||
name: "_shares",
|
||||
},
|
||||
] as const;
|
||||
const FN_OUTPUTS = [] as const;
|
||||
|
||||
/**
|
||||
* Checks if the `initialize` method is supported by the given contract.
|
||||
* @param contract The ThirdwebContract.
|
||||
* @returns A promise that resolves to a boolean indicating if the `initialize` method is supported.
|
||||
* @extension PREBUILTS
|
||||
* @example
|
||||
* ```ts
|
||||
* import { isInitializeSupported } from "thirdweb/extensions/prebuilts";
|
||||
*
|
||||
* const supported = await isInitializeSupported(contract);
|
||||
* ```
|
||||
*/
|
||||
export async function isInitializeSupported(contract: ThirdwebContract<any>) {
|
||||
return detectMethod({
|
||||
contract,
|
||||
method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the parameters for the "initialize" function.
|
||||
* @param options - The options for the initialize function.
|
||||
* @returns The encoded ABI parameters.
|
||||
* @extension PREBUILTS
|
||||
* @example
|
||||
* ```ts
|
||||
* import { encodeInitializeParams } "thirdweb/extensions/prebuilts";
|
||||
* const result = encodeInitializeParams({
|
||||
* defaultAdmin: ...,
|
||||
* contractURI: ...,
|
||||
* trustedForwarders: ...,
|
||||
* payees: ...,
|
||||
* shares: ...,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function encodeInitializeParams(options: InitializeParams) {
|
||||
return encodeAbiParameters(FN_INPUTS, [
|
||||
options.defaultAdmin,
|
||||
options.contractURI,
|
||||
options.trustedForwarders,
|
||||
options.payees,
|
||||
options.shares,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the "initialize" function into a Hex string with its parameters.
|
||||
* @param options - The options for the initialize function.
|
||||
* @returns The encoded hexadecimal string.
|
||||
* @extension PREBUILTS
|
||||
* @example
|
||||
* ```ts
|
||||
* import { encodeInitialize } "thirdweb/extensions/prebuilts";
|
||||
* const result = encodeInitialize({
|
||||
* defaultAdmin: ...,
|
||||
* contractURI: ...,
|
||||
* trustedForwarders: ...,
|
||||
* payees: ...,
|
||||
* shares: ...,
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function encodeInitialize(options: InitializeParams) {
|
||||
// we do a "manual" concat here to avoid the overhead of the "concatHex" function
|
||||
// we can do this because we know the specific formats of the values
|
||||
return (FN_SELECTOR +
|
||||
encodeInitializeParams(options).slice(
|
||||
2,
|
||||
)) as `${typeof FN_SELECTOR}${string}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a transaction to call the "initialize" function on the contract.
|
||||
* @param options - The options for the "initialize" function.
|
||||
* @returns A prepared transaction object.
|
||||
* @extension PREBUILTS
|
||||
* @example
|
||||
* ```ts
|
||||
* import { initialize } from "thirdweb/extensions/prebuilts";
|
||||
*
|
||||
* const transaction = initialize({
|
||||
* contract,
|
||||
* defaultAdmin: ...,
|
||||
* contractURI: ...,
|
||||
* trustedForwarders: ...,
|
||||
* payees: ...,
|
||||
* shares: ...,
|
||||
* overrides: {
|
||||
* ...
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // Send the transaction
|
||||
* ...
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
export function initialize(
|
||||
options: BaseTransactionOptions<
|
||||
| InitializeParams
|
||||
| {
|
||||
asyncParams: () => Promise<InitializeParams>;
|
||||
}
|
||||
>,
|
||||
) {
|
||||
const asyncOptions = once(async () => {
|
||||
return "asyncParams" in options ? await options.asyncParams() : options;
|
||||
});
|
||||
|
||||
return prepareContractCall({
|
||||
contract: options.contract,
|
||||
method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const,
|
||||
params: async () => {
|
||||
const resolvedOptions = await asyncOptions();
|
||||
return [
|
||||
resolvedOptions.defaultAdmin,
|
||||
resolvedOptions.contractURI,
|
||||
resolvedOptions.trustedForwarders,
|
||||
resolvedOptions.payees,
|
||||
resolvedOptions.shares,
|
||||
] as const;
|
||||
},
|
||||
value: async () => (await asyncOptions()).overrides?.value,
|
||||
accessList: async () => (await asyncOptions()).overrides?.accessList,
|
||||
gas: async () => (await asyncOptions()).overrides?.gas,
|
||||
gasPrice: async () => (await asyncOptions()).overrides?.gasPrice,
|
||||
maxFeePerGas: async () => (await asyncOptions()).overrides?.maxFeePerGas,
|
||||
maxPriorityFeePerGas: async () =>
|
||||
(await asyncOptions()).overrides?.maxPriorityFeePerGas,
|
||||
nonce: async () => (await asyncOptions()).overrides?.nonce,
|
||||
extraGas: async () => (await asyncOptions()).overrides?.extraGas,
|
||||
erc20Value: async () => (await asyncOptions()).overrides?.erc20Value,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "~test/chains.js";
|
||||
import { TEST_CLIENT } from "~test/test-clients.js";
|
||||
import { TEST_ACCOUNT_A } from "~test/test-wallets.js";
|
||||
import { isAddress } from "../../utils/address.js";
|
||||
import { deploySplitContract } from "./deploy-split.js";
|
||||
|
||||
describe("deploy-split contract", () => {
|
||||
it("should deploy Split contract", async () => {
|
||||
const address = await deploySplitContract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
params: {
|
||||
name: "split-contract",
|
||||
payees: [
|
||||
"0x12345674b599ce99958242b3D3741e7b01841DF3",
|
||||
"0xA6f11e47dE28B3dB934e945daeb6F538E9019694",
|
||||
],
|
||||
shares: [
|
||||
5100n, // 51%
|
||||
4900n, // 49%
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(address).toBeDefined();
|
||||
expect(isAddress(address)).toBe(true);
|
||||
// Further tests to verify the functionality of this contract
|
||||
// are done in other Split tests
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,144 @@
|
||||
import type { ThirdwebClient } from "../../client/client.js";
|
||||
import type { ThirdwebContract } from "../../contract/contract.js";
|
||||
import { deployViaAutoFactory } from "../../contract/deployment/deploy-via-autofactory.js";
|
||||
import { getOrDeployInfraForPublishedContract } from "../../contract/deployment/utils/bootstrap.js";
|
||||
import { upload } from "../../storage/upload.js";
|
||||
import type { FileOrBufferOrString } from "../../storage/upload/types.js";
|
||||
import type { Prettify } from "../../utils/type-utils.js";
|
||||
import type { ClientAndChainAndAccount } from "../../utils/types.js";
|
||||
import { initialize } from "./__generated__/Split/write/initialize.js";
|
||||
|
||||
/**
|
||||
* @extension PREBUILT
|
||||
*/
|
||||
export type SplitContractParams = {
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* An array of strings containing wallet addresses of the recipients
|
||||
* For example:
|
||||
* ```ts
|
||||
* ["0x...123", "0x...456"]
|
||||
* ```
|
||||
*/
|
||||
payees: string[];
|
||||
/**
|
||||
* An array of bigints containing the shared percentages of each respective payees.
|
||||
* Must have the same length as `payees`
|
||||
* @example
|
||||
* ```ts
|
||||
* [
|
||||
* 5100n, // 51%
|
||||
* 4900n, // 49%
|
||||
* ]
|
||||
* ```
|
||||
*/
|
||||
shares: bigint[];
|
||||
description?: string;
|
||||
image?: FileOrBufferOrString;
|
||||
external_link?: string;
|
||||
social_urls?: Record<string, string>;
|
||||
symbol?: string;
|
||||
contractURI?: string;
|
||||
defaultAdmin?: string;
|
||||
trustedForwarders?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* @extension DEPLOY
|
||||
*/
|
||||
export type DeploySplitContractOptions = Prettify<
|
||||
ClientAndChainAndAccount & {
|
||||
params: SplitContractParams;
|
||||
}
|
||||
>;
|
||||
|
||||
/**
|
||||
* Deploys a thirdweb [`Split contract`](https://thirdweb.com/thirdweb.eth/Split)
|
||||
* On chains where the thirdweb infrastructure contracts are not deployed, this function will deploy them as well.
|
||||
* @param options - The deployment options.
|
||||
* @returns The deployed contract address.
|
||||
* @extension DEPLOY
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { deploySplitContract } from "thirdweb/deploys";
|
||||
* const contractAddress = await deploySplitContract({
|
||||
* chain,
|
||||
* client,
|
||||
* account,
|
||||
* params: {
|
||||
* name: "Split contract",
|
||||
* payees: ["0x...123", "0x...456"],
|
||||
* shares: [5100, 4900], // See type `SplitContractParams` for more context
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export async function deploySplitContract(options: DeploySplitContractOptions) {
|
||||
const { chain, client, account, params } = options;
|
||||
const { cloneFactoryContract, implementationContract } =
|
||||
await getOrDeployInfraForPublishedContract({
|
||||
chain,
|
||||
client,
|
||||
account,
|
||||
contractId: "Split",
|
||||
constructorParams: [],
|
||||
});
|
||||
const initializeTransaction = await getInitializeTransaction({
|
||||
client,
|
||||
implementationContract,
|
||||
params,
|
||||
accountAddress: account.address,
|
||||
});
|
||||
|
||||
return deployViaAutoFactory({
|
||||
client,
|
||||
chain,
|
||||
account,
|
||||
cloneFactoryContract,
|
||||
initializeTransaction,
|
||||
});
|
||||
}
|
||||
|
||||
async function getInitializeTransaction(options: {
|
||||
client: ThirdwebClient;
|
||||
implementationContract: ThirdwebContract;
|
||||
params: SplitContractParams;
|
||||
accountAddress: string;
|
||||
}) {
|
||||
const { client, implementationContract, params, accountAddress } = options;
|
||||
const {
|
||||
name,
|
||||
description,
|
||||
symbol,
|
||||
image,
|
||||
external_link,
|
||||
social_urls,
|
||||
payees,
|
||||
shares,
|
||||
} = params;
|
||||
const contractURI =
|
||||
params.contractURI ||
|
||||
(await upload({
|
||||
client,
|
||||
files: [
|
||||
{
|
||||
name,
|
||||
description,
|
||||
symbol,
|
||||
image,
|
||||
external_link,
|
||||
social_urls,
|
||||
},
|
||||
],
|
||||
})) ||
|
||||
"";
|
||||
return initialize({
|
||||
contract: implementationContract,
|
||||
defaultAdmin: params.defaultAdmin || accountAddress,
|
||||
contractURI,
|
||||
trustedForwarders: params.trustedForwarders || [],
|
||||
payees,
|
||||
shares,
|
||||
});
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import { readContract } from "../../../../../transaction/read-contract.js";
|
||||
import type { BaseTransactionOptions } from "../../../../../transaction/types.js";
|
||||
|
||||
import { decodeAbiParameters } from "viem";
|
||||
import type { Hex } from "../../../../../utils/encoding/hex.js";
|
||||
import type { ThirdwebContract } from "../../../../../contract/contract.js";
|
||||
import { detectMethod } from "../../../../../utils/bytecode/detectExtension.js";
|
||||
|
||||
export const FN_SELECTOR = "0x3a98ef39" as const;
|
||||
const FN_INPUTS = [] as const;
|
||||
const FN_OUTPUTS = [
|
||||
{
|
||||
type: "uint256",
|
||||
},
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Checks if the `totalShares` method is supported by the given contract.
|
||||
* @param contract The ThirdwebContract.
|
||||
* @returns A promise that resolves to a boolean indicating if the `totalShares` method is supported.
|
||||
* @extension SPLIT
|
||||
* @example
|
||||
* ```ts
|
||||
* import { isTotalSharesSupported } from "thirdweb/extensions/split";
|
||||
*
|
||||
* const supported = await isTotalSharesSupported(contract);
|
||||
* ```
|
||||
*/
|
||||
export async function isTotalSharesSupported(contract: ThirdwebContract<any>) {
|
||||
return detectMethod({
|
||||
contract,
|
||||
method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes the result of the totalShares function call.
|
||||
* @param result - The hexadecimal result to decode.
|
||||
* @returns The decoded result as per the FN_OUTPUTS definition.
|
||||
* @extension SPLIT
|
||||
* @example
|
||||
* ```ts
|
||||
* import { decodeTotalSharesResult } from "thirdweb/extensions/split";
|
||||
* const result = decodeTotalSharesResult("...");
|
||||
* ```
|
||||
*/
|
||||
export function decodeTotalSharesResult(result: Hex) {
|
||||
return decodeAbiParameters(FN_OUTPUTS, result)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the "totalShares" function on the contract.
|
||||
* @param options - The options for the totalShares function.
|
||||
* @returns The parsed result of the function call.
|
||||
* @extension SPLIT
|
||||
* @example
|
||||
* ```ts
|
||||
* import { totalShares } from "thirdweb/extensions/split";
|
||||
*
|
||||
* const result = await totalShares({
|
||||
* contract,
|
||||
* });
|
||||
*
|
||||
* ```
|
||||
*/
|
||||
export async function totalShares(options: BaseTransactionOptions) {
|
||||
return readContract({
|
||||
contract: options.contract,
|
||||
method: [FN_SELECTOR, FN_INPUTS, FN_OUTPUTS] as const,
|
||||
params: [],
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "~test/chains.js";
|
||||
import { TEST_CLIENT } from "~test/test-clients.js";
|
||||
import { TEST_ACCOUNT_A } from "~test/test-wallets.js";
|
||||
import { getContract } from "../../../contract/contract.js";
|
||||
import { deploySplitContract } from "../../../extensions/prebuilts/deploy-split.js";
|
||||
import { getAllRecipientsAddresses } from "./getAllRecipientsAddresses.js";
|
||||
|
||||
const chain = ANVIL_CHAIN;
|
||||
const client = TEST_CLIENT;
|
||||
|
||||
describe("getAllRecipientsAddresses", () => {
|
||||
it("should work", async () => {
|
||||
const payees = [
|
||||
"0x12345674b599ce99958242b3D3741e7b01841DF3",
|
||||
"0xA6f11e47dE28B3dB934e945daeb6F538E9019694",
|
||||
];
|
||||
const address = await deploySplitContract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
params: {
|
||||
name: "split-contract",
|
||||
payees,
|
||||
shares: [
|
||||
5100n, // 51%
|
||||
4900n, // 49%
|
||||
],
|
||||
},
|
||||
});
|
||||
const contract = getContract({
|
||||
address,
|
||||
chain,
|
||||
client,
|
||||
});
|
||||
const result = await getAllRecipientsAddresses({ contract });
|
||||
expect(result).toStrictEqual(payees);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { BaseTransactionOptions } from "../../../transaction/types.js";
|
||||
import { payee } from "../__generated__/Split/read/payee.js";
|
||||
import { payeeCount } from "../__generated__/Split/read/payeeCount.js";
|
||||
|
||||
/**
|
||||
* Get the addresses of all recipients of a [`thirdweb Split contract`](https://thirdweb.com/thirdweb.eth/Split)
|
||||
* @extension SPLIT
|
||||
* @returns an array of wallet addresses
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getAllRecipientsAddresses } from "thirdweb/extensions/split";
|
||||
*
|
||||
* const addresses = await getAllRecipientsAddresses({ contract });
|
||||
*/
|
||||
export async function getAllRecipientsAddresses(
|
||||
options: BaseTransactionOptions,
|
||||
): Promise<string[]> {
|
||||
const { contract } = options;
|
||||
const _totalRecipients = await payeeCount(options);
|
||||
const indexes = Array.from({ length: Number(_totalRecipients) }, (_, i) => i);
|
||||
const recipientAddresses = await Promise.all(
|
||||
indexes.map((index) => payee({ contract, index: BigInt(index) })),
|
||||
);
|
||||
return recipientAddresses;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "~test/chains.js";
|
||||
import { TEST_CLIENT } from "~test/test-clients.js";
|
||||
import { TEST_ACCOUNT_A } from "~test/test-wallets.js";
|
||||
import { getContract } from "../../../contract/contract.js";
|
||||
import { deploySplitContract } from "../../../extensions/prebuilts/deploy-split.js";
|
||||
import { getAllRecipientsPercentages } from "./getAllRecipientsPercentages.js";
|
||||
|
||||
const chain = ANVIL_CHAIN;
|
||||
const client = TEST_CLIENT;
|
||||
|
||||
describe("getAllRecipientsPercentages", () => {
|
||||
it("should work", async () => {
|
||||
const payees = [
|
||||
"0x12345674b599ce99958242b3D3741e7b01841DF3",
|
||||
"0xA6f11e47dE28B3dB934e945daeb6F538E9019694",
|
||||
];
|
||||
const address = await deploySplitContract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
params: {
|
||||
name: "split-contract",
|
||||
payees,
|
||||
shares: [
|
||||
5100n, // 51%
|
||||
4900n, // 49%
|
||||
],
|
||||
},
|
||||
});
|
||||
const contract = getContract({
|
||||
address,
|
||||
chain,
|
||||
client,
|
||||
});
|
||||
const result = await getAllRecipientsPercentages({ contract });
|
||||
expect(result).toStrictEqual([
|
||||
{
|
||||
address: "0x12345674b599ce99958242b3D3741e7b01841DF3",
|
||||
splitPercentage: 51,
|
||||
},
|
||||
{
|
||||
address: "0xA6f11e47dE28B3dB934e945daeb6F538E9019694",
|
||||
splitPercentage: 49,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { BaseTransactionOptions } from "../../../transaction/types.js";
|
||||
import { getAllRecipientsAddresses } from "./getAllRecipientsAddresses.js";
|
||||
import {
|
||||
type SplitRecipient,
|
||||
getRecipientSplitPercentage,
|
||||
} from "./getRecipientSplitPercentage.js";
|
||||
|
||||
/**
|
||||
* Get all the recipients of a Split contracts
|
||||
* @extension SPLIT
|
||||
* @returns an array of recipients' addresses and split percentage of each
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getAllRecipientsPercentages } from "thirdweb/extensions/split";
|
||||
*
|
||||
* const allRecipients = await getAllRecipientsPercentages({ contract });
|
||||
* // Example result:
|
||||
* [
|
||||
* {
|
||||
* address: "0x1...",
|
||||
* splitPercentage: 25, // 25%
|
||||
* },
|
||||
* {
|
||||
* address: "0x2...",
|
||||
* splitPercentage: 75, // 75%
|
||||
* },
|
||||
* ];
|
||||
* ```
|
||||
*/
|
||||
export async function getAllRecipientsPercentages(
|
||||
options: BaseTransactionOptions,
|
||||
): Promise<SplitRecipient[]> {
|
||||
const { contract } = options;
|
||||
const recipientAddresses = await getAllRecipientsAddresses({ contract });
|
||||
return await Promise.all(
|
||||
recipientAddresses.map((recipientAddress) =>
|
||||
getRecipientSplitPercentage({ contract, recipientAddress }),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { ANVIL_CHAIN } from "~test/chains.js";
|
||||
import { TEST_CLIENT } from "~test/test-clients.js";
|
||||
import { TEST_ACCOUNT_A } from "~test/test-wallets.js";
|
||||
import { getContract } from "../../../contract/contract.js";
|
||||
import { deploySplitContract } from "../../../extensions/prebuilts/deploy-split.js";
|
||||
import { getRecipientSplitPercentage } from "./getRecipientSplitPercentage.js";
|
||||
const chain = ANVIL_CHAIN;
|
||||
const client = TEST_CLIENT;
|
||||
|
||||
describe("getRecipientSplitPercentage", () => {
|
||||
it("should work", async () => {
|
||||
const payees = [
|
||||
"0x12345674b599ce99958242b3D3741e7b01841DF3",
|
||||
"0xA6f11e47dE28B3dB934e945daeb6F538E9019694",
|
||||
];
|
||||
const address = await deploySplitContract({
|
||||
account: TEST_ACCOUNT_A,
|
||||
client: TEST_CLIENT,
|
||||
chain: ANVIL_CHAIN,
|
||||
params: {
|
||||
name: "split-contract",
|
||||
payees,
|
||||
shares: [
|
||||
5100n, // 51%
|
||||
4900n, // 49%
|
||||
],
|
||||
},
|
||||
});
|
||||
const contract = getContract({
|
||||
address,
|
||||
chain,
|
||||
client,
|
||||
});
|
||||
const result = await getRecipientSplitPercentage({
|
||||
contract,
|
||||
recipientAddress: "0x12345674b599ce99958242b3D3741e7b01841DF3",
|
||||
});
|
||||
|
||||
expect(result).toStrictEqual({
|
||||
address: "0x12345674b599ce99958242b3D3741e7b01841DF3",
|
||||
splitPercentage: 51,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { BaseTransactionOptions } from "../../../transaction/types.js";
|
||||
import { shares } from "../__generated__/Split/read/shares.js";
|
||||
import { totalShares } from "../__generated__/Split/read/totalShares.js";
|
||||
|
||||
/**
|
||||
* @extension SPLIT
|
||||
*/
|
||||
export interface SplitRecipient {
|
||||
/**
|
||||
* The address of the recipient
|
||||
*/
|
||||
address: string;
|
||||
|
||||
/**
|
||||
* The split of the recipient as a percentage of the total amount
|
||||
*
|
||||
* I.e. If a recipient has a split of 50%, and the asset sells for 100 ETH,
|
||||
* the recipient will receive 50 ETH.
|
||||
*/
|
||||
splitPercentage: number;
|
||||
}
|
||||
|
||||
export async function getRecipientSplitPercentage(
|
||||
options: BaseTransactionOptions<{ recipientAddress: string }>,
|
||||
): Promise<SplitRecipient> {
|
||||
const { contract, recipientAddress } = options;
|
||||
const [_totalShares, walletsShares] = await Promise.all([
|
||||
totalShares({ contract }),
|
||||
shares({ contract, account: recipientAddress }),
|
||||
]);
|
||||
// We convert to basis points to avoid floating point loss of precision
|
||||
// 7544n -> 75.44 (75.44 %)
|
||||
// also we don't have to worry about number overflow in this particular context
|
||||
const splitPercentage =
|
||||
(Number(walletsShares) * 1e7) / Number(_totalShares) / 1e5;
|
||||
return {
|
||||
address: recipientAddress,
|
||||
splitPercentage,
|
||||
};
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
import { TEST_CLIENT } from "./test-clients.js";
|
||||
import { getContract } from "../../src/contract/contract.js";
|
||||
import { USDT_ABI } from "./abis/usdt.js";
|
||||
import { FORKED_ETHEREUM_CHAIN } from "./chains.js";
|
||||
import { getContract } from "../../src/contract/contract.js";
|
||||
import { TEST_CLIENT } from "./test-clients.js";
|
||||
|
||||
// ERC20
|
||||
|
||||
export const USDT_CONTRACT_ADDRESS =
|
||||
"0xdAC17F958D2ee523a2206206994597C13D831ec7";
|
||||
"0xdAC17F958D2ee523a2206206994597C13D831ec7";
|
||||
|
||||
export const USDT_CONTRACT = getContract({
|
||||
client: TEST_CLIENT,
|
||||
address: USDT_CONTRACT_ADDRESS,
|
||||
chain: FORKED_ETHEREUM_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
address: USDT_CONTRACT_ADDRESS,
|
||||
chain: FORKED_ETHEREUM_CHAIN,
|
||||
});
|
||||
|
||||
export const USDT_CONTRACT_WITH_ABI = getContract({
|
||||
client: TEST_CLIENT,
|
||||
address: USDT_CONTRACT_ADDRESS,
|
||||
chain: FORKED_ETHEREUM_CHAIN,
|
||||
abi: USDT_ABI,
|
||||
client: TEST_CLIENT,
|
||||
address: USDT_CONTRACT_ADDRESS,
|
||||
chain: FORKED_ETHEREUM_CHAIN,
|
||||
abi: USDT_ABI,
|
||||
});
|
||||
|
||||
// ERC721
|
||||
@@ -26,18 +26,19 @@ export const USDT_CONTRACT_WITH_ABI = getContract({
|
||||
const DOODLES_ADDRESS = "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e";
|
||||
|
||||
export const DOODLES_CONTRACT = getContract({
|
||||
client: TEST_CLIENT,
|
||||
address: DOODLES_ADDRESS,
|
||||
chain: FORKED_ETHEREUM_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
address: DOODLES_ADDRESS,
|
||||
chain: FORKED_ETHEREUM_CHAIN,
|
||||
});
|
||||
|
||||
const NFT_DROP_ADDRESS = "0xE333cD2f6e26A949Ce1F3FB15d7BfAc2871cc9e4";
|
||||
export const NFT_DROP_IMPLEMENTATION = "0x6f6010fb5da6f757d5b1822aadf1d3b806d6546d"; // https://etherscan.io/address/0x6f6010fb5da6f757d5b1822aadf1d3b806d6546d#code
|
||||
export const NFT_DROP_IMPLEMENTATION =
|
||||
"0x6f6010fb5da6f757d5b1822aadf1d3b806d6546d"; // https://etherscan.io/address/0x6f6010fb5da6f757d5b1822aadf1d3b806d6546d#code
|
||||
|
||||
export const NFT_DROP_CONTRACT = getContract({
|
||||
client: TEST_CLIENT,
|
||||
address: NFT_DROP_ADDRESS,
|
||||
chain: FORKED_ETHEREUM_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
address: NFT_DROP_ADDRESS,
|
||||
chain: FORKED_ETHEREUM_CHAIN,
|
||||
});
|
||||
|
||||
// ERC1155
|
||||
@@ -45,9 +46,9 @@ export const NFT_DROP_CONTRACT = getContract({
|
||||
const AURA_ADDRESS = "0x42d3641255C946CC451474295d29D3505173F22A";
|
||||
|
||||
export const DROP1155_CONTRACT = getContract({
|
||||
client: TEST_CLIENT,
|
||||
address: AURA_ADDRESS,
|
||||
chain: FORKED_ETHEREUM_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
address: AURA_ADDRESS,
|
||||
chain: FORKED_ETHEREUM_CHAIN,
|
||||
});
|
||||
|
||||
// Uniswap
|
||||
@@ -55,14 +56,14 @@ export const DROP1155_CONTRACT = getContract({
|
||||
const UNISWAPV3_FACTORY_ADDRESS = "0x1F98431c8aD98523631AE4a59f267346ea31F984";
|
||||
|
||||
export const OHM_CONTRACT_ADDRESS =
|
||||
"0x64aa3364F17a4D01c6f1751Fd97C2BD3D7e7f1D5";
|
||||
"0x64aa3364F17a4D01c6f1751Fd97C2BD3D7e7f1D5";
|
||||
export const WETH_CONTRACT_ADDRESS =
|
||||
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
|
||||
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
|
||||
export const MOG_CONTRACT_ADDRESS =
|
||||
"0xaaeE1A9723aaDB7afA2810263653A34bA2C21C7a";
|
||||
"0xaaeE1A9723aaDB7afA2810263653A34bA2C21C7a";
|
||||
|
||||
export const UNISWAPV3_FACTORY_CONTRACT = getContract({
|
||||
client: TEST_CLIENT,
|
||||
address: UNISWAPV3_FACTORY_ADDRESS,
|
||||
chain: FORKED_ETHEREUM_CHAIN,
|
||||
client: TEST_CLIENT,
|
||||
address: UNISWAPV3_FACTORY_ADDRESS,
|
||||
chain: FORKED_ETHEREUM_CHAIN,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user