@shapeshiftoss/swapper 8.3.1 → 8.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/api.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AssetId, ChainId } from '@shapeshiftoss/caip';
2
- import { avalanche, ethereum } from '@shapeshiftoss/chain-adapters';
2
+ import { avalanche, cosmos, ethereum, osmosis } from '@shapeshiftoss/chain-adapters';
3
3
  import { HDWallet } from '@shapeshiftoss/hdwallet-core';
4
4
  import { Asset, BIP44Params, ChainSpecific, KnownChainIds, UtxoAccountType } from '@shapeshiftoss/types';
5
5
  export declare const SwapError: {
@@ -67,17 +67,22 @@ declare type CommonTradeInput = {
67
67
  receiveAddress: string;
68
68
  };
69
69
  export declare type EvmSupportedChainIds = KnownChainIds.EthereumMainnet | KnownChainIds.AvalancheMainnet;
70
+ export declare type CosmosSdkSupportedChainIds = KnownChainIds.CosmosMainnet | KnownChainIds.OsmosisMainnet;
70
71
  export declare type EvmSupportedChainAdapters = ethereum.ChainAdapter | avalanche.ChainAdapter;
72
+ export declare type CosmosSdkSupportedChainAdapters = cosmos.ChainAdapter | osmosis.ChainAdapter;
71
73
  export declare type GetEvmTradeQuoteInput = CommonTradeInput & {
72
74
  chainId: EvmSupportedChainIds;
73
75
  };
76
+ export declare type GetCosmosSdkTradeQuoteInput = CommonTradeInput & {
77
+ chainId: CosmosSdkSupportedChainIds;
78
+ };
74
79
  declare type GetBtcTradeQuoteInput = CommonTradeInput & {
75
80
  chainId: KnownChainIds.BitcoinMainnet;
76
81
  accountType: UtxoAccountType;
77
82
  bip44Params: BIP44Params;
78
83
  wallet: HDWallet;
79
84
  };
80
- export declare type GetTradeQuoteInput = GetBtcTradeQuoteInput | GetEvmTradeQuoteInput;
85
+ export declare type GetTradeQuoteInput = GetBtcTradeQuoteInput | GetEvmTradeQuoteInput | GetCosmosSdkTradeQuoteInput;
81
86
  export declare type BuildTradeInput = GetTradeQuoteInput & {
82
87
  buyAssetAccountNumber: number;
83
88
  slippage?: string;
@@ -131,6 +136,7 @@ export declare enum SwapperType {
131
136
  ZrxEthereum = "0xEthereum",
132
137
  ZrxAvalanche = "0xAvalanche",
133
138
  Thorchain = "Thorchain",
139
+ Osmosis = "Osmosis",
134
140
  CowSwap = "CowSwap",
135
141
  Test = "Test"
136
142
  }
package/dist/api.js CHANGED
@@ -9,6 +9,7 @@ var SwapperType;
9
9
  SwapperType["ZrxEthereum"] = "0xEthereum";
10
10
  SwapperType["ZrxAvalanche"] = "0xAvalanche";
11
11
  SwapperType["Thorchain"] = "Thorchain";
12
+ SwapperType["Osmosis"] = "Osmosis";
12
13
  SwapperType["CowSwap"] = "CowSwap";
13
14
  SwapperType["Test"] = "Test";
14
15
  })(SwapperType = exports.SwapperType || (exports.SwapperType = {}));
@@ -1,4 +1,5 @@
1
1
  export * from './zrx/ZrxSwapper';
2
2
  export * from './thorchain/ThorchainSwapper';
3
3
  export * from './test/TestSwapper';
4
+ export * from './osmosis/OsmosisSwapper';
4
5
  export * from './cow/CowSwapper';
@@ -17,4 +17,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./zrx/ZrxSwapper"), exports);
18
18
  __exportStar(require("./thorchain/ThorchainSwapper"), exports);
19
19
  __exportStar(require("./test/TestSwapper"), exports);
20
+ __exportStar(require("./osmosis/OsmosisSwapper"), exports);
20
21
  __exportStar(require("./cow/CowSwapper"), exports);
