Skip to content

Commit

Permalink
feat: update swapAndBridge function (#116)
Browse files Browse the repository at this point in the history
* feat: update swapAndBridge function

Signed-off-by: zemyblue <zemyblue@gmail.com>

* chore: update changelog

Signed-off-by: zemyblue <zemyblue@gmail.com>

---------

Signed-off-by: zemyblue <zemyblue@gmail.com>
  • Loading branch information
zemyblue committed Jun 17, 2024
1 parent e088f7f commit ca0afa3
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 22 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

### Changed
* [\#116](https://github.com/Finschia/finschia-js/pull/116) Update swapAndBridge function

### Deprecated

Expand Down
53 changes: 53 additions & 0 deletions packages/finschia/src/finschiaclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -580,4 +580,57 @@ export class FinschiaClient {
};
});
}

/**
* poll and get the tx result of txHash.
*/
public async pollForTxResult(
txId: Uint8Array,
timeoutMs = 60_000,
pollIntervalMs = 3_000,
): Promise<DeliverTxResponse> {
let timedOut = false;
const txPollTimeout = setTimeout(() => {
timedOut = true;
}, timeoutMs);
const pollForTx = async (txId: string): Promise<DeliverTxResponse> => {
if (timedOut) {
throw new TimeoutError(
`Transaction with ID ${txId} was submitted but was not yet found on the chain. You might want to check later. There was a wait of ${
timeoutMs / 10000
} seconds.`,
txId,
);
}
await sleep(pollIntervalMs);
const result = await this.getTx(txId);
return result === null
? pollForTx(txId)
: {
code: result.code,
height: result.height,
txIndex: result.txIndex,
events: result.events,
rawLog: result.rawLog,
transactionHash: txId,
msgResponses: result.msgResponses,
gasUsed: result.gasUsed,
gasWanted: result.gasWanted,
};
};

const transactionId = toHex(txId).toUpperCase();
return new Promise((resolve, reject) =>
pollForTx(transactionId).then(
(value) => {
clearTimeout(txPollTimeout);
resolve(value);
},
(error) => {
clearTimeout(txPollTimeout);
reject(error);
},
),
);
}
}
53 changes: 37 additions & 16 deletions packages/finschia/src/signingfinschiaclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@ import {
InstantiateResult,
JsonObject,
MigrateResult,
MsgInstantiateContract2EncodeObject,
MsgStoreCodeEncodeObject,
UploadResult,
} from "@cosmjs/cosmwasm-stargate";
import {
MsgClearAdminEncodeObject,
MsgExecuteContractEncodeObject,
MsgInstantiateContract2EncodeObject,
MsgInstantiateContractEncodeObject,
MsgMigrateContractEncodeObject,
MsgStoreCodeEncodeObject,
MsgUpdateAdminEncodeObject,
UploadResult,
} from "@cosmjs/cosmwasm-stargate";
import { sha256 } from "@cosmjs/crypto";
import { fromBase64, toHex, toUtf8 } from "@cosmjs/encoding";
Expand Down Expand Up @@ -62,11 +60,11 @@ import {
MsgClearAdmin,
MsgExecuteContract,
MsgInstantiateContract,
MsgInstantiateContract2,
MsgMigrateContract,
MsgStoreCode,
MsgUpdateAdmin,
} from "cosmjs-types/cosmwasm/wasm/v1/tx";
import { MsgInstantiateContract2 } from "cosmjs-types/cosmwasm/wasm/v1/tx";
import { MsgStoreCode } from "cosmjs-types/cosmwasm/wasm/v1/tx";
import { AccessConfig } from "cosmjs-types/cosmwasm/wasm/v1/types";
import { MsgTransfer } from "cosmjs-types/ibc/applications/transfer/v1/tx";
import { Height } from "cosmjs-types/ibc/core/client/v1/client";
Expand Down Expand Up @@ -100,6 +98,15 @@ export interface UploadAndInstantiateResult {
readonly gasUsed: number;
}

export interface SwapAndBridgeOptions {
readonly gas?: StdFee | number;
readonly memo?: string;
/** The balance to swap and bridge. */
readonly amount?: string | number;
/** Set broadcast mode. If true, BROADCAST_ASYNC_MODE, if false, BROADCAST_SYNC_MODE */
readonly asyncBroadcast?: boolean;
}

function createDeliverTxResponseErrorMessage(result: DeliverTxResponse): string {
return `Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`;
}
Expand Down Expand Up @@ -560,9 +567,8 @@ export class SigningFinschiaClient extends FinschiaClient {
public async swapAndBridge(
senderAddress: string,
toAddress: string,
gas: StdFee | number = 150_000,
memo = "",
): Promise<DeliverTxResponse> {
{ gas = 150_000, memo = "", amount = undefined, asyncBroadcast = false }: SwapAndBridgeOptions = {},
): Promise<Uint8Array | DeliverTxResponse> {
// query swap rate
const swapsRes = await this.forceGetQueryClientForV4().fswap.swaps();
if (swapsRes.swaps.length == 0) {
Expand All @@ -587,13 +593,24 @@ export class SigningFinschiaClient extends FinschiaClient {
amount: coins(gasLimit * 0.015, swap.fromDenom),
gas: gasLimit.toString(),
};
} else {
} else if (gas !== undefined) {
usedFee = gas;
} else {
throw new Error("No gas value provided.");
}

let swapAmount: bigint;
if (amount === undefined) {
swapAmount = BigInt(balance.amount) - BigInt(usedFee.amount[0].amount);
} else {
if (BigInt(amount) + BigInt(usedFee.amount[0].amount) > BigInt(balance.amount)) {
throw new Error("no enough balance");
}
swapAmount = BigInt(amount);
}

// calculate swapRate and total swapAmount, amount to transfer by bridge
const swapRate = Decimal.fromAtomics(swap.swapRate, 18);
const swapAmount = BigInt(balance.amount) - BigInt(usedFee.amount[0].amount);
const swappedAmount = swapAmount * BigInt(swapRate.toString());

const msgSwap: MsgSwapEncodeObject = {
Expand All @@ -613,12 +630,16 @@ export class SigningFinschiaClient extends FinschiaClient {
}),
};

const result = await this.signAndBroadcast(senderAddress, [msgSwap, msgBridge], usedFee, memo);
if (isDeliverTxFailure(result)) {
throw new Error(createDeliverTxResponseErrorMessage(result));
const txRaw = await this.sign(senderAddress, [msgSwap, msgBridge], usedFee, memo);
const txBytes = TxRaw.encode(txRaw).finish();

// If BROADCAST_ASYNC_MODE
if (asyncBroadcast) {
const broadcasted = await this.forceGetTmClient().broadcastTxAsync({ tx: txBytes });
return broadcasted.hash;
}

return result;
return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs);
}

