@mictonode/widget 0.3.16 → 0.3.18

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.
Files changed (69) hide show
  1. package/.github/FUNDING.yml +3 -3
  2. package/.github/workflows/npm-publish.yml +34 -34
  3. package/.prettierrc.json +9 -9
  4. package/.vscode/extensions.json +3 -3
  5. package/LICENSE +674 -674
  6. package/README.md +41 -41
  7. package/dist/{main-d0e5d1e4.js → main-bbea3e54.js} +155 -153
  8. package/dist/ping-widget.js +2 -2
  9. package/dist/ping-widget.umd.cjs +18 -18
  10. package/dist/{query.lcd-3d4d3495.js → query.lcd-88036522.js} +1 -1
  11. package/dist/{query.rpc.Query-b7807c29.js → query.rpc.Query-3a2b14f8.js} +1 -1
  12. package/dist/{tx.rpc.msg-59c3408a.js → tx.rpc.msg-39f0b824.js} +1 -1
  13. package/example/.vscode/extensions.json +3 -3
  14. package/example/README.md +7 -7
  15. package/example/index.html +12 -12
  16. package/example/package.json +19 -19
  17. package/example/pnpm-lock.yaml +465 -465
  18. package/example/public/cdn.html +19 -19
  19. package/example/src/App.vue +73 -73
  20. package/example/src/main.js +4 -4
  21. package/example/vite.config.js +7 -7
  22. package/index.html +12 -12
  23. package/lib/components/ConnectWallet/index.vue +262 -262
  24. package/lib/components/TokenConvert/index.vue +1033 -1033
  25. package/lib/components/TokenConvert/tokens.ts +36 -36
  26. package/lib/components/TxDialog/index.vue +422 -422
  27. package/lib/components/TxDialog/messages/Delegate.vue +174 -174
  28. package/lib/components/TxDialog/messages/Deposit.vue +96 -96
  29. package/lib/components/TxDialog/messages/Redelegate.vue +160 -160
  30. package/lib/components/TxDialog/messages/Send.vue +142 -142
  31. package/lib/components/TxDialog/messages/Transfer.vue +248 -248
  32. package/lib/components/TxDialog/messages/Unbond.vue +103 -103
  33. package/lib/components/TxDialog/messages/Vote.vue +62 -62
  34. package/lib/components/TxDialog/messages/Withdraw.vue +55 -55
  35. package/lib/components/TxDialog/messages/WithdrawCommission.vue +74 -74
  36. package/lib/components/TxDialog/wasm/ClearAdmin.vue +55 -55
  37. package/lib/components/TxDialog/wasm/ExecuteContract.vue +98 -98
  38. package/lib/components/TxDialog/wasm/InstantiateContract.vue +108 -108
  39. package/lib/components/TxDialog/wasm/MigrateContract.vue +76 -76
  40. package/lib/components/TxDialog/wasm/MigrateContract2.vue +76 -76
  41. package/lib/components/TxDialog/wasm/StoreCode.vue +100 -100
  42. package/lib/components/TxDialog/wasm/UpdateAdmin.vue +74 -74
  43. package/lib/main.css +591 -7
  44. package/lib/main.ts +24 -23
  45. package/lib/utils/TokenUnitConverter.ts +44 -44
  46. package/lib/utils/format.ts +16 -16
  47. package/lib/utils/http.ts +154 -154
  48. package/lib/utils/type.ts +56 -56
  49. package/lib/wallet/EthermintMessageAdapter.ts +135 -135
  50. package/lib/wallet/UniClient.ts +144 -144
  51. package/lib/wallet/Wallet.ts +142 -142
  52. package/lib/wallet/wallets/KeplerWallet.ts +141 -141
  53. package/lib/wallet/wallets/LeapWallet.ts +141 -141
  54. package/lib/wallet/wallets/LedgerWallet.ts +202 -202
  55. package/lib/wallet/wallets/MetamaskSnapWallet.ts +105 -105
  56. package/lib/wallet/wallets/MetamaskWallet.ts +173 -173
  57. package/lib/wallet/wallets/OKXWallet.ts +202 -202
  58. package/lib/wallet/wallets/UnisatWallet.ts +198 -198
  59. package/package.json +5 -6
  60. package/postcss.config.js +6 -6
  61. package/src/App.vue +225 -225
  62. package/src/main.ts +7 -4
  63. package/src/styles/design-system.css +184 -0
  64. package/src/vite-env.d.ts +1 -1
  65. package/tailwind.config.js +158 -34
  66. package/tsconfig.json +25 -25
  67. package/tsconfig.node.json +10 -10
  68. package/vite.config.ts +62 -62
  69. package/vue-shim.d.ts +6 -6
