Skip to content

Commit 35fb9c7

Browse files
committed
feat: estimate gasWanted
Signed-off-by: D4ryl00 <[email protected]>
1 parent c805d4d commit 35fb9c7

File tree

7 files changed

+353
-166
lines changed

7 files changed

+353
-166
lines changed

mobile/.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ EXPO_PUBLIC_GNO_CHAIN_ID=portal-loop
66

77
# EXPO_PUBLIC_GNO_REMOTE=https://api.gno.berty.io:443
88
# EXPO_PUBLIC_FAUCET_REMOTE=https://faucetpass.gno.berty.io
9+
EXPO_PUBLIC_TXINDEXER_REMOTE=https://txindexer.gno.berty.io
910
# EXPO_PUBLIC_GNO_CHAIN_ID=dev
1011

1112
# local
1213

1314
# EXPO_PUBLIC_GNO_REMOTE=http://localhost:26657
1415
# EXPO_PUBLIC_FAUCET_REMOTE=http://localhost:5050
16+
# EXPO_PUBLIC_TXINDEXER_REMOTE=http://localhost:26657

mobile/app/(app)/tosign/index.tsx

Lines changed: 120 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,140 @@
11
import { Layout } from "@/components";
22
import Button from "@/components/button";
33
import Text from "@/components/text";
4-
import { selectClientName, selectBech32Address, selectTxInput, signTx, useAppDispatch, useAppSelector, reasonSelector, selectCallback, selectKeyInfo, clearLinking, selectChainId, selectRemote } from "@/redux";
4+
import {
5+
estimateGasWanted,
6+
selectClientName,
7+
selectBech32Address,
8+
selectTxInput,
9+
signTx,
10+
useAppDispatch,
11+
useAppSelector,
12+
reasonSelector,
13+
selectCallback,
14+
selectKeyInfo,
15+
clearLinking,
16+
selectChainId,
17+
selectRemote,
18+
} from "@/redux";
519
import { useGnoNativeContext } from "@gnolang/gnonative";
620
import { router } from "expo-router";
721
import { useEffect, useState } from "react";
8-
import * as Linking from 'expo-linking';
22+
import * as Linking from "expo-linking";
923
import { View } from "react-native";
1024
import { Spacer } from "@/modules/ui-components";
1125