/**
Expand Down
56 changes: 51 additions & 5 deletions packages/finschia/src/signingfinschiaclientv4.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { coins, Secp256k1HdWallet } from "@cosmjs/amino";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
import { assertIsDeliverTxSuccess } from "@cosmjs/stargate";
import { assertIsDeliverTxSuccess, DeliverTxResponse } from "@cosmjs/stargate";
import { assertDefined } from "@cosmjs/utils";

import { makeLinkPath } from "./paths";
import { SigningFinschiaClient } from "./signingfinschiaclient";
Expand All @@ -13,7 +14,7 @@ describe("SigningFinschiaClient", () => {
};

describe("swapAndBridge", () => {
it("works", async () => {
it("works (Async broadcast)", async () => {
pendingWithoutSimapp();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, {
hdPaths: [makeLinkPath(0), makeLinkPath(200)],
Expand All @@ -29,10 +30,53 @@ describe("SigningFinschiaClient", () => {
const result = await client.sendTokens(addrs[0], addrs[1], coins(1000000, "cony"), defaultFee);
assertIsDeliverTxSuccess(result);
}
// swap & bridge partial balance
const toAddr = "0xf7bAc63fc7CEaCf0589F25454Ecf5C2ce904997c";
const result = await client.swapAndBridge(addrs[1], toAddr);
assertIsDeliverTxSuccess(result);
const result = await client.swapAndBridge(addrs[1], toAddr, { amount: 500_000 });
assertIsDeliverTxSuccess(result as DeliverTxResponse);

const balanceCony = await client.getBalance(addrs[1], "cony");
expect(balanceCony.amount).toEqual("497750");
const balance = await client.getBalance(addrs[1], "pdt");
expect(balance.amount).toEqual("0");

// swap & bridge all balance
const result2 = await client.swapAndBridge(addrs[1], toAddr);
assertIsDeliverTxSuccess(result2 as DeliverTxResponse);

const balanceCony2 = await client.getBalance(addrs[1], "cony");
expect(balanceCony2.amount).toEqual("0");
const balance2 = await client.getBalance(addrs[1], "pdt");
expect(balance2.amount).toEqual("0");
});

it("works (Sync broadcast)", async () => {
pendingWithoutSimapp();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, {
hdPaths: [makeLinkPath(0), makeLinkPath(200)],
prefix: simapp.prefix,
});
const options = { ...defaultSigningClientOptions, prefix: simapp.prefix };
const client = await SigningFinschiaClient.connectWithSigner(simapp.tendermintUrl, wallet, options);

const addrs = (await wallet.getAccounts()).map((item) => item.address);

// bank send for test
{
const result = await client.sendTokens(addrs[0], addrs[1], coins(1000000, "cony"), defaultFee);
assertIsDeliverTxSuccess(result);
}
const toAddr = "0xf7bAc63fc7CEaCf0589F25454Ecf5C2ce904997c";
const result = await client.swapAndBridge(addrs[1], toAddr, { asyncBroadcast: true });
assertDefined(result);

{
const txRes = await client.pollForTxResult(result as Uint8Array);
assertIsDeliverTxSuccess(txRes);
}

const balanceCony = await client.getBalance(addrs[1], "cony");
expect(balanceCony.amount).toEqual("0");
const balance = await client.getBalance(addrs[1], "pdt");
expect(balance.amount).toEqual("0");
});
Expand All @@ -55,8 +99,10 @@ describe("SigningFinschiaClient", () => {
}
const toAddr = "0xf7bAc63fc7CEaCf0589F25454Ecf5C2ce904997c";
const result = await client.swapAndBridge(addrs[1], toAddr);
assertIsDeliverTxSuccess(result);
assertIsDeliverTxSuccess(result as DeliverTxResponse);

const balanceCony = await client.getBalance(addrs[1], "cony");
expect(balanceCony.amount).toEqual("0");
const balance = await client.getBalance(addrs[1], "pdt");
expect(balance.amount).toEqual("0");
});
Expand Down
1 change: 0 additions & 1 deletion packages/finschia/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { _instantiate2AddressIntermediate } from "@cosmjs/cosmwasm-stargate";
import { Decimal, Uint64 } from "@cosmjs/math";
import { Duration } from "cosmjs-types/google/protobuf/duration";
import Long from "long";
Expand Down

0 comments on commit ca0afa3

Please sign in to comment.