Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/stage' into jose/fe-1248-update-…
Browse files Browse the repository at this point in the history
…nomic-relayers-array
  • Loading branch information
JoseRFelix committed Nov 27, 2024
2 parents 7e79824 + 8427eb3 commit 49e841d
Show file tree
Hide file tree
Showing 53 changed files with 1,443 additions and 1,170 deletions.
1 change: 1 addition & 0 deletions packages/bridge/src/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ const txSnapshotSchema = z.object({
})
),
estimatedArrivalUnix: z.number(),
nomicCheckpointIndex: z.number().optional(),
});

export type TxSnapshot = z.infer<typeof txSnapshotSchema>;
Expand Down
68 changes: 42 additions & 26 deletions packages/bridge/src/nomic/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ import {
deriveCosmosAddress,
getAllBtcMinimalDenom,
getnBTCMinimalDenom,
getNomicRelayerUrl,
isCosmosAddressValid,
timeout,
} from "@osmosis-labs/utils";
import {
BaseDepositOptions,
buildDestination,
Checkpoint,
DepositInfo,
generateDepositAddressIbc,
getCheckpoint,
getPendingDeposits,
IbcDepositOptions,
} from "nomic-bitcoin";
Expand All @@ -43,9 +46,10 @@ import {
} from "../interface";
import { getGasAsset } from "../utils/gas";
import { getLaunchDarklyFlagValue } from "../utils/launchdarkly";
import { NomicProviderId } from "./utils";