1226
export default function Page() {
13-
14-
const dispatch = useAppDispatch();
15-
const { gnonative } = useGnoNativeContext();
16-
const [accountName, setAccountName] = useState<string | undefined>(undefined);
17-
const clientName = useAppSelector(selectClientName);
18-
19-
const reason = useAppSelector(reasonSelector);
20-
const bech32Address = useAppSelector(selectBech32Address);
21-
const txInput = useAppSelector(selectTxInput);
22-
const callback = useAppSelector(selectCallback);
23-
const keyInfo = useAppSelector(selectKeyInfo);
24-
const chainId = useAppSelector(selectChainId);
25-
const remote = useAppSelector(selectRemote);
26-
27-
console.log('txInput', txInput);
28-
console.log('bech32Address', bech32Address);
29-
console.log('clientName', clientName);
30-
console.log('reason', reason);
31-
32-
useEffect(() => {
33-
(async () => {
34-
35-
if (!chainId || !remote) throw new Error("No chainId or remote found.");
36-
gnonative.setChainID(chainId);
37-
gnonative.setRemote(remote);
38-
39-
const accountNameStr = await gnonative.qEval("gno.land/r/sys/users", `ResolveAddress("${bech32Address}").Name()`);
40-
setAccountName(accountNameStr);
41-
})();
42-
}, [bech32Address]);
43-
44-
const signTxAndReturnToRequester = async () => {
45-
console.log('onChangeAccountHandler', keyInfo);
27+
const dispatch = useAppDispatch();
28+
const { gnonative } = useGnoNativeContext();
29+
const [accountName, setAccountName] = useState<string | undefined>(undefined);
30+
const clientName = useAppSelector(selectClientName);
31+
const [signedTx, setSignedTx] = useState<string | undefined>(undefined);
32+
const [gasWanted, setGasWanted] = useState<bigint>(BigInt(0));
33+
34+
const reason = useAppSelector(reasonSelector);
35+
const bech32Address = useAppSelector(selectBech32Address);
36+
const txInput = useAppSelector(selectTxInput);
37+
const callback = useAppSelector(selectCallback);
38+
const keyInfo = useAppSelector(selectKeyInfo);
39+
const chainId = useAppSelector(selectChainId);
40+
const remote = useAppSelector(selectRemote);
41+
42+
console.log("txInput", txInput);
43+
console.log("bech32Address", bech32Address);
44+
console.log("clientName", clientName);
45+
console.log("reason", reason);
46+
47+
useEffect(() => {
48+
(async () => {
49+
if (!chainId || !remote) throw new Error("No chainId or remote found.");
50+
gnonative.setChainID(chainId);
51+
gnonative.setRemote(remote);
52+
53+
const accountNameStr = await gnonative.qEval("gno.land/r/sys/users", `ResolveAddress("${bech32Address}").Name()`);
54+
setAccountName(accountNameStr);
55+
})();
56+
}, [bech32Address]);
57+
58+
useEffect(() => {
59+
(async () => {
60+
try {
61+
console.log("onChangeAccountHandler", keyInfo);
4662

4763
if (!txInput || !keyInfo) {
48-
throw new Error("No transaction input or keyInfo found.");
64+
throw new Error("No transaction input or keyInfo found.");
4965
}
5066

51-
const signedTx = await dispatch(signTx({ keyInfo })).unwrap();
52-
53-
const path = callback ? callback : 'tech.berty.dsocial://post';
54-
const url = `${path}?tx=${encodeURIComponent(signedTx.signedTxJson)}`;
55-
console.log("response URL " + url);
56-
Linking.openURL(url);
67+
const { gasWanted } = await dispatch(estimateGasWanted({ keyInfo, updateTx: true })).unwrap();
5768

58-
router.push("/home")
69+
const signedTx = await dispatch(signTx({ keyInfo })).unwrap();
70+
setSignedTx(signedTx.signedTxJson);
71+
setGasWanted(gasWanted);
72+
} catch (error: unknown | Error) {
73+
console.error(error);
74+
}
75+
})();
76+
}, [txInput, keyInfo]);
77+
78+
const returnSignedTxToRequester = async () => {
79+
if (!signedTx) {
80+
throw new Error("No signedTx found.");
5981
}
6082

61-
const onCancel = () => {
62-
dispatch(clearLinking());
63-
if (callback) {
64-
Linking.openURL(`${callback}?status=cancelled`); // callback to requester
65-
}
66-
router.replace("/home");
67-
}
83+
const path = callback ? callback : "tech.berty.dsocial://post";
84+
const url = `${path}?tx=${encodeURIComponent(signedTx)}&gaswanted=${gasWanted.toString()}`;
85+
console.log("response URL " + url);
86+
Linking.openURL(url);
6887

69-
return (
70-
<>
71-
<Layout.Container>
72-
<Layout.BodyAlignedBotton>
73-
<Text.Title>{clientName} is requiring permission to {reason}.</Text.Title>
74-
<Spacer space={16} />
75-
76-
<Spacer space={16} />
77-
<Text.Body>reason: {reason}</Text.Body>
78-
<Text.Body>callback: {callback}</Text.Body>
79-
<Text.Body>client_name: {clientName}</Text.Body>
80-
<Text.Body>accountName: {accountName}</Text.Body>
81-
<View style={{ flexDirection: 'row', alignItems:'center' }}>
82-
<Text.Body>Address:</Text.Body>
83-
<Text.Caption1>{bech32Address}</Text.Caption1>
84-
</View>
85-
<View style={{ flexDirection: 'row', alignItems:'center' }}>
86-
<Text.Body>Keyinfo:</Text.Body>
87-
<Text.Caption1>{keyInfo?.name}</Text.Caption1>
88-
</View>
89-
<Text.Body>remote: {remote}</Text.Body>
90-
<Text.Body>chainId: {chainId}</Text.Body>
91-
92-
<Spacer space={16} />
93-
94-
<Button.TouchableOpacity title="Approve" variant="primary" onPress={signTxAndReturnToRequester}></Button.TouchableOpacity>
95-
<Button.TouchableOpacity title="Cancel" variant="primary-red" onPress={onCancel}></Button.TouchableOpacity>
96-
</Layout.BodyAlignedBotton>
97-
</Layout.Container>
98-
</>
99-
)
88+
router.push("/home");
89+
};
10090

91+
const onCancel = () => {
92+
dispatch(clearLinking());
93+
if (callback) {
94+
Linking.openURL(`${callback}?status=cancelled`); // callback to requester
95+
}
96+
router.replace("/home");
97+
};
98+
99+
return (
100+
<>
101+
<Layout.Container>
102+
<Layout.BodyAlignedBotton>
103+
<Text.Title>
104+
{clientName} is requiring permission to {reason}.
105+
</Text.Title>
106+
<Spacer space={16} />
107+
108+
<Spacer space={16} />
109+
<Text.Body>reason: {reason}</Text.Body>
110+
<Text.Body>callback: {callback}</Text.Body>
111+
<Text.Body>client_name: {clientName}</Text.Body>
112+
<Text.Body>accountName: {accountName}</Text.Body>
113+
<View style={{ flexDirection: "row", alignItems: "center" }}>
114+
<Text.Body>Address:</Text.Body>
115+
<Text.Caption1>{bech32Address}</Text.Caption1>
116+
</View>
117+
<View style={{ flexDirection: "row", alignItems: "center" }}>
118+
<Text.Body>Keyinfo:</Text.Body>
119+
<Text.Caption1>{keyInfo?.name}</Text.Caption1>
120+
</View>
121+
<View style={{ flexDirection: "row", alignItems: "center" }}>
122+
<Text.Body>Gas Wanted:</Text.Body>
123+
<Text.Caption1>{gasWanted?.toString()}</Text.Caption1>
124+
</View>
125+
<Text.Body>remote: {remote}</Text.Body>
126+
<Text.Body>chainId: {chainId}</Text.Body>
127+
128+
<Spacer space={16} />
129+
130+
<Button.TouchableOpacity
131+
title="Approve"
132+
variant="primary"
133+
onPress={returnSignedTxToRequester}
134+
></Button.TouchableOpacity>
135+
<Button.TouchableOpacity title="Cancel" variant="primary-red" onPress={onCancel}></Button.TouchableOpacity>
136+
</Layout.BodyAlignedBotton>
137+
</Layout.Container>
138+
</>
139+
);
101140
}

mobile/app/_layout.tsx

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Stack } from "expo-router";
22
import { ThemeProvider as ThemeProvider2 } from "@react-navigation/native";
33
import { Guard } from "@/components/auth/guard";
44
import { GnoNativeProvider } from "@gnolang/gnonative";
5-
import { LinkingProvider, ReduxProvider } from "@/providers";
5+
import { IndexerProvider, LinkingProvider, ReduxProvider } from "@/providers";
66
import { DefaultTheme } from "@/assets/styles";
77
import { ThemeProvider } from "@/modules/ui-components";
88