@@ -1,1033 +1,1033 @@
1
- <script setup lang="ts">
2
- import { computed, onMounted, ref } from 'vue';
3
- import { Icon } from '@iconify/vue';
4
- import { fromBech32, toBech32, toHex } from '@cosmjs/encoding';
5
- import { sha256 } from '@cosmjs/crypto';
6
- import ChainRegistryClient from '@ping-pub/chain-registry-client';
7
- import {
8
- Asset,
9
- Chain,
10
- IBCInfo,
11
- IBCPath,
12
- } from '@ping-pub/chain-registry-client/dist/types';
13
- import {
14
- getBalance,
15
- getStakingParam,
16
- getOsmosisPools,
17
- getLatestBlock,
18
- getAccount,
19
- getTxByHash,
20
- } from '../../utils/http';
21
- import {
22
- Account,
23
- ConnectedWallet,
24
- WalletName,
25
- createWallet,
26
- readWallet,
27
- writeWallet,
28
- } from '../../wallet/Wallet';
29
- import { Coin } from '../../utils/type';
30
- import { osmosis, ibc, getSigningOsmosisClient } from 'osmojs';
31
- import { tokens, type TokenConfig } from './tokens';
32
- import { decimal2percent } from '../../utils/format';
33
- import { StdFee, coin } from '@cosmjs/stargate';
34
- import Long from 'long';
35
- import { UniClient } from '../../wallet/UniClient';
36
-
37
- const props = defineProps({
38
- chainName: { type: String, required: true },
39
- endpoint: { type: String, required: true },
40
- hdPath: String,
41
- });
42
- const OSMOSIS_RPC = 'https://rpc.osmosis.zone';
43
- const OSMOSIS_REST = 'https://lcd.osmosis.zone';
44
- const DEFAULT_HDPATH = "m/44'/118/0'/0/0";
45
-
46
- const view = ref('connect'); // [connect, swap, deposit, withdraw]
47
-
48
- function switchView(v: string) {
49
- view.value = v;
50
- }
51
-
52
- // variables
53
- const direction = ref('buy');
54
- const sending = ref(false); // show status on send tx
55
- const open = ref(false);
56
- const error = ref('');
57
- const chains = ref([] as IBCPath[]);
58
- const osmosisPath = ref({} as IBCPath | undefined);
59
- const osmosisPathInfo = ref({} as IBCInfo);
60
- const defaultDenom = ref('');
61
- const sender = ref({} as ConnectedWallet);
62
- const localBalances = ref([] as Coin[]);
63
- const osmoBalances = ref([] as Coin[]);
64
- const localChainInfo = ref({} as Chain);
65
- const localCoinInfo = ref([] as Asset[]);
66
- const swapIn = ref({} as TokenConfig | undefined);
67
- const swapOut = ref({} as TokenConfig | undefined);
68
- const amountIn = ref('');
69
- const allPools = ref([] as any[]);
70
- const client = new ChainRegistryClient();
71
-
72
- // swap logic
73
-
74
- async function initData() {
75
- error.value = ''; //reset error
76
- sender.value = readWallet(props.hdPath);
77
- if (!sender.value.cosmosAddress) {
78
- view.value = 'connect';
79
- return;
80
- }
81
- if (open.value) {
82
- view.value = 'swap';
83
- localChainInfo.value = {} as Chain;
84
- localCoinInfo.value = [];
85
- recipient.value = localAddress(sender.value.cosmosAddress)
86
- await client
87
- .fetchChainInfo(props.chainName)
88
- .then((res) => {
89
- localChainInfo.value = res;
90
- if (Number(res.slip44) !== 118) {
91
- error.value === `Coin type ${res.slip44} is not supported`;
92
- }
93
- })
94
- .catch(() => {
95
- error.value = 'Not found IBC Path';
96
- });
97
- getBalance(OSMOSIS_REST, osmoAddress(sender.value.cosmosAddress)).then(
98
- (res) => {
99
- osmoBalances.value = res.balances.filter(
100
- (x) => !x.denom.startsWith('gamm')
101
- );
102
- }
103
- );
104
- getBalance(
105
- props.endpoint,
106
- localAddress(sender.value.cosmosAddress)
107
- ).then((res) => {
108
- localBalances.value = res.balances;
109
- });
110
-
111
- client.fetchAssetsList(props.chainName).then((al) => {
112
- localCoinInfo.value = al.assets;
113
- });
114
-
115
- getStakingParam(props.endpoint).then((x) => {
116
- defaultDenom.value = x.params.bond_denom;
117
- });
118
- getOsmosisPools(OSMOSIS_REST).then((res) => {
119
- allPools.value = res.pools;
120
- });
121
-
122
- client.fetchIBCPaths().then((paths) => {
123
- chains.value = paths.filter(
124
- (x) => x.from === props.chainName || x.to === props.chainName
125
- );
126
- const path = chains.value.find(
127
- (x) => x.from === 'osmosis' || x.to === 'osmosis'
128
- );
129
- if (path) {
130
- osmosisPath.value = path;
131
- client.fetchIBCPathInfo(path.path).then((info) => {
132
- osmosisPathInfo.value = info;
133
- });
134
- }
135
- });
136
- }
137
- }
138
-
139
- function osmoAddress(addr?: string) {
140
- if (!addr) return '';
141
- const { data } = fromBech32(addr);
142
- return toBech32('osmo', data);
143
- }
144
-
145
- function localAddress(addr?: string) {
146
- if (!addr) return '';
147
- const { data } = fromBech32(addr);
148
- const prefix = localChainInfo.value?.bech32_prefix || 'cosmos';
149
- return toBech32(prefix, data);
150
- }
151
-
152
- function showBalance(denom?: string, decimal = 0) {
153
- const bal = osmoBalances.value.find((x) => x.denom === denom || '');
154
- if (bal) {
155
- return Number(bal.amount) / 10 ** decimal;
156
- }
157
- return 0;
158
- }
159
-
160
- function findlocalCoinDecimal(denom: string) {
161
- const localCoin = localCoinInfo.value.find((x) => x.base === denom);
162
- if (localCoin) {
163
- const unit = localCoin.denom_units.find(
164
- (x) => x.denom === localCoin.symbol.toLowerCase()
165
- );
166
- if (unit) return unit.exponent;
167
- }
168
- return 0;
169
- }
170
-
171
- function findTokenUrl(coinInfo?: Asset) {
172
- const url =
173
- coinInfo?.logo_URIs?.svg ||
174
- coinInfo?.logo_URIs?.png ||
175
- coinInfo?.logo_URIs?.jpeg;
176
- return url
177
- ? url.replace(
178
- 'https://raw.githubusercontent.com/cosmos/chain-registry/master',
179
- 'https://registry.ping.pub'
180
- )
181
- : '';
182
- }
183
-
184
- const localTokenOnOsmosis = computed(() => {
185
- const channel = osmosisPathInfo.value.channels?.find(
186
- (x) => x.chain_1.port_id === 'transfer'
187
- );
188
- const channelId =
189
- osmosisPathInfo.value.chain_1?.chain_name === 'osmosis'
190
- ? channel?.chain_1.channel_id
191
- : channel?.chain_2.channel_id;
192
- const ibcDenom = toHex(
193
- sha256(
194
- new TextEncoder().encode(
195
- `transfer/${channelId}/${defaultDenom.value}`
196
- )
197
- )
198
- ).toUpperCase();
199
- return localCoinInfo.value?.map((localCoin) => ({
200
- denom: defaultDenom.value,
201
- symbol: localCoin.symbol || '',
202
- ibcDenom: `ibc/${ibcDenom}`,
203
- sourceChannelId: channelId,
204
- decimals: findlocalCoinDecimal(defaultDenom.value),
205
- coinImageUrl: findTokenUrl(localCoin),
206
- }));
207
- });
208
-
209
- const localSourceChannelID = computed(() => {
210
- const channel = osmosisPathInfo.value.channels?.find(
211
- (x) => x.chain_1.port_id === 'transfer'
212
- );
213
- const channelId =
214
- osmosisPathInfo.value.chain_1?.chain_name === 'osmosis'
215
- ? channel?.chain_2.channel_id
216
- : channel?.chain_1.channel_id;
217
- return channelId;
218
- });
219
-
220
- const withdrawable = computed(() => {
221
- return (
222
- localTokenOnOsmosis.value.findIndex(
223
- (x) => x.denom === swapOut.value?.denom
224
- ) > -1
225
- );
226
- });
227
-
228
- const depositable = computed(() => {
229
- return (
230
- localTokenOnOsmosis.value.findIndex(
231
- (x) => x.denom === swapIn.value?.denom
232
- ) > -1
233
- );
234
- });
235
-
236
- const inTokens = computed(() => {
237
- if (direction.value === 'buy') {
238
- swapIn.value = tokens[0];
239
- return tokens;
240
- } else {
241
- swapIn.value = localTokenOnOsmosis.value[0];
242
- return localTokenOnOsmosis.value;
243
- }
244
- });
245
-
246
- const outTokens = computed(() => {
247
- if (direction.value === 'buy') {
248
- swapOut.value = localTokenOnOsmosis.value[0];
249
- return localTokenOnOsmosis.value;
250
- } else {
251
- swapOut.value = tokens[0];
252
- return tokens;
253
- }
254
- });
255
-
256
- function selectInput(v) {
257
- swapIn.value = v;
258
- amountIn.value = '';
259
- }
260
-
261
- function selectOutput(v) {
262
- swapOut.value = v;
263
- amountIn.value = '';
264
- }
265
-
266
- const pool = computed(() => {
267
- // find the available pools and sort by liquidity depth
268
- const a = allPools.value
269
- .filter(
270
- (x) =>
271
- x.pool_assets?.findIndex(
272
- (a) => a.token.denom === swapIn.value?.ibcDenom
273
- ) > -1
274
- )
275
- .filter(
276
- (x) =>
277
- x.pool_assets?.findIndex(
278
- (a) => a.token.denom === swapOut.value?.ibcDenom
279
- ) > -1
280
- )
281
- .sort(
282
- (a, b) =>
283
- Number(b.total_shares.amount) - Number(a.total_shares.amount)
284
- );
285
- // console.log(a)
286
- return a.length > 0 ? a[0] : null;
287
- });
288
-
289
- function switchDirection() {
290
- direction.value = direction.value === 'buy' ? 'sell' : 'buy';
291
- }
292
-
293
- const disabled = computed(() => {
294
- const amount = Number(amountIn.value || 0);
295
- if (amount <= 0 || outAmount.value <= 0) return true;
296
- const token = swapIn.value;
297
- const b = osmoBalances.value.find((x) => x.denom === token?.ibcDenom || '');
298
- if (b && token) {
299
- return Number(b.amount) < amount * 10 ** token.decimals;
300
- }
301
- return true;
302
- });
303
-
304
- const outAmount = computed(() => {
305
- // tokenBalanceOut * [1 - { tokenBalanceIn / (tokenBalanceIn + (1 - swapFee) * tokenAmountIn)} ^ (tokenWeightIn / tokenWeightOut)]
306
- const p = pool.value;
307
- if (p) {
308
- const tokenBalanceOut = p.pool_assets?.find(
309
- (x) => x.token.denom === swapOut.value?.ibcDenom
310
- );
311
- const tokenBalanceIn = p.pool_assets?.find(
312
- (x) => x.token.denom === swapIn.value?.ibcDenom
313
- );
314
- if (tokenBalanceIn && tokenBalanceOut) {
315
- const balanceOut = Number(tokenBalanceOut.token.amount);
316
- const balanceIn = Number(tokenBalanceIn.token.amount);
317
- const weightOut = Number(tokenBalanceOut.weight);
318
- const weightIn = Number(tokenBalanceIn.weight);
319
- const swapFee = Number(p.pool_params.swap_fee);
320
- const amount = Number(amountIn.value) * (1 - swapFee);
321
- const out =
322
- balanceOut *
323
- (1 -
324
- balanceIn / (balanceIn + amount) ** (weightIn / weightOut));
325
- return out;
326
- }
327
- }
328
- return 0;
329
- });
330
-
331
- async function doSwap() {
332
- sending.value = true;
333
- try {
334
- const latest = await getLatestBlock(OSMOSIS_REST);
335
-
336
- const stargateClient = await getSigningOsmosisClient({
337
- rpcEndpoint: OSMOSIS_RPC,
338
- // @ts-ignore
339
- signer: window.getOfflineSigner(latest.block.header.chain_id),
340
- });
341
- const address = osmoAddress(sender.value.cosmosAddress);
342
-
343
- if (!swapIn.value || !swapOut.value || !address) return;
344
- const { swapExactAmountIn } =
345
- osmosis.gamm.v1beta1.MessageComposer.withTypeUrl;
346
-
347
- const amount = Number(amountIn.value || 0);
348
- const msg = swapExactAmountIn({
349
- sender: address,
350
- routes: [
351
- {
352
- poolId: Long.fromNumber(pool.value.id),
353
- tokenOutDenom: swapOut.value.ibcDenom,
354
- },
355
- ],
356
- tokenIn: {
357
- amount: (amount * 10 ** swapIn.value.decimals).toFixed(),
358
- denom: swapIn.value.ibcDenom,
359
- },
360
- tokenOutMinAmount: (
361
- outAmount.value *
362
- 0.99 *
363
- 10 ** swapOut.value.decimals
364
- ).toFixed(), // slippage: 1%
365
- });
366
-
367
- const gas = await stargateClient.simulate(address, [msg], '');
368
-
369
- const fee: StdFee = {
370
- amount: [
371
- {
372
- denom: 'uosmo',
373
- amount: '864',
374
- },
375
- ],
376
- gas: (gas * 1.25).toFixed(),
377
- };
378
- const response = await stargateClient.signAndBroadcast(
379
- address,
380
- [msg],
381
- fee,
382
- 'Token Convert from mictonode.com'
383
- );
384
- if (response.code === 0) {
385
- await showResult(response.transactionHash)
386
- await getBalance(
387
- OSMOSIS_REST,
388
- osmoAddress(sender.value.cosmosAddress)
389
- ).then((res) => {
390
- osmoBalances.value = res.balances.filter(
391
- (x) => !x.denom.startsWith('gamm')
392
- );
393
- });
394
- } else {
395
- if (response.rawLog) error.value = response.rawLog;
396
- }
397
- } catch (err) {
398
- error.value = err;
399
- }
400
- sending.value = false;
401
- }
402
-
403
- // deposit logic
404
- const depositAmount = ref('');
405
-
406
- function showLocalBalance(denom = '', decimals = 0) {
407
- const b = localBalances.value?.find((x) => x.denom === denom);
408
- if (b) {
409
- return Number(b.amount) / 10 ** decimals;
410
- }
411
- return 0;
412
- }
413
- const disableDeposit = computed(() => {
414
- const token = swapIn.value;
415
- if (token) {
416
- const b = localBalances.value?.find((x) => x.denom === token.denom);
417
- if (b) {
418
- const amount = Number(depositAmount.value || 0);
419
- return (
420
- amount <= 0 || Number(b.amount) < amount * 10 ** token.decimals
421
- );
422
- }
423
- }
424
- return true;
425
- });
426
-
427
- async function doDeposit() {
428
- sending.value = true;
429
-
430
- const address = localAddress(sender.value.cosmosAddress);
431
-
432
- if (!swapIn.value || !address) return;
433
-
434
- const latest = await getLatestBlock(props.endpoint);
435
- const acc = await getAccount(props.endpoint, address);
436
-
437
- const chainId = latest.block.header.chain_id;
438
- const timeout = Date.now() + new Date().getTimezoneOffset() + 3600000;
439
- const amount = Number(depositAmount.value || 0) * 10 ** swapIn.value.decimals;
440
- const tx = {
441
- chainId,
442
- signerAddress: address,
443
- messages: [
444
- {
445
- typeUrl: '/ibc.applications.transfer.v1.MsgTransfer',
446
- value: {
447
- sourcePort: 'transfer',
448
- sourceChannel: localSourceChannelID.value || '',
449
- token: {
450
- amount: String(amount),
451
- denom: swapIn.value.denom,
452
- },
453
- sender: address,
454
- receiver: osmoAddress(address),
455
- timeoutTimestamp: `${timeout}000000`,
456
- },
457
- },
458
- ],
459
- fee: {
460
- gas: '200000',
461
- amount: [{ amount: '5000', denom: swapIn.value.denom }],
462
- },
463
- memo: '',
464
- signerData: {
465
- chainId: chainId.value,
466
- accountNumber: Number(acc.account.account_number),
467
- sequence: Number(acc.account.sequence),
468
- },
469
- };
470
-
471
- try {
472
- const client = new UniClient(WalletName.Keplr, { chainId });
473
-
474
- // console.log("gasInfo:", gasInfo)
475
- const txRaw = await client.sign(tx);
476
- const response = await client.broadcastTx(props.endpoint, txRaw);
477
- if (response.tx_response?.code === 0) {
478
- setTimeout(async () => {
479
- await getBalance(
480
- OSMOSIS_REST,
481
- osmoAddress(sender.value.cosmosAddress)
482
- ).then((res) => {
483
- osmoBalances.value = res.balances.filter(
484
- (x) => !x.denom.startsWith('gamm')
485
- );
486
- });
487
- await getBalance(
488
- props.endpoint,
489
- localAddress(sender.value.cosmosAddress)
490
- ).then((res) => {
491
- localBalances.value = res.balances;
492
- });
493
- }, 6000);
494
- }
495
- } catch (e) {
496
- sending.value = false;
497
- error.value = e;
498
- setTimeout(() => (error.value = ''), 5000);
499
- }
500
- sending.value = false;
501
- }
502
-
503
- // withdraw logic
504
- const withdrawAmount = ref('');
505
- const recipient = ref(localAddress(sender.value.cosmosAddress))
506
- const disableWithdraw = computed(() => {
507
- const token = swapOut.value;
508
- if (token) {
509
- const b = osmoBalances.value?.find((x) => x.denom === token.ibcDenom);
510
- if (b) {
511
- const amount = Number(withdrawAmount.value || 0);
512
- return (
513
- amount <= 0 || Number(b.amount) < amount * 10 ** token.decimals
514
- );
515
- }
516
- }
517
- return true;
518
- });
519
- async function doWithdraw() {
520
- sending.value = true;
521
- try {
522
- const latest = await getLatestBlock(OSMOSIS_REST);
523
-
524
- const stargateClient = await getSigningOsmosisClient({
525
- rpcEndpoint: OSMOSIS_RPC,
526
- // @ts-ignore
527
- signer: window.getOfflineSigner(latest.block.header.chain_id),
528
- });
529
- const address = osmoAddress(sender.value.cosmosAddress);
530
-
531
- if (!swapIn.value || !swapOut.value || !address) return;
532
-
533
- const timeout = Date.now() + new Date().getTimezoneOffset() + 3600000;
534
- const amount = Number(withdrawAmount.value || 0);
535
- const { transfer } =
536
- ibc.applications.transfer.v1.MessageComposer.withTypeUrl;
537
- const msg = transfer({
538
- sender: address,
539
- sourceChannel: swapOut.value.sourceChannelId || '',
540
- sourcePort: 'transfer',
541
- token: {
542
- amount: (amount * 10 ** swapOut.value.decimals).toFixed(),
543
- denom: swapOut.value.ibcDenom,
544
- },
545
- receiver: recipient.value,
546
- timeoutTimestamp: Long.fromString(`${timeout}000000`),
547
- timeoutHeight: {
548
- revisionHeight: Long.fromNumber(0),
549
- revisionNumber: Long.fromNumber(0),
550
- },
551
- memo: '',
552
- });
553
-
554
- const gas = await stargateClient.simulate(address, [msg], '');
555
-
556
- const fee: StdFee = {
557
- amount: [
558
- {
559
- denom: 'uosmo',
560
- amount: '1864',
561
- },
562
- ],
563
- gas: (gas * 1.25).toFixed(),
564
- };
565
- const response = await stargateClient.signAndBroadcast(
566
- address,
567
- [msg],
568
- fee,
569
- 'Convert on ping.pub'
570
- );
571
- if (response.code === 0) {
572
- await getBalance(
573
- OSMOSIS_REST,
574
- osmoAddress(sender.value.cosmosAddress)
575
- ).then((res) => {
576
- osmoBalances.value = res.balances.filter(
577
- (x) => !x.denom.startsWith('gamm')
578
- );
579
- });
580
- await getBalance(
581
- props.endpoint,
582
- localAddress(sender.value.cosmosAddress)
583
- ).then((res) => {
584
- localBalances.value = res.balances;
585
- });
586
- } else {
587
- if (response.rawLog) error.value = response.rawLog;
588
- }
589
- } catch (err) {
590
- error.value = err
591
- }
592
-
593
- sending.value = false;
594
- }
595
-
596
- async function connect() {
597
- sending.value = true;
598
- let accounts = [] as Account[];
599
- try {
600
- const latest = await getLatestBlock(props.endpoint);
601
- const wa = createWallet(WalletName.Keplr, {
602
- chainId: latest.block.header.chain_id,
603
- hdPath: props.hdPath || DEFAULT_HDPATH,
604
- });
605
- await wa
606
- .getAccounts()
607
- .then((x) => {
608
- accounts = x;
609
- if (accounts.length > 0) {
610
- const [first] = accounts;
611
- const connected = {
612
- wallet: WalletName.Keplr,
613
- cosmosAddress: first.address,
614
- hdPath: props.hdPath || DEFAULT_HDPATH,
615
- };
616
- writeWallet(connected, props.hdPath || DEFAULT_HDPATH);
617
- }
618
- })
619
- .catch((e) => {
620
- error.value = e;
621
- });
622
- initData();
623
- view.value = 'swap'
624
- } catch (e) {
625
- error.value = e.message;
626
- }
627
- sending.value = false;
628
- }
629
-
630
- // Excuting result
631
- const delay = ref(0);
632
- const step = ref(0);
633
- const msg = ref('');
634
- const sleep = 6000;
635
- const emit = defineEmits(['completed']);
636
-
637
- function showResult(hash: string) {
638
- view.value = 'submitting';
639
- delay.value = 1;
640
- step.value = 20;
641
- error.value = '';
642
- msg.value = 'Proccessing...';
643
- setTimeout(() => {
644
- fetchTx(hash);
645
- }, sleep);
646
- }
647
-
648
- function fetchTx(tx: string) {
649
- delay.value += 1;
650
- step.value += 20;
651
- getTxByHash(OSMOSIS_REST, tx)
652
- .then((res) => {
653
- step.value = 100;
654
- if (res.tx_response.code > 0) {
655
- error.value = res.tx_response.raw_log;
656
- } else {
657
- msg.value = `Congratulations! Swap completed successfully.`;
658
- emit('completed', { hash: tx, });
659
- }
660
- })
661
- .catch(() => {
662
- if (delay.value < 5) {
663
- setTimeout(() => fetchTx(tx), sleep);
664
- } else {
665
- error.value = 'Timeout';
666
- }
667
- });
668
- }
669
- </script>
670
- <template>
671
- <div>
672
- <!-- modal content -->
673
- <input v-model="open" type="checkbox" id="PingTokenConvert" class="modal-toggle" @change="initData()" />
674
-
675
- <label for="PingTokenConvert" class="modal cursor-pointer">
676
- <label class="modal-box dark:bg-[#1c1f2e] rounded-lg" for="">
677
- <div v-show="view === 'swap'">
678
- <div class="absolute right-4 top-4 dropdown dropdown-end dropdown-hover">
679
- <label tabindex="0" class="text-info">
680
- <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
681
- class="w-6 h-6 stroke-current">
682
- <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
683
- d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
684
- </svg>
685
- </label>
686
- <div tabindex="0"
687
- class="card compact dropdown-content dark:bg-[#1c1f2e] bg-slate-300 shadow rounded-box w-64 z-40">
688
- <div class="card-body">
689
- <ul class="text-right">
690
- <li>Liquidity is provided by Osmosis</li>
691
- <li>Powered by Ping.pub</li>
692
- </ul>
693
- </div>
694
- </div>
695
- </div>
696
- <h3 class="text-xl font-semibold">Token Convert</h3>
697
- <div v-if="!osmosisPath || chainName === 'osmosis'" class="text-error mt-3">
698
- <span>This feature is not available [{{
699
- chainName
700
- }}]</span>
701
- </div>
702
- <div
703
- class="flex items-center relative h-14 bg-gray-100 dark:bg-[#1c1f2e] rounded-tl-lg rounded-tr-lg mt-4">
704
- <div class="dropdown">
705
- <label tabindex="0" class="flex items-center h-12 px-4 cursor-pointer">
706
- <img :src="swapIn?.coinImageUrl" class="w-8 h-8 mr-3 rounded-full" />
707
- <div class="text-lg font-semibold mr-2">
708
- {{ swapIn?.symbol }}
709
- </div>
710
- <Icon icon="mdi:chevron-down" class="text-lg" />
711
- </label>
712
- <div tabindex="0" class="dropdown-content shadow bg-base-100 dark:bg-[#1c1f2e] rounded-lg w-64 z-40">
713
- <div class="py-2">
714
- <div v-for="(item, index) in inTokens" :key="index"
715
- class="flex items-center px-4 py-2 hover:bg-gray-200 dark:hover:bg-[#232333] cursor-pointer"
716
- @click="selectInput(item)">
717
- <img class="w-7 h-7 rounded-full mr-2" :src="item.coinImageUrl" />
718
- <div class="flex-1 text-sm">
719
- {{ item.symbol }}
720
- </div>
721
- <div class="text-sm font-semibold text-gray-600">
722
- {{
723
- showBalance(
724
- item.ibcDenom || item.denom,
725
- item.decimals
726
- )
727
- }}
728
- </div>
729
- </div>
730
- </div>
731
- </div>
732
- </div>
733
- <input v-model="amountIn" type="number" placeholder="1"
734
- class="input bg-transparent flex-1 h-14 text-right text-lg font-bold" />
735
- </div>
736
-
737
- <div class="flex items-center py-2 px-4 bg-gray-200 dark:bg-[#171721] rounded-bl-lg rounded-br-lg">
738
- <div class="mr-3 text-sm">Balance:</div>
739
- <div class="text-base font-semibold">
740
- {{
741
- showBalance(swapIn?.ibcDenom, swapIn?.decimals)
742
- }}
743
- </div>
744
- <Icon v-if="depositable" icon="mdi:plus-box-outline" class="ml-2 cursor-pointer"
745
- @click="switchView('deposit')" />
746
- </div>
747
-
748
- <!-- switch btn -->
749
- <div class="flex items-center justify-center -mt-3 -mb-3">
750
- <div class="inline-block px-4 cursor-pointer" @click="switchDirection">
751
- <Icon icon="mdi:arrow-down-circle" color="#676cf6"
752
- class="text-4xl dark:bg-gray-50 rounded-full origin-center duration-300 transform hover:rotate-180" />
753
- </div>
754
- </div>
755
-
756
- <div class="flex items-center h-14 rounded-tl-lg rounded-tr-lg bg-gray-100 dark:bg-[#1c1f2e]">
757
- <div v-if="outTokens.length === 0">
758
- <span v-if="error" class="text-red-500">No tradable tokens found.</span>
759
- <button v-else class="btn btn-ghost" :class="{ 'loading relative start-0': sending }">
760
- loading...
761
- </button>
762
- </div>
763
- <div v-if="outTokens && outTokens.length > 0" class="dropdown">
764
- <label tabindex="0" class="flex items-center h-12 px-4 cursor-pointer">
765
- <img :src="swapOut?.coinImageUrl" class="w-8 h-8 mr-3 rounded-full" />
766
- <div class="text-lg font-semibold mr-2">
767
- {{ swapOut?.symbol }}
768
- </div>
769
- <Icon icon="mdi:chevron-down" class="text-lg" />
770
- </label>
771
- <div tabindex="0" class="compact dropdown-content shadow bg-base-100 dark:bg-[#1c1f2e] w-64 rounded-lg">
772
- <div class="py-2 max-h-40 overflow-y-auto">
773
- <div v-for="(item, index) in outTokens" :key="index" @click="selectOutput(item)"
774
- class="flex items-center px-4 py-2 max-h-36 overflow-y-auto hover:bg-gray-200 dark:hover:bg-[#232333] cursor-pointer">
775
- <img class="w-7 h-7 rounded-full mr-2" :src="item.coinImageUrl" />
776
- <div class="flex-1 text-sm">
777
- {{ item.symbol }}
778
- </div>
779
- <div class="text-sm font-semibold text-gray-600">
780
- {{
781
- showBalance(
782
- item.ibcDenom || item.denom,
783
- item.decimals
784
- )
785
- }}
786
- </div>
787
- </div>
788
- </div>
789
- </div>
790
- </div>
791
- <div class="flex-1 w-0 text-xl text-right font-semibold text-gray-600 dark:text-gray-50 pr-4">
792
- {{ `≈ ${parseFloat(outAmount.toFixed(6))}` }}
793
- </div>
794
- </div>
795
-
796
- <div class="flex items-center py-2 px-4 bg-gray-200 dark:bg-[#171721] rounded-bl-lg rounded-br-lg">
797
- <div class="mr-3 text-sm dark:text-gray-400">
798
- Balance:
799
- </div>
800
- <div class="text-base font-semibold dark:text-gray-200">
801
- {{
802
- showBalance(
803
- swapOut?.ibcDenom,
804
- swapOut?.decimals
805
- )
806
- }}
807
- </div>
808
- <Icon v-if="withdrawable" icon="mdi:minus-box-outline" class="ml-2 cursor-pointer"
809
- @click="switchView('withdraw')" />
810
- </div>
811
-
812
- <div class="px-4 mt-4">
813
- <div class="flex items-center justify-between">
814
- <div class="text-sm text-gray-600 dark:text-gray-400">
815
- Swap Fee
816
- </div>
817
- <div class="text-base text-gray-800 dark:text-gray-200">
818
- {{
819
- decimal2percent(pool?.pool_params.swap_fee)
820
- }}%
821
- </div>
822
- </div>
823
- </div>
824
-
825
- <div v-if="error" class="text-error mt-3">
826
- <span>{{ error }}.</span>
827
- </div>
828
-
829
- <div class="mt-5">
830
- <button class="btn btn-primary w-full ping-connect-confirm capitalize text-base"
831
- :disabled="disabled" @click="doSwap">
832
- <span v-if="sending" class="loading loading-spinner"></span>
833
- Convert
834
- </button>
835
- </div>
836
- </div>
837
- <!-- deposit -->
838
- <div v-show="view === 'deposit'">
839
- <h3 class="text-xl font-semibold flex">
840
- <Icon class="mt-1" icon="mdi:chevron-left" @click="switchView('swap')"></Icon>
841
- Deposit
842
- </h3>
843
-
844
- <div class="form-control">
845
- <label class="label">
846
- <span class="label-text">Deposit assets into Osmosis:</span>
847
- <span class="lable-text">{{
848
- showBalance(swapIn?.ibcDenom, swapIn?.decimals)
849
- }}</span>
850
- </label>
851
- <input :value="osmoAddress(sender.cosmosAddress)" readonly type="text"
852
- class="input border border-gray-300 dark:border-[#aac7fc]" />
853
- </div>
854
- <div
855
- class="flex items-center relative h-14 bg-gray-100 dark:bg-[#1c1f2e] rounded-tl-lg rounded-tr-lg mt-4">
856
- <label tabindex="0" class="flex items-center h-12 px-4 cursor-pointer">
857
- <img :src="swapIn?.coinImageUrl" class="w-8 h-8 mr-3 rounded-full" />
858
- <div class="text-lg font-semibold mr-2">
859
- {{ swapIn?.symbol }}
860
- </div>
861
- </label>
862
- <input v-model="depositAmount" type="number" placeholder="1"
863
- class="input bg-transparent flex-1 h-14 text-right text-lg font-bold" />
864
- </div>
865
-
866
- <div
867
- class="flex flex-row-reverse items-center py-2 px-4 bg-gray-200 dark:bg-[#171721] rounded-bl-lg rounded-br-lg">
868
- <div class="text-base font-semibold">
869
- {{
870
- showLocalBalance(
871
- swapIn?.denom,
872
- swapIn?.decimals
873
- )
874
- }}
875
- </div>
876
- <div class="mr-3 text-sm">Balance:</div>
877
- </div>
878
-
879
- <div v-if="error" class="text-error mt-3">
880
- <span>{{ error }}.</span>
881
- </div>
882
-
883
- <div class="mt-5 flex">
884
- <label class="btn dark:bg-[#1f67f5] dark:hover:bg-[#1c1f2e] mr-1" @click="switchView('swap')">
885
- <svg fill="#ffffff" height="20px" width="20px" version="1.1" id="Layer_1"
886
- xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
887
- viewBox="0 0 330 330" xml:space="preserve">
888
- <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
889
- <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
890
- <g id="SVGRepo_iconCarrier">
891
- <path id="XMLID_6_"
892
- d="M165,0C74.019,0,0,74.019,0,165s74.019,165,165,165s165-74.019,165-165S255.981,0,165,0z M205.606,234.394 c5.858,5.857,5.858,15.355,0,21.213C202.678,258.535,198.839,260,195,260s-7.678-1.464-10.606-4.394l-80-79.998 c-2.813-2.813-4.394-6.628-4.394-10.606c0-3.978,1.58-7.794,4.394-10.607l80-80.002c5.857-5.858,15.355-5.858,21.213,0 c5.858,5.857,5.858,15.355,0,21.213l-69.393,69.396L205.606,234.394z">
893
- </path>
894
- </g>
895
- </svg>
896
- </label>
897
- <button class="btn btn-primary grow ping-connect-confirm capitalize text-base"
898
- :disabled="disableDeposit" @click="doDeposit">
899
- <span v-if="sending" class="loading loading-spinner"></span>
900
- Deposit
901
- </button>
902
- </div>
903
- </div>
904
-
905
- <!-- withdraw -->
906
- <div v-show="view === 'withdraw'">
907
- <h3 class="text-xl font-semibold flex">
908
- <Icon class="mt-1" icon="mdi:chevron-left" @click="switchView('swap')"></Icon>
909
- Withdraw
910
- </h3>
911
-
912
- <div
913
- class="flex justify-between j items-center py-2 px-4 bg-gray-200 dark:bg-[#171721] rounded-tl-lg rounded-tr-lg mt-4">
914
- <div class="text-sm">Withdrawable Balance:</div>
915
- <div class="text-base font-semibold">
916
- {{
917
- showBalance(
918
- swapOut?.ibcDenom,
919
- swapOut?.decimals
920
- )
921
- }}
922
- </div>
923
- </div>
924
- <div class="flex items-center relative h-14 bg-gray-100 dark:bg-[#1c1f2e] rounded-bl-lg rounded-br-lg">
925
- <label tabindex="0" class="flex items-center h-12 px-4 cursor-pointer">
926
- <img :src="swapOut?.coinImageUrl" class="w-8 h-8 mr-3 rounded-full" />
927
- <div class="text-lg font-semibold mr-2">
928
- {{ swapOut?.symbol }}
929
- </div>
930
- </label>
931
- <input v-model="withdrawAmount" type="number" placeholder="1"
932
- class="input bg-transparent flex-1 h-14 text-right text-lg font-bold" />
933
- </div>
934
-
935
- <div class="form-control">
936
- <label class="label">
937
- <span class="label-text">Recipient on
938
- {{ localChainInfo.pretty_name }}</span>
939
- <span class="lable-text">{{
940
- showLocalBalance(
941
- swapOut?.denom,
942
- swapOut?.decimals
943
- )
944
- }}</span>
945
- </label>
946
- <input v-model="recipient" type="text" class="input border border-gray-300 dark:border-[#aac7fc]" />
947
- </div>
948
- <div v-if="error" class="text-error mt-3">
949
- <span>{{ error }}.</span>
950
- </div>
951
-
952
- <div class="mt-5 flex">
953
- <label class="btn dark:bg-[#1f67f5] dark:hover:bg-[#1c1f2e] mr-1" @click="switchView('swap')">
954
- <svg fill="#ffffff" height="20px" width="20px" version="1.1" id="Layer_1"
955
- xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
956
- viewBox="0 0 330 330" xml:space="preserve">
957
- <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
958
- <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
959
- <g id="SVGRepo_iconCarrier">
960
- <path id="XMLID_6_"
961
- d="M165,0C74.019,0,0,74.019,0,165s74.019,165,165,165s165-74.019,165-165S255.981,0,165,0z M205.606,234.394 c5.858,5.857,5.858,15.355,0,21.213C202.678,258.535,198.839,260,195,260s-7.678-1.464-10.606-4.394l-80-79.998 c-2.813-2.813-4.394-6.628-4.394-10.606c0-3.978,1.58-7.794,4.394-10.607l80-80.002c5.857-5.858,15.355-5.858,21.213,0 c5.858,5.857,5.858,15.355,0,21.213l-69.393,69.396L205.606,234.394z">
962
- </path>
963
- </g>
964
- </svg>
965
- </label>
966
- <button class="btn btn-primary dark:bg-[#1f67f5] grow ping-connect-confirm capitalize text-base"
967
- :disabled="disableWithdraw" @click="doWithdraw()">
968
- <span v-if="sending" class="loading loading-spinner"></span>
969
- Withdraw
970
- </button>
971
- </div>
972
- </div>
973
-
974
- <!-- Connect Wallet -->
975
- <div v-show="view === 'connect'">
976
- <h3 class="text-xl font-semibold flex dark:text-white">Connect Wallet</h3>
977
- <div class="form-control mt-5 dark:bg-[#1c1f2e]">
978
- <select class="select">
979
- <option>Keplr</option>
980
- </select>
981
- </div>
982
-
983
- <div v-if="error" class="text-error mt-3">
984
- <span>{{ error }}.</span>
985
- </div>
986
-
987
- <div class="mt-5">
988
- <button class="btn btn-primary dark:bg-[#1f67f5] w-full ping-connect-confirm capitalize text-base" @click="connect()">
989
- <span v-if="sending" class="loading loading-spinner"></span>
990
- Connect
991
- </button>
992
- </div>
993
- </div>
994
-
995
- <div v-show="view === 'submitting'">
996
- <div class="mt-10 mb-6">
997
- <div v-if="error" class="my-5 text-center text-red-500">
998
- {{ error }}
999
- </div>
1000
- <div v-else class="my-5 text-center text-lg text-green-500">
1001
- {{ msg }}
1002
- </div>
1003
- <div class="overflow-hidden h-5 mb-2 text-xs flex rounded bg-green-100">
1004
- <div :style="`width: ${step}%`"
1005
- class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-green-400">
1006
- </div>
1007
- </div>
1008
- <div class="flex items-center justify-between">
1009
- <div>
1010
- <span
1011
- class="text-xs font-semibold inline-block py-1 px-2 rounded text-gray-600 dark:text-white">
1012
- Submitted
1013
- </span>
1014
- </div>
1015
- <div class="text-right">
1016
- <span class="text-xs font-semibold inline-block text-gray-600 dark:text-white">
1017
- {{ step }}%
1018
- </span>
1019
- </div>
1020
- </div>
1021
- </div>
1022
- <div class="mb-0 text-center">
1023
- <label class="btn btn-link" @click="view = 'swap'">Continue</label>
1024
- </div>
1025
- </div>
1026
- </label>
1027
- </label>
1028
- </div></template>
1029
- <script lang="ts">
1030
- export default {
1031
- name: 'TokenConvert',
1032
- };
1033
- </script>
1
+ <script setup lang="ts">
2
+ import { computed, onMounted, ref } from 'vue';
3
+ import { Icon } from '@iconify/vue';
4
+ import { fromBech32, toBech32, toHex } from '@cosmjs/encoding';
5
+ import { sha256 } from '@cosmjs/crypto';
6
+ import ChainRegistryClient from '@ping-pub/chain-registry-client';
7
+ import {
8
+ Asset,
9
+ Chain,
10
+ IBCInfo,
11
+ IBCPath,
12
+ } from '@ping-pub/chain-registry-client/dist/types';
13
+ import {
14
+ getBalance,
15
+ getStakingParam,
16
+ getOsmosisPools,
17
+ getLatestBlock,
18
+ getAccount,
19
+ getTxByHash,
20
+ } from '../../utils/http';
21
+ import {
22
+ Account,
23
+ ConnectedWallet,
24
+ WalletName,
25
+ createWallet,
26
+ readWallet,
27
+ writeWallet,
28
+ } from '../../wallet/Wallet';
29
+ import { Coin } from '../../utils/type';
30
+ import { osmosis, ibc, getSigningOsmosisClient } from 'osmojs';
31
+ import { tokens, type TokenConfig } from './tokens';
32
+ import { decimal2percent } from '../../utils/format';
33
+ import { StdFee, coin } from '@cosmjs/stargate';
34
+ import Long from 'long';
35
+ import { UniClient } from '../../wallet/UniClient';
36
+
37
+ const props = defineProps({
38
+ chainName: { type: String, required: true },
39
+ endpoint: { type: String, required: true },
40
+ hdPath: String,
41
+ });
42
+ const OSMOSIS_RPC = 'https://rpc.osmosis.zone';
43
+ const OSMOSIS_REST = 'https://lcd.osmosis.zone';
44
+ const DEFAULT_HDPATH = "m/44'/118/0'/0/0";
45
+
46
+ const view = ref('connect'); // [connect, swap, deposit, withdraw]
47
+
48
+ function switchView(v: string) {
49
+ view.value = v;
50
+ }
51
+
52
+ // variables
53
+ const direction = ref('buy');
54
+ const sending = ref(false); // show status on send tx
55
+ const open = ref(false);
56
+ const error = ref('');
57
+ const chains = ref([] as IBCPath[]);
58
+ const osmosisPath = ref({} as IBCPath | undefined);
59
+ const osmosisPathInfo = ref({} as IBCInfo);
60
+ const defaultDenom = ref('');
61
+ const sender = ref({} as ConnectedWallet);
62
+ const localBalances = ref([] as Coin[]);
63
+ const osmoBalances = ref([] as Coin[]);
64
+ const localChainInfo = ref({} as Chain);
65
+ const localCoinInfo = ref([] as Asset[]);
66
+ const swapIn = ref({} as TokenConfig | undefined);
67
+ const swapOut = ref({} as TokenConfig | undefined);
68
+ const amountIn = ref('');
69
+ const allPools = ref([] as any[]);
70
+ const client = new ChainRegistryClient();
71
+
72
+ // swap logic
73
+
74
+ async function initData() {
75
+ error.value = ''; //reset error
76
+ sender.value = readWallet(props.hdPath);
77
+ if (!sender.value.cosmosAddress) {
78
+ view.value = 'connect';
79
+ return;
80
+ }
81
+ if (open.value) {
82
+ view.value = 'swap';
83
+ localChainInfo.value = {} as Chain;
84
+ localCoinInfo.value = [];
85
+ recipient.value = localAddress(sender.value.cosmosAddress)
86
+ await client
87
+ .fetchChainInfo(props.chainName)
88
+ .then((res) => {
89
+ localChainInfo.value = res;
90
+ if (Number(res.slip44) !== 118) {
91
+ error.value === `Coin type ${res.slip44} is not supported`;
92
+ }
93
+ })
94
+ .catch(() => {
95
+ error.value = 'Not found IBC Path';
96
+ });
97
+ getBalance(OSMOSIS_REST, osmoAddress(sender.value.cosmosAddress)).then(
98
+ (res) => {
99
+ osmoBalances.value = res.balances.filter(
100
+ (x) => !x.denom.startsWith('gamm')
101
+ );
102
+ }
103
+ );
104
+ getBalance(
105
+ props.endpoint,
106
+ localAddress(sender.value.cosmosAddress)
107
+ ).then((res) => {
108
+ localBalances.value = res.balances;
109
+ });
110
+
111
+ client.fetchAssetsList(props.chainName).then((al) => {
112
+ localCoinInfo.value = al.assets;
113
+ });
114
+
115
+ getStakingParam(props.endpoint).then((x) => {
116
+ defaultDenom.value = x.params.bond_denom;
117
+ });
118
+ getOsmosisPools(OSMOSIS_REST).then((res) => {
119
+ allPools.value = res.pools;
120
+ });
121
+
122
+ client.fetchIBCPaths().then((paths) => {
123
+ chains.value = paths.filter(
124
+ (x) => x.from === props.chainName || x.to === props.chainName
125
+ );
126
+ const path = chains.value.find(
127
+ (x) => x.from === 'osmosis' || x.to === 'osmosis'
128
+ );
129
+ if (path) {
130
+ osmosisPath.value = path;
131
+ client.fetchIBCPathInfo(path.path).then((info) => {
132
+ osmosisPathInfo.value = info;
133
+ });
134
+ }
135
+ });
136
+ }
137
+ }
138
+
139
+ function osmoAddress(addr?: string) {
140
+ if (!addr) return '';
141
+ const { data } = fromBech32(addr);
142
+ return toBech32('osmo', data);
143
+ }
144
+
145
+ function localAddress(addr?: string) {
146
+ if (!addr) return '';
147
+ const { data } = fromBech32(addr);
148
+ const prefix = localChainInfo.value?.bech32_prefix || 'cosmos';
149
+ return toBech32(prefix, data);
150
+ }
151
+
152
+ function showBalance(denom?: string, decimal = 0) {
153
+ const bal = osmoBalances.value.find((x) => x.denom === denom || '');
154
+ if (bal) {
155
+ return Number(bal.amount) / 10 ** decimal;
156
+ }
157
+ return 0;
158
+ }
159
+
160
+ function findlocalCoinDecimal(denom: string) {
161
+ const localCoin = localCoinInfo.value.find((x) => x.base === denom);
162
+ if (localCoin) {
163
+ const unit = localCoin.denom_units.find(
164
+ (x) => x.denom === localCoin.symbol.toLowerCase()
165
+ );
166
+ if (unit) return unit.exponent;
167
+ }
168
+ return 0;
169
+ }
170
+
171
+ function findTokenUrl(coinInfo?: Asset) {
172
+ const url =
173
+ coinInfo?.logo_URIs?.svg ||
174
+ coinInfo?.logo_URIs?.png ||
175
+ coinInfo?.logo_URIs?.jpeg;
176
+ return url
177
+ ? url.replace(
178
+ 'https://raw.githubusercontent.com/cosmos/chain-registry/master',
179
+ 'https://registry.ping.pub'
180
+ )
181
+ : '';
182
+ }
183
+
184
+ const localTokenOnOsmosis = computed(() => {
185
+ const channel = osmosisPathInfo.value.channels?.find(
186
+ (x) => x.chain_1.port_id === 'transfer'
187
+ );
188
+ const channelId =
189
+ osmosisPathInfo.value.chain_1?.chain_name === 'osmosis'
190
+ ? channel?.chain_1.channel_id
191
+ : channel?.chain_2.channel_id;
192
+ const ibcDenom = toHex(
193
+ sha256(
194
+ new TextEncoder().encode(
195
+ `transfer/${channelId}/${defaultDenom.value}`
196
+ )
197
+ )
198
+ ).toUpperCase();
199
+ return localCoinInfo.value?.map((localCoin) => ({
200
+ denom: defaultDenom.value,
201
+ symbol: localCoin.symbol || '',
202
+ ibcDenom: `ibc/${ibcDenom}`,
203
+ sourceChannelId: channelId,
204
+ decimals: findlocalCoinDecimal(defaultDenom.value),
205
+ coinImageUrl: findTokenUrl(localCoin),
206
+ }));
207
+ });
208
+
209
+ const localSourceChannelID = computed(() => {
210
+ const channel = osmosisPathInfo.value.channels?.find(
211
+ (x) => x.chain_1.port_id === 'transfer'
212
+ );
213
+ const channelId =
214
+ osmosisPathInfo.value.chain_1?.chain_name === 'osmosis'
215
+ ? channel?.chain_2.channel_id
216
+ : channel?.chain_1.channel_id;
217
+ return channelId;
218
+ });
219
+
220
+ const withdrawable = computed(() => {
221
+ return (
222
+ localTokenOnOsmosis.value.findIndex(
223
+ (x) => x.denom === swapOut.value?.denom
224
+ ) > -1
225
+ );
226
+ });
227
+
228
+ const depositable = computed(() => {
229
+ return (
230
+ localTokenOnOsmosis.value.findIndex(
231
+ (x) => x.denom === swapIn.value?.denom
232
+ ) > -1
233
+ );
234
+ });
235
+
236
+ const inTokens = computed(() => {
237
+ if (direction.value === 'buy') {
238
+ swapIn.value = tokens[0];
239
+ return tokens;
240
+ } else {
241
+ swapIn.value = localTokenOnOsmosis.value[0];
242
+ return localTokenOnOsmosis.value;
243
+ }
244
+ });
245
+
246
+ const outTokens = computed(() => {
247
+ if (direction.value === 'buy') {
248
+ swapOut.value = localTokenOnOsmosis.value[0];
249
+ return localTokenOnOsmosis.value;
250
+ } else {
251
+ swapOut.value = tokens[0];
252
+ return tokens;
253
+ }
254
+ });
255
+
256
+ function selectInput(v) {
257
+ swapIn.value = v;
258
+ amountIn.value = '';
259
+ }
260
+
261
+ function selectOutput(v) {
262
+ swapOut.value = v;
263
+ amountIn.value = '';
264
+ }
265
+
266
+ const pool = computed(() => {
267
+ // find the available pools and sort by liquidity depth
268
+ const a = allPools.value
269
+ .filter(
270
+ (x) =>
271
+ x.pool_assets?.findIndex(
272
+ (a) => a.token.denom === swapIn.value?.ibcDenom
273
+ ) > -1
274
+ )
275
+ .filter(
276
+ (x) =>
277
+ x.pool_assets?.findIndex(
278
+ (a) => a.token.denom === swapOut.value?.ibcDenom
279
+ ) > -1
280
+ )
281
+ .sort(
282
+ (a, b) =>
283
+ Number(b.total_shares.amount) - Number(a.total_shares.amount)
284
+ );
285
+ // console.log(a)
286
+ return a.length > 0 ? a[0] : null;
287
+ });
288
+
289
+ function switchDirection() {
290
+ direction.value = direction.value === 'buy' ? 'sell' : 'buy';
291
+ }
292
+
293
+ const disabled = computed(() => {
294
+ const amount = Number(amountIn.value || 0);
295
+ if (amount <= 0 || outAmount.value <= 0) return true;
296
+ const token = swapIn.value;
297
+ const b = osmoBalances.value.find((x) => x.denom === token?.ibcDenom || '');
298
+ if (b && token) {
299
+ return Number(b.amount) < amount * 10 ** token.decimals;
300
+ }
301
+ return true;
302
+ });
303
+
304
+ const outAmount = computed(() => {
305
+ // tokenBalanceOut * [1 - { tokenBalanceIn / (tokenBalanceIn + (1 - swapFee) * tokenAmountIn)} ^ (tokenWeightIn / tokenWeightOut)]
306
+ const p = pool.value;
307
+ if (p) {
308
+ const tokenBalanceOut = p.pool_assets?.find(
309
+ (x) => x.token.denom === swapOut.value?.ibcDenom
310
+ );
311
+ const tokenBalanceIn = p.pool_assets?.find(
312
+ (x) => x.token.denom === swapIn.value?.ibcDenom
313
+ );
314
+ if (tokenBalanceIn && tokenBalanceOut) {
315
+ const balanceOut = Number(tokenBalanceOut.token.amount);
316
+ const balanceIn = Number(tokenBalanceIn.token.amount);
317
+ const weightOut = Number(tokenBalanceOut.weight);
318
+ const weightIn = Number(tokenBalanceIn.weight);
319
+ const swapFee = Number(p.pool_params.swap_fee);
320
+ const amount = Number(amountIn.value) * (1 - swapFee);
321
+ const out =
322
+ balanceOut *
323
+ (1 -
324
+ balanceIn / (balanceIn + amount) ** (weightIn / weightOut));
325
+ return out;
326
+ }
327
+ }
328
+ return 0;
329
+ });
330
+
331
+ async function doSwap() {
332
+ sending.value = true;
333
+ try {
334
+ const latest = await getLatestBlock(OSMOSIS_REST);
335
+
336
+ const stargateClient = await getSigningOsmosisClient({
337
+ rpcEndpoint: OSMOSIS_RPC,
338
+ // @ts-ignore
339
+ signer: window.getOfflineSigner(latest.block.header.chain_id),
340
+ });
341
+ const address = osmoAddress(sender.value.cosmosAddress);
342
+
343
+ if (!swapIn.value || !swapOut.value || !address) return;
344
+ const { swapExactAmountIn } =
345
+ osmosis.gamm.v1beta1.MessageComposer.withTypeUrl;
346
+
347
+ const amount = Number(amountIn.value || 0);
348
+ const msg = swapExactAmountIn({
349
+ sender: address,
350
+ routes: [
351
+ {
352
+ poolId: Long.fromNumber(pool.value.id),
353
+ tokenOutDenom: swapOut.value.ibcDenom,
354
+ },
355
+ ],
356
+ tokenIn: {
357
+ amount: (amount * 10 ** swapIn.value.decimals).toFixed(),
358
+ denom: swapIn.value.ibcDenom,
359
+ },
360
+ tokenOutMinAmount: (
361
+ outAmount.value *
362
+ 0.99 *
363
+ 10 ** swapOut.value.decimals
364
+ ).toFixed(), // slippage: 1%
365
+ });
366
+
367
+ const gas = await stargateClient.simulate(address, [msg], '');
368
+
369
+ const fee: StdFee = {
370
+ amount: [
371
+ {
372
+ denom: 'uosmo',
373
+ amount: '864',
374
+ },
375
+ ],
376
+ gas: (gas * 1.25).toFixed(),
377
+ };
378
+ const response = await stargateClient.signAndBroadcast(
379
+ address,
380
+ [msg],
381
+ fee,
382
+ 'Token Convert from mictonode.com'
383
+ );
384
+ if (response.code === 0) {
385
+ await showResult(response.transactionHash)
386
+ await getBalance(
387
+ OSMOSIS_REST,
388
+ osmoAddress(sender.value.cosmosAddress)
389
+ ).then((res) => {
390
+ osmoBalances.value = res.balances.filter(
391
+ (x) => !x.denom.startsWith('gamm')
392
+ );
393
+ });
394
+ } else {
395
+ if (response.rawLog) error.value = response.rawLog;
396
+ }
397
+ } catch (err) {
398
+ error.value = err;
399
+ }
400
+ sending.value = false;
401
+ }
402
+
403
+ // deposit logic
404
+ const depositAmount = ref('');
405
+
406
+ function showLocalBalance(denom = '', decimals = 0) {
407
+ const b = localBalances.value?.find((x) => x.denom === denom);
408
+ if (b) {
409
+ return Number(b.amount) / 10 ** decimals;
410
+ }
411
+ return 0;
412
+ }
413
+ const disableDeposit = computed(() => {
414
+ const token = swapIn.value;
415
+ if (token) {
416
+ const b = localBalances.value?.find((x) => x.denom === token.denom);
417
+ if (b) {
418
+ const amount = Number(depositAmount.value || 0);
419
+ return (
420
+ amount <= 0 || Number(b.amount) < amount * 10 ** token.decimals
421
+ );
422
+ }
423
+ }
424
+ return true;
425
+ });
426
+
427
+ async function doDeposit() {
428
+ sending.value = true;
429
+
430
+ const address = localAddress(sender.value.cosmosAddress);
431
+
432
+ if (!swapIn.value || !address) return;
433
+
434
+ const latest = await getLatestBlock(props.endpoint);
435
+ const acc = await getAccount(props.endpoint, address);
436
+
437
+ const chainId = latest.block.header.chain_id;
438
+ const timeout = Date.now() + new Date().getTimezoneOffset() + 3600000;
439
+ const amount = Number(depositAmount.value || 0) * 10 ** swapIn.value.decimals;
440
+ const tx = {
441
+ chainId,
442
+ signerAddress: address,
443
+ messages: [
444
+ {
445
+ typeUrl: '/ibc.applications.transfer.v1.MsgTransfer',
446
+ value: {
447
+ sourcePort: 'transfer',
448
+ sourceChannel: localSourceChannelID.value || '',
449
+ token: {
450
+ amount: String(amount),
451
+ denom: swapIn.value.denom,
452
+ },
453
+ sender: address,
454
+ receiver: osmoAddress(address),
455
+ timeoutTimestamp: `${timeout}000000`,
456
+ },
457
+ },
458
+ ],
459
+ fee: {
460
+ gas: '200000',
461
+ amount: [{ amount: '5000', denom: swapIn.value.denom }],
462
+ },
463
+ memo: '',
464
+ signerData: {
465
+ chainId: chainId.value,
466
+ accountNumber: Number(acc.account.account_number),
467
+ sequence: Number(acc.account.sequence),
468
+ },
469
+ };
470
+
471
+ try {
472
+ const client = new UniClient(WalletName.Keplr, { chainId });
473
+
474
+ // console.log("gasInfo:", gasInfo)
475
+ const txRaw = await client.sign(tx);
476
+ const response = await client.broadcastTx(props.endpoint, txRaw);
477
+ if (response.tx_response?.code === 0) {
478
+ setTimeout(async () => {
479
+ await getBalance(
480
+ OSMOSIS_REST,
481
+ osmoAddress(sender.value.cosmosAddress)
482
+ ).then((res) => {
483
+ osmoBalances.value = res.balances.filter(
484
+ (x) => !x.denom.startsWith('gamm')
485
+ );
486
+ });
487
+ await getBalance(
488
+ props.endpoint,
489
+ localAddress(sender.value.cosmosAddress)
490
+ ).then((res) => {
491
+ localBalances.value = res.balances;
492
+ });
493
+ }, 6000);
494
+ }
495
+ } catch (e) {
496
+ sending.value = false;
497
+ error.value = e;
498
+ setTimeout(() => (error.value = ''), 5000);
499
+ }
500
+ sending.value = false;
501
+ }
502
+
503
+ // withdraw logic
504
+ const withdrawAmount = ref('');
505
+ const recipient = ref(localAddress(sender.value.cosmosAddress))
506
+ const disableWithdraw = computed(() => {
507
+ const token = swapOut.value;
508
+ if (token) {
509
+ const b = osmoBalances.value?.find((x) => x.denom === token.ibcDenom);
510
+ if (b) {
511
+ const amount = Number(withdrawAmount.value || 0);
512
+ return (
513
+ amount <= 0 || Number(b.amount) < amount * 10 ** token.decimals
514
+ );
515
+ }
516
+ }
517
+ return true;
518
+ });
519
+ async function doWithdraw() {
520
+ sending.value = true;
521
+ try {
522
+ const latest = await getLatestBlock(OSMOSIS_REST);
523
+
524
+ const stargateClient = await getSigningOsmosisClient({
525
+ rpcEndpoint: OSMOSIS_RPC,
526
+ // @ts-ignore
527
+ signer: window.getOfflineSigner(latest.block.header.chain_id),
528
+ });
529
+ const address = osmoAddress(sender.value.cosmosAddress);
530
+
531
+ if (!swapIn.value || !swapOut.value || !address) return;
532
+
533
+ const timeout = Date.now() + new Date().getTimezoneOffset() + 3600000;
534
+ const amount = Number(withdrawAmount.value || 0);
535
+ const { transfer } =
536
+ ibc.applications.transfer.v1.MessageComposer.withTypeUrl;
537
+ const msg = transfer({
538
+ sender: address,
539
+ sourceChannel: swapOut.value.sourceChannelId || '',
540
+ sourcePort: 'transfer',
541
+ token: {
542
+ amount: (amount * 10 ** swapOut.value.decimals).toFixed(),
543
+ denom: swapOut.value.ibcDenom,
544
+ },
545
+ receiver: recipient.value,
546
+ timeoutTimestamp: Long.fromString(`${timeout}000000`),
547
+ timeoutHeight: {
548
+ revisionHeight: Long.fromNumber(0),
549
+ revisionNumber: Long.fromNumber(0),
550
+ },
551
+ memo: '',
552
+ });
553
+
554
+ const gas = await stargateClient.simulate(address, [msg], '');
555
+
556
+ const fee: StdFee = {
557
+ amount: [
558
+ {
559
+ denom: 'uosmo',
560
+ amount: '1864',
561
+ },
562
+ ],
563
+ gas: (gas * 1.25).toFixed(),
564
+ };
565
+ const response = await stargateClient.signAndBroadcast(
566
+ address,
567
+ [msg],
568
+ fee,
569
+ 'Convert on ping.pub'
570
+ );
571
+ if (response.code === 0) {
572
+ await getBalance(
573
+ OSMOSIS_REST,
574
+ osmoAddress(sender.value.cosmosAddress)
575
+ ).then((res) => {
576
+ osmoBalances.value = res.balances.filter(
577
+ (x) => !x.denom.startsWith('gamm')
578
+ );
579
+ });
580
+ await getBalance(
581
+ props.endpoint,
582
+ localAddress(sender.value.cosmosAddress)
583
+ ).then((res) => {
584
+ localBalances.value = res.balances;
585
+ });
586
+ } else {
587
+ if (response.rawLog) error.value = response.rawLog;
588
+ }
589
+ } catch (err) {
590
+ error.value = err
591
+ }
592
+
593
+ sending.value = false;
594
+ }
595
+
596
+ async function connect() {
597
+ sending.value = true;
598
+ let accounts = [] as Account[];
599
+ try {
600
+ const latest = await getLatestBlock(props.endpoint);
601
+ const wa = createWallet(WalletName.Keplr, {
602
+ chainId: latest.block.header.chain_id,
603
+ hdPath: props.hdPath || DEFAULT_HDPATH,
604
+ });
605
+ await wa
606
+ .getAccounts()
607
+ .then((x) => {
608
+ accounts = x;
609
+ if (accounts.length > 0) {
610
+ const [first] = accounts;
611
+ const connected = {
612
+ wallet: WalletName.Keplr,
613
+ cosmosAddress: first.address,
614
+ hdPath: props.hdPath || DEFAULT_HDPATH,
615
+ };
616
+ writeWallet(connected, props.hdPath || DEFAULT_HDPATH);
617
+ }
618
+ })
619
+ .catch((e) => {
620
+ error.value = e;
621
+ });
622
+ initData();
623
+ view.value = 'swap'
624
+ } catch (e) {
625
+ error.value = e.message;
626
+ }
627
+ sending.value = false;
628
+ }
629
+
630
+ // Excuting result
631
+ const delay = ref(0);
632
+ const step = ref(0);
633
+ const msg = ref('');
634
+ const sleep = 6000;
635
+ const emit = defineEmits(['completed']);
636
+
637
+ function showResult(hash: string) {
638
+ view.value = 'submitting';
639
+ delay.value = 1;
640
+ step.value = 20;
641
+ error.value = '';
642
+ msg.value = 'Proccessing...';
643
+ setTimeout(() => {
644
+ fetchTx(hash);
645
+ }, sleep);
646
+ }
647
+
648
+ function fetchTx(tx: string) {
649
+ delay.value += 1;
650
+ step.value += 20;
651
+ getTxByHash(OSMOSIS_REST, tx)
652
+ .then((res) => {
653
+ step.value = 100;
654
+ if (res.tx_response.code > 0) {
655
+ error.value = res.tx_response.raw_log;
656
+ } else {
657
+ msg.value = `Congratulations! Swap completed successfully.`;
658
+ emit('completed', { hash: tx, });
659
+ }
660
+ })
661
+ .catch(() => {
662
+ if (delay.value < 5) {
663
+ setTimeout(() => fetchTx(tx), sleep);
664
+ } else {
665
+ error.value = 'Timeout';
666
+ }
667
+ });
668
+ }
669
+ </script>
670
+ <template>
671
+ <div>
672
+ <!-- modal content -->
673
+ <input v-model="open" type="checkbox" id="PingTokenConvert" class="modal-toggle" @change="initData()" />
674
+
675
+ <label for="PingTokenConvert" class="modal cursor-pointer">
676
+ <label class="modal-box rounded-lg" for="">
677
+ <div v-show="view === 'swap'">
678
+ <div class="absolute right-4 top-4 dropdown dropdown-end dropdown-hover">
679
+ <label tabindex="0" class="text-info">
680
+ <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
681
+ class="w-6 h-6 stroke-current">
682
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
683
+ d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
684
+ </svg>
685
+ </label>
686
+ <div tabindex="0"
687
+ class="card dropdown-content bg-slate-300 shadow-lg rounded-lg w-64 z-40">
688
+ <div class="card-body">
689
+ <ul class="text-right">
690
+ <li>Liquidity is provided by Osmosis</li>
691
+ <li>Powered by Ping.pub</li>
692
+ </ul>
693
+ </div>
694
+ </div>
695
+ </div>
696
+ <h3 class="text-xl font-semibold">Token Convert</h3>
697
+ <div v-if="!osmosisPath || chainName === 'osmosis'" class="text-error mt-3">
698
+ <span>This feature is not available [{{
699
+ chainName
700
+ }}]</span>
701
+ </div>
702
+ <div
703
+ class="flex items-center relative h-14 bg-gray-100 dark:bg-[#0f1419] rounded-tl-lg rounded-tr-lg mt-4">
704
+ <div class="dropdown">
705
+ <label tabindex="0" class="flex items-center h-12 px-4 cursor-pointer">
706
+ <img :src="swapIn?.coinImageUrl" class="w-8 h-8 mr-3 rounded-full" />
707
+ <div class="text-lg font-semibold mr-2">
708
+ {{ swapIn?.symbol }}
709
+ </div>
710
+ <Icon icon="mdi:chevron-down" class="text-lg" />
711
+ </label>
712
+ <div tabindex="0" class="dropdown-content shadow rounded-lg w-64 z-40">
713
+ <div class="py-2">
714
+ <div v-for="(item, index) in inTokens" :key="index"
715
+ class="flex items-center px-4 py-2 hover:bg-gray-200 dark:hover:bg-neutral-800 cursor-pointer"
716
+ @click="selectInput(item)">
717
+ <img class="w-7 h-7 rounded-full mr-2" :src="item.coinImageUrl" />
718
+ <div class="flex-1 text-sm">
719
+ {{ item.symbol }}
720
+ </div>
721
+ <div class="text-sm font-semibold text-gray-600">
722
+ {{
723
+ showBalance(
724
+ item.ibcDenom || item.denom,
725
+ item.decimals
726
+ )
727
+ }}
728
+ </div>
729
+ </div>
730
+ </div>
731
+ </div>
732
+ </div>
733
+ <input v-model="amountIn" type="number" placeholder="1"
734
+ class="input bg-transparent flex-1 h-14 text-right text-lg font-bold" />
735
+ </div>
736
+
737
+ <div class="flex items-center py-2 px-4 bg-gray-200 dark:bg-neutral-900 rounded-bl-lg rounded-br-lg">
738
+ <div class="mr-3 text-sm">Balance:</div>
739
+ <div class="text-base font-semibold">
740
+ {{
741
+ showBalance(swapIn?.ibcDenom, swapIn?.decimals)
742
+ }}
743
+ </div>
744
+ <Icon v-if="depositable" icon="mdi:plus-box-outline" class="ml-2 cursor-pointer"
745
+ @click="switchView('deposit')" />
746
+ </div>
747
+
748
+ <!-- switch btn -->
749
+ <div class="flex items-center justify-center -mt-3 -mb-3">
750
+ <div class="inline-block px-4 cursor-pointer" @click="switchDirection">
751
+ <Icon icon="mdi:arrow-down-circle" color="#676cf6"
752
+ class="text-4xl dark:bg-gray-50 rounded-full origin-center duration-300 transform hover:rotate-180" />
753
+ </div>
754
+ </div>
755
+
756
+ <div class="flex items-center h-14 rounded-tl-lg rounded-tr-lg bg-gray-100 dark:bg-[#0f1419]">
757
+ <div v-if="outTokens.length === 0">
758
+ <span v-if="error" class="text-red-500">No tradable tokens found.</span>
759
+ <button v-else class="btn btn-ghost" :class="{ 'loading relative start-0': sending }">
760
+ loading...
761
+ </button>
762
+ </div>
763
+ <div v-if="outTokens && outTokens.length > 0" class="dropdown">
764
+ <label tabindex="0" class="flex items-center h-12 px-4 cursor-pointer">
765
+ <img :src="swapOut?.coinImageUrl" class="w-8 h-8 mr-3 rounded-full" />
766
+ <div class="text-lg font-semibold mr-2">
767
+ {{ swapOut?.symbol }}
768
+ </div>
769
+ <Icon icon="mdi:chevron-down" class="text-lg" />
770
+ </label>
771
+ <div tabindex="0" class="dropdown-content shadow w-64 rounded-lg">
772
+ <div class="py-2 max-h-40 overflow-y-auto">
773
+ <div v-for="(item, index) in outTokens" :key="index" @click="selectOutput(item)"
774
+ class="flex items-center px-4 py-2 max-h-36 overflow-y-auto hover:bg-gray-200 dark:hover:bg-neutral-800 cursor-pointer">
775
+ <img class="w-7 h-7 rounded-full mr-2" :src="item.coinImageUrl" />
776
+ <div class="flex-1 text-sm">
777
+ {{ item.symbol }}
778
+ </div>
779
+ <div class="text-sm font-semibold text-gray-600">
780
+ {{
781
+ showBalance(
782
+ item.ibcDenom || item.denom,
783
+ item.decimals
784
+ )
785
+ }}
786
+ </div>
787
+ </div>
788
+ </div>
789
+ </div>
790
+ </div>
791
+ <div class="flex-1 w-0 text-xl text-right font-semibold text-gray-600 dark:text-gray-50 pr-4">
792
+ {{ `≈ ${parseFloat(outAmount.toFixed(6))}` }}
793
+ </div>
794
+ </div>
795
+
796
+ <div class="flex items-center py-2 px-4 bg-gray-200 dark:bg-neutral-900 rounded-bl-lg rounded-br-lg">
797
+ <div class="mr-3 text-sm dark:text-gray-400">
798
+ Balance:
799
+ </div>
800
+ <div class="text-base font-semibold dark:text-gray-200">
801
+ {{
802
+ showBalance(
803
+ swapOut?.ibcDenom,
804
+ swapOut?.decimals
805
+ )
806
+ }}
807
+ </div>
808
+ <Icon v-if="withdrawable" icon="mdi:minus-box-outline" class="ml-2 cursor-pointer"
809
+ @click="switchView('withdraw')" />
810
+ </div>
811
+
812
+ <div class="px-4 mt-4">
813
+ <div class="flex items-center justify-between">
814
+ <div class="text-sm text-gray-600 dark:text-gray-400">
815
+ Swap Fee
816
+ </div>
817
+ <div class="text-base text-gray-800 dark:text-gray-200">
818
+ {{
819
+ decimal2percent(pool?.pool_params.swap_fee)
820
+ }}%
821
+ </div>
822
+ </div>
823
+ </div>
824
+
825
+ <div v-if="error" class="text-error mt-3">
826
+ <span>{{ error }}.</span>
827
+ </div>
828
+
829
+ <div class="mt-5">
830
+ <button class="btn btn-primary w-full ping-connect-confirm capitalize text-base"
831
+ :disabled="disabled" @click="doSwap">
832
+ <span v-if="sending" class="loading loading-spinner"></span>
833
+ Convert
834
+ </button>
835
+ </div>
836
+ </div>
837
+ <!-- deposit -->
838
+ <div v-show="view === 'deposit'">
839
+ <h3 class="text-xl font-semibold flex">
840
+ <Icon class="mt-1" icon="mdi:chevron-left" @click="switchView('swap')"></Icon>
841
+ Deposit
842
+ </h3>
843
+
844
+ <div class="form-control">
845
+ <label class="label">
846
+ <span class="label-text">Deposit assets into Osmosis:</span>
847
+ <span class="lable-text">{{
848
+ showBalance(swapIn?.ibcDenom, swapIn?.decimals)
849
+ }}</span>
850
+ </label>
851
+ <input :value="osmoAddress(sender.cosmosAddress)" readonly type="text"
852
+ class="input" />
853
+ </div>
854
+ <div
855
+ class="flex items-center relative h-14 bg-gray-100 dark:bg-[#0f1419] rounded-tl-lg rounded-tr-lg mt-4">
856
+ <label tabindex="0" class="flex items-center h-12 px-4 cursor-pointer">
857
+ <img :src="swapIn?.coinImageUrl" class="w-8 h-8 mr-3 rounded-full" />
858
+ <div class="text-lg font-semibold mr-2">
859
+ {{ swapIn?.symbol }}
860
+ </div>
861
+ </label>
862
+ <input v-model="depositAmount" type="number" placeholder="1"
863
+ class="input bg-transparent flex-1 h-14 text-right text-lg font-bold" />
864
+ </div>
865
+
866
+ <div
867
+ class="flex flex-row-reverse items-center py-2 px-4 bg-gray-200 dark:bg-neutral-900 rounded-bl-lg rounded-br-lg">
868
+ <div class="text-base font-semibold">
869
+ {{
870
+ showLocalBalance(
871
+ swapIn?.denom,
872
+ swapIn?.decimals
873
+ )
874
+ }}
875
+ </div>
876
+ <div class="mr-3 text-sm">Balance:</div>
877
+ </div>
878
+
879
+ <div v-if="error" class="text-error mt-3">
880
+ <span>{{ error }}.</span>
881
+ </div>
882
+
883
+ <div class="mt-5 flex">
884
+ <label class="btn mr-1" @click="switchView('swap')">
885
+ <svg fill="#ffffff" height="20px" width="20px" version="1.1" id="Layer_1"
886
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
887
+ viewBox="0 0 330 330" xml:space="preserve">
888
+ <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
889
+ <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
890
+ <g id="SVGRepo_iconCarrier">
891
+ <path id="XMLID_6_"
892
+ d="M165,0C74.019,0,0,74.019,0,165s74.019,165,165,165s165-74.019,165-165S255.981,0,165,0z M205.606,234.394 c5.858,5.857,5.858,15.355,0,21.213C202.678,258.535,198.839,260,195,260s-7.678-1.464-10.606-4.394l-80-79.998 c-2.813-2.813-4.394-6.628-4.394-10.606c0-3.978,1.58-7.794,4.394-10.607l80-80.002c5.857-5.858,15.355-5.858,21.213,0 c5.858,5.857,5.858,15.355,0,21.213l-69.393,69.396L205.606,234.394z">
893
+ </path>
894
+ </g>
895
+ </svg>
896
+ </label>
897
+ <button class="btn btn-primary grow ping-connect-confirm capitalize text-base"
898
+ :disabled="disableDeposit" @click="doDeposit">
899
+ <span v-if="sending" class="loading loading-spinner"></span>
900
+ Deposit
901
+ </button>
902
+ </div>
903
+ </div>
904
+
905
+ <!-- withdraw -->
906
+ <div v-show="view === 'withdraw'">
907
+ <h3 class="text-xl font-semibold flex">
908
+ <Icon class="mt-1" icon="mdi:chevron-left" @click="switchView('swap')"></Icon>
909
+ Withdraw
910
+ </h3>
911
+
912
+ <div
913
+ class="flex justify-between j items-center py-2 px-4 bg-gray-200 dark:bg-neutral-900 rounded-tl-lg rounded-tr-lg mt-4">
914
+ <div class="text-sm">Withdrawable Balance:</div>
915
+ <div class="text-base font-semibold">
916
+ {{
917
+ showBalance(
918
+ swapOut?.ibcDenom,
919
+ swapOut?.decimals
920
+ )
921
+ }}
922
+ </div>
923
+ </div>
924
+ <div class="flex items-center relative h-14 bg-gray-100 dark:bg-[#0f1419] rounded-bl-lg rounded-br-lg">
925
+ <label tabindex="0" class="flex items-center h-12 px-4 cursor-pointer">
926
+ <img :src="swapOut?.coinImageUrl" class="w-8 h-8 mr-3 rounded-full" />
927
+ <div class="text-lg font-semibold mr-2">
928
+ {{ swapOut?.symbol }}
929
+ </div>
930
+ </label>
931
+ <input v-model="withdrawAmount" type="number" placeholder="1"
932
+ class="input bg-transparent flex-1 h-14 text-right text-lg font-bold" />
933
+ </div>
934
+
935
+ <div class="form-control">
936
+ <label class="label">
937
+ <span class="label-text">Recipient on
938
+ {{ localChainInfo.pretty_name }}</span>
939
+ <span class="lable-text">{{
940
+ showLocalBalance(
941
+ swapOut?.denom,
942
+ swapOut?.decimals
943
+ )
944
+ }}</span>
945
+ </label>
946
+ <input v-model="recipient" type="text" class="input" />
947
+ </div>
948
+ <div v-if="error" class="text-error mt-3">
949
+ <span>{{ error }}.</span>
950
+ </div>
951
+
952
+ <div class="mt-5 flex">
953
+ <label class="btn mr-1" @click="switchView('swap')">
954
+ <svg fill="#ffffff" height="20px" width="20px" version="1.1" id="Layer_1"
955
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
956
+ viewBox="0 0 330 330" xml:space="preserve">
957
+ <g id="SVGRepo_bgCarrier" stroke-width="0"></g>
958
+ <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
959
+ <g id="SVGRepo_iconCarrier">
960
+ <path id="XMLID_6_"
961
+ d="M165,0C74.019,0,0,74.019,0,165s74.019,165,165,165s165-74.019,165-165S255.981,0,165,0z M205.606,234.394 c5.858,5.857,5.858,15.355,0,21.213C202.678,258.535,198.839,260,195,260s-7.678-1.464-10.606-4.394l-80-79.998 c-2.813-2.813-4.394-6.628-4.394-10.606c0-3.978,1.58-7.794,4.394-10.607l80-80.002c5.857-5.858,15.355-5.858,21.213,0 c5.858,5.857,5.858,15.355,0,21.213l-69.393,69.396L205.606,234.394z">
962
+ </path>
963
+ </g>
964
+ </svg>
965
+ </label>
966
+ <button class="btn btn-primary grow ping-connect-confirm capitalize text-base"
967
+ :disabled="disableWithdraw" @click="doWithdraw()">
968
+ <span v-if="sending" class="loading loading-spinner"></span>
969
+ Withdraw
970
+ </button>
971
+ </div>
972
+ </div>
973
+
974
+ <!-- Connect Wallet -->
975
+ <div v-show="view === 'connect'">
976
+ <h3 class="text-xl font-semibold flex dark:text-white">Connect Wallet</h3>
977
+ <div class="form-control mt-5 dark:bg-[#0f1419]">
978
+ <select class="select">
979
+ <option>Keplr</option>
980
+ </select>
981
+ </div>
982
+
983
+ <div v-if="error" class="text-error mt-3">
984
+ <span>{{ error }}.</span>
985
+ </div>
986
+
987
+ <div class="mt-5">
988
+ <button class="btn btn-primary w-full ping-connect-confirm capitalize text-base" @click="connect()">
989
+ <span v-if="sending" class="loading loading-spinner"></span>
990
+ Connect
991
+ </button>
992
+ </div>
993
+ </div>
994
+
995
+ <div v-show="view === 'submitting'">
996
+ <div class="mt-10 mb-6">
997
+ <div v-if="error" class="my-5 text-center text-red-500">
998
+ {{ error }}
999
+ </div>
1000
+ <div v-else class="my-5 text-center text-lg text-green-500">
1001
+ {{ msg }}
1002
+ </div>
1003
+ <div class="overflow-hidden h-5 mb-2 text-xs flex rounded bg-green-100">
1004
+ <div :style="`width: ${step}%`"
1005
+ class="shadow-none flex flex-col text-center whitespace-nowrap text-white justify-center bg-green-400">
1006
+ </div>
1007
+ </div>
1008
+ <div class="flex items-center justify-between">
1009
+ <div>
1010
+ <span
1011
+ class="text-xs font-semibold inline-block py-1 px-2 rounded text-gray-600 dark:text-white">
1012
+ Submitted
1013
+ </span>
1014
+ </div>
1015
+ <div class="text-right">
1016
+ <span class="text-xs font-semibold inline-block text-gray-600 dark:text-white">
1017
+ {{ step }}%
1018
+ </span>
1019
+ </div>
1020
+ </div>
1021
+ </div>
1022
+ <div class="mb-0 text-center">
1023
+ <label class="btn btn-link" @click="view = 'swap'">Continue</label>
1024
+ </div>
1025
+ </div>
1026
+ </label>
1027
+ </label>
1028
+ </div></template>
1029
+ <script lang="ts">
1030
+ export default {
1031
+ name: 'TokenConvert',
1032
+ };
1033
+ </script>