Skip to content

Commit c231522

Browse files
authored
feat: fixing ibc transfers gas estimate (#1567)
* feat: fixing ibc shielded gas estimate * feat: moving round of decimal places to gas fn * feat: adding channel as required prop for gas estimation
1 parent 4b83183 commit c231522

File tree

9 files changed

+193
-109
lines changed

9 files changed

+193
-109
lines changed

apps/namadillo/src/App/Ibc/IbcTransfer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ export const IbcTransfer = (): JSX.Element => {
184184
isShielded: shielded,
185185
onChangeShielded: setShielded,
186186
}}
187-
gasConfig={gasConfig}
187+
gasConfig={gasConfig.data}
188188
changeFeeEnabled={false}
189189
submittingText={currentProgress}
190190
isSubmitting={transferStatus === "pending" || !!currentProgress}

apps/namadillo/src/App/Transfer/TransferModule.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,6 @@ export const TransferModule = ({
148148
return "NoSelectedAsset";
149149
} else if (!source.amount || source.amount.eq(0)) {
150150
return "NoAmount";
151-
} else if (!gasConfig) {
152-
return "NoTransactionFee";
153151
} else if (
154152
!availableAmountMinusFees ||
155153
source.amount.gt(availableAmountMinusFees)

apps/namadillo/src/atoms/integrations/services.ts

Lines changed: 19 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
assertIsDeliverTxSuccess,
55
calculateFee,
66
DeliverTxResponse,
7+
MsgTransferEncodeObject,
78
SigningStargateClient,
89
StargateClient,
910
StdFee,
@@ -15,7 +16,6 @@ import { getIndexerApi } from "atoms/api";
1516
import { queryForAck, queryForIbcTimeout } from "atoms/transactions";
1617
import BigNumber from "bignumber.js";
1718
import { getDefaultStore } from "jotai";
18-
import { createIbcTransferMessage } from "lib/transactions";
1919
import toml from "toml";
2020
import {
2121
AddressWithAsset,
@@ -25,7 +25,6 @@ import {
2525
LocalnetToml,
2626
TransferStep,
2727
} from "types";
28-
import { toBaseAmount } from "utils";
2928
import { getKeplrWallet } from "utils/ibc";
3029
import { getSdkInstance } from "utils/sdk";
3130
import { rpcByChainAtom } from "./atoms";
@@ -105,35 +104,14 @@ export const createStargateClient = async (
105104

106105
export const getSignedMessage = async (
107106
client: SigningStargateClient,
108-
transferParams: IbcTransferParams,
109-
maspCompatibleMemo: string = ""
107+
transferMsg: MsgTransferEncodeObject,
108+
gasConfig: GasConfig
110109
): Promise<TxRaw> => {
111-
const {
112-
sourceAddress,
113-
amount: displayAmount,
114-
asset,
115-
sourceChannelId,
116-
gasConfig,
117-
} = transferParams;
118-
119-
// cosmjs expects amounts to be represented in the base denom, so convert
120-
const baseAmount = toBaseAmount(asset.asset, displayAmount);
121-
122110
const fee: StdFee = calculateFee(
123-
gasConfig.gasLimit.toNumber(),
111+
Math.ceil(gasConfig.gasLimit.toNumber()),
124112
`${gasConfig.gasPrice.toString()}${gasConfig.gasToken}`
125113
);
126-
127-
const transferMsg = createIbcTransferMessage(
128-
sourceChannelId,
129-
sourceAddress,
130-
transferParams.destinationAddress,
131-
baseAmount,
132-
asset.originalAddress,
133-
maspCompatibleMemo
134-
);
135-
136-
return await client.sign(sourceAddress, [transferMsg], fee, "");
114+
return await client.sign(transferMsg.value.sender!, [transferMsg], fee, "");
137115
};
138116

139117
export const broadcastIbcTransaction = async (
@@ -281,3 +259,17 @@ export const fetchIbcChannelFromRegistry = async (
281259
const channelInfo: IBCInfo = await (await fetch(queryUrl.toString())).json();
282260
return getChannelFromIbcInfo(ibcChainName, channelInfo) || null;
283261
};
262+
263+
export const simulateIbcTransferFee = async (
264+
stargateClient: SigningStargateClient,
265+
sourceAddress: string,
266+
transferMsg: MsgTransferEncodeObject,
267+
additionalPercentage: number = 0.05
268+
): Promise<number> => {
269+
const estimatedGas = await stargateClient.simulate(
270+
sourceAddress!,
271+
[transferMsg],
272+
undefined
273+
);
274+
return estimatedGas * (1 + additionalPercentage);
275+
};

apps/namadillo/src/constants.ts

Whitespace-only changes.
Lines changed: 105 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,6 @@
1-
import { useMemo, useState } from "react";
2-
import {
3-
Address,
4-
AddressWithAssetAndAmount,
5-
ChainRegistryEntry,
6-
GasConfig,
7-
IbcTransferStage,
8-
TransferStep,
9-
TransferTransactionData,
10-
} from "types";
11-
12-
type useIbcTransactionProps = {
13-
sourceAddress?: string;
14-
registry?: ChainRegistryEntry;
15-
sourceChannel?: string;
16-
shielded?: boolean;
17-
destinationChannel?: Address;
18-
selectedAsset?: AddressWithAssetAndAmount;
19-
};
20-
1+
import { SigningStargateClient } from "@cosmjs/stargate";
212
import { QueryStatus } from "@tanstack/query-core";
3+
import { useQuery, UseQueryResult } from "@tanstack/react-query";
224
import { TokenCurrency } from "App/Common/TokenCurrency";
235
import {
246
broadcastIbcTransactionAtom,
@@ -32,13 +14,35 @@ import {
3214
dispatchToastNotificationAtom,
3315
} from "atoms/notifications";
3416
import BigNumber from "bignumber.js";
35-
import { getIbcGasConfig } from "integrations/utils";
3617
import invariant from "invariant";
3718
import { useAtomValue, useSetAtom } from "jotai";
38-
import { createTransferDataFromIbc } from "lib/transactions";
19+
import {
20+
createIbcTransferMessage,
21+
createTransferDataFromIbc,
22+
} from "lib/transactions";
23+
import { useState } from "react";
24+
import {
25+
Address,
26+
AddressWithAssetAndAmount,
27+
ChainRegistryEntry,
28+
GasConfig,
29+
IbcTransferStage,
30+
TransferStep,
31+
TransferTransactionData,
32+
} from "types";
3933
import { toBaseAmount } from "utils";
4034
import { sanitizeAddress } from "utils/address";
4135
import { getKeplrWallet, sanitizeChannel } from "utils/ibc";
36+
import { useSimulateIbcTransferFee } from "./useSimulateIbcTransferFee";
37+
38+
type useIbcTransactionProps = {
39+
sourceAddress?: string;
40+
registry?: ChainRegistryEntry;
41+
sourceChannel?: string;
42+
shielded?: boolean;
43+
destinationChannel?: Address;
44+
selectedAsset?: AddressWithAssetAndAmount;
45+
};
4246

4347
type useIbcTransactionOutput = {
4448
transferToNamada: (
@@ -48,7 +52,7 @@ type useIbcTransactionOutput = {
4852
onUpdateStatus?: (status: string) => void
4953
) => Promise<TransferTransactionData>;
5054
transferStatus: "idle" | QueryStatus;
51-
gasConfig: GasConfig | undefined;
55+
gasConfig: UseQueryResult<GasConfig>;
5256
};
5357

5458
export const useIbcTransaction = ({
@@ -62,6 +66,37 @@ export const useIbcTransaction = ({
6266
const broadcastIbcTx = useAtomValue(broadcastIbcTransactionAtom);
6367
const dispatchNotification = useSetAtom(dispatchToastNotificationAtom);
6468
const [txHash, setTxHash] = useState<string | undefined>();
69+
const [rpcUrl, setRpcUrl] = useState<string | undefined>();
70+
const [stargateClient, setStargateClient] = useState<
71+
SigningStargateClient | undefined
72+
>();
73+
74+
// Avoid the same client being created twice for the same chain and provide
75+
// a way to refetch the query in case it throws an error trying to connect to the RPC
76+
useQuery({
77+
queryKey: ["store-stargate-client", registry?.chain.chain_id],
78+
enabled: Boolean(registry?.chain),
79+
queryFn: async () => {
80+
invariant(registry?.chain, "Error: Invalid chain");
81+
setRpcUrl(undefined);
82+
setStargateClient(undefined);
83+
return await queryAndStoreRpc(registry.chain, async (rpc: string) => {
84+
const client = await createStargateClient(rpc, registry.chain);
85+
setStargateClient(client);
86+
setRpcUrl(rpc);
87+
return client;
88+
});
89+
},
90+
});
91+
92+
const gasConfigQuery = useSimulateIbcTransferFee({
93+
stargateClient,
94+
registry,
95+
selectedAsset,
96+
isShieldedTransfer: shielded,
97+
sourceAddress,
98+
channel: sourceChannel,
99+
});
65100

66101
const dispatchPendingTxNotification = (tx: TransferTransactionData): void => {
67102
invariant(tx.hash, "Error: Transaction hash not provided");
@@ -90,13 +125,6 @@ export const useIbcTransaction = ({
90125
});
91126
};
92127

93-
const gasConfig = useMemo(() => {
94-
if (typeof registry !== "undefined") {
95-
return getIbcGasConfig(registry);
96-
}
97-
return undefined;
98-
}, [registry]);
99-
100128
const getIbcTransferStage = (shielded: boolean): IbcTransferStage => {
101129
return shielded ?
102130
{ type: "IbcToShielded", currentStep: TransferStep.IbcToShielded }
@@ -116,12 +144,14 @@ export const useIbcTransaction = ({
116144
invariant(selectedAsset, "Error: No asset is selected");
117145
invariant(registry, "Error: Invalid chain");
118146
invariant(sourceChannel, "Error: Invalid IBC source channel");
119-
invariant(gasConfig, "Error: No transaction fee is set");
147+
invariant(stargateClient, "Error: Stargate client not initialized");
148+
invariant(rpcUrl, "Error: RPC URL not initialized");
120149
invariant(
121150
!shielded || destinationChannel,
122151
"Error: Destination channel not provided"
123152
);
124153

154+
// Set Keplr option to allow Namadillo to set the transaction fee
125155
const baseKeplr = getKeplrWallet();
126156
const savedKeplrOptions = baseKeplr.defaultOptions;
127157
baseKeplr.defaultOptions = {
@@ -132,6 +162,8 @@ export const useIbcTransaction = ({
132162

133163
try {
134164
const baseAmount = toBaseAmount(selectedAsset.asset, displayAmount);
165+
166+
// This step might require a bit of time
135167
const { memo: maspCompatibleMemo, receiver: maspCompatibleReceiver } =
136168
await (async () => {
137169
onUpdateStatus?.("Generating MASP parameters...");
@@ -145,48 +177,49 @@ export const useIbcTransaction = ({
145177
: { memo, receiver: destinationAddress };
146178
})();
147179

148-
// Set Keplr option to allow Namadillo to set the transaction fee
149180
const chainId = registry.chain.chain_id;
150-
151-
return await queryAndStoreRpc(registry.chain, async (rpc: string) => {
152-
onUpdateStatus?.("Waiting for signature...");
153-
const client = await createStargateClient(rpc, registry.chain);
154-
const ibcTransferParams = {
155-
signer: baseKeplr.getOfflineSigner(chainId),
156-
chainId,
157-
sourceAddress: sanitizeAddress(sourceAddress),
158-
destinationAddress: sanitizeAddress(maspCompatibleReceiver),
159-
amount: displayAmount,
160-
asset: selectedAsset,
161-
gasConfig,
162-
sourceChannelId: sanitizeChannel(sourceChannel!),
163-
destinationChannelId: sanitizeChannel(destinationChannel!) || "",
164-
isShielded: !!shielded,
165-
};
166-
167-
const signedMessage = await getSignedMessage(
168-
client,
169-
ibcTransferParams,
170-
maspCompatibleMemo
171-
);
172-
173-
onUpdateStatus?.("Broadcasting transaction...");
174-
const txResponse = await broadcastIbcTx.mutateAsync({
175-
client,
176-
tx: signedMessage,
177-
});
178-
179-
const tx = createTransferDataFromIbc(
180-
txResponse,
181-
rpc,
182-
selectedAsset.asset,
183-
chainId,
184-
getIbcTransferStage(!!shielded)
185-
);
186-
dispatchPendingTxNotification(tx);
187-
setTxHash(tx.hash);
188-
return tx;
181+
const transferMsg = createIbcTransferMessage(
182+
sanitizeChannel(sourceChannel!),
183+
sanitizeAddress(sourceAddress),
184+
sanitizeAddress(maspCompatibleReceiver),
185+
baseAmount,
186+
selectedAsset.originalAddress,
187+
maspCompatibleMemo
188+
);
189+
190+
// In case the first estimate has failed for some reason, we try to refetch
191+
const gasConfig = await (async () => {
192+
if (!gasConfigQuery.data) {
193+
onUpdateStatus?.("Estimating required gas...");
194+
return (await gasConfigQuery.refetch()).data;
195+
}
196+
return gasConfigQuery.data;
197+
})();
198+
invariant(gasConfig, "Error: Failed to estimate gas usage");
199+
200+
onUpdateStatus?.("Waiting for signature...");
201+
const signedMessage = await getSignedMessage(
202+
stargateClient,
203+
transferMsg,
204+
gasConfig
205+
);
206+
207+
onUpdateStatus?.("Broadcasting transaction...");
208+
const txResponse = await broadcastIbcTx.mutateAsync({
209+
client: stargateClient,
210+
tx: signedMessage,
189211
});
212+
213+
const tx = createTransferDataFromIbc(
214+
txResponse,
215+
rpcUrl || "",
216+
selectedAsset.asset,
217+
chainId,
218+
getIbcTransferStage(!!shielded)
219+
);
220+
dispatchPendingTxNotification(tx);
221+
setTxHash(tx.hash);
222+
return tx;
190223
} catch (err) {
191224
dispatchErrorTxNotification(err);
192225
throw err;
@@ -197,7 +230,7 @@ export const useIbcTransaction = ({
197230

198231
return {
199232
transferToNamada,
200-
gasConfig,
233+
gasConfig: gasConfigQuery,
201234
transferStatus: broadcastIbcTx.status,
202235
};
203236
};

0 commit comments

Comments
 (0)