@@ -13,26 +13,33 @@ const gnoDefaultConfig = {
1313
chain_id: process.env.EXPO_PUBLIC_GNO_CHAIN_ID!,
1414
};
1515

16+
const indexerConfig = {
17+
// @ts-ignore
18+
remote: process.env.EXPO_PUBLIC_TXINDEXER_REMOTE!,
19+
};
20+
1621
export default function AppLayout() {
1722
return (
1823
<GnoNativeProvider config={gnoDefaultConfig}>
19-
<ReduxProvider>
20-
<ThemeProvider>
21-
<ThemeProvider2 value={DefaultTheme}>
22-
<LinkingProvider>
23-
<Guard>
24-
<Stack
25-
screenOptions={{
26-
headerShown: false,
27-
headerLargeTitle: true,
28-
headerBackVisible: false,
29-
}}
30-
/>
31-
</Guard>
32-
</LinkingProvider>
33-
</ThemeProvider2>
34-
</ThemeProvider>
35-
</ReduxProvider>
24+
<IndexerProvider config={indexerConfig}>
25+
<ReduxProvider>
26+
<ThemeProvider>
27+
<ThemeProvider2 value={DefaultTheme}>
28+
<LinkingProvider>
29+
<Guard>
30+
<Stack
31+
screenOptions={{
32+
headerShown: false,
33+
headerLargeTitle: true,
34+
headerBackVisible: false,
35+
}}
36+
/>
37+
</Guard>
38+
</LinkingProvider>
39+
</ThemeProvider2>
40+
</ThemeProvider>
41+
</ReduxProvider>
42+
</IndexerProvider>
3643
</GnoNativeProvider>
3744
);
3845
}