export class NomicBridgeProvider implements BridgeProvider {
static readonly ID = "Nomic";
static readonly ID = NomicProviderId;
readonly providerName = NomicBridgeProvider.ID;

readonly relayers: string[];
Expand All @@ -54,19 +58,11 @@ export class NomicBridgeProvider implements BridgeProvider {
protected protoRegistry: Registry | null = null;

constructor(protected readonly ctx: BridgeProviderContext) {
this.relayers = getNomicRelayerUrl({ env: this.ctx.env });
this.allBtcMinimalDenom = getAllBtcMinimalDenom({ env: this.ctx.env });
this.nBTCMinimalDenom = getnBTCMinimalDenom({
env: this.ctx.env,
});

this.relayers =
this.ctx.env === "testnet"
? ["https://testnet-relayer.nomic.io:8443"]
: [
"https://btc-relayer.nomic.io",
"https://btc-relay.nomic-main.ccvalidators.com",
"https://nomic-relayer.forbole.com",
];
}

async getQuote(params: GetBridgeQuoteParams): Promise<BridgeQuote> {
Expand Down Expand Up @@ -186,16 +182,29 @@ export class NomicBridgeProvider implements BridgeProvider {
}),
};

const [ibcTxMessages, estimatedTime] = await Promise.all([
ibcProvider.getTxMessages({
...transactionDataParams,
memo: destMemo,
}),
ibcProvider.estimateTransferTime(
transactionDataParams.fromChain.chainId.toString(),
transactionDataParams.toChain.chainId.toString()
),
]);
const [ibcTxMessages, ibcEstimatedTimeSeconds, nomicCheckpoint] =
await Promise.all([
ibcProvider.getTxMessages({
...transactionDataParams,
memo: destMemo,
}),
ibcProvider.estimateTransferTime(
transactionDataParams.fromChain.chainId.toString(),
transactionDataParams.toChain.chainId.toString()
),
getCheckpoint({
relayers: this.relayers,
bitcoinNetwork: this.ctx.env === "mainnet" ? "bitcoin" : "testnet",
}),
]);

// 4 hours
const nomicEstimatedTimeSeconds = 4 * 60 * 60;

const transferFeeInSats = Math.ceil(
(nomicCheckpoint as Checkpoint & { minerFee: number }).minerFee * 64 + 546
);
const transferFeeInMicroSats = transferFeeInSats * 1e6;

const msgs = [...swapMessages, ...ibcTxMessages];

Expand Down Expand Up @@ -242,22 +251,29 @@ export class NomicBridgeProvider implements BridgeProvider {
...params.fromAsset,
},
expectedOutput: {
amount: !!swapRoute
? swapRoute.amount.toCoin().amount
: params.fromAmount,
amount: (!!swapRoute
? new Dec(swapRoute.amount.toCoin().amount)
: new Dec(params.fromAmount)
)
// Use micro sats because the amount will always be nomic btc which has 14 decimals (micro sats)
.sub(new Dec(transferFeeInMicroSats))
.toString(),
...nomicBridgeAsset,
denom: "BTC",
priceImpact: swapRoute?.priceImpactTokenOut?.toDec().toString() ?? "0",
},
fromChain: params.fromChain,
toChain: params.toChain,
// currently subsidized by relayers, but could be paid by user in future by charging the user the gas cost of
transferFee: {
...params.fromAsset,
denom: "BTC",
chainId: params.fromChain.chainId,
amount: "0",
amount: (params.fromAsset.decimals === 14
? transferFeeInMicroSats
: transferFeeInSats
).toString(),
},
estimatedTime,
estimatedTime: ibcEstimatedTimeSeconds + nomicEstimatedTimeSeconds,
estimatedGasFee: gasFee
? {
address: gasAsset?.address ?? gasFee.denom,
Expand Down
97 changes: 97 additions & 0 deletions packages/bridge/src/nomic/transfer-status.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { Chain } from "@osmosis-labs/types";
import { getNomicRelayerUrl, isNil, poll } from "@osmosis-labs/utils";
import { getCheckpoint } from "nomic-bitcoin";

import type {
BridgeEnvironment,
BridgeTransferStatus,
TransferStatusProvider,
TransferStatusReceiver,
TxSnapshot,
} from "../interface";
import { NomicProviderId } from "./utils";

export class NomicTransferStatusProvider implements TransferStatusProvider {
readonly providerId = NomicProviderId;
readonly sourceDisplayName = "Nomic Bridge";
public statusReceiverDelegate?: TransferStatusReceiver;

constructor(
protected readonly chainList: Chain[],
readonly env: BridgeEnvironment
) {}

/** Request to start polling a new transaction. */
async trackTxStatus(snapshot: TxSnapshot): Promise<void> {
const { sendTxHash } = snapshot;

if (!snapshot.nomicCheckpointIndex) {
throw new Error("Nomic checkpoint index is required. Skipping tracking.");
}

await poll({
fn: async () => {
const checkpoint = await getCheckpoint(
{
relayers: getNomicRelayerUrl({ env: this.env }),
bitcoinNetwork: this.env === "mainnet" ? "bitcoin" : "testnet",
},
snapshot.nomicCheckpointIndex!
);

if (isNil(checkpoint.txid)) {
return;
}

return {
id: snapshot.sendTxHash,
status: "success",
} as BridgeTransferStatus;
},
validate: (incomingStatus) => incomingStatus !== undefined,
interval: 30_000,
maxAttempts: undefined, // unlimited attempts while tab is open or until success/fail
})
.then((s) => {
if (s) this.receiveConclusiveStatus(sendTxHash, s);
})
.catch((e) => console.error(`Polling Nomic has failed`, e));
}

receiveConclusiveStatus(
sendTxHash: string,
txStatus: BridgeTransferStatus | undefined
): void {
if (txStatus && txStatus.id) {
const { status, reason } = txStatus;
this.statusReceiverDelegate?.receiveNewTxStatus(
sendTxHash,
status,
reason
);
} else {
console.error(
"Nomic transfer finished poll but neither succeeded or failed"
);
}
}

makeExplorerUrl(snapshot: TxSnapshot): string {
const {
sendTxHash,
fromChain: { chainId: fromChainId },
} = snapshot;

const chain = this.chainList.find(
(chain) => chain.chain_id === fromChainId
);

if (!chain) throw new Error("Chain not found: " + fromChainId);
if (chain.explorers.length === 0) {
// attempt to link to mintscan since this is an IBC transfer
return `https://www.mintscan.io/${chain.chain_name}/txs/${sendTxHash}`;
}

return chain.explorers[0].tx_page.replace("{txHash}", sendTxHash);
}
}
1 change: 1 addition & 0 deletions packages/bridge/src/nomic/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const NomicProviderId = "Nomic";
15 changes: 13 additions & 2 deletions packages/stores/src/account/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -515,11 +515,12 @@ export class AccountStore<Injects extends Record<string, any>[] = []> {
fee?: StdFee,
signOptions?: SignOptions,
onTxEvents?:
| ((tx: DeliverTxResponse) => void)
| ((tx: DeliverTxResponse) => void | Promise<void>)
| {
onBroadcastFailed?: (e?: Error) => void;
onBroadcasted?: (txHash: Uint8Array) => void;
onFulfill?: (tx: DeliverTxResponse) => void;
onSign?: () => Promise<void> | void;
}
) {
runInAction(() => {
Expand Down Expand Up @@ -547,13 +548,15 @@ export class AccountStore<Injects extends Record<string, any>[] = []> {

let onBroadcasted: ((txHash: Uint8Array) => void) | undefined;
let onFulfill: ((tx: DeliverTxResponse) => void) | undefined;
let onSign: (() => Promise<void> | void) | undefined;

if (onTxEvents) {
if (typeof onTxEvents === "function") {
onFulfill = onTxEvents;
} else {
onBroadcasted = onTxEvents?.onBroadcasted;
onFulfill = onTxEvents?.onFulfill;
onSign = onTxEvents?.onSign;
}
}

Expand Down Expand Up @@ -598,6 +601,14 @@ export class AccountStore<Injects extends Record<string, any>[] = []> {
const { TxRaw } = await import("cosmjs-types/cosmos/tx/v1beta1/tx");
const encodedTx = TxRaw.encode(txRaw).finish();

if (this.options.preTxEvents?.onSign) {
await this.options.preTxEvents.onSign();
}

if (onSign) {
await onSign();
}

const restEndpoint = getEndpointString(
await wallet.getRestEndpoint(true)
);
Expand Down Expand Up @@ -709,7 +720,7 @@ export class AccountStore<Injects extends Record<string, any>[] = []> {
}

if (onFulfill) {
onFulfill(tx);
await onFulfill(tx);
}
} catch (e) {
const error = e as Error | AccountStoreNoBroadcastErrorEvent;
Expand Down
Loading

0 comments on commit 49e841d

Please sign in to comment.