@samudai_xyz/web3-sdk
Version:
## All in one web3 integrations for Samudai
637 lines (636 loc) • 28.2 kB
JavaScript
import { ethers } from 'ethers';
import { EthSignSignature } from '@gnosis.pm/safe-core-sdk';
import Safe, { EthersAdapter } from '@safe-global/protocol-kit';
import axios from 'axios';
import { Networks } from '../utils/networks';
import SafeApiKit from '@safe-global/api-kit';
import { encodeData, getDecimalsForToken } from '../lib/helpers';
export class Gnosis {
//Constructor
constructor(provider, chainId) {
this.safeAddress = '';
this.provider = null;
this.etherAdapter = null;
this.txServiceUrl = '';
// private generateBatchTransaction = (
// value: string,
// receiverAddresses: string[],
// tokenAddress?: string
// ): MetaTransactionData[] => {
// const transactions: MetaTransactionData[] = []
// const receiverValue: number = parseInt(value) / receiverAddresses.length
// if (tokenAddress) {
// receiverAddresses.map((receiverAddress) => {
// const encodedData = encodeData(
// ethers.utils.getAddress(receiverAddress),
// receiverValue.toString()
// )
// transactions.push({
// to: ethers.utils.getAddress(tokenAddress),
// value: '0',
// data: encodedData,
// operation: 0,
// })
// })
// } else {
// receiverAddresses.map((receiverAddress) => {
// transactions.push({
// to: receiverAddress,
// value: receiverValue.toString(),
// data: '0x',
// operation: 0,
// })
// })
// }
// return transactions
// }
this.generateCustomERC20Transaction = (receiverAddress, customERC20Token) => {
const transactions = [];
customERC20Token.map((token) => {
if (token.tokenAddress) {
const encodedData = encodeData(ethers.utils.getAddress(receiverAddress), token.value);
transactions.push({
to: ethers.utils.getAddress(token.tokenAddress),
value: '0',
data: encodedData,
operation: 0,
});
}
else {
transactions.push({
to: receiverAddress,
value: token.value,
data: '0x',
operation: 0,
});
}
});
return transactions;
};
this.createSingleGnosisTx = async (receiverAddress, value, safeAddress, senderAddress, tokenAddress) => {
try {
this.safeAddress = ethers.utils.getAddress(safeAddress);
if (this.provider) {
const safeOwner = await this.provider.getSigner(0);
let finalValue = value;
this.etherAdapter = new EthersAdapter({
ethers: ethers,
signerOrProvider: safeOwner,
});
const safeService = new SafeApiKit({
txServiceUrl: this.txServiceUrl,
ethAdapter: this.etherAdapter,
});
const safeSDK = await Safe.create({
ethAdapter: this.etherAdapter,
safeAddress: this.safeAddress,
});
const nonce = await safeService.getNextNonce(this.safeAddress);
let encodedCallData = '0x';
if (tokenAddress) {
const decimals = await getDecimalsForToken(this.chainId, tokenAddress);
finalValue = ethers.utils.parseUnits(value, decimals).toString();
}
else {
finalValue = ethers.utils.parseEther(value).toString();
}
if (tokenAddress) {
encodedCallData = encodeData(ethers.utils.getAddress(receiverAddress), finalValue);
}
const to = tokenAddress ? tokenAddress : receiverAddress;
const tokenValue = tokenAddress ? '0' : finalValue;
const safeTransactionData = {
to: ethers.utils.getAddress(to),
data: encodedCallData,
value: tokenValue,
operation: 0,
nonce: nonce,
};
const safeTransaction = await safeSDK.createTransaction({
safeTransactionData,
});
const safeTxHash = await safeSDK.getTransactionHash(safeTransaction);
const senderSignature = await safeSDK.signTransactionHash(safeTxHash);
const result = await safeService.proposeTransaction({
safeAddress: this.safeAddress,
safeTransactionData: safeTransaction.data,
safeTxHash,
senderAddress: ethers.utils.getAddress(senderAddress),
senderSignature: senderSignature.data,
origin: 'Samudai Platform',
});
const data = {
safeTxHash: safeTxHash,
proposedSafeTx: result,
};
return data;
}
else {
throw new Error('Provider not found');
}
}
catch (err) {
throw err;
}
};
this.createBatchTx = async (transactions, safeAddress, senderAddress) => {
try {
this.safeAddress = ethers.utils.getAddress(safeAddress);
if (this.provider) {
const safeOwner = await this.provider.getSigner(0);
this.etherAdapter = new EthersAdapter({
ethers: ethers,
signerOrProvider: safeOwner,
});
const safeService = new SafeApiKit({
txServiceUrl: this.txServiceUrl,
ethAdapter: this.etherAdapter,
});
const safeSDK = await Safe.create({
ethAdapter: this.etherAdapter,
safeAddress: this.safeAddress,
});
const nonce = await safeService.getNextNonce(this.safeAddress);
const options = {
nonce: nonce,
};
const safeTransactionData = [];
await Promise.all(transactions.map(async (tx) => {
if ((tx === null || tx === void 0 ? void 0 : tx.tokenAddress) === '') {
const safeTransactionDataItem = {
to: '',
data: '',
value: '',
};
safeTransactionDataItem.to = ethers.utils.getAddress(tx.to);
safeTransactionDataItem.data = '0x';
safeTransactionDataItem.value = ethers.utils
.parseEther(tx.value)
.toString();
safeTransactionData.push(safeTransactionDataItem);
}
else {
const safeTransactionDataItem = {
to: '',
data: '',
value: '',
};
const decimals = await getDecimalsForToken(this.chainId, tx === null || tx === void 0 ? void 0 : tx.tokenAddress);
const val = ethers.utils
.parseUnits(tx.value, decimals)
.toString();
safeTransactionDataItem.value = '0';
const encodedCallData = encodeData(ethers.utils.getAddress(tx.to), val);
safeTransactionDataItem.to = ethers.utils.getAddress(tx === null || tx === void 0 ? void 0 : tx.tokenAddress);
safeTransactionDataItem.data = encodedCallData;
safeTransactionData.push(safeTransactionDataItem);
}
}));
const safeTransaction = await safeSDK.createTransaction({
safeTransactionData: safeTransactionData,
onlyCalls: true,
options,
});
const safeTxHash = await safeSDK.getTransactionHash(safeTransaction);
const senderSignature = await safeSDK.signTransactionHash(safeTxHash);
const result = await safeService.proposeTransaction({
safeAddress: this.safeAddress,
safeTransactionData: safeTransaction.data,
safeTxHash,
senderAddress: ethers.utils.getAddress(senderAddress),
senderSignature: senderSignature.data,
origin: 'Samudai Platform',
});
const data = {
safeTxHash: safeTxHash,
proposedSafeTx: result,
};
return data;
}
else {
throw new Error('Provider not found');
}
}
catch (error) {
throw error;
}
};
this.getPendingTransactions = async (safeAddress) => {
try {
if (this.provider) {
const safeOwner = await this.provider.getSigner(0);
this.etherAdapter = new EthersAdapter({
ethers: ethers,
signerOrProvider: safeOwner,
});
const safeService = new SafeApiKit({
txServiceUrl: this.txServiceUrl,
ethAdapter: this.etherAdapter,
});
const pendingTxs = await safeService.getPendingTransactions(ethers.utils.getAddress(safeAddress));
return pendingTxs;
}
else {
throw new Error('Provider not found');
}
}
catch (err) {
throw err;
}
};
/**
* Function to return all safe transactions executed
* @param safeAddress string - The address of the Gnosis Safe
* @returns SafeTransactions - The response from the Gnosis Safe
*/
this.getExecutedTransactions = async (safeAddress) => {
try {
const res = await axios.get(`${this.txServiceUrl}/api/v1/safes/${safeAddress}/multisig-transactions/?executed=true`);
return res.data;
}
catch (err) {
throw err;
}
};
this.getRecentTransactions = async (safeAddress) => {
try {
const res = await axios.get(`${this.txServiceUrl}/api/v1/safes/${safeAddress}/all-transactions/?limit=40&executed=false&queued=true&trusted=true`);
return res.data;
}
catch (err) {
throw err;
}
};
this.getTransactionDetails = async (safeTxHash) => {
try {
if (this.provider) {
const safeOwner = await this.provider.getSigner(0);
this.etherAdapter = new EthersAdapter({
ethers: ethers,
signerOrProvider: safeOwner,
});
const safeService = new SafeApiKit({
txServiceUrl: this.txServiceUrl,
ethAdapter: this.etherAdapter,
});
const tx = await safeService.getTransaction(safeTxHash);
const safeOwners = await safeService.getSafeInfo(this.safeAddress);
const data = {
safeMultisigTransactionResponse: tx,
confirmation: safeOwners.threshold,
};
return data;
}
else {
throw new Error('Provider not found');
}
}
catch (err) {
throw err;
}
};
this.isTransactionOwner = async (safeAddress) => {
try {
if (this.provider) {
let isOwner = false;
const safeOwner = await this.provider.getSigner(0);
const userAddress = await safeOwner.getAddress();
this.etherAdapter = new EthersAdapter({
ethers: ethers,
signerOrProvider: safeOwner,
});
const safeService = new SafeApiKit({
txServiceUrl: this.txServiceUrl,
ethAdapter: this.etherAdapter,
});
const safeInfo = await safeService.getSafeInfo(safeAddress);
safeInfo.owners.find((owner) => {
if (owner === userAddress) {
isOwner = true;
}
else {
isOwner = false;
}
});
return isOwner;
}
else {
throw new Error('Provider not found');
}
}
catch (err) {
throw err;
}
};
this.isTransactionExecutable = async (safeTxHash, safeAddress) => {
try {
if (this.provider) {
let isOwner = false;
let isExecutable = false;
let isConfirmation = false;
const safeOwner = await this.provider.getSigner(0);
const userAddress = await safeOwner.getAddress();
this.etherAdapter = new EthersAdapter({
ethers: ethers,
signerOrProvider: safeOwner,
});
const safeService = new SafeApiKit({
txServiceUrl: this.txServiceUrl,
ethAdapter: this.etherAdapter,
});
const transaction = await safeService.getTransaction(safeTxHash);
const safeInfo = await safeService.getSafeInfo(safeAddress);
const isSafeOwner = safeInfo.owners.find((owner) => owner === ethers.utils.getAddress(userAddress));
if (isSafeOwner) {
isOwner = true;
}
else {
isOwner = false;
}
if (isOwner) {
const confirmation = transaction.confirmations.find((confirmation) => confirmation.owner === ethers.utils.getAddress(userAddress));
if (confirmation) {
isConfirmation = false;
}
else {
isConfirmation = true;
}
if (transaction.confirmations) {
if (transaction.confirmations.length >= safeInfo.threshold) {
isExecutable = true;
}
else {
isExecutable = false;
}
}
return {
isOwner,
isConfirmation,
isExecutable,
};
}
else {
return {
isOwner,
};
}
}
else {
throw new Error('Provider not found');
}
}
catch (err) {
throw err;
}
};
this.confirmTransaction = async (safeTxHash, safeAddress) => {
try {
if (this.provider) {
this.safeAddress = ethers.utils.getAddress(safeAddress);
const safeOwner = await this.provider.getSigner(0);
this.etherAdapter = new EthersAdapter({
ethers: ethers,
signerOrProvider: safeOwner,
});
const safeService = new SafeApiKit({
txServiceUrl: this.txServiceUrl,
ethAdapter: this.etherAdapter,
});
const safeSDK = await Safe.create({
ethAdapter: this.etherAdapter,
safeAddress: this.safeAddress,
});
const signature = await safeSDK.signTransactionHash(safeTxHash);
const result = await safeService.confirmTransaction(safeTxHash, signature.data);
return result;
}
else {
throw new Error('Provider not found');
}
}
catch (err) {
throw err;
}
};
this.rejectTransaction = async (safeAddress, nonce, senderAddress) => {
try {
if (this.provider) {
this.safeAddress = ethers.utils.getAddress(safeAddress);
const safeOwner = await this.provider.getSigner(0);
this.etherAdapter = new EthersAdapter({
ethers: ethers,
signerOrProvider: safeOwner,
});
const safeService = new SafeApiKit({
txServiceUrl: this.txServiceUrl,
ethAdapter: this.etherAdapter,
});
const safeSDK = await Safe.create({
ethAdapter: this.etherAdapter,
safeAddress: this.safeAddress,
});
const safeTransaction = await safeSDK.createRejectionTransaction(nonce);
const safeTxHash = await safeSDK.getTransactionHash(safeTransaction);
const senderSignature = await safeSDK.signTransactionHash(safeTxHash);
const result = await safeService.proposeTransaction({
safeAddress: this.safeAddress,
safeTransactionData: safeTransaction.data,
safeTxHash,
senderAddress: ethers.utils.getAddress(senderAddress),
senderSignature: senderSignature.data,
origin: 'Samudai Platform',
});
const data = {
safeTxHash: safeTxHash,
proposedSafeTx: result,
};
return data;
}
else {
throw new Error('Provider not found');
}
}
catch (error) {
throw error;
}
};
this.executeTransaction = async (safeTxHash, safeAddress) => {
try {
if (this.provider) {
this.safeAddress = ethers.utils.getAddress(safeAddress);
const safeOwner = await this.provider.getSigner(0);
this.etherAdapter = new EthersAdapter({
ethers: ethers,
signerOrProvider: safeOwner,
});
const safeService = new SafeApiKit({
txServiceUrl: this.txServiceUrl,
ethAdapter: this.etherAdapter,
});
const safeSDK = await Safe.create({
ethAdapter: this.etherAdapter,
safeAddress: this.safeAddress,
});
const transaction = await safeService.getTransaction(safeTxHash);
const safeTransactionData = {
to: transaction.to,
value: transaction.value,
data: transaction.data || '0x',
operation: transaction.operation,
safeTxGas: transaction.safeTxGas.toString(),
baseGas: transaction.baseGas.toString(),
gasPrice: parseInt(transaction.gasPrice).toString(),
gasToken: transaction.gasToken,
refundReceiver: transaction.refundReceiver,
nonce: transaction.nonce,
};
const safeTransaction = await safeSDK.createTransaction({
safeTransactionData,
});
transaction.confirmations.forEach((confirmation) => {
const signature = new EthSignSignature(confirmation.owner, confirmation.signature);
safeTransaction.addSignature(signature);
});
const executeTxResponse = await safeSDK.executeTransaction(safeTransaction);
const receipt = executeTxResponse.transactionResponse &&
(await executeTxResponse.transactionResponse.wait());
return receipt;
}
else {
throw new Error('Provider not found');
}
}
catch (err) {
throw err;
}
};
this.getSafeOwners = async (safeAddress) => {
try {
const owners = [];
const result = await axios.get(`${this.txServiceUrl}/api/v1/safes/${safeAddress}/`);
const safeOwners = result.data.owners;
for (const owner of safeOwners) {
//const address = (await this.provider?.lookupAddress(owner)) || owner
owners.push(owner);
}
return owners;
}
catch (err) {
return null;
}
};
this.connectGnosis = async (userAddress) => {
try {
const UserSafes = [];
const result = await axios.get(`${this.txServiceUrl}/api/v1/owners/${userAddress}/safes/`);
const userSafes = result.data.safes;
for (const userSafe of userSafes) {
const owners = await this.getSafeOwners(userSafe);
UserSafes.push({
safeAddress: userSafe,
owners: owners,
});
}
return UserSafes;
}
catch (err) {
throw err;
}
};
// Reminder to change the api url before pushing to dev
this.getSafeBalance = async (safeAddress) => {
try {
const result = await axios.get(`${this.txServiceUrl}/api/v1/safes/${safeAddress}/balances/usd/?trusted=false&exclude_spam=false`
// `https://safe-transaction-goerli.safe.global/api/v1/safes/0x6744fC3A5A9CAAeC22c939Bb0737679b768C5e4c/balances/usd/?trusted=false&exclude_spam=false`
);
const balance = result.data;
return balance;
}
catch (err) {
throw err;
}
};
// Reminder to change the api url before pushing to dev
this.getSafeBalanceinUSD = async (safeAddress) => {
try {
const result = await axios.get(`https://api.portals.fi/v2/account?owner=${safeAddress}&networks=ethereum`);
const balance = result.data;
return balance;
}
catch (err) {
throw err;
}
};
this.verifySafe = async (safeAddress) => {
try {
const result = await axios.get(`${this.txServiceUrl}/api/v1/safes/${safeAddress}/`);
if (result.status === 200) {
return true;
}
else {
return false;
}
}
catch (err) {
throw err;
}
};
this.customERC20Transfer = async (safeAddress, receiverAddress, senderAddress, customERC20Token) => {
try {
this.safeAddress = ethers.utils.getAddress(safeAddress);
if (this.provider) {
const safeOwner = await this.provider.getSigner(0);
this.etherAdapter = new EthersAdapter({
ethers: ethers,
signerOrProvider: safeOwner,
});
const safeService = new SafeApiKit({
txServiceUrl: this.txServiceUrl,
ethAdapter: this.etherAdapter,
});
const safeSDK = await Safe.create({
ethAdapter: this.etherAdapter,
safeAddress: this.safeAddress,
});
const nonce = await safeService.getNextNonce(this.safeAddress);
const safeTransactionData = this.generateCustomERC20Transaction(receiverAddress, customERC20Token);
const options = {
nonce, // Optional
};
const safeTransaction = await safeSDK.createTransaction({
safeTransactionData,
options,
});
const safeTxHash = await safeSDK.getTransactionHash(safeTransaction);
const senderSignature = await safeSDK.signTransactionHash(safeTxHash);
const result = await safeService.proposeTransaction({
safeAddress: this.safeAddress,
safeTransactionData: safeTransaction.data,
safeTxHash,
senderAddress: ethers.utils.getAddress(senderAddress),
senderSignature: senderSignature.data,
origin: 'Samudai Platform',
});
const data = {
safeTxHash: safeTxHash,
proposedSafeTx: result,
};
return data;
}
else {
throw new Error('Provider not found');
}
}
catch (err) {
throw err;
}
};
this.provider = provider;
this.chainId = chainId;
Networks.forEach((network) => {
if (network.chainId === chainId) {
this.txServiceUrl = network.url;
}
});
}
}