-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #37 from polywrap/nerfzael/multi-send
Multisend with disperse contract
- Loading branch information
Showing
13 changed files
with
431 additions
and
34 deletions.
There are no files selected for viewing
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
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
export function distributeWeights(weights: number[], total: number, decimals: number): number[] { | ||
// Calculate initial amounts | ||
let amounts = weights.map(weight => weight * total); | ||
|
||
// Round amounts to two decimals and calculate the sum of these amounts | ||
let roundedAmounts = amounts.map(amount => parseFloat(amount.toFixed(decimals))); | ||
let sumOfRoundedAmounts = roundedAmounts.reduce((a, b) => a + b, 0); | ||
|
||
// Calculate the remainder | ||
let remainder = total - sumOfRoundedAmounts; | ||
|
||
// Distribute the remainder | ||
// The idea here is to distribute the remainder starting from the largest fraction part | ||
while (Math.abs(remainder) >= 0.01) { | ||
let index = amounts.findIndex((amount, idx) => | ||
roundedAmounts[idx] < amount && | ||
(remainder > 0 || roundedAmounts[idx] > 0) | ||
); | ||
|
||
if (index === -1) { | ||
break; // Break if no suitable item is found | ||
} | ||
|
||
if (remainder > 0) { | ||
roundedAmounts[index] += 0.01; | ||
remainder -= 0.01; | ||
} else { | ||
roundedAmounts[index] -= 0.01; | ||
remainder += 0.01; | ||
} | ||
|
||
roundedAmounts[index] = parseFloat(roundedAmounts[index].toFixed(decimals)); | ||
} | ||
|
||
return roundedAmounts; | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { WalletState } from "@web3-onboard/core"; | ||
import { SUPPORTED_NETWORKS, NetworkName } from "."; | ||
|
||
export function getSupportedNetworkFromWallet(wallet: WalletState | null) { | ||
return wallet | ||
? wallet.chains.length | ||
? Object.values(SUPPORTED_NETWORKS).includes(+wallet.chains[0].id as any) | ||
? Object.keys(SUPPORTED_NETWORKS).find((key) => | ||
SUPPORTED_NETWORKS[key as NetworkName] === +wallet.chains[0].id | ||
) as NetworkName | ||
: undefined | ||
: undefined | ||
: undefined; | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { NetworkName, TokenInformation, supportedErc20TokensByNetwork } from "."; | ||
|
||
export function getTokensForNetwork(network: NetworkName): TokenInformation[] { | ||
if (!network || !supportedErc20TokensByNetwork[network]) return []; | ||
|
||
const tokensForNetwork = supportedErc20TokensByNetwork[network]; | ||
|
||
if (!tokensForNetwork) return []; | ||
|
||
const tokens: TokenInformation[] = Object.values(tokensForNetwork) as TokenInformation[]; | ||
|
||
return tokens.filter(token => token); | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export * from './getSupportedNetworkFromWallet'; | ||
export * from './getTokensForNetwork'; | ||
export * from './splitTransferFunds'; | ||
export * from './supportedErc20Tokens'; | ||
export * from './supportedErc20TokensByNetwork'; | ||
export * from './supportedNetworks'; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
import { ethers, BigNumber } from "ethers"; | ||
|
||
const ERC20_ABI = [ | ||
"function transfer(address to, uint256 value) external returns (bool)", | ||
"function transferFrom(address from, address to, uint256 value) external returns (bool)", | ||
"function approve(address spender, uint256 value) external returns (bool)", | ||
"function allowance(address owner, address spender) external view returns (uint256)", | ||
]; | ||
|
||
const DISPERSE_ABI = [ | ||
"function disperseEther(address[] recipients, uint256[] values) external payable", | ||
"function disperseToken(address token, address[] recipients, uint256[] values) external", | ||
"function disperseTokenSimple(address token, address[] recipients, uint256[] values) external" | ||
]; | ||
|
||
const DISPERSE_CONTRACT_ADDRESS = "0xD152f549545093347A162Dce210e7293f1452150"; | ||
|
||
// Use address(0) or 'undefined' for ETH | ||
export async function splitTransferFunds( | ||
addresses: string[], | ||
amounts: number[], | ||
signer: ethers.Signer, | ||
tokenAddress?: string, | ||
tokenDecimals?: number | ||
) { | ||
const disperseContract = new ethers.Contract(DISPERSE_CONTRACT_ADDRESS, DISPERSE_ABI, signer); | ||
|
||
const validAddresses = addresses.filter((address) => ethers.utils.getAddress(address)); | ||
const values = amounts.map((amount) => ethers.utils.parseUnits(amount.toString(), tokenDecimals)); | ||
const totalValue = values.reduce((acc, value) => acc.add(value), ethers.constants.Zero); | ||
|
||
if (!tokenAddress || tokenAddress === ethers.constants.AddressZero) { | ||
// Ether transfer | ||
console.log("ether transfer"); | ||
await disperseContract.disperseEther(validAddresses, values, { | ||
value: totalValue, | ||
}); | ||
} else { | ||
// ERC20 token transfer | ||
console.log("tokenAddress", tokenAddress); | ||
const tokenContract = new ethers.Contract(tokenAddress, ERC20_ABI, signer); | ||
|
||
const currentAllowance: BigNumber = await tokenContract.allowance( | ||
await signer.getAddress(), | ||
DISPERSE_CONTRACT_ADDRESS | ||
); | ||
console.log("currentAllowance", currentAllowance); | ||
|
||
if (currentAllowance.lt(totalValue)) { | ||
const approveTx = await tokenContract.approve(DISPERSE_CONTRACT_ADDRESS, totalValue); | ||
await approveTx.wait(1); | ||
} | ||
|
||
const transferTx = await disperseContract.disperseTokenSimple(tokenAddress, validAddresses, values); | ||
await transferTx.wait(1); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { NetworkName } from "./supportedNetworks"; | ||
|
||
export const supportedErc20Tokens = ["USDC", "USDT", "DAI", "WETH"] as const; | ||
export type SupportedERC20Tokens = (typeof supportedErc20Tokens)[number]; | ||
|
||
export interface TokenInformation { | ||
address: string; | ||
decimals: number; | ||
name: string; | ||
} | ||
|
||
export type SupportedTokensInformation = Partial<Record< | ||
NetworkName, | ||
Partial<Record<SupportedERC20Tokens, TokenInformation>> | ||
>>; | ||
|
||
export const isValidToken = (token: string): token is SupportedERC20Tokens => { | ||
const tokens = supportedErc20Tokens as unknown as string[]; | ||
return tokens.includes(token); | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { NetworkName } from "."; | ||
import { SupportedTokensInformation } from "./supportedErc20Tokens"; | ||
|
||
export const supportedErc20TokensByNetwork: SupportedTokensInformation = { | ||
Sepolia: { | ||
WETH: { | ||
address: "0xfff9976782d46cc05630d1f6ebab18b2324d6b14", | ||
decimals: 18, | ||
name: "WETH", | ||
}, | ||
}, | ||
Mainnet: { | ||
USDC: { | ||
address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", | ||
decimals: 6, | ||
name: "USDC", | ||
}, | ||
}, | ||
Polygon: { | ||
USDC: { | ||
address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359", | ||
decimals: 6, | ||
name: "USDC", | ||
}, | ||
}, | ||
ArbitrumOne: { | ||
USDC: { | ||
address: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", | ||
decimals: 6, | ||
name: "USDC", | ||
}, | ||
}, | ||
Optimism: { | ||
USDC: { | ||
address: "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85", | ||
decimals: 6, | ||
name: "USDC", | ||
}, | ||
}, | ||
}; |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
export const SUPPORTED_NETWORKS = { | ||
Mainnet: 1, | ||
Polygon: 137, | ||
ArbitrumOne: 42161, | ||
Optimism: 10, | ||
Base: 8453, | ||
FantomOpera: 250, | ||
Sepolia: 11155111, | ||
// TODO: Disperse contract is not deployed on these networks | ||
// zkSync: 324, | ||
// Avalanche: 43114, | ||
// PGN: 424, | ||
} as const | ||
|
||
export type NetworkName = keyof typeof SUPPORTED_NETWORKS; | ||
export type NetworkId = (typeof SUPPORTED_NETWORKS)[NetworkName] | ||
|
||
export const isValidNetworkName = (network: string): network is NetworkName => { | ||
const values = Object.keys(SUPPORTED_NETWORKS) as unknown as string[]; | ||
return values.includes(network); | ||
}; |
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
Oops, something went wrong.