Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SmartDeploy Dapp v2.0 #11

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
0536dc2
chore: Use bindings from soroban-cli v20.30 + new smartdeploy ID
asanson1404 Feb 15, 2024
7855c29
chore: Split React components into different files
asanson1404 Feb 19, 2024
2ee0bce
feat: Query Deploy and Publish events from shuttle endpoints
asanson1404 Feb 19, 2024
8131f7d
feat: Add a Wallet React Context
asanson1404 Feb 21, 2024
66cc2bb
feat: Parse the API events data into ts objects
asanson1404 Feb 23, 2024
26ab596
feat: Add My Contracts section on Deployed and Published Tab
asanson1404 Feb 29, 2024
75ed16c
feat: Display Instances, TTL and associated published contract on Tabs
asanson1404 Feb 29, 2024
8b0755c
feat: Update PopUp to subscribe to automatic bump
asanson1404 Mar 1, 2024
aef0718
build: workspace: put contract client in packages/
asanson1404 Mar 6, 2024
c67a8d3
feat: Display MY_PUBLISHED_CONTRACTS
asanson1404 Mar 6, 2024
db85ccd
chore: Adapt deploy function with the new features
asanson1404 Mar 8, 2024
8602a97
feat: Display MY_DEPLOYED_CONTRACTS
asanson1404 Mar 8, 2024
8697daa
feat: Calculate and display Ledger Instance Expiration Date
asanson1404 Mar 11, 2024
1bdca17
fix: Fix bug regarding My Contracts Tabs
asanson1404 Mar 11, 2024
d53adb0
feat: Count Down till the next bump
asanson1404 Mar 12, 2024
0d86b59
chore: New soroban version and new smartdeploy contract ID
asanson1404 Mar 12, 2024
f793a97
chore: Polish the bumping subscription popup
asanson1404 Mar 12, 2024
5450ab9
fix: Speed up the time to display the countdown before the bump
asanson1404 Mar 12, 2024
f122e08
feat: Automatically bump contract instance when it's about to expire
asanson1404 Mar 15, 2024
34e79b8
feat: Store TTL and bumping contracts data in a postgres DB
asanson1404 Mar 18, 2024
4a7b47c
styles: Change font style and center ttl cells
asanson1404 Mar 19, 2024
889daca
feat: Display Claimed contracts
asanson1404 Mar 21, 2024
b0edadb
chore: Change Published Contracts to Published Binary
asanson1404 Mar 21, 2024
41ebc55
feat: Import an already deployed contract contract
asanson1404 Apr 7, 2024
d7f9f65
fix: Correct import contract bug
asanson1404 Apr 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# paths = ["/path/to/override"] # path dependency overrides

[alias] # command aliases
install_soroban = "install --git https://github.com/ahalabs/soroban-tools --rev ad3d6b6da54c47bc7a2343ba484ca173cd48f21f --debug --root ./target soroban-cli"
install_soroban = "install --version 20.3.1 --debug --root ./target soroban-cli"
# c = "check"
# t = "test"
# r = "run"
Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ yarn-error.log*
pnpm-debug.log*

# local env files
.env
.env*.local

# environment variables
Expand All @@ -46,4 +47,8 @@ next-env.d.ts

# Cargo/Rust build directory
target
.soroban/
.soroban/

