Skip to content

Commit

Permalink
feat: add mobile sign and broadcast
Browse files Browse the repository at this point in the history
  • Loading branch information
JoseRFelix committed Jan 13, 2025
1 parent a44fa27 commit c4463b7
Show file tree
Hide file tree
Showing 15 changed files with 556 additions and 122 deletions.
26 changes: 9 additions & 17 deletions packages/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { DefaultTheme } from "~/constants/themes";
import { useWallets } from "~/hooks/use-wallets";
import { useCurrentWalletStore } from "~/stores/current-wallet";
import { useKeyringStore } from "~/stores/keyring";
import { getMobileAssetListAndChains } from "~/utils/asset-lists";
import { getCachedAssetListAndChains } from "~/utils/asset-lists";
import { mmkvStorage } from "~/utils/mmkv";
import { api, RouterKeys } from "~/utils/trpc";
import { appRouter } from "~/utils/trpc-routers/root-router";
Expand Down Expand Up @@ -110,19 +110,11 @@ export default function RootLayout() {
const servers = {
local: localLink({
router: appRouter,
getLists: async () => {
const data = await queryClient.ensureQueryData({
queryKey: ["assetLists-and-chainLists"],
queryFn: async () =>
getMobileAssetListAndChains({
environment: "mainnet",
}),
cacheTime: 1000 * 60 * 30, // 30 minutes
retry: 3,
});

return data;
},
getLists: async () =>
getCachedAssetListAndChains({
queryClient,
environment: "mainnet",
}),
opentelemetryServiceName: "osmosis-mobile",
})(runtime),
[constructEdgeRouterKey("main")]: makeSkipBatchLink(
Expand Down Expand Up @@ -223,7 +215,9 @@ const OnboardingObserver = () => {
} = api.local.oneClickTrading.getSessionAuthenticator.useQuery(
{
publicKey:
currentWallet?.type === "smart-account" ? currentWallet!.publicKey : "",
currentWallet?.type === "smart-account"
? currentWallet!.accountOwnerPublicKey
: "",
userOsmoAddress: currentWallet?.address ?? "",
},
{
Expand All @@ -241,8 +235,6 @@ const OnboardingObserver = () => {
}
);

console.log(sessionAuthenticatorError);

useEffect(() => {
const handleSessionError = async () => {
if (
Expand Down
16 changes: 11 additions & 5 deletions packages/mobile/app/onboarding/camera-scan.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,15 @@ const useWalletCreationWebRTC = ({
data.encryptedData,
secretRef.current
);
const { address, allowedMessages, key, publicKey } =
MobileSessionEncryptedDataSchema.parse(
JSON.parse(decryptedData)
);
const {
address,
allowedMessages,
key,
accountOwnerPublicKey,
authenticatorId,
} = MobileSessionEncryptedDataSchema.parse(
JSON.parse(decryptedData)
);

// Store the decrypted data in secure storage
await new WalletFactory().createWallet({
Expand All @@ -156,9 +161,10 @@ const useWalletCreationWebRTC = ({
address,
allowedMessages,
privateKey: key,
publicKey,
accountOwnerPublicKey,
name: "Wallet 1",
version: data.version,
authenticatorId,
},
});
useCurrentWalletStore.setState({
Expand Down
3 changes: 3 additions & 0 deletions packages/mobile/hooks/use-environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const useEnvironment = () => {
return { environment: "mainnet" as const };
};
174 changes: 174 additions & 0 deletions packages/mobile/hooks/use-estimate-tx-fees.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import type { EncodeObject } from "@cosmjs/proto-signing";
import { DEFAULT_VS_CURRENCY, superjson } from "@osmosis-labs/server";
import {
InsufficientBalanceForFeeError,
SwapRequiresError,
} from "@osmosis-labs/stores";
import {
estimateGasFee,
getRegistry,
getSmartAccountExtensionOptions,
QuoteStdFee,
} from "@osmosis-labs/tx";
import { CoinPretty, Dec, DecUtils, PricePretty } from "@osmosis-labs/unit";
import { isNil } from "@osmosis-labs/utils";
import { QueryClient, useQuery, useQueryClient } from "@tanstack/react-query";
import { useMemo } from "react";

import { useWallets } from "~/hooks/use-wallets";
import { getCachedAssetListAndChains } from "~/utils/asset-lists";
import { api } from "~/utils/trpc";

interface QueryResult {
gasUsdValueToPay: PricePretty;
gasAmount: CoinPretty;
gasLimit: string;
amount: QuoteStdFee["amount"];
}

async function estimateTxFeesQueryFn({
messages,
apiUtils,
chainId,
queryClient,
address,
authenticatorId,
}: {
chainId: string;
messages: EncodeObject[];
apiUtils: ReturnType<typeof api.useUtils>;
queryClient: QueryClient;
address: string | undefined;
authenticatorId: string | undefined;
}): Promise<QueryResult> {
if (!messages.length) throw new Error("No messages");
if (!address) throw new Error("No address");

const registry = await getRegistry();
const encodedMessages = messages.map((m) => registry.encodeAsAny(m));

const { chainList } = await getCachedAssetListAndChains({
queryClient,
environment: "mainnet",
});

const { amount, gas } = await estimateGasFee({
chainId,
chainList,
bech32Address: address,
body: {
messages: encodedMessages,
nonCriticalExtensionOptions: authenticatorId
? await getSmartAccountExtensionOptions({
authenticatorId,
})
: undefined,
},
gasMultiplier: 1.5,
});

const fee = amount[0];
const asset = await getCachedAssetWithPrice(apiUtils, fee.denom);

if (!fee || !asset?.currentPrice) {
throw new Error("Failed to estimate fees");
}

const coinAmountDec = new Dec(fee.amount);
const usdValue = coinAmountDec
.quo(DecUtils.getTenExponentN(asset.coinDecimals))
.mul(asset.currentPrice.toDec());
const gasUsdValueToPay = new PricePretty(DEFAULT_VS_CURRENCY, usdValue);

return {
gasUsdValueToPay,
gasAmount: new CoinPretty(asset, coinAmountDec),
gasLimit: gas,
amount,
};
}

export function useEstimateTxFees({
messages,
chainId,
enabled = true,
}: {
messages: EncodeObject[] | undefined;
chainId: string;
enabled?: boolean;
}) {
const apiUtils = api.useUtils();
const { currentWallet } = useWallets();
const queryClient = useQueryClient();

const queryResult = useQuery<QueryResult, Error, QueryResult, string[]>({
queryKey: ["estimate-tx-fees", superjson.stringify(messages)],
queryFn: () => {
return estimateTxFeesQueryFn({
messages: messages!,
apiUtils,
chainId,
queryClient,
address: currentWallet?.address,
authenticatorId:
currentWallet?.type === "smart-account"
? currentWallet?.authenticatorId
: undefined,
});
},
staleTime: 3_000, // 3 seconds
cacheTime: 3_000, // 3 seconds
retry: false,
enabled:
enabled &&
!isNil(messages) &&
Array.isArray(messages) &&
messages.length > 0 &&
currentWallet?.type === "smart-account" &&
currentWallet?.authenticatorId !== undefined &&
currentWallet?.address !== undefined &&
typeof currentWallet?.address === "string",
});

const specificError = useMemo(() => {
if (
queryResult.error instanceof Error &&
(queryResult.error.message.includes(
"No fee tokens found with sufficient balance on account"
) ||
queryResult.error.message.includes(
"Insufficient alternative balance for transaction fees"
) ||
queryResult.error.message.includes("insufficient funds"))
) {
return new InsufficientBalanceForFeeError(queryResult.error.message);
}

if (
queryResult.error instanceof Error &&
(queryResult.error.message.includes("Swap requires") ||
queryResult.error.message.includes("is greater than max amount"))
) {
return new SwapRequiresError(queryResult.error.message);
}

return queryResult.error;
}, [queryResult.error]);

return { ...queryResult, error: specificError };
}

function getCachedAssetWithPrice(
apiUtils: ReturnType<typeof api.useUtils>,
coinMinimalDenom: string
) {
return apiUtils.local.assets.getAssetWithPrice.ensureData(
{
findMinDenomOrSymbol: coinMinimalDenom,
},
{
staleTime: 1000 * 10, // 10 seconds
cacheTime: 1000 * 10, // 10 seconds
}
);
}
75 changes: 75 additions & 0 deletions packages/mobile/hooks/use-osmosis-chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Chain } from "@osmosis-labs/types";

export function useOsmosisChain(): Chain {
return {
chain_name: "osmosis",
status: "live",
network_type: "mainnet",
pretty_name: "Osmosis",
chain_id: "osmosis-1",
bech32_prefix: "osmo",
bech32_config: {
bech32PrefixAccAddr: "osmo",
bech32PrefixAccPub: "osmopub",
bech32PrefixValAddr: "osmovaloper",
bech32PrefixValPub: "osmovaloperpub",
bech32PrefixConsAddr: "osmovalcons",
bech32PrefixConsPub: "osmovalconspub",
},
slip44: 118,
fees: {
fee_tokens: [
{
denom: "uosmo",
fixed_min_gas_price: 0.0025,
low_gas_price: 0.0025,
average_gas_price: 0.025,
high_gas_price: 0.04,
},
],
},
staking: {
staking_tokens: [
{
denom: "uosmo",
},
],
lock_duration: {
time: "1209600s",
},
},
description:
"Osmosis (OSMO) is the premier DEX and cross-chain DeFi hub within the Cosmos ecosystem, a network of over 50 sovereign, interoperable blockchains seamlessly connected through the Inter-Blockchain Communication Protocol (IBC). Pioneering in its approach, Osmosis offers a dynamic trading and liquidity provision experience, integrating non-IBC assets from other ecosystems, including Ethereum, Solana, Avalanche, and Polkadot. Initially adopting Balancer-style pools, Osmosis now also features a concentrated liquidity model that is orders of magnitude more capital efficient, meaning that significantly less liquidity is required to handle the same amount of trading volume with minimal slippage.\n\nAs a true appchain, Osmosis has greater control over the full blockchain stack than traditional smart contract DEXs, which must follow the code of the parent chain that it is built on. This fine-grained control has enabled, for example, the development of Superfluid Staking, an extension of Proof of Stake that allows assets at the application layer to be staked to secure the chain. The customizability of appchains also allows implementing features like the Protocol Revenue module, which enables Osmosis to conduct on-chain arbitrage on behalf of OSMO stakers, balancing prices across pools while generating real yield revenue from this volume. Additionally, as a sovereign appchain, Osmosis governance can vote on upgrades to the protocol. One example of this was the introduction of a Taker Fee, which switched on the collection of exchange fees to generate diverse yield from Osmosis volume and distribute it to OSMO stakers.\n\nOsmosis is bringing the full centralized exchange experience to the decentralized world by building a cross-chain native DEX and trading suite that connects all chains over IBC, including Ethereum and Bitcoin. To reach this goal, Osmosis hosts an ever-expanding suite of DeFi applications aimed at providing a one-stop experience that includes lending, credit, margin, DeFi strategy vaults, power perps, fiat on-ramps, NFTs, stablecoins, and more — all of the functionalities that centralized exchange offer and more, in the trust-minimized environment of decentralized finance.",
apis: {
rpc: [
{
address: "https://rpc-osmosis.keplr.app",
},
],
rest: [
{
address: "https://lcd-osmosis.keplr.app",
},
],
},
explorers: [
{
tx_page: "https://www.mintscan.io/osmosis/txs/{txHash}",
},
],
logoURIs: {
svg: "https://raw.githubusercontent.com/cosmos/chain-registry/master/osmosis/images/osmo.svg",
png: "https://raw.githubusercontent.com/cosmos/chain-registry/master/osmosis/images/osmo.png",
theme: {
primary_color_hex: "#760dbb",
},
},
features: [
"ibc-go",
"ibc-transfer",
"cosmwasm",
"wasmd_0.24+",
"osmosis-txfees",
],
};
}
Loading

0 comments on commit c4463b7

Please sign in to comment.