Skip to content

Commit

Permalink
Merge pull request #3528 from osmosis-labs/stage
Browse files Browse the repository at this point in the history
Publish Stage
  • Loading branch information
jonator authored Jul 17, 2024
2 parents 38074cf + 09f2224 commit 34cbd2b
Show file tree
Hide file tree
Showing 17 changed files with 285 additions and 121 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/monitoring-e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
echo "matrix={\"include\":[{ \"base-url\":\"https://app.osmosis.zone\", \"server-url\":\"https://sqs.osmosis.zone\", \"env\": \"production\", \"timeseries-url\":\"https://stage-proxy-data-api.osmosis-labs.workers.dev\"}, { \"base-url\":\"https://stage.osmosis.zone\", \"server-url\":\"https://sqs.stage.osmosis.zone\", \"env\": \"staging\", \"timeseries-url\":\"https://stage-proxy-data-api.osmosis-labs.workers.dev\"}]}" >> "$GITHUB_OUTPUT"
frontend-e2e-tests:
name: production
name: production-swap
runs-on: macos-latest
environment:
name: prod_swap_test
Expand Down
19 changes: 17 additions & 2 deletions packages/bridge/src/axelar/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export class AxelarBridgeProvider implements BridgeProvider {
) {
throw new BridgeQuoteError({
bridgeId: AxelarBridgeProvider.ID,
errorType: "UnsupportedQuoteError",
errorType: "InsufficientAmountError",
message: `Negative output amount ${new IntPretty(
expectedOutputAmount
).trim(true)} for asset in: ${new IntPretty(fromAmount).trim(
Expand Down Expand Up @@ -345,7 +345,7 @@ export class AxelarBridgeProvider implements BridgeProvider {
return foundVariants.assets;
} catch (e) {
// Avoid returning options if there's an unexpected error, such as the provider being down
if (process.env.NODE_ENV === "development") {
if (process.env.NODE_ENV !== "production") {
console.error(
AxelarBridgeProvider.ID,
"failed to get supported assets:",
Expand Down Expand Up @@ -413,6 +413,21 @@ export class AxelarBridgeProvider implements BridgeProvider {
],
},
bech32Address: params.fromAddress,
}).catch((e) => {
if (
e instanceof Error &&
e.message.includes(
"No fee tokens found with sufficient balance on account"
)
) {
throw new BridgeQuoteError({
bridgeId: AxelarBridgeProvider.ID,
errorType: "InsufficientAmountError",
message: e.message,
});
}

throw e;
});

const gasFee = txSimulation.amount[0];
Expand Down
17 changes: 16 additions & 1 deletion packages/bridge/src/ibc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,21 @@ export class IbcBridgeProvider implements BridgeProvider {
],
},
bech32Address: params.fromAddress,
}).catch((e) => {
if (
e instanceof Error &&
e.message.includes(
"No fee tokens found with sufficient balance on account"
)
) {
throw new BridgeQuoteError({
bridgeId: IbcBridgeProvider.ID,
errorType: "InsufficientAmountError",
message: e.message,
});
}

throw e;
});
const gasFee = txSimulation.amount[0];
const gasAsset = this.getGasAsset(fromChainId, gasFee.denom);
Expand Down Expand Up @@ -124,7 +139,7 @@ export class IbcBridgeProvider implements BridgeProvider {
];
} catch (e) {
// Avoid returning options if there's an unexpected error, such as the provider being down
if (process.env.NODE_ENV === "development") {
if (process.env.NODE_ENV !== "production") {
console.error(
IbcBridgeProvider.ID,
"failed to get supported assets:",
Expand Down
79 changes: 69 additions & 10 deletions packages/bridge/src/skip/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,43 @@ export class SkipBridgeProvider implements BridgeProvider {
});
}

const route = await this.skipClient.route({
source_asset_denom: sourceAsset.denom,
source_asset_chain_id: fromChain.chainId.toString(),
dest_asset_denom: destinationAsset.denom,
dest_asset_chain_id: toChain.chainId.toString(),
amount_in: fromAmount,
});
const route = await this.skipClient
.route({
source_asset_denom: sourceAsset.denom,
source_asset_chain_id: fromChain.chainId.toString(),
dest_asset_denom: destinationAsset.denom,
dest_asset_chain_id: toChain.chainId.toString(),
amount_in: fromAmount,
})
.catch((e) => {
if (e instanceof Error) {
const msg = e.message;
if (
msg.includes(
"Input amount is too low to cover"
// Could be Axelar or CCTP
)
) {
throw new BridgeQuoteError({
bridgeId: SkipBridgeProvider.ID,
errorType: "InsufficientAmountError",
message: msg,
});
}
if (
msg.includes(
"cannot transfer across cctp after route demands swap"
)
) {
throw new BridgeQuoteError({
bridgeId: SkipBridgeProvider.ID,
errorType: "NoQuotesError",
message: msg,
});
}
}
throw e;
});

const addressList = await this.getAddressList(
route.chain_ids,
Expand Down Expand Up @@ -234,11 +264,25 @@ export class SkipBridgeProvider implements BridgeProvider {
a.coinMinimalDenom.toLowerCase() === asset.address.toLowerCase()
);

for (const counterparty of assetListAsset?.counterparty ?? []) {
const counterparties = assetListAsset?.counterparty ?? [];
// since skip supports cosmos swap, we can include other asset list
// counterparties of the same variant
if (assetListAsset) {
const variantAssets = this.ctx.assetLists.flatMap(({ assets }) =>
assets.filter(
(asset) => asset.variantGroupKey === assetListAsset.variantGroupKey
)
);
counterparties.push(
...variantAssets.flatMap((asset) => asset.counterparty)
);
}

for (const counterparty of counterparties) {
// check if supported by skip
if (!("chainId" in counterparty)) continue;
if (
!assets[counterparty.chainId].assets.some(
!assets[counterparty.chainId]?.assets.some(
(a) =>
a.denom.toLowerCase() === counterparty.sourceDenom.toLowerCase()
)
Expand Down Expand Up @@ -334,7 +378,7 @@ export class SkipBridgeProvider implements BridgeProvider {
return foundVariants.assets;
} catch (e) {
// Avoid returning options if there's an unexpected error, such as the provider being down
if (process.env.NODE_ENV === "development") {
if (process.env.NODE_ENV !== "production") {
console.error(
SkipBridgeProvider.ID,
"failed to get supported assets:",
Expand Down Expand Up @@ -818,6 +862,21 @@ export class SkipBridgeProvider implements BridgeProvider {
],
},
bech32Address: params.fromAddress,
}).catch((e) => {
if (
e instanceof Error &&
e.message.includes(
"No fee tokens found with sufficient balance on account"
)
) {
throw new BridgeQuoteError({
bridgeId: SkipBridgeProvider.ID,
errorType: "InsufficientAmountError",
message: e.message,
});
}

throw e;
});

const gasFee = txSimulation.amount[0];
Expand Down
36 changes: 36 additions & 0 deletions packages/bridge/src/squid/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ApiClientError } from "@osmosis-labs/utils";
import { z } from "zod";

const SquidErrors = z.object({
errors: z.array(
z.object({
path: z.string().optional(),
errorType: z.string(),
message: z.string(),
})
),
});

type SquidErrors = z.infer<typeof SquidErrors>;

/**
* Squid returns error data in the form of an errors object containing an array of errors.
* This function returns the list of those errors, and sets the Error.message to a concatenation of those errors.
* @returns list of error messages
*/
export function getSquidErrors(error: ApiClientError): SquidErrors {
try {
const e = error as ApiClientError<SquidErrors>;
const squidError = SquidErrors.parse(e.data);
const msgs = squidError.errors.map(
({ message }, i) => `${i + 1}) ${message}`
);
e.message = msgs.join(", ");
return squidError;
} catch (e) {
if (e instanceof z.ZodError) {
throw new Error("Squid error validation failed:" + e.errors.join(", "));
}
throw new Error("Squid errors: An unexpected error occurred");
}
}
23 changes: 22 additions & 1 deletion packages/bridge/src/squid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
} from "../interface";
import { cosmosMsgOpts, cosmwasmMsgOpts } from "../msg";
import { BridgeAssetMap } from "../utils";
import { getSquidErrors } from "./error";

const IbcTransferType = "/ibc.applications.transfer.v1.MsgTransfer";
const WasmTransferType = "/cosmwasm.wasm.v1.MsgExecuteContract";
Expand Down Expand Up @@ -119,6 +120,26 @@ export class SquidBridgeProvider implements BridgeProvider {
headers: {
"x-integrator-id": this.integratorId,
},
}).catch((e) => {
if (e instanceof ApiClientError) {
const errMsgs = getSquidErrors(e);

if (
errMsgs.errors.some(({ message }) =>
message.includes(
"The input amount is not high enough to cover the bridge fee"
)
)
) {
throw new BridgeQuoteError({
bridgeId: SquidBridgeProvider.ID,
errorType: "InsufficientAmountError",
message: e.message,
});
}
}

throw e;
});

const {
Expand Down Expand Up @@ -349,7 +370,7 @@ export class SquidBridgeProvider implements BridgeProvider {
return foundVariants.assets;
} catch (e) {
// Avoid returning options if there's an unexpected error, such as the provider being down
if (process.env.NODE_ENV === "development") {
if (process.env.NODE_ENV !== "production") {
console.error(
SquidBridgeProvider.ID,
"failed to get supported assets:",
Expand Down
12 changes: 6 additions & 6 deletions packages/bridge/src/wormhole/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,20 +63,20 @@ export class WormholeBridgeProvider implements BridgeProvider {
fromAsset,
toAsset,
}: GetBridgeExternalUrlParams): Promise<BridgeExternalUrl | undefined> {
// For now we use in-osmosis
const url = new URL("https://app.osmosis.zone/wormhole");
// For now we use Portal Bridge
const url = new URL("https://portalbridge.com/");

url.searchParams.set(
"from",
"sourceChain",
fromChain.chainName?.toLowerCase() ?? fromChain.chainId.toString()
);
url.searchParams.set(
"to",
"targetChain",
toChain.chainName?.toLowerCase() ?? toChain.chainId.toString()
);
url.searchParams.set(
"token",
fromChain.chainType === "solana" ? fromAsset.denom : toAsset.denom
"asset",
fromChain.chainType === "solana" ? fromAsset.address : toAsset.address
);

return {
Expand Down
54 changes: 47 additions & 7 deletions packages/web/components/bridge/amount-and-review-screen.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CoinPretty } from "@keplr-wallet/unit";
import type { Bridge } from "@osmosis-labs/bridge";
import { isNil, noop } from "@osmosis-labs/utils";
import { observer } from "mobx-react-lite";
import { useMemo, useState } from "react";
Expand All @@ -14,7 +15,10 @@ import { AmountScreen } from "./amount-screen";
import { ImmersiveBridgeScreen } from "./immersive-bridge";
import { ReviewScreen } from "./review-screen";
import { QuotableBridge, useBridgeQuotes } from "./use-bridge-quotes";
import { SupportedAsset } from "./use-bridges-supported-assets";
import {
SupportedAsset,
useBridgesSupportedAssets,
} from "./use-bridges-supported-assets";

export type SupportedAssetWithAmount = SupportedAsset & { amount: CoinPretty };

Expand Down Expand Up @@ -78,17 +82,51 @@ export const AmountAndReviewScreen = observer(
? evmConnector?.icon
: toChainCosmosAccount?.walletInfo.logo;

const { data: assetsInOsmosis } =
api.edge.assets.getCanonicalAssetWithVariants.useQuery(
{
findMinDenomOrSymbol: selectedAssetDenom ?? "",
},
{
enabled: !isNil(selectedAssetDenom),
cacheTime: 10 * 60 * 1000, // 10 minutes
staleTime: 10 * 60 * 1000, // 10 minutes
}
);

const supportedAssets = useBridgesSupportedAssets({
assets: assetsInOsmosis,
chain: {
chainId: accountStore.osmosisChainId,
chainType: "cosmos",
},
});
const { supportedAssetsByChainId: counterpartySupportedAssetsByChainId } =
supportedAssets;

/** Filter for bridges that currently support quoting. */
const quoteBridges = useMemo(() => {
const assetSupportedBridges =
(direction === "deposit"
? fromAsset?.supportedVariants[toAsset?.address ?? ""]
: toAsset?.supportedVariants[fromAsset?.address ?? ""]) ?? [];
const assetSupportedBridges = new Set<Bridge>();

if (direction === "deposit" && fromAsset) {
Object.values(fromAsset.supportedVariants)
.flat()
.forEach((provider) => assetSupportedBridges.add(provider));
} else if (direction === "withdraw" && fromAsset && toAsset) {
// withdraw
counterpartySupportedAssetsByChainId[toAsset.chainId].forEach(
(asset) => {
asset.supportedVariants[fromAsset.address]?.forEach((provider) => {
assetSupportedBridges.add(provider);
});
}
);
}

return assetSupportedBridges.filter(
return Array.from(assetSupportedBridges).filter(
(bridge) => bridge !== "Nomic" && bridge !== "Wormhole"
) as QuotableBridge[];
}, [direction, fromAsset, toAsset]);
}, [direction, fromAsset, toAsset, counterpartySupportedAssetsByChainId]);

const quote = useBridgeQuotes({
toAddress,
Expand Down Expand Up @@ -141,6 +179,8 @@ export const AmountAndReviewScreen = observer(
<AmountScreen
direction={direction}
selectedDenom={selectedAssetDenom!}
assetsInOsmosis={assetsInOsmosis}
bridgesSupportedAssets={supportedAssets}
fromChain={fromChain}
setFromChain={setFromChain}
toChain={toChain}
Expand Down
Loading

0 comments on commit 34cbd2b

Please sign in to comment.