Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 5 additions & 4 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ jobs:
- name: Install Dioxus CLI
run: cargo install dioxus-cli --locked

- name: Build Dioxus Web App
run: dx build --release
- name: Build Dioxus Web App (in frontend/)
working-directory: frontend
run: dx build --platform web

- name: Upload artifact
- name: Upload GitHub Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: target/dioxus/release/web
path: frontend/target/dx/dioxus_meta_app/debug/web/public

deploy:
runs-on: ubuntu-latest
Expand Down
16 changes: 9 additions & 7 deletions frontend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,23 @@ version = "0.1.0"
edition = "2024"

[dependencies]
dioxus = { version = "0.7", features = ["macro","web"] }
dioxus-web = "0.7"
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
js-sys = "0.3"
web-sys = { version = "0.3", features = ["Window", "console", "HtmlInputElement"] }
dioxus = { version = "0.7.1", features = ["asset", "devtools", "document", "hooks", "html", "launch", "macro", "signals"], default-features = false }
wasm-bindgen = { version = "0.2", default-features = false }
wasm-bindgen-futures = { version = "0.4", default-features = false }
js-sys = { version = "0.3", default-features = false }
web-sys = { version = "0.3", default-features = false }
serde-wasm-bindgen = "0.6"
serde = { version = "1.0", features = ["derive"] }
serde = { version = "1.0", default-features = false }
serde_json = "1.0"
log = "0.4"
console_log = "1.0"
alloy = {version = "1.1", default-features = false }

[profile]

# [profile.release]
# strip = false

[profile.wasm-dev]
inherits = "dev"
opt-level = 1
Expand Down
15 changes: 15 additions & 0 deletions frontend/Dioxus.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[application]
name = "cAssets_dToken_Wrapper"
default_platform = "web"

[web.app]
title = "cAssets_dToken_Wrapper"
base_path = "cAssets_dToken_wrapper"
## keeps CSP happy
csp_hash = true
## avoids SWC minifier errors
minify_js = false

[web.wasm]
## ensures WASM + JS are together
mode = "bundled"
42 changes: 23 additions & 19 deletions frontend/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use alloy::primitives::{utils::{format_units, parse_units},U256};
use dioxus::prelude::*;
use wasm_bindgen_futures::spawn_local;
use serde_wasm_bindgen::from_value;
use crate::metamask::{TokenWrapperInfo, connect_metamask, get_token_balance, fetch_wrappers, wrap_tokens, unwrap_tokens};
use crate::metamask::{TokenWrapperInfo, connect_metamask, get_token_balance, get_all_wrappers, wrap_tokens, unwrap_tokens};