# contract clients are auto-built at run/compile time and considered build artifacts
packages/*
!packages/.gitkeep
2 changes: 1 addition & 1 deletion components/dapp-info-popup/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Popup from 'reactjs-popup';
import styles from './style.module.css';
import { useState, useEffect, ChangeEvent } from 'react';
import { useThemeContext } from '../ThemeContext'
import { useThemeContext } from '../../context/ThemeContext'

export default function PopupDappInfo() {

Expand Down
192 changes: 192 additions & 0 deletions components/deployed-tab/backend.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
import { smartdeploy } from "@/pages";
import { DeployEventData, ClaimEventData, bumpContractInstance } from '@/mercury_indexer/smartdeploy-api-client';
import { TimeToLive } from '@/context/TimeToLiveContext'
import { format } from 'date-fns'
import { Ok, Err, Version } from 'smartdeploy-client'
import { WalletContextType } from '@/context/WalletContext'
import { SetStateAction, Dispatch } from "react";

export interface DeployedContract {
index: number;
name: string;
address: string;
deployer: string;
fromPublished: string;
version: Version | undefined;
}

export async function listAllDeployedContracts(
deployEvents: DeployEventData[] | undefined,
claimEvents: ClaimEventData[] | undefined,
) {

if (deployEvents && claimEvents) {
try {

///@ts-ignore
const { result } = await smartdeploy.listDeployedContracts({ start: undefined, limit: undefined });
const response = result;

if (response instanceof Ok) {

let deployedContracts: DeployedContract[] = [];

const contractArray = response.unwrap();

///@ts-ignore
contractArray.forEach(([name, address], i) => {

let eventData: DeployEventData | ClaimEventData | undefined = deployEvents.find(event => event.contractId == address);

// Contract deployed with SmartDeploy => trigger a Deploy event
if (eventData) {
const parsedDeployedContract: DeployedContract = {
index: i,
name: name,
address: address,
deployer: eventData.deployer,
fromPublished: eventData.publishedName,
version: eventData.version
}

deployedContracts.push(parsedDeployedContract);
}
// If no Deploy events the contract has been claimed
else {
eventData = claimEvents.find(event => event.contractId == address);
const parsedDeployedContract: DeployedContract = {
index: i,
name: name,
address: address,
deployer: eventData!.claimer,
fromPublished: eventData!.wasmHash,
version: undefined
}

deployedContracts.push(parsedDeployedContract);
}

});

return deployedContracts;

} else if (response instanceof Err) {
response.unwrap();
} else {
throw new Error("listDeployedContracts returns undefined. Impossible to fetch the deployed contracts.");
}
} catch (error) {
console.error(error);
window.alert(error);
}
}
else {
return 0;
}
}

export function getMyDeployedContracts(deployedContracts: DeployedContract[], address: string) {

const myDeployedContracts: DeployedContract[] = deployedContracts.filter(deployedContract => deployedContract.deployer === address);
return myDeployedContracts;

}

export type ImportArgsObj = {
deployed_name: string,
id: string,
owner: string,
};

export async function importContract(
walletContext: WalletContextType,
importData: ImportArgsObj,
setDeployedName: Dispatch<SetStateAction<string>>,
setContractId: Dispatch<SetStateAction<string>>,
setAdmin: Dispatch<SetStateAction<string>>,
setIsImporting: Dispatch<SetStateAction<boolean>>,
setBumping: Dispatch<SetStateAction<boolean | null>>,
) {

// Check if the Wallet is connected
if (walletContext.address === "") {
alert("Wallet not connected. Please, connect a Stellar account.");
}
// Check is the network is Futurenet
else if (walletContext.network.replace(" ", "").toUpperCase() !== "TESTNET") {
alert("Wrong Network. Please, switch to Testnet.");
}
else {
// Check if deployed name contains spaces
if (importData.deployed_name.includes(' ')) {
alert("Deployed name cannot includes spaces. Please, remove the spaces.");
}
// Check if contract_id contains spaces
if (importData.id.includes(' ')) {
alert("Contract Id cannot includes spaces. Please, remove the spaces.");
}
// Check if admin contains spaces
if (importData.owner.includes(' ')) {
alert("Admin address cannot includes spaces. Please, remove the spaces.");
}
// Now that everything is ok, import the contract
else {

try {
const tx = await smartdeploy.claimAlreadyDeployedContract(importData);
await tx.signAndSend();

setDeployedName("");
setContractId("");
setAdmin("");
setIsImporting(false);
setBumping(null);

} catch (error) {
console.error(error);
window.alert(error);
return false;
}

}

}
}

export function formatCountDown(initialTime: number) {

const days = Math.floor(initialTime / (60 * 60 * 24));
const hours = Math.floor((initialTime % (60 * 60 * 24)) / (60 * 60));
const minutes = Math.floor((initialTime % (60 * 60)) / 60);

return `${days}d${hours}h${minutes}m`;
}

export async function bumpAndQueryNewTtl(
contract_id: String,
ledgers_to_extend: number,
ttl: TimeToLive,
newMap: Map<String, TimeToLive>,
) {
try {
const datas = await bumpContractInstance(contract_id, ledgers_to_extend);
if (datas != 0) {
const newTtl = datas as number;
const newTtlSec = newTtl * 5;
const now = new Date();
const newExpirationDate = format(now.getTime() + newTtlSec * 1000, "MM/dd/yyyy");

const newValue = {
...ttl,
date: newExpirationDate,
ttlSec: newTtlSec,
countdown: formatCountDown(newTtlSec)
}
newMap.set(contract_id, newValue);

}
} catch (error) {
console.error(error);
window.alert(error);
}
}
66 changes: 66 additions & 0 deletions components/deployed-tab/copy-component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import styles from './style.module.css';

import { useState, useEffect, Dispatch, SetStateAction } from "react";
import { FaRegClipboard } from "react-icons/fa";
import { MdDone } from "react-icons/md"
import { useThemeContext } from '@/context/ThemeContext';
import Popup from 'reactjs-popup';

async function copyAddr(setCopied: Dispatch<SetStateAction<boolean>> , text: string) {
await navigator.clipboard
.writeText(text)
.then(() => {
setCopied(true);
})
.catch((err) => {
console.error("Failed to copy element: ", err);
window.alert(err);
});
}

export default function CopyComponent ({hash} : {hash: string}) {

const [copied, setCopied] = useState<boolean>(false);
const [ openCopyPopUp, setOpenCopyPopup] = useState(false);

useEffect(() => {
if(copied === true) {
const timer = setTimeout(() => {
setCopied(false)
}, 1500);
return () => clearTimeout(timer);
}
}, [copied]);

return (
<>
{!copied ? (
<p onClick={() => {copyAddr(setCopied, hash); setOpenCopyPopup(false)}}
onMouseEnter={() => setOpenCopyPopup(true)}
onMouseLeave={() => setOpenCopyPopup(false)}
>
hash: {hash.slice(0, 7)}...
</p>
) : (
<p className={styles.copiedMessage}><MdDone style={{ marginRight: '0.2rem' }}/>Hash Copied!</p>
)}
<CopyPopUp openCopyPopUp={openCopyPopUp} />
</>
);
}

const CopyPopUp = ({openCopyPopUp} : {openCopyPopUp: boolean}) => {

// Import the current Theme
const { activeTheme } = useThemeContext();

return (
<>
{openCopyPopUp && (
<div data-theme={activeTheme} className={styles.copyPopupContainer}>
<p className={styles.copyPopUpContent}>Click to copy Hash</p>
</div>
)}
</>
);
}
54 changes: 54 additions & 0 deletions components/deployed-tab/deployed-tab-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import styles from './style.module.css';
import { useThemeContext } from '../../context/ThemeContext'
import { TtlPopUp } from './ttl-popup';

type DeployedTabData = {
title: string;
displayedContracts: JSX.Element[] | string;
}

export function DeployedTabContent(props: DeployedTabData) {

// Import the current Theme
const { activeTheme } = useThemeContext();

return (
<div className={styles.deployedTabContainer} data-theme={activeTheme}>
<table className={styles.deployedTabHead}>
<caption data-theme={activeTheme}>{props.title}</caption>
<colgroup>
<col className={styles.contractCol}></col>
<col className={styles.addressCol}></col>
<col className={styles.fromCol}></col>
<col className={styles.ttlCol}></col>
</colgroup>
<thead data-theme={activeTheme}>
<tr>
<th>Contract</th>
<th>Address</th>
<th>From</th>
<th className={styles.ttlThead}><p>TTL</p> <TtlPopUp/></th>
</tr>
</thead>
</table>
<div className={styles.deployedTabContentContainer}>
<table className={styles.deployedTabContent}>
<colgroup>
<col className={styles.contractCol}></col>
<col className={styles.addressCol}></col>
<col className={styles.fromCol}></col>
<col className={styles.ttlCol}></col>
</colgroup>
{typeof(props.displayedContracts) != "string" && (
<tbody>
{props.displayedContracts}
</tbody>
)}
</table>
{typeof(props.displayedContracts) == "string" && (
<div className={styles.tabMessage} data-theme={activeTheme}>{props.displayedContracts}</div>
)}
</div>
</div>
)
}
Loading