mobile/providers/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { LinkingProvider } from "./linking-provider";
22
import { ReduxProvider } from "@/providers/redux-provider";
3+
import { IndexerProvider } from "@/providers/indexer-provider";
34

4-
export { LinkingProvider, ReduxProvider };
5+
export { IndexerProvider, LinkingProvider, ReduxProvider };

mobile/providers/indexer-provider.tsx

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { createContext, useContext } from "react";
2+
3+
export interface GasPriceResponse {
4+
low: bigint;
5+
average: bigint;
6+
high: bigint;
7+
denom: string;
8+
}
9+
10+
export interface IndexerContextProps {
11+
getGasPrice: () => Promise<GasPriceResponse>;
12+
}
13+
14+
interface ConfigProps {
15+
remote: string;
16+
}
17+
18+
interface IndexerProviderProps {
19+
config: ConfigProps;
20+
children: React.ReactNode;
21+
}
22+
23+
const IndexerContext = createContext<IndexerContextProps | null>(null);
24+
25+
const IndexerProvider: React.FC<IndexerProviderProps> = ({ children, config }) => {
26+
if (!config.remote) {
27+
throw new Error("IndexerProvider requires a remote config");
28+
}
29+
30+
const getGasPrice = async (): Promise<GasPriceResponse> => {
31+
const response = await fetch(config.remote, {
32+
method: "POST",
33+
headers: {
34+
Accept: "application/json",
35+
"Content-Type": "application/json",
36+
},
37+
body: JSON.stringify({
38+
jsonrpc: "2.0",
39+
method: "getGasPrice",
40+
}),
41+
});
42+
43+
const text = await response.text();
44+
// convert number to bigint that is not automatically converted by JSON.parse
45+
const jsonData = JSON.parse(text, (_, value) => {
46+
if (typeof value === "number") {
47+
return BigInt(value);
48+
}
49+
return value;
50+
});
51+
52+
const data: GasPriceResponse[] = jsonData.result;
53+
54+
for (const item of data) {
55+
if (item.denom == "ugnot") {
56+
console.log("getGasPrice found: ", item);
57+
return item;
58+
}
59+
}
60+
61+
throw new Error("No gas price found");
62+
};
63+
64+
const value = {
65+
getGasPrice,
66+
};
67+
68+
return <IndexerContext.Provider value={value}>{children}</IndexerContext.Provider>;
69+
};
70+
71+
function useIndexerContext() {
72+
const context = useContext(IndexerContext) as IndexerContextProps;
73+
74+
if (context === undefined) {
75+
throw new Error("useIndexerContext must be used within a IndexerProvider");
76+
}
77+
return context;
78+
}
79+
80+
export { IndexerProvider, useIndexerContext };

0 commit comments

Comments
 (0)