#[derive(Clone, Debug)]
struct TokenInfo {
Expand Down Expand Up @@ -84,23 +84,27 @@ pub fn App() -> Element {
let mut to_selected = to_selected;

async move {
let addr = connect_metamask().await;
address.set(addr.as_string().unwrap_or_default());
let addr = address.read().clone();
match fetch_wrappers(factory_address).await {
Ok(list) => {
if let Some(first) = list.first() {
let (from,to) = update_pair(&first.d_token_symbol, &list);
to_selected.set(to);
from_selected.set(from);
if let Ok(bal) = from_value::<String>(get_token_balance(&addr, &first.d_token_address).await) {
log::debug!("GetTokenBalance of address {} for token address {} :{:?}",addr, first.d_token_address, bal);
balance.set(bal);
}
match connect_metamask().await{
Ok(addr) => {
address.set(addr);
let addr = address.read().clone();
match get_all_wrappers(factory_address).await {
Ok(list) => {
if let Some(first) = list.first() {
let (from,to) = update_pair(&first.d_token_symbol, &list);
to_selected.set(to);
from_selected.set(from);
if let Ok(bal) = get_token_balance(&addr, &first.d_token_address).await {
log::debug!("GetTokenBalance of address {} for token address {} :{:?}",addr, first.d_token_address, bal);
balance.set(bal);
}
}
wrappers.set(list);
},
Err(e) => log::error!("Error fetching wrappers: {:?}", e)
}
wrappers.set(list);
},
Err(e) => log::error!("Error fetching wrappers: {:?}", e)
Err(e) => log::error!("Error connecting metamask: {:?}", e)
}
}
});
Expand All @@ -114,7 +118,7 @@ pub fn App() -> Element {

spawn_local(async move {
if let Some(from_sel) = from_sel
&& let Ok(bal) = from_value::<String>(get_token_balance(&addr, &from_sel.address).await) {
&& let Ok(bal) = get_token_balance(&addr, &from_sel.address).await {
log::debug!("GetTokenBalance of address {} for token address {} :{:?}",addr, from_sel.address, bal);
balance.set(bal);
}
Expand Down Expand Up @@ -285,8 +289,8 @@ pub fn App() -> Element {
} else {
unwrap_tokens(router_address, &from_selected.address.to_string(), &amount.read(), &to_selected.address.to_string()).await
};
tx_status.set(format!("{:?}", serde_wasm_bindgen::from_value::<String>(res)));
if let Ok(bal) = from_value::<String>(get_token_balance(&address(), &from_selected.address.to_string()).await) {
tx_status.set(format!("{:?}", res));
if let Ok(bal) = get_token_balance(&address(), &from_selected.address.to_string()).await {
log::debug!("TokenBalance {:?}",bal);
balance.set(bal);
}
Expand Down
183 changes: 113 additions & 70 deletions frontend/src/metamask.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,92 +5,123 @@ let provider;
let signer;

// METAMASK
export async function connect_metamask() {
if (!window.ethereum) throw new Error("MetaMask not installed");
await window.ethereum.request({ method: 'eth_requestAccounts' });
provider = new ethers.BrowserProvider(window.ethereum);
signer = await provider.getSigner();
return await signer.getAddress();
export async function js_connect_metamask() {
try {
if (!window.ethereum) throw new Error("MetaMask not installed");
await window.ethereum.request({ method: 'eth_requestAccounts' });
provider = new ethers.BrowserProvider(window.ethereum);
signer = await provider.getSigner();
const addr = await signer.getAddress();
return {
ok: true,
value: JSON.stringify(addr)
};
}catch (err) {
console.error(err);
return {
ok: false,
value: err.reason || err.message || "Unknown error"
};
}
}

// ERC20
export async function get_token_balance(user, token) {
export async function js_get_token_balance(user, token) {
const abi = ["function balanceOf(address) view returns (uint256)",
"function decimals() view returns (uint8)"];
try {
const erc20 = new ethers.Contract(token, abi, provider);
const [bal, decimals] = await Promise.all([erc20.balanceOf(user), erc20.decimals()]);
return ethers.formatUnits(bal, decimals);
return {
ok: true,
value: JSON.stringify(ethers.formatUnits(bal, decimals))
};
} catch (err) {
// console.error("ERC20 get balance error: ",err);
return `Error: ${err.reason || err.message}`;
console.error(err);
return {
ok: false,
value: err.reason || err.message || "Unknown error"
};
}
}

// FACTORY
export async function get_all_wrappers(factoryAddress) {
export async function js_get_all_wrappers(factoryAddress) {
// Factory ABI
console.log("GetAllWrappers called");
const factoryAbi = ["function getAllWraps() view returns (address[])"];
const factory = new ethers.Contract(factoryAddress, factoryAbi, provider);
// Wrapper ABI
const wrapperAbi = ["function info() view returns (tuple(address dTokenAddress, address cAssetAddress, uint8 dTokenDecimals, uint8 cAssetDecimals, uint256 dTokenInFeeBps, uint256 dTokenOutFeeBps, address dTokenTreasury, address cAssetTreasury))"];
const wrapAddresses = await factory.getAllWraps();
// console.log("WrapAddresses:", wrapAddresses);
const tokenList = [];

for (const wrapperAddr of wrapAddresses) {
console.log("WrapperAddress:", wrapperAddr);
try {
const wrapper = new ethers.Contract(wrapperAddr, wrapperAbi, provider);
const info = await wrapper.info();

const dTokenContract = new ethers.Contract(
info.dTokenAddress,
["function symbol() view returns (string)", "function decimals() view returns (uint8)"],
provider
);

const [dtoken_symbol, dtoken_decimals] = await Promise.all([
dTokenContract.symbol(),
dTokenContract.decimals()
]);

const cAssetContract = new ethers.Contract(
info.cAssetAddress,
["function symbol() view returns (string)", "function decimals() view returns (uint8)"],
provider
);

const [casset_symbol, casset_decimals] = await Promise.all([
cAssetContract.symbol(),
cAssetContract.decimals()
]);


tokenList.push({
wrapper: wrapperAddr,
dTokenSymbol: dtoken_symbol,
dTokenAddress: info.dTokenAddress,
dTokenDecimals: info.dTokenDecimals,
cAssetSymbol: casset_symbol,
cAssetAddress: info.cAssetAddress,
cAssetDecimals: info.cAssetDecimals,
fees: {
inBps: info.dTokenInFeeBps,
outBps: info.dTokenOutFeeBps
}
});
} catch (err) {
console.warn("Skipping wrapper", wrapperAddr, err);
try {
const factoryAbi = ["function getAllWraps() view returns (address[])"];
const factory = new ethers.Contract(factoryAddress, factoryAbi, provider);
// Wrapper ABI
const wrapperAbi = ["function info() view returns (tuple(address dTokenAddress, address cAssetAddress, uint8 dTokenDecimals, uint8 cAssetDecimals, uint256 dTokenInFeeBps, uint256 dTokenOutFeeBps))"];
const wrapAddresses = await factory.getAllWraps();
// console.log("WrapAddresses:", wrapAddresses);
const tokenList = [];

for (const wrapperAddr of wrapAddresses) {
console.log("WrapperAddress:", wrapperAddr);
try {
const wrapper = new ethers.Contract(wrapperAddr, wrapperAbi, provider);
const info = await wrapper.info();

const dTokenContract = new ethers.Contract(
info.dTokenAddress,
["function symbol() view returns (string)", "function decimals() view returns (uint8)"],
provider
);

const [dtoken_symbol, dtoken_decimals] = await Promise.all([
dTokenContract.symbol(),
dTokenContract.decimals()
]);

const cAssetContract = new ethers.Contract(
info.cAssetAddress,
["function symbol() view returns (string)", "function decimals() view returns (uint8)"],
provider
);

const [casset_symbol, casset_decimals] = await Promise.all([
cAssetContract.symbol(),
cAssetContract.decimals()
]);


tokenList.push({
wrapper: wrapperAddr,
dTokenSymbol: dtoken_symbol,
dTokenAddress: info.dTokenAddress,
dTokenDecimals: info.dTokenDecimals,
cAssetSymbol: casset_symbol,
cAssetAddress: info.cAssetAddress,
cAssetDecimals: info.cAssetDecimals,
fees: {
inBps: info.dTokenInFeeBps,
outBps: info.dTokenOutFeeBps
}
});
} catch (err) {
console.warn("Skipping wrapper", wrapperAddr, err);
}
}
return {
ok: true,
value: JSON.stringify(tokenList , (key, value) =>
typeof value === "bigint" ? value.toString() : value
)
};
} catch (err) {
return {
ok: false,
value: err.reason || err.message || "Unknown error"
};

}
return tokenList; // Array of objects with wrapper + cAsset info
}


// ROUTER
export async function wrap_tokens(contractAddress, dToken, amount, cAsset) {
export async function js_wrap_tokens(contractAddress, dToken, amount, cAsset) {
try {
const abi = ["function wrap(address dTokent, uint256 amount, address cAsset) external"];
const approveAbi = ["function approve(address spender, uint256 amount) external returns (bool)",
Expand All @@ -106,15 +137,21 @@ export async function wrap_tokens(contractAddress, dToken, amount, cAsset) {
const connected = contract.connect(signer);
const tx = await connected.wrap(dToken, amount_u256, cAsset);
const receipt = await tx.wait();
return "Wrap successful: ", receipt.hash;
return {
ok: true,
value: JSON.stringify(`${receipt.hash}`)
};
} catch (err) {
console.error(err);
return `Error: ${err.reason || err.message}`;
return {
ok: false,
value: err.reason || err.message || "Unknown error"
};
}

}

export async function unwrap_tokens(contractAddress, cAsset, amount, dToken) {
export async function js_unwrap_tokens(contractAddress, cAsset, amount, dToken) {
try {
const abi = ["function unwrap(address cAsset, uint256 amount, address dToken) external"];
const approveAbi = ["function approve(address spender, uint256 amount) external returns (bool)",
Expand All @@ -130,9 +167,15 @@ export async function unwrap_tokens(contractAddress, cAsset, amount, dToken) {
const connected = contract.connect(signer);
const tx = await connected.unwrap(cAsset, amount_u256, dToken);
const receipt = await tx.wait();
return "Unwrap successful: ", receipt.hash;
return {
ok: true,
value: JSON.stringify(`${receipt.hash}`)
};
} catch (err) {
console.error(err);
return `Error: ${err.reason || err.message}`;
return {
ok: false,
value: err.reason || err.message || "Unknown error"
};
}
}
Loading