Skip to content

Commit a5ed969

Browse files
authored
Merge pull request #42 from D4ryl00/feat/return-estimation-gas
feat: add gasEstimate function
2 parents a1a7ce2 + 92c57d2 commit a5ed969

File tree

12 files changed

+206
-66
lines changed

12 files changed

+206
-66
lines changed

.github/workflows/pull-request.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,20 @@ jobs:
1313
uses: actions/checkout@v4
1414

1515
- name: Setup asdf
16-
uses: asdf-vm/actions/setup@v3
16+
uses: ynab/[email protected]
17+
with:
18+
version: 0.16.7
1719

1820
- name: Setup Node version
1921
working-directory: mobile
2022
run: |
2123
asdf plugin add nodejs
2224
asdf install nodejs
23-
echo "node_version=$(asdf current nodejs | xargs | cut -d ' ' -f 2)" >> $GITHUB_ENV
25+
echo "node_version=$(asdf current nodejs | xargs | cut -d ' ' -f 6)" >> $GITHUB_ENV
2426
2527
- name: Set nodejs as global exec
2628
run: |
27-
asdf global nodejs ${{ env.node_version }}
29+
asdf set -u nodejs ${{ env.node_version }}
2830
2931
- name: Install dependencies
3032
working-directory: mobile

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,10 @@ land.gno.gnokey://tosign?tx=%7B%22msg%22%3A%5B%7B%22%40type%22%3A%22%2Fvm.m_call
5959
- address: bech32 address of whoever you want to sign the transaction.
6060
- remote: the connection address for the remote node where the transaction will be sent
6161
- chain_id: the chain ID for the remote
62-
- client_name: the name of the app that is calling the Gnokey Mobile app. It will be displayed to the user (if no session).
63-
- reason: the reason behind this action. It will be displayed to the user (if no session).
62+
- client_name: the name of the app that is calling the Gnokey Mobile app. It will be displayed to the user.
63+
- reason: the reason behind this action. It will be displayed to the user.
6464
- callback: the URL that Gnokey Mobile will call after signing the tx.
65-
- want_session: boolean, if true and no session then create and return a new session key
66-
- session (optional): the session key json from the previous call with want_session true. If present, sign immediately and return
65+
- update_tx (optional): if "true" then update gas_wanted in the tx with the estimated gas.
6766

6867
Example response:
6968

@@ -75,8 +74,7 @@ tech.berty.dsocial://post?tx=%7B%22msg%22%3A%5B%7B%22%40type%22%3A%22%2Fvm.m_cal
7574
- Base URL: The `callback` from the request. In this case, `tech.berty.dsocial://post`
7675
- Parameters (values are percent-escaped, to be decoded with `decodeURIComponent`):
7776
- tx: the signed transaction json to pass to `gnonative.broadcastTxCommit(...)`
78-
- session: if want_session then the session key json to use in future `tosign`. Example: `{"expires_at":"2025-03-24T14:35:16.970Z","key":"673768235734692"}`
79-
- status: either "success" or an error such as "session expired"
77+
- status: either "success" or an error
8078

8179
### Testing on iOS simulator
8280

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)/home/home.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export default function Page() {
5757
};
5858

5959
const navigateToAddKey = () => {
60-
route.push("/tosign");
60+
route.push("/home/vault-add-modal");
6161
}
6262

6363
const onBookmarkPress = (keyInfo: Vault) => async () => {

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

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Layout, Ruller, TextInput } from "@/components";
22
import {
3-
selectClientName, selectBech32Address, selectTxInput, signTx, useAppDispatch,
3+
estimateGasWanted, selectClientName, selectBech32Address, selectTxInput, selectUpdateTx, signTx, useAppDispatch,
44
useAppSelector, reasonSelector, selectCallback, selectKeyInfo, clearLinking, selectChainId,
55
selectRemote, selectSession, selectSessionWanted, newSessionKey, SessionKeyInfo
66
} from "@/redux";
@@ -25,10 +25,13 @@ export default function Page() {
2525
const reason = useAppSelector(reasonSelector);
2626
const bech32Address = useAppSelector(selectBech32Address);
2727
const txInput = useAppSelector(selectTxInput);
28+
const updateTx = useAppSelector(selectUpdateTx) ?? false;
2829
const callback = useAppSelector(selectCallback);
2930
const keyInfo = useAppSelector(selectKeyInfo);
3031
const chainId = useAppSelector(selectChainId);
3132
const remote = useAppSelector(selectRemote);
33+
const [signedTx, setSignedTx] = useState<string | undefined>(undefined);
34+
const [gasWanted, setGasWanted] = useState<bigint>(BigInt(0));
3235
// const session = useAppSelector(selectSession);
3336
// const sessionWanted = useAppSelector(selectSessionWanted);
3437

@@ -59,6 +62,29 @@ export default function Page() {
5962
})();
6063
}, [bech32Address]);
6164

65+
useEffect(() => {
66+
(async () => {
67+
try {
68+
console.log("onChangeAccountHandler", keyInfo);
69+
70+
if (!txInput || !keyInfo) {
71+
throw new Error("No transaction input or keyInfo found.");
72+
}
73+
74+
const { gasWanted } = await dispatch(estimateGasWanted({ keyInfo, updateTx: updateTx })).unwrap();
75+
76+
// need to pause to let the Keybase DB close before using it again
77+
await new Promise((f) => setTimeout(f, 1000));
78+
79+
const signedTx = await dispatch(signTx({ keyInfo })).unwrap();
80+
setSignedTx(signedTx.signedTxJson);
81+
setGasWanted(gasWanted);
82+
} catch (error: unknown | Error) {
83+
console.error(error);
84+
}
85+
})();
86+
}, [txInput, keyInfo]);
87+
6288
const signTxAndReturnToRequester = async () => {
6389
console.log('signing the tx', keyInfo);
6490

@@ -137,8 +163,8 @@ export default function Page() {
137163

138164
<Ruller />
139165

140-
<FormItemInline label="Max Amount" >
141-
<TextBodyWhite>{gasFee} ugnot</TextBodyWhite>
166+
<FormItemInline label="Gas Wanted" >
167+
<TextBodyWhite>{gasWanted?.toString()}</TextBodyWhite>
142168
</FormItemInline>
143169

144170
{/* {sessionWanted &&

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/package-lock.json

Lines changed: 4 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mobile/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@connectrpc/connect": "^1.2.1",
1818
"@connectrpc/connect-web": "^1.2.1",
1919
"@expo/vector-icons": "^14.0.4",
20-
"@gnolang/gnonative": "^4.0.0",
20+
"@gnolang/gnonative": "^4.1.0",
2121
"@react-native-async-storage/async-storage": "1.23.1",
2222
"@reduxjs/toolkit": "^2.1.0",
2323
"date-fns": "^3.6.0",

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)