@@ -0,0 +1,23 @@
1
+ import { AssetId, ChainId } from '@shapeshiftoss/caip';
2
+ import { Asset } from '@shapeshiftoss/types';
3
+ import { ApprovalNeededOutput, BuildTradeInput, BuyAssetBySellIdInput, ExecuteTradeInput, GetTradeQuoteInput, MinMaxOutput, Swapper, SwapperType, Trade, TradeQuote, TradeResult, TradeTxs } from '../../api';
4
+ import { OsmoSwapperDeps } from './utils/types';
5
+ export declare class OsmosisSwapper implements Swapper<ChainId> {
6
+ readonly name = "Osmosis";
7
+ supportAssets: string[];
8
+ deps: OsmoSwapperDeps;
9
+ getType(): SwapperType;
10
+ constructor(deps: OsmoSwapperDeps);
11
+ getTradeTxs(tradeResult: TradeResult): Promise<TradeTxs>;
12
+ getUsdRate(input: Pick<Asset, 'symbol' | 'assetId'>): Promise<string>;
13
+ getMinMax(input: {
14
+ sellAsset: Asset;
15
+ }): Promise<MinMaxOutput>;
16
+ approvalNeeded(): Promise<ApprovalNeededOutput>;
17
+ approveInfinite(): Promise<string>;
18
+ filterBuyAssetsBySellAssetId(args: BuyAssetBySellIdInput): string[];
19
+ filterAssetIdsBySellable(): AssetId[];
20
+ buildTrade(args: BuildTradeInput): Promise<Trade<ChainId>>;
21
+ getTradeQuote(input: GetTradeQuoteInput): Promise<TradeQuote<ChainId>>;
22
+ executeTrade({ trade, wallet }: ExecuteTradeInput<ChainId>): Promise<TradeResult>;
23
+ }
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OsmosisSwapper = void 0;
4
+ const caip_1 = require("@shapeshiftoss/caip");
5
+ const api_1 = require("../../api");
6
+ const bignumber_1 = require("../utils/bignumber");
7
+ const constants_1 = require("./utils/constants");
8
+ const helpers_1 = require("./utils/helpers");
9
+ class OsmosisSwapper {
10
+ constructor(deps) {
11
+ this.name = 'Osmosis';
12
+ this.deps = deps;
13
+ this.supportAssets = [caip_1.cosmosAssetId, caip_1.osmosisAssetId];
14
+ }
15
+ getType() {
16
+ return api_1.SwapperType.Osmosis;
17
+ }
18
+ async getTradeTxs(tradeResult) {
19
+ return {
20
+ sellTxid: tradeResult.tradeId,
21
+ buyTxid: tradeResult.tradeId
22
+ };
23
+ }
24
+ async getUsdRate(input) {
25
+ const { symbol } = input;
26
+ const sellAssetSymbol = symbol;
27
+ const buyAssetSymbol = 'USDC';
28
+ const sellAmount = '1';
29
+ const { rate: osmoRate } = await (0, helpers_1.getRateInfo)('OSMO', buyAssetSymbol, sellAmount, this.deps.osmoUrl);
30
+ if (sellAssetSymbol !== 'OSMO') {
31
+ const { rate } = await (0, helpers_1.getRateInfo)(sellAssetSymbol, 'OSMO', sellAmount, this.deps.osmoUrl);
32
+ return (0, bignumber_1.bnOrZero)(rate).times(osmoRate).toString();
33
+ }
34
+ return osmoRate;
35
+ }
36
+ async getMinMax(input) {
37
+ const { sellAsset } = input;
38
+ const usdRate = await this.getUsdRate({ ...sellAsset });
39
+ const minimum = (0, bignumber_1.bn)(1).dividedBy((0, bignumber_1.bnOrZero)(usdRate)).toString();
40
+ const maximum = constants_1.MAX_SWAPPER_SELL;
41
+ return {
42
+ minimum,
43
+ maximum
44
+ };
45
+ }
46
+ async approvalNeeded() {
47
+ return { approvalNeeded: false };
48
+ }
49
+ async approveInfinite() {
50
+ throw new api_1.SwapError('OsmosisSwapper: approveInfinite unimplemented', {
51
+ code: api_1.SwapErrorTypes.RESPONSE_ERROR
52
+ });
53
+ }
54
+ filterBuyAssetsBySellAssetId(args) {
55
+ const { sellAssetId } = args;
56
+ if (!this.supportAssets.includes(sellAssetId))
57
+ return [];
58
+ return this.supportAssets;
59
+ }
60
+ filterAssetIdsBySellable() {
61
+ return this.supportAssets;
62
+ }
63
+ async buildTrade(args) {
64
+ const { sellAsset, buyAsset, sellAmount, receiveAddress } = args;
65
+ if (!sellAmount) {
66
+ throw new api_1.SwapError('sellAmount is required', {
67
+ code: api_1.SwapErrorTypes.BUILD_TRADE_FAILED
68
+ });
69
+ }
70
+ const { tradeFee, rate, buyAmount } = await (0, helpers_1.getRateInfo)(sellAsset.symbol, buyAsset.symbol, sellAmount !== '0' ? sellAmount : '1', this.deps.osmoUrl);
71
+ //convert amount to base
72
+ const amountBaseSell = String((0, bignumber_1.bnOrZero)(sellAmount).dp(0));
73
+ const osmosisAdapter = this.deps.adapterManager.get(caip_1.osmosisChainId);
74
+ if (!osmosisAdapter)
75
+ throw new api_1.SwapError('Failed to get Osmosis adapter', {
76
+ code: api_1.SwapErrorTypes.BUILD_TRADE_FAILED
77
+ });
78
+ const feeData = await osmosisAdapter.getFeeData({});
79
+ const fee = feeData.average.txFee;
80
+ return {
81
+ buyAmount,
82
+ buyAsset,
83
+ feeData: { fee, tradeFee },
84
+ rate,
85
+ receiveAddress,
86
+ sellAmount: amountBaseSell,
87
+ sellAsset,
88
+ sellAssetAccountNumber: 0,
89
+ sources: [{ name: 'Osmosis', proportion: '100' }]
90
+ };
91
+ }
92
+ async getTradeQuote(input) {
93
+ const { sellAsset, buyAsset, sellAmount } = input;
94
+ if (!sellAmount) {
95
+ throw new api_1.SwapError('sellAmount is required', {
96
+ code: api_1.SwapErrorTypes.RESPONSE_ERROR
97
+ });
98
+ }
99
+ const { tradeFee, rate, buyAmount } = await (0, helpers_1.getRateInfo)(sellAsset.symbol, buyAsset.symbol, sellAmount !== '0' ? sellAmount : '1', this.deps.osmoUrl);
100
+ const { minimum, maximum } = await this.getMinMax(input);
101
+ const osmosisAdapter = this.deps.adapterManager.get(caip_1.osmosisChainId);
102
+ if (!osmosisAdapter)
103
+ throw new api_1.SwapError('Failed to get Osmosis adapter', {
104
+ code: api_1.SwapErrorTypes.TRADE_QUOTE_FAILED
105
+ });
106
+ const feeData = await osmosisAdapter.getFeeData({});
107
+ const fee = feeData.average.txFee;
108
+ return {
109
+ buyAsset,
110
+ feeData: { fee, tradeFee },
111
+ maximum,
112
+ minimum,
113
+ sellAssetAccountNumber: 0,
114
+ rate,
115
+ sellAsset,
116
+ sellAmount,
117
+ buyAmount,
118
+ sources: constants_1.DEFAULT_SOURCE,
119
+ allowanceContract: ''
120
+ };
121
+ }
122
+ async executeTrade({ trade, wallet }) {
123
+ const { sellAsset, buyAsset, sellAmount, sellAssetAccountNumber, receiveAddress } = trade;
124
+ const isFromOsmo = sellAsset.assetId === caip_1.osmosisAssetId;
125
+ const sellAssetDenom = helpers_1.symbolDenomMapping[sellAsset.symbol];
126
+ const buyAssetDenom = helpers_1.symbolDenomMapping[buyAsset.symbol];
127
+ let ibcSellAmount;
128
+ const osmosisAdapter = this.deps.adapterManager.get(caip_1.osmosisChainId);
129
+ const cosmosAdapter = this.deps.adapterManager.get(caip_1.cosmosChainId);
130
+ if (!cosmosAdapter || !osmosisAdapter) {
131
+ throw new api_1.SwapError('Failed to get adapters', {
132
+ code: api_1.SwapErrorTypes.EXECUTE_TRADE_FAILED
133
+ });
134
+ }
135
+ let sellAddress;
136
+ const feeData = await osmosisAdapter.getFeeData({});
137
+ const gas = feeData.average.chainSpecific.gasLimit;
138
+ if (!isFromOsmo) {
139
+ const sellBip44Params = cosmosAdapter.buildBIP44Params({
140
+ accountNumber: Number(sellAssetAccountNumber)
141
+ });
142
+ sellAddress = await cosmosAdapter.getAddress({ wallet, bip44Params: sellBip44Params });
143
+ if (!sellAddress)
144
+ throw new api_1.SwapError('Failed to get address', {
145
+ code: api_1.SwapErrorTypes.EXECUTE_TRADE_FAILED
146
+ });
147
+ const transfer = {
148
+ sender: sellAddress,
149
+ receiver: receiveAddress,
150
+ amount: sellAmount
151
+ };
152
+ const responseAccount = await cosmosAdapter.getAccount(sellAddress);
153
+ const accountNumber = responseAccount.chainSpecific.accountNumber || '0';
154
+ const sequence = responseAccount.chainSpecific.sequence || '0';
155
+ const { tradeId } = await (0, helpers_1.performIbcTransfer)(transfer, cosmosAdapter, wallet, this.deps.osmoUrl, 'uatom', constants_1.COSMO_OSMO_CHANNEL, '0', accountNumber, sequence, gas);
156
+ // wait till confirmed
157
+ const pollResult = await (0, helpers_1.pollForComplete)(tradeId, this.deps.cosmosUrl);
158
+ if (pollResult !== 'success')
159
+ throw new api_1.SwapError('ibc transfer failed', {
160
+ code: api_1.SwapErrorTypes.EXECUTE_TRADE_FAILED
161
+ });
162
+ ibcSellAmount = await (0, helpers_1.pollForAtomChannelBalance)(receiveAddress, this.deps.osmoUrl);
163
+ }
164
+ else {
165
+ const sellBip44Params = osmosisAdapter.buildBIP44Params({
166
+ accountNumber: Number(sellAssetAccountNumber)
167
+ });
168
+ sellAddress = await osmosisAdapter.getAddress({ wallet, bip44Params: sellBip44Params });
169
+ if (!sellAddress)
170
+ throw new api_1.SwapError('failed to get osmoAddress', {
171
+ code: api_1.SwapErrorTypes.EXECUTE_TRADE_FAILED
172
+ });
173
+ }
174
+ const osmoAddress = isFromOsmo ? sellAddress : receiveAddress;
175
+ const signTxInput = await (0, helpers_1.buildTradeTx)({
176
+ osmoAddress,
177
+ adapter: osmosisAdapter,
178
+ trade,
179
+ buyAssetDenom,
180
+ sellAssetDenom,
181
+ sellAmount: ibcSellAmount !== null && ibcSellAmount !== void 0 ? ibcSellAmount : sellAmount,
182
+ gas,
183
+ wallet
184
+ });
185
+ const signed = await osmosisAdapter.signTransaction(signTxInput);
186
+ const tradeId = await osmosisAdapter.broadcastTransaction(signed);
187
+ if (isFromOsmo) {
188
+ const pollResult = await (0, helpers_1.pollForComplete)(tradeId, this.deps.osmoUrl);
189
+ if (pollResult !== 'success')
190
+ throw new api_1.SwapError('osmo swap failed', {
191
+ code: api_1.SwapErrorTypes.EXECUTE_TRADE_FAILED
192
+ });
193
+ const amount = await (0, helpers_1.pollForAtomChannelBalance)(sellAddress, this.deps.osmoUrl);
194
+ const transfer = {
195
+ sender: sellAddress,
196
+ receiver: receiveAddress,
197
+ amount
198
+ };
199
+ const ibcResponseAccount = await osmosisAdapter.getAccount(sellAddress);
200
+ const ibcAccountNumber = ibcResponseAccount.chainSpecific.accountNumber || '0';
201
+ const ibcSequence = ibcResponseAccount.chainSpecific.sequence || '0';
202
+ await (0, helpers_1.performIbcTransfer)(transfer, osmosisAdapter, wallet, this.deps.cosmosUrl, buyAssetDenom, constants_1.OSMO_COSMO_CHANNEL, trade.feeData.fee, ibcAccountNumber, ibcSequence, gas);
203
+ }
204
+ return { tradeId: tradeId || 'error' };
205
+ }
206
+ }
207
+ exports.OsmosisSwapper = OsmosisSwapper;
@@ -0,0 +1,8 @@
1
+ export declare const DEFAULT_SOURCE: {
2
+ name: string;
3
+ proportion: string;
4
+ }[];
5
+ export declare const MAX_SWAPPER_SELL = "100000000000000000000000000";
6
+ export declare const OSMOSIS_PRECISION = 6;
7
+ export declare const COSMO_OSMO_CHANNEL = "channel-141";
8
+ export declare const OSMO_COSMO_CHANNEL = "channel-0";
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OSMO_COSMO_CHANNEL = exports.COSMO_OSMO_CHANNEL = exports.OSMOSIS_PRECISION = exports.MAX_SWAPPER_SELL = exports.DEFAULT_SOURCE = void 0;
4
+ exports.DEFAULT_SOURCE = [{ name: 'Osmosis', proportion: '1' }];
5
+ exports.MAX_SWAPPER_SELL = '100000000000000000000000000';
6
+ exports.OSMOSIS_PRECISION = 6;
7
+ exports.COSMO_OSMO_CHANNEL = 'channel-141';
8
+ exports.OSMO_COSMO_CHANNEL = 'channel-0';
@@ -0,0 +1,69 @@
1
+ import { ChainId } from '@shapeshiftoss/caip';
2
+ import { osmosis } from '@shapeshiftoss/chain-adapters';
3
+ import { HDWallet } from '@shapeshiftoss/hdwallet-core';
4
+ import { CosmosSdkSupportedChainAdapters, Trade, TradeResult } from '../../../index';
5
+ import { IbcTransferInput } from './types';
6
+ export interface SymbolDenomMapping {
7
+ OSMO: string;
8
+ ATOM: string;
9
+ USDC: string;
10
+ }
11
+ export declare const symbolDenomMapping: {
12
+ OSMO: string;
13
+ ATOM: string;
14
+ USDC: string;
15
+ };
16
+ export declare const pollForComplete: (txid: string, baseUrl: string) => Promise<string>;
17
+ export declare const getAtomChannelBalance: (address: string, osmoUrl: string) => Promise<number>;
18
+ export declare const pollForAtomChannelBalance: (address: string, osmoUrl: string) => Promise<string>;
19
+ export declare const getRateInfo: (sellAsset: string, buyAsset: string, sellAmount: string, osmoUrl: string) => Promise<{
20
+ rate: string;
21
+ priceImpact: string;
22
+ tradeFee: string;
23
+ buyAmount: string;
24
+ }>;
25
+ export declare const performIbcTransfer: (input: IbcTransferInput, adapter: CosmosSdkSupportedChainAdapters, wallet: HDWallet, blockBaseUrl: string, denom: string, sourceChannel: string, feeAmount: string, accountNumber: string, sequence: string, gas: string) => Promise<TradeResult>;
26
+ export declare const buildTradeTx: ({ osmoAddress, adapter, trade, buyAssetDenom, sellAssetDenom, sellAmount, gas, wallet }: {
27
+ osmoAddress: string;
28
+ adapter: osmosis.ChainAdapter;
29
+ trade: Trade<ChainId>;
30
+ buyAssetDenom: string;
31
+ sellAssetDenom: string;
32
+ sellAmount: string;
33
+ gas: string;
34
+ wallet: HDWallet;
35
+ }) => Promise<{
36
+ txToSign: {
37
+ tx: {
38
+ memo: string;
39
+ fee: {
40
+ amount: {
41
+ amount: string;
42
+ denom: string;
43
+ }[];
44
+ gas: string;
45
+ };
46
+ signatures: null;
47
+ msg: {
48
+ type: string;
49
+ value: {
50
+ sender: string;
51
+ routes: {
52
+ poolId: string;
53
+ tokenOutDenom: string;
54
+ }[];
55
+ tokenIn: {
56
+ denom: string;
57
+ amount: string;
58
+ };
59
+ tokenOutMinAmount: string;
60
+ };
61
+ }[];
62
+ };
63
+ addressNList: number[];
64
+ chain_id: "osmosis-1";
65
+ account_number: string;
66
+ sequence: string;
67
+ };
68
+ wallet: HDWallet;
69
+ }>;
@@ -0,0 +1,282 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.buildTradeTx = exports.performIbcTransfer = exports.getRateInfo = exports.pollForAtomChannelBalance = exports.getAtomChannelBalance = exports.pollForComplete = exports.symbolDenomMapping = void 0;
7
+ const caip_1 = require("@shapeshiftoss/caip");
8
+ const chain_adapters_1 = require("@shapeshiftoss/chain-adapters");
9
+ const hdwallet_core_1 = require("@shapeshiftoss/hdwallet-core");
10
+ const axios_1 = __importDefault(require("axios"));
11
+ const lodash_1 = require("lodash");
12
+ const index_1 = require("../../../index");
13
+ const bignumber_1 = require("../../utils/bignumber");
14
+ const constants_1 = require("./constants");
15
+ exports.symbolDenomMapping = {
16
+ OSMO: 'uosmo',
17
+ ATOM: 'ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2',
18
+ USDC: 'ibc/D189335C6E4A68B513C10AB227BF1C1D38C746766278BA3EEB4FB14124F1D858'
19
+ };
20
+ const txStatus = async (txid, baseUrl) => {
21
+ var _a, _b, _c;
22
+ try {
23
+ const txResponse = await axios_1.default.get(`${baseUrl}/txs/${txid}`);
24
+ if (!((_a = txResponse === null || txResponse === void 0 ? void 0 : txResponse.data) === null || _a === void 0 ? void 0 : _a.codespace) && !!((_b = txResponse === null || txResponse === void 0 ? void 0 : txResponse.data) === null || _b === void 0 ? void 0 : _b.gas_used))
25
+ return 'success';
26
+ if ((_c = txResponse === null || txResponse === void 0 ? void 0 : txResponse.data) === null || _c === void 0 ? void 0 : _c.codespace)
27
+ return 'failed';
28
+ }
29
+ catch (e) {
30
+ console.error('Failed to get status');
31
+ }
32
+ return 'not found';
33
+ };
34
+ // TODO: leverage chain-adapters websockets
35
+ const pollForComplete = async (txid, baseUrl) => {
36
+ return new Promise((resolve, reject) => {
37
+ const timeout = 120000; // 2 mins
38
+ const startTime = Date.now();
39
+ const interval = 5000; // 5 seconds
40
+ const poll = async function () {
41
+ const status = await txStatus(txid, baseUrl);
42
+ if (status === 'success') {
43
+ resolve(status);
44
+ }
45
+ else if (Date.now() - startTime > timeout) {
46
+ reject(new index_1.SwapError(`Couldnt find tx ${txid}`, {
47
+ code: index_1.SwapErrorTypes.RESPONSE_ERROR
48
+ }));
49
+ }
50
+ else {
51
+ setTimeout(poll, interval);
52
+ }
53
+ };
54
+ poll();
55
+ });
56
+ };
57
+ exports.pollForComplete = pollForComplete;
58
+ const getAtomChannelBalance = async (address, osmoUrl) => {
59
+ const osmoResponseBalance = await (async () => {
60
+ try {
61
+ return axios_1.default.get(`${osmoUrl}/bank/balances/${address}`);
62
+ }
63
+ catch (e) {
64
+ throw new index_1.SwapError('failed to get balance', {
65
+ code: index_1.SwapErrorTypes.RESPONSE_ERROR
66
+ });
67
+ }
68
+ })();
69
+ let toAtomChannelBalance = 0;
70
+ try {
71
+ const { amount } = (0, lodash_1.find)(osmoResponseBalance.data.result, (b) => b.denom === exports.symbolDenomMapping.ATOM);
72
+ toAtomChannelBalance = Number(amount);
73
+ }
74
+ catch (e) {
75
+ console.error('no channel balance');
76
+ }
77
+ return toAtomChannelBalance;
78
+ };
79
+ exports.getAtomChannelBalance = getAtomChannelBalance;
80
+ const pollForAtomChannelBalance = async (address, osmoUrl) => {
81
+ return new Promise((resolve, reject) => {
82
+ const timeout = 120000; // 2 mins
83
+ const startTime = Date.now();
84
+ const interval = 5000; // 5 seconds
85
+ const poll = async function () {
86
+ const balance = await (0, exports.getAtomChannelBalance)(address, osmoUrl);
87
+ if (balance > 0) {
88
+ resolve(balance.toString());
89
+ }
90
+ else if (Date.now() - startTime > timeout) {
91
+ reject(new index_1.SwapError(`Couldnt find channel balance for ${address}`, {
92
+ code: index_1.SwapErrorTypes.RESPONSE_ERROR
93
+ }));
94
+ }
95
+ else {
96
+ setTimeout(poll, interval);
97
+ }
98
+ };
99
+ poll();
100
+ });
101
+ };
102
+ exports.pollForAtomChannelBalance = pollForAtomChannelBalance;
103
+ const findPool = async (sellAssetSymbol, buyAssetSymbol, osmoUrl) => {
104
+ const sellAssetDenom = exports.symbolDenomMapping[sellAssetSymbol];
105
+ const buyAssetDenom = exports.symbolDenomMapping[buyAssetSymbol];
106
+ const poolsUrl = osmoUrl + '/osmosis/gamm/v1beta1/pools?pagination.limit=1000';
107
+ const poolsResponse = await (async () => {
108
+ try {
109
+ return axios_1.default.get(poolsUrl);
110
+ }
111
+ catch (e) {
112
+ throw new index_1.SwapError('failed to get pool', {
113
+ code: index_1.SwapErrorTypes.POOL_NOT_FOUND
114
+ });
115
+ }
116
+ })();
117
+ const foundPool = (0, lodash_1.find)(poolsResponse.data.pools, (pool) => {
118
+ const token0Denom = pool.poolAssets[0].token.denom;
119
+ const token1Denom = pool.poolAssets[1].token.denom;
120
+ return ((token0Denom === sellAssetDenom && token1Denom === buyAssetDenom) ||
121
+ (token0Denom === buyAssetDenom && token1Denom === sellAssetDenom));
122
+ });
123
+ if (!foundPool)
124
+ throw new index_1.SwapError('could not find pool', {
125
+ code: index_1.SwapErrorTypes.POOL_NOT_FOUND
126
+ });
127
+ const { sellAssetIndex, buyAssetIndex } = (() => {
128
+ if (foundPool.poolAssets[0].token.denom === sellAssetDenom) {
129
+ return { sellAssetIndex: 0, buyAssetIndex: 1 };
130
+ }
131
+ else {
132
+ return { sellAssetIndex: 1, buyAssetIndex: 0 };
133
+ }
134
+ })();
135
+ return { pool: foundPool, sellAssetIndex, buyAssetIndex };
136
+ };
137
+ const getInfoFromPool = (sellAmount, pool, sellAssetIndex, buyAssetIndex) => {
138
+ const constantProduct = (0, bignumber_1.bnOrZero)(pool.poolAssets[0].token.amount).times(pool.poolAssets[1].token.amount);
139
+ const sellAssetInitialPoolSize = (0, bignumber_1.bnOrZero)(pool.poolAssets[sellAssetIndex].token.amount);
140
+ const buyAssetInitialPoolSize = (0, bignumber_1.bnOrZero)(pool.poolAssets[buyAssetIndex].token.amount);
141
+ const initialMarketPrice = sellAssetInitialPoolSize.dividedBy(buyAssetInitialPoolSize);
142
+ const sellAssetFinalPoolSize = sellAssetInitialPoolSize.plus(sellAmount);
143
+ const buyAssetFinalPoolSize = constantProduct.dividedBy(sellAssetFinalPoolSize);
144
+ const finalMarketPrice = sellAssetFinalPoolSize.dividedBy(buyAssetFinalPoolSize);
145
+ const buyAmount = buyAssetInitialPoolSize.minus(buyAssetFinalPoolSize).toString();
146
+ const rate = (0, bignumber_1.bnOrZero)(buyAmount).dividedBy(sellAmount).toString();
147
+ const priceImpact = (0, bignumber_1.bn)(1).minus(initialMarketPrice.dividedBy(finalMarketPrice)).abs().toString();
148
+ const tradeFeeBase = (0, bignumber_1.bnOrZero)(buyAmount).times((0, bignumber_1.bnOrZero)(pool.poolParams.swapFee));
149
+ const tradeFee = tradeFeeBase.dividedBy((0, bignumber_1.bn)(10).exponentiatedBy(constants_1.OSMOSIS_PRECISION)).toString();
150
+ return {
151
+ rate,
152
+ priceImpact,
153
+ tradeFee,
154
+ buyAmount
155
+ };
156
+ };
157
+ const getRateInfo = async (sellAsset, buyAsset, sellAmount, osmoUrl) => {
158
+ const { pool, sellAssetIndex, buyAssetIndex } = await findPool(sellAsset, buyAsset, osmoUrl);
159
+ return getInfoFromPool(sellAmount, pool, sellAssetIndex, buyAssetIndex);
160
+ };
161
+ exports.getRateInfo = getRateInfo;
162
+ // TODO: move to chain adapters
163
+ const performIbcTransfer = async (input, adapter, wallet, blockBaseUrl, denom, sourceChannel, feeAmount, accountNumber, sequence, gas) => {
164
+ const { sender, receiver, amount } = input;
165
+ const responseLatestBlock = await (async () => {
166
+ try {
167
+ return axios_1.default.get(`${blockBaseUrl}/blocks/latest`);
168
+ }
169
+ catch (e) {
170
+ throw new index_1.SwapError('failed to get latest block', {
171
+ code: index_1.SwapErrorTypes.RESPONSE_ERROR
172
+ });
173
+ }
174
+ })();
175
+ const latestBlock = responseLatestBlock.data.block.header.height;
176
+ const bip44Params = adapter.buildBIP44Params({
177
+ accountNumber: 0 // TODO: Use real accountNumbers
178
+ });
179
+ const path = (0, chain_adapters_1.toPath)(bip44Params);
180
+ const addressNList = (0, hdwallet_core_1.bip32ToAddressNList)(path);
181
+ const tx1 = {
182
+ memo: '',
183
+ fee: {
184
+ amount: [
185
+ {
186
+ amount: feeAmount.toString(),
187
+ denom: 'uosmo'
188
+ }
189
+ ],
190
+ gas
191
+ },
192
+ signatures: null,
193
+ msg: [
194
+ {
195
+ type: 'cosmos-sdk/MsgTransfer',
196
+ value: {
197
+ source_port: 'transfer',
198
+ source_channel: sourceChannel,
199
+ token: {
200
+ denom,
201
+ amount
202
+ },
203
+ sender,
204
+ receiver,
205
+ timeout_height: {
206
+ revision_number: '4',
207
+ revision_height: String(Number(latestBlock) + 100)
208
+ }
209
+ }
210
+ }
211
+ ]
212
+ };
213
+ const signed = await adapter.signTransaction({
214
+ txToSign: {
215
+ tx: tx1,
216
+ addressNList,
217
+ chain_id: caip_1.CHAIN_REFERENCE.OsmosisMainnet,
218
+ account_number: accountNumber,
219
+ sequence
220
+ },
221
+ wallet
222
+ });
223
+ const tradeId = await adapter.broadcastTransaction(signed);
224
+ return {
225
+ tradeId
226
+ };
227
+ };
228
+ exports.performIbcTransfer = performIbcTransfer;
229
+ // TODO: move to chain adapters
230
+ const buildTradeTx = async ({ osmoAddress, adapter, trade, buyAssetDenom, sellAssetDenom, sellAmount, gas, wallet }) => {
231
+ const responseAccount = await adapter.getAccount(osmoAddress);
232
+ const accountNumber = responseAccount.chainSpecific.accountNumber || '0';
233
+ const sequence = responseAccount.chainSpecific.sequence || '0';
234
+ const bip44Params = adapter.buildBIP44Params({
235
+ accountNumber: 0 // TODO: Use real accountNumbers
236
+ });
237
+ const path = (0, chain_adapters_1.toPath)(bip44Params);
238
+ const osmoAddressNList = (0, hdwallet_core_1.bip32ToAddressNList)(path);
239
+ const tx = {
240
+ memo: '',
241
+ fee: {
242
+ amount: [
243
+ {
244
+ amount: trade.feeData.fee.toString(),
245
+ denom: 'uosmo'
246
+ }
247
+ ],
248
+ gas
249
+ },
250
+ signatures: null,
251
+ msg: [
252
+ {
253
+ type: 'osmosis/gamm/swap-exact-amount-in',
254
+ value: {
255
+ sender: osmoAddress,
256
+ routes: [
257
+ {
258
+ poolId: '1',
259
+ tokenOutDenom: buyAssetDenom
260
+ }
261
+ ],
262
+ tokenIn: {
263
+ denom: sellAssetDenom,
264
+ amount: sellAmount
265
+ },
266
+ tokenOutMinAmount: '1' // slippage tolerance
267
+ }
268
+ }
269
+ ]
270
+ };
271
+ return {
272
+ txToSign: {
273
+ tx,
274
+ addressNList: osmoAddressNList,
275
+ chain_id: caip_1.CHAIN_REFERENCE.OsmosisMainnet,
276
+ account_number: accountNumber,
277
+ sequence
278
+ },
279
+ wallet
280
+ };
281
+ };
282
+ exports.buildTradeTx = buildTradeTx;
@@ -0,0 +1,22 @@
1
+ import { ChainAdapterManager } from '@shapeshiftoss/chain-adapters';
2
+ export declare type OsmoSwapperDeps = {
3
+ adapterManager: ChainAdapterManager;
4
+ osmoUrl: string;
5
+ cosmosUrl: string;
6
+ };
7
+ export declare type IbcTransferInput = {
8
+ sender: string;
9
+ receiver: string;
10
+ amount: string;
11
+ };
12
+ export declare type PoolInfo = {
13
+ poolAssets: PoolAssetInfo[];
14
+ poolParams: {
15
+ swapFee: string;
16
+ };
17
+ };
18
+ export declare type PoolAssetInfo = {
19
+ token: {
20
+ amount: string;
21
+ };
22
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -4,13 +4,27 @@ exports.thorTradeApproveInfinite = void 0;
4
4
  const types_1 = require("@shapeshiftoss/types");
5
5
  const api_1 = require("../../../api");
6
6
  const erc20_abi_1 = require("../../utils/abi/erc20-abi");
7
+ const constants_1 = require("../../utils/constants");
7
8
  const helpers_1 = require("../../utils/helpers/helpers");
8
- const constants_1 = require("../utils/constants");
9
+ const constants_2 = require("../utils/constants");
9
10
  const thorTradeApproveInfinite = async ({ deps, input }) => {
10
11
  try {
11
12
  const { adapterManager, web3 } = deps;
12
13
  const { quote, wallet } = input;
13
- const sellAssetChainId = quote.sellAsset.chainId;
14
+ const approvalQuote = {
15
+ ...quote,
16
+ sellAmount: constants_2.MAX_ALLOWANCE,
17
+ feeData: {
18
+ ...quote.feeData,
19
+ chainSpecific: {
20
+ ...quote.feeData.chainSpecific,
21
+ // Thor approvals are cheaper than trades, but we don't have dynamic quote data for them.
22
+ // Instead, we use a hardcoded gasLimit estimate in place of the estimatedGas in the Thor quote response.
23
+ estimatedGas: constants_1.APPROVAL_GAS_LIMIT
24
+ }
25
+ }
26
+ };
27
+ const sellAssetChainId = approvalQuote.sellAsset.chainId;
14
28
  const adapter = adapterManager.get(types_1.KnownChainIds.EthereumMainnet);
15
29
  if (!adapter)
16
30
  throw new api_1.SwapError(`[thorTradeApproveInfinite] - No chain adapter found for ${sellAssetChainId}.`, {
@@ -18,10 +32,7 @@ const thorTradeApproveInfinite = async ({ deps, input }) => {
18
32
  details: { sellAssetChainId }
19
33
  });
20
34
  const allowanceGrantRequired = await (0, helpers_1.grantAllowance)({
21
- quote: {
22
- ...quote,
23
- sellAmount: constants_1.MAX_ALLOWANCE
24
- },
35
+ quote: approvalQuote,
25
36
  wallet,
26
37
  adapter,
27
38
  erc20Abi: erc20_abi_1.erc20Abi,
@@ -11,7 +11,6 @@ const constants_2 = require("../utils/constants");
11
11
  const helpers_2 = require("../utils/helpers/helpers");
12
12
  const zrxService_1 = require("../utils/zrxService");
13
13
  async function getZrxTradeQuote(input) {
14
- var _a;
15
14
  try {
16
15
  const { sellAsset, buyAsset, sellAmount, sellAssetAccountNumber } = input;
17
16
  if (buyAsset.chainId !== input.chainId || sellAsset.chainId !== input.chainId) {
@@ -46,27 +45,31 @@ async function getZrxTradeQuote(input) {
46
45
  sellAmount: normalizedSellAmount
47
46
  }
48
47
  });
49
- const { data } = quoteResponse;
50
- const estimatedGas = (0, bignumber_1.bnOrZero)(data.estimatedGas).times(1.5);
51
- const rate = useSellAmount ? data.price : (0, bignumber_1.bn)(1).div(data.price).toString();
48
+ const { data: { estimatedGas: estimatedGasResponse, gasPrice, price, sellAmount: sellAmountResponse, buyAmount, sources, allowanceTarget } } = quoteResponse;
49
+ const estimatedGas = (0, bignumber_1.bnOrZero)(estimatedGasResponse).times(1.5);
50
+ const rate = useSellAmount ? price : (0, bignumber_1.bn)(1).div(price).toString();
51
+ const fee = (0, bignumber_1.bnOrZero)(estimatedGas).multipliedBy((0, bignumber_1.bnOrZero)(gasPrice)).toString();
52
+ // 0x approvals are cheaper than trades, but we don't have dynamic quote data for them.
53
+ // Instead, we use a hardcoded gasLimit estimate in place of the estimatedGas in the 0x quote response.
54
+ const approvalFee = sellAssetErc20Address &&
55
+ (0, bignumber_1.bnOrZero)(constants_1.APPROVAL_GAS_LIMIT).multipliedBy((0, bignumber_1.bnOrZero)(gasPrice)).toString();
52
56
  return {
53
57
  rate,
54
58
  minimum,
55
59
  maximum,
56
60
  feeData: {
57
- fee: (0, bignumber_1.bnOrZero)(estimatedGas).multipliedBy((0, bignumber_1.bnOrZero)(data.gasPrice)).toString(),
61
+ fee,
58
62
  chainSpecific: {
59
63
  estimatedGas: estimatedGas.toString(),
60
- gasPrice: data.gasPrice,
61
- approvalFee: sellAssetErc20Address &&
62
- (0, bignumber_1.bnOrZero)(constants_1.APPROVAL_GAS_LIMIT).multipliedBy((0, bignumber_1.bnOrZero)(data.gasPrice)).toString()
64
+ gasPrice,
65
+ approvalFee
63
66
  },
64
67
  tradeFee: '0'
65
68
  },
66
- sellAmount: data.sellAmount,
67
- buyAmount: data.buyAmount,
68
- sources: ((_a = data.sources) === null || _a === void 0 ? void 0 : _a.filter((s) => parseFloat(s.proportion) > 0)) || constants_2.DEFAULT_SOURCE,
69
- allowanceContract: data.allowanceTarget,
69
+ sellAmount: sellAmountResponse,
70
+ buyAmount,
71
+ sources: (sources === null || sources === void 0 ? void 0 : sources.filter((s) => parseFloat(s.proportion) > 0)) || constants_2.DEFAULT_SOURCE,
72
+ allowanceContract: allowanceTarget,
70
73
  buyAsset,
71
74
  sellAsset,
72
75
  sellAssetAccountNumber
@@ -18,6 +18,7 @@ export declare const setupExecuteTrade: () => {
18
18
  sellAsset: Asset;
19
19
  sendMax: boolean;
20
20
  sellAssetAccountNumber: number;
21
+ buyAssetAccountNumber: number;
21
22
  txData: string;
22
23
  depositAddress: string;
23
24
  receiveAddress: string;
@@ -34,6 +34,7 @@ const setupExecuteTrade = () => {
34
34
  sellAsset,
35
35
  sendMax: false,
36
36
  sellAssetAccountNumber: 0,
37
+ buyAssetAccountNumber: 0,
37
38
  txData: '0x0',
38
39
  depositAddress: '0x0',
39
40
  receiveAddress: '0x0',
@@ -3,15 +3,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.zrxApproveInfinite = void 0;
4
4
  const api_1 = require("../../../api");
5
5
  const erc20_abi_1 = require("../../utils/abi/erc20-abi");
6
+ const constants_1 = require("../../utils/constants");
6
7
  const helpers_1 = require("../../utils/helpers/helpers");
7
- const constants_1 = require("../utils/constants");
8
+ const constants_2 = require("../utils/constants");
8
9
  async function zrxApproveInfinite({ adapter, web3 }, { quote, wallet }) {
9
10
  try {
11
+ const approvalQuote = {
12
+ ...quote,
13
+ sellAmount: constants_2.MAX_ALLOWANCE,
14
+ feeData: {
15
+ ...quote.feeData,
16
+ chainSpecific: {
17
+ ...quote.feeData.chainSpecific,
18
+ // 0x approvals are cheaper than trades, but we don't have dynamic quote data for them.
19
+ // Instead, we use a hardcoded gasLimit estimate in place of the estimatedGas in the 0x quote response.
20
+ estimatedGas: constants_1.APPROVAL_GAS_LIMIT
21
+ }
22
+ }
23
+ };
10
24
  const allowanceGrantRequired = await (0, helpers_1.grantAllowance)({
11
- quote: {
12
- ...quote,
13
- sellAmount: constants_1.MAX_ALLOWANCE
14
- },
25
+ quote: approvalQuote,
15
26
  wallet,
16
27
  adapter,
17
28
  erc20Abi: erc20_abi_1.erc20Abi,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshiftoss/swapper",
3
- "version": "8.3.1",
3
+ "version": "8.5.1",
4
4
  "description": "An interface and implementations to trade coins via various protocols",
5
5
  "repository": "https://github.com/shapeshift/lib",
6
6
  "license": "MIT",