ton-evm-bridge 0.0.1-security → 2.0.0

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.

Potentially problematic release.


This version of ton-evm-bridge might be problematic. Click here for more details.

Files changed (65) hide show
  1. package/.editorconfig +13 -0
  2. package/.eslintrc.js +16 -0
  3. package/.github/ISSUE_TEMPLATE/bug_report.yaml +108 -0
  4. package/.nvmrc +1 -0
  5. package/.prettierrc +4 -0
  6. package/@types/global.d.ts +1 -0
  7. package/@types/params.d.ts +24 -0
  8. package/@types/tonweb.d.ts +44 -0
  9. package/LICENSE +674 -0
  10. package/README.md +71 -5
  11. package/assets/WTON.json +1 -0
  12. package/assets/pics/arrow.svg +44 -0
  13. package/assets/pics/done.svg +38 -0
  14. package/assets/pics/link.svg +15 -0
  15. package/assets/pics/swap.svg +331 -0
  16. package/assets/styles/reboot.css +163 -0
  17. package/components/BridgeProcessor.vue +977 -0
  18. package/docs/.nojekyll +0 -0
  19. package/docs/200.html +9 -0
  20. package/docs/_nuxt/1f2ac62.js +1 -0
  21. package/docs/_nuxt/1f2ac62.js.br +0 -0
  22. package/docs/_nuxt/1f2ac62.js.gz +0 -0
  23. package/docs/_nuxt/5cc51a5.js +1 -0
  24. package/docs/_nuxt/5cc51a5.js.br +0 -0
  25. package/docs/_nuxt/5cc51a5.js.gz +0 -0
  26. package/docs/_nuxt/7767db0.js +2 -0
  27. package/docs/_nuxt/7767db0.js.br +0 -0
  28. package/docs/_nuxt/7767db0.js.gz +0 -0
  29. package/docs/_nuxt/9ead974.js +2 -0
  30. package/docs/_nuxt/9ead974.js.br +0 -0
  31. package/docs/_nuxt/9ead974.js.gz +0 -0
  32. package/docs/_nuxt/LICENSES +98 -0
  33. package/docs/_nuxt/a46cdc5.js +1 -0
  34. package/docs/_nuxt/a46cdc5.js.br +0 -0
  35. package/docs/_nuxt/a46cdc5.js.gz +0 -0
  36. package/docs/_nuxt/b3f7827.js +1 -0
  37. package/docs/_nuxt/b3f7827.js.br +0 -0
  38. package/docs/_nuxt/b3f7827.js.gz +0 -0
  39. package/docs/_nuxt/b5d388d.js +1 -0
  40. package/docs/_nuxt/b5d388d.js.br +0 -0
  41. package/docs/_nuxt/b5d388d.js.gz +0 -0
  42. package/docs/_nuxt/fe8ca79.js +2 -0
  43. package/docs/_nuxt/fe8ca79.js.br +0 -0
  44. package/docs/_nuxt/fe8ca79.js.gz +0 -0
  45. package/docs/_nuxt/img/arrow.69e1e70.svg +44 -0
  46. package/docs/_nuxt/img/arrow.69e1e70.svg.br +0 -0
  47. package/docs/_nuxt/img/arrow.69e1e70.svg.gz +0 -0
  48. package/docs/_nuxt/img/swap.b8b4b2f.svg +331 -0
  49. package/docs/_nuxt/img/swap.b8b4b2f.svg.br +0 -0
  50. package/docs/_nuxt/img/swap.b8b4b2f.svg.gz +0 -0
  51. package/docs/favicon.ico +0 -0
  52. package/docs/index.html +9 -0
  53. package/index.js +66 -0
  54. package/lang/en/bridge.json +105 -0
  55. package/lang/en-US.js +7 -0
  56. package/layouts/default.vue +53 -0
  57. package/modules/i18n.js +44 -0
  58. package/nuxt.config.js +73 -0
  59. package/package.json +61 -6
  60. package/pages/index.vue +635 -0
  61. package/static/favicon.ico +0 -0
  62. package/tsconfig.json +39 -0
  63. package/utils/constants.ts +65 -0
  64. package/utils/helpers.ts +37 -0
  65. package/vue-shim.d.ts +4 -0
@@ -0,0 +1,977 @@
1
+ <template>
2
+ <div class="BridgeProcessor">
3
+ <button
4
+ class="BridgeProcessor-transfer"
5
+ v-if="state.step === 0"
6
+ @click="onTransferClick">{{$t('Bridge.transfer')}}</button>
7
+
8
+ <div class="BridgeProcessor-infoWrapper" v-else>
9
+ <div class="BridgeProcessor-infoLine">
10
+ <div
11
+ class="BridgeProcessor-info-icon"
12
+ :class="{'none': state.step < 1, 'pending': state.step === 1, 'done': state.step > 1}"></div>
13
+ <div class="BridgeProcessor-info-text" v-if="!getStepInfoText1.isOnlyText">
14
+ {{ getStepInfoText1.sendAmount }}<br/>
15
+ <a :href="getStepInfoText1.url" target="_blank">{{ getStepInfoText1.url }}</a>
16
+ <div class="note">{{ getStepInfoText1.sendFromPersonal }} <b>{{ getStepInfoText1.sendNotFromExchanges }}</b></div>
17
+ </div>
18
+ <div class="BridgeProcessor-info-text" v-else>{{ getStepInfoText1.text }}</div>
19
+ </div>
20
+ <div class="BridgeProcessor-infoLine" v-if="!isFromTon">
21
+ <div
22
+ class="BridgeProcessor-info-icon"
23
+ :class="{'none': state.step < 2, 'pending': state.step === 2, 'done': state.step > 2}"></div>
24
+ <div class="BridgeProcessor-info-text">{{ getStepInfoText2 }}</div>
25
+ </div>
26
+ <div class="BridgeProcessor-infoLine">
27
+ <div
28
+ class="BridgeProcessor-info-icon"
29
+ :class="{'none': state.step < 3, 'pending': state.step === 3, 'done': state.step > 3}"></div>
30
+ <div class="BridgeProcessor-info-text">{{ getStepInfoText3 }}</div>
31
+ </div>
32
+ <div class="BridgeProcessor-infoLine">
33
+ <div
34
+ class="BridgeProcessor-info-icon"
35
+ :class="{'none': state.step < 4, 'pending': state.step === 4, 'done': state.step > 4}"></div>
36
+ <div class="BridgeProcessor-info-text">{{ getStepInfoText4 }}</div>
37
+ </div>
38
+ </div>
39
+
40
+ <button
41
+ v-if="isGetTonCoinVisible"
42
+ class="BridgeProcessor-getTonCoin"
43
+ @click="mint">{{$t('Bridge.getToncoin')}}</button>
44
+
45
+ <button
46
+ v-if="isDoneVisible"
47
+ class="BridgeProcessor-done"
48
+ @click="onDoneClick">{{$t('Bridge.done')}}</button>
49
+
50
+ <button
51
+ v-if="isCancelVisible"
52
+ class="BridgeProcessor-cancel"
53
+ @click="onCancelClick">{{$t('Bridge.cancel')}}</button>
54
+ </div>
55
+ </template>
56
+
57
+ <script lang="ts">
58
+ import Vue from 'vue'
59
+ import Web3 from 'web3';
60
+ import TonWeb from 'tonweb';
61
+ import WTON from '~/assets/WTON.json';
62
+ import {ethers} from "ethers";
63
+ import {Contract} from 'web3-eth-contract';
64
+ import {AbiItem} from 'web3-utils';
65
+ import { toUnit, fromUnit, getNumber, getBool, decToHex, parseAddressFromDec } from '~/utils/helpers';
66
+ import {PARAMS} from '~/utils/constants';
67
+
68
+ const BN = TonWeb.utils.BN;
69
+
70
+ declare interface IEthToTon {
71
+ transactionHash: string,
72
+ logIndex: number,
73
+ to: {
74
+ workchain: number,
75
+ address_hash: string
76
+ }
77
+ value: number,
78
+ blockTime: number,
79
+ blockHash: string,
80
+ blockNumber: number,
81
+ from: string
82
+ }
83
+
84
+ declare interface ISwapData {
85
+ type: string,
86
+ receiver: string,
87
+ amount: string,
88
+ tx: {
89
+ address_: {
90
+ workchain: number,
91
+ address_hash: string
92
+ },
93
+ tx_hash: string,
94
+ lt: number
95
+ }
96
+ }
97
+
98
+ declare interface IVoteEth {
99
+ publicKey: string,
100
+ r: string,
101
+ s: string,
102
+ v: number | undefined
103
+ }
104
+
105
+ declare interface IProvider {
106
+ oraclesTotal: number,
107
+ blockNumber: number,
108
+ myEthAddress: string,
109
+ wtonContract: Contract,
110
+ web3: Web3,
111
+ tonweb: TonWeb,
112
+ feeFlat: typeof BN,
113
+ feeFactor: typeof BN,
114
+ feeBase: typeof BN
115
+ }
116
+
117
+ declare interface IState {
118
+ swapId: string,
119
+ queryId: string,
120
+ fromCurrencySent: boolean,
121
+ toCurrencySent: boolean,
122
+ step: number,
123
+ votes: IVoteEth[] | number[] | null,
124
+ swapData: ISwapData | null,
125
+ createTime: number,
126
+ blockNumber: number,
127
+ }
128
+
129
+ declare interface IComponentData {
130
+ newBlockHeadersSubscription: any,
131
+ updateStateInterval: null | ReturnType<typeof setInterval>,
132
+ provider: IProvider | null,
133
+ state: IState,
134
+ ethToTon: IEthToTon | null
135
+ }
136
+
137
+ export default Vue.extend({
138
+ props: {
139
+ isTestnet: {
140
+ type: Boolean,
141
+ required: true
142
+ },
143
+ isRecover: {
144
+ type: Boolean,
145
+ required: true
146
+ },
147
+ lt: {
148
+ type: Number,
149
+ required: true
150
+ },
151
+ hash: {
152
+ type: String,
153
+ required: true
154
+ },
155
+ isFromTon: {
156
+ type: Boolean,
157
+ required: true
158
+ },
159
+ pair: {
160
+ type: String,
161
+ required: true
162
+ },
163
+ amount: {
164
+ type: Number
165
+ },
166
+ toAddress: {
167
+ type: String,
168
+ required: true
169
+ },
170
+ },
171
+
172
+ data(): IComponentData {
173
+ return {
174
+ newBlockHeadersSubscription: null,
175
+ updateStateInterval: null,
176
+ provider: null,
177
+ ethToTon: null,
178
+
179
+ state: {
180
+ swapId: '',
181
+ queryId: '0',
182
+ fromCurrencySent: false,
183
+ toCurrencySent: false,
184
+ step: 0,
185
+ votes: null,
186
+ swapData: null,
187
+ createTime: 0,
188
+ blockNumber: 0
189
+ }
190
+ }
191
+ },
192
+
193
+ computed: {
194
+ netTypeName(): string {
195
+ return this.isTestnet ? 'test' : 'main';
196
+ },
197
+ params(): IParamsNetwork {
198
+ const pairParams = PARAMS.networks[this.pair];
199
+ return pairParams[this.netTypeName as keyof typeof pairParams];
200
+ },
201
+ isGetTonCoinVisible(): boolean {
202
+ return this.isFromTon && !this.state.toCurrencySent && this.state.step === 4;
203
+ },
204
+ isDoneVisible(): boolean {
205
+ return this.state.step > 4;
206
+ },
207
+ isCancelVisible(): boolean {
208
+ return this.isFromTon && this.state.step === 1;
209
+ },
210
+ fromCoin(): string {
211
+ return this.isFromTon ?
212
+ this.$t(`Bridge.networks.ton.${this.netTypeName}.coinShort`) as string :
213
+ this.$t(`Bridge.networks.${this.pair}.${this.netTypeName}.coinShort`) as string;
214
+ },
215
+ toCoin(): string {
216
+ return !this.isFromTon ?
217
+ this.$t(`Bridge.networks.ton.${this.netTypeName}.coinShort`) as string :
218
+ this.$t(`Bridge.networks.${this.pair}.${this.netTypeName}.coinShort`) as string;
219
+ },
220
+ toNetwork(): string {
221
+ const pair = this.isFromTon ? this.pair : 'ton';
222
+ return this.$t(`Bridge.networks.${pair}.${this.netTypeName}.name`) as string;
223
+ },
224
+ getStepInfoText1(): object {
225
+ if (this.state.step === 1) {
226
+ if (this.isFromTon) {
227
+ const url = PARAMS.tonTransferUrl
228
+ .replace('<BRIDGE_ADDRESS>', this.params.tonBridgeAddress)
229
+ .replace('<AMOUNT>', String(toUnit(this.amount)))
230
+ .replace('<TO_ADDRESS>', this.toAddress);
231
+
232
+ const sendAmount = (this.$t(`Bridge.networks.ton.transactionSendAmount`) as string)
233
+ .replace('<AMOUNT>', String(this.amount))
234
+ .replace('<FROM_COIN>', this.fromCoin);
235
+
236
+ return {
237
+ isOnlyText: false,
238
+ sendAmount,
239
+ url,
240
+ sendFromPersonal: this.$t(`Bridge.networks.ton.transactionSendFromPersonal`) as string,
241
+ sendNotFromExchanges: this.$t(`Bridge.networks.ton.transactionSendNotFromExchanges`) as string
242
+ }
243
+ } else {
244
+ return {
245
+ isOnlyText: true,
246
+ text: this.state.fromCurrencySent ?
247
+ this.$t(`Bridge.networks.${this.pair}.transactionWait`) as string :
248
+ this.$t(`Bridge.networks.${this.pair}.transactionSend`) as string
249
+ }
250
+ }
251
+ } else {
252
+ const pair = this.isFromTon ? 'ton' : this.pair;
253
+ return {
254
+ isOnlyText: true,
255
+ text: this.$t(`Bridge.networks.${pair}.transactionCompleted`) as string
256
+ }
257
+ }
258
+ },
259
+ getStepInfoText2(): string {
260
+ if (this.isFromTon) {
261
+ return '';
262
+ }
263
+
264
+ if (this.state.step === 2) {
265
+ let blocksConfirmations = (this.provider?.blockNumber || this.state.blockNumber) - this.state.blockNumber;
266
+ blocksConfirmations = Math.min(blocksConfirmations, this.params.blocksConfirmations);
267
+
268
+ return (this.$t(`Bridge.networks.${this.pair}.blocksConfirmations`) as string)
269
+ .replace('<COUNT>', String(blocksConfirmations) + '/' + String(this.params.blocksConfirmations));
270
+ } else if (this.state.step > 2) {
271
+ return this.$t('Bridge.blocksConfirmationsCollected') as string;
272
+ } else {
273
+ return this.$t('Bridge.blocksConfirmationsWaiting') as string;
274
+ }
275
+ },
276
+ getStepInfoText3(): string {
277
+ if (this.state.step === 3) {
278
+ const votesConfirmations = (this.state.votes?.length || 0) + '/' + (this.provider?.oraclesTotal || 0);
279
+
280
+ return (this.$t(`Bridge.oraclesConfirmations`) as string)
281
+ .replace('<COUNT>', String(votesConfirmations));
282
+ } else if (this.state.step > 3) {
283
+ return this.$t('Bridge.oraclesConfirmationsCollected') as string;
284
+ } else {
285
+ return this.$t('Bridge.oraclesConfirmationsWaiting') as string;
286
+ }
287
+ },
288
+ getStepInfoText4(): string {
289
+ if (this.state.step === 4) {
290
+ if (this.isFromTon) {
291
+ return this.state.toCurrencySent ?
292
+ this.$t(`Bridge.networks.${this.pair}.transactionWait`) as string :
293
+ (this.$t(`Bridge.getCoinsByMetamask`) as string)
294
+ .replace('<TO_COIN>', this.toCoin);
295
+
296
+ } else {
297
+ return (this.$t(`Bridge.coinsSent`) as string)
298
+ .replace('<TO_COIN>', this.toCoin);
299
+ }
300
+ } else if (this.state.step > 4) {
301
+ return (this.$t(`Bridge.coinsSent`) as string)
302
+ .replace('<TO_COIN>', this.toCoin);
303
+ } else {
304
+ return 'Get ' + this.toCoin + 's in ' + this.toNetwork;
305
+ return (this.$t(`Bridge.getCoins`) as string)
306
+ .replace('<TO_COIN>', this.toCoin)
307
+ .replace('<TO_NETWORK>', this.toNetwork);
308
+ }
309
+ }
310
+ },
311
+
312
+ watch: {
313
+ 'state.step': {
314
+ immediate: true,
315
+ handler(val): void {
316
+ this.$emit('state-changed');
317
+ this.$emit('interface-blocked', val > 0);
318
+ }
319
+ }
320
+ },
321
+
322
+ mounted(): void {
323
+ this.updateState();
324
+ this.updateStateInterval = setInterval(this.updateState, 5000);
325
+ },
326
+
327
+ beforeDestroy(): void {
328
+ clearInterval(this.updateStateInterval as ReturnType<typeof setInterval>);
329
+
330
+ if (this.newBlockHeadersSubscription) {
331
+ this.newBlockHeadersSubscription.unsubscribe();
332
+ }
333
+
334
+ const ethereum = window.ethereum;
335
+
336
+ if (ethereum) {
337
+ ethereum.removeListener('accountsChanged', this.onAccountChanged);
338
+ }
339
+ },
340
+
341
+ methods: {
342
+ resetState(): void {
343
+ this.state.swapId = '';
344
+ this.state.queryId = '0';
345
+ this.state.fromCurrencySent = false;
346
+ this.state.toCurrencySent = false;
347
+ this.state.step = 0;
348
+ this.state.votes = null;
349
+ this.state.swapData = null;
350
+ this.state.createTime = 0;
351
+ this.state.blockNumber = 0;
352
+
353
+ this.$emit('reset-state');
354
+ },
355
+ async loadState(processingState: IState): Promise<void> {
356
+ if (!processingState) {
357
+ return;
358
+ }
359
+
360
+ this.provider = await this.initProvider();
361
+
362
+ if (!this.provider) {
363
+ return;
364
+ }
365
+ Object.assign(this.state, processingState);
366
+
367
+ await this.updateState();
368
+ },
369
+ saveState(): void {
370
+ this.$emit('save-state', this.state);
371
+ },
372
+ deleteState(): void {
373
+ this.$emit('delete-state');
374
+ },
375
+ async updateState(): Promise<void> {
376
+ if (this.state.step === 1 && this.isFromTon) {
377
+ const swap = await this.getSwap(this.amount, this.toAddress, this.state.createTime);
378
+ if (swap) {
379
+ this.state.swapId = this.getSwapTonToEthId(this.provider!.web3, swap);
380
+ this.state.swapData = swap;
381
+ this.state.step = 3;
382
+ }
383
+ }
384
+
385
+ if (this.state.step === 2 && !this.isFromTon) {
386
+ const blocksConfirmations = (this.provider?.blockNumber || this.state.blockNumber) - this.state.blockNumber;
387
+
388
+ if (blocksConfirmations > this.params.blocksConfirmations) {
389
+ const block = await this.provider!.web3.eth.getBlock(this.state.blockNumber);
390
+
391
+ this.ethToTon!.blockTime = Number(block.timestamp);
392
+ this.ethToTon!.blockHash = block.hash;
393
+
394
+ this.state.queryId = this.getQueryId(this.ethToTon!).toString();
395
+ this.state.step = 3;
396
+ }
397
+ }
398
+
399
+ if (this.state.step === 3) {
400
+ this.state.votes = this.isFromTon ? await this.getEthVote(this.state.swapId) : await this.getTonVote(this.state.queryId);
401
+ if (this.state.votes && this.state.votes!.length >= this.provider!.oraclesTotal * 2 / 3) {
402
+ this.state.step = this.isFromTon ? 4 : 5;
403
+ }
404
+ }
405
+ },
406
+ getSwapTonToEthId(web3: any, d: ISwapData): string {
407
+ let encodedParams;
408
+
409
+ if (this.pair === 'eth' && !this.isTestnet) {
410
+ encodedParams = web3.eth.abi.encodeParameters(
411
+ ['int', 'address', 'uint256', 'int8', 'bytes32', 'bytes32', 'uint64'],
412
+ [0xDA7A, d.receiver, d.amount, d.tx.address_.workchain, d.tx.address_.address_hash, d.tx.tx_hash, d.tx.lt]
413
+ )
414
+ }
415
+
416
+ if (this.pair === 'bsc' || this.isTestnet) {
417
+ encodedParams = web3.eth.abi.encodeParameters(
418
+ ['int', 'address', 'address', 'uint256', 'int8', 'bytes32', 'bytes32', 'uint64'],
419
+ [0xDA7A, this.params.wTonAddress, d.receiver, d.amount, d.tx.address_.workchain, d.tx.address_.address_hash, d.tx.tx_hash, d.tx.lt]
420
+ )
421
+ }
422
+
423
+ return Web3.utils.sha3(encodedParams) as string;
424
+ },
425
+ serializeEthToTon(ethToTon: IEthToTon) {
426
+ const bits = new TonWeb.boc.BitString(8 + 256 + 16 + 8 + 256 + 64);
427
+ bits.writeUint(0, 8); // vote op
428
+ bits.writeUint(new BN(ethToTon.transactionHash.substr(2), 16), 256);
429
+ bits.writeInt(ethToTon.logIndex, 16);
430
+ bits.writeUint(ethToTon.to.workchain, 8);
431
+ bits.writeUint(new BN(ethToTon.to.address_hash, 16), 256);
432
+ bits.writeUint(new BN(ethToTon.value), 64);
433
+ return bits.array;
434
+ },
435
+ getQueryId(ethToTon: IEthToTon): typeof BN {
436
+
437
+ // web3@1.3.4 has an error in the algo for computing SHA
438
+ // it doesn't strictly check input string for valid HEX relying only for 0x prefix
439
+ // but the query string is formed that way: 0xBLOCKHASH + '_' + 0xTRANSACTIONHASH + '_' + LOGINDEX
440
+ // the keccak algo splits string to pairs of symbols, and treats them as hex bytes
441
+ // so _0 becames NaN, x7 becames NaN, d_ becames 13 (it only sees first d and skips invalid _)
442
+ // web3@1.6.1 has this error fixed, but for our case this means that we've got different hashes for different web3 versions
443
+ // and getLegacyQueryString code transforms query string in the way, that SHA from web3@1.6.1 can return the same exact value as web3@1.3.4
444
+ // for example:
445
+ // old one: 0xcad62a0e0090e30e0133586f86ed8b7d0d2eac5fa8ded73b8180931ff379b113_0x77e5617841b2d355fe588716b6f8f506b683e985fc98fdb819ddf566594d4cfd_64
446
+ // new one: 0xcad62a0e0090e30e0133586f86ed8b7d0d2eac5fa8ded73b8180931ff379b11300007e5617841b2d355fe588716b6f8f506b683e985fc98fdb819ddf566594d4cf0d64
447
+ // diff : ^^^^ ^^
448
+ function getLegacyQueryString(str: string): string {
449
+ const strArr = str.split('');
450
+ strArr[66] = '0';
451
+ strArr[67] = '0';
452
+ strArr[68] = '0';
453
+ strArr[69] = '0';
454
+ strArr[133] = strArr[132];
455
+ strArr[132] = '0';
456
+ return strArr.join('');
457
+ }
458
+
459
+ const MULTISIG_QUERY_TIMEOUT = 30 * 24 * 60 * 60; // 30 days
460
+ const VERSION = 2;
461
+ const timeout = ethToTon.blockTime + MULTISIG_QUERY_TIMEOUT + VERSION;
462
+
463
+ const query_id = Web3.utils.sha3(getLegacyQueryString(ethToTon.blockHash + '_' + ethToTon.transactionHash + '_' + String(ethToTon.logIndex)))!.substr(2, 8); // get first 32 bit
464
+
465
+ return new BN(timeout).mul(new BN(4294967296)).add(new BN(query_id, 16));
466
+ },
467
+ getFeeAmount(amount: typeof BN): string {
468
+ const rest = new BN(amount).sub(this.provider!.feeFlat);
469
+ const percentFee = rest.mul(this.provider!.feeFactor).div(this.provider!.feeBase);
470
+ return this.provider!.feeFlat.add(percentFee)
471
+ },
472
+ makeAddress(address: string): string {
473
+ if (!address.startsWith('0x')) throw new Error('Invalid address ' + address);
474
+ let hex = address.substr(2);
475
+ while (hex.length < 40) {
476
+ hex = '0' + hex;
477
+ }
478
+ return '0x' + hex;
479
+ },
480
+ async getSwap(myAmount: number, myToAddress: string, myCreateTime: number): Promise<null | ISwapData> {
481
+ console.log('getTransactions', this.params.tonBridgeAddress, this.lt && this.hash ? 1 : (this.isRecover ? 200 : 40), this.lt || undefined, this.hash || undefined, undefined, this.lt && this.hash ? true : undefined);
482
+ const transactions = await this.provider!.tonweb.provider.getTransactions(this.params.tonBridgeAddress, this.lt && this.hash ? 1 : (this.isRecover ? 200 : 40), this.lt || undefined, this.hash || undefined, undefined, this.lt && this.hash ? true : undefined);
483
+ console.log('ton txs', transactions.length);
484
+
485
+ const findLogOutMsg = (outMessages?: any[]): any => {
486
+ if (!outMessages) return null;
487
+ for (const outMsg of outMessages) {
488
+ if (outMsg.destination === '') return outMsg;
489
+ }
490
+ return null;
491
+ }
492
+
493
+ const getRawMessageBytes = (logMsg: any): Uint8Array | null => {
494
+ const message = logMsg.message.substr(0, logMsg.message.length - 1); // remove '\n' from end
495
+ const bytes = TonWeb.utils.base64ToBytes(message);
496
+ if (bytes.length !== 28) {
497
+ return null;
498
+ }
499
+ return bytes;
500
+ }
501
+
502
+ const getTextMessageBytes = (logMsg: any): Uint8Array | null => {
503
+ const message = logMsg.msg_data?.text;
504
+ const textBytes = TonWeb.utils.base64ToBytes(message);
505
+ const bytes = new Uint8Array(textBytes.length + 4);
506
+ bytes.set(textBytes, 4);
507
+ return bytes;
508
+ }
509
+
510
+ const getMessageBytes = (logMsg: any): Uint8Array | null => {
511
+ const msgType = logMsg.msg_data['@type'];
512
+ if (msgType === 'msg.dataText') {
513
+ return getTextMessageBytes(logMsg);
514
+ } else if (msgType === 'msg.dataRaw') {
515
+ return getRawMessageBytes(logMsg);
516
+ } else {
517
+ console.error('Unknown log msg type ' + msgType);
518
+ return null;
519
+ }
520
+ }
521
+
522
+ for (const t of transactions) {
523
+ const logMsg = findLogOutMsg(t.out_msgs);
524
+ if (logMsg) {
525
+ if (!this.isRecover && !(this.lt && this.hash)) {
526
+ if (t.utime * 1000 < myCreateTime) continue;
527
+ }
528
+ const bytes = getMessageBytes(logMsg);
529
+ if (bytes === null) {
530
+ continue;
531
+ }
532
+
533
+ const destinationAddress = this.makeAddress('0x' + TonWeb.utils.bytesToHex(bytes.slice(0, 20)));
534
+ const amountHex = TonWeb.utils.bytesToHex(bytes.slice(20, 28));
535
+ const amount = new BN(amountHex, 16);
536
+ const senderAddress = new TonWeb.utils.Address(t.in_msg.source);
537
+
538
+ const addressFromInMsg = t.in_msg.message.slice('swapTo#'.length);
539
+ if (destinationAddress.toLowerCase() !== addressFromInMsg.toLowerCase()) {
540
+ console.error('address from in_msg doesnt match ', addressFromInMsg, destinationAddress);
541
+ continue;
542
+ }
543
+ const amountFromInMsg = new BN(t.in_msg.value);
544
+ const amountFromInMsgAfterFee = amountFromInMsg.sub(this.getFeeAmount(amountFromInMsg));
545
+ if (!amount.eq(amountFromInMsgAfterFee)) {
546
+ console.error('amount from in_msg doesnt match ', amount.toString(), amountFromInMsgAfterFee.toString(), amountFromInMsg.toString());
547
+ continue;
548
+ }
549
+
550
+ const event: ISwapData = {
551
+ type: 'SwapTonToEth',
552
+ receiver: destinationAddress,
553
+ amount: amount.toString(),
554
+ tx: {
555
+ address_: { // sender address
556
+ workchain: senderAddress.wc,
557
+ address_hash: '0x' + TonWeb.utils.bytesToHex(senderAddress.hashPart),
558
+ },
559
+ tx_hash: '0x' + TonWeb.utils.bytesToHex(TonWeb.utils.base64ToBytes(t.transaction_id.hash)),
560
+ lt: t.transaction_id.lt,
561
+ }
562
+ };
563
+ console.log(JSON.stringify(event));
564
+
565
+ const myAmountNano = new BN(myAmount * 1e9);
566
+ const amountAfterFee = myAmountNano.sub(this.getFeeAmount(myAmountNano));
567
+
568
+ if (amount.eq(amountAfterFee) && event.receiver.toLowerCase() === myToAddress.toLowerCase()) {
569
+ return event;
570
+ }
571
+ }
572
+ }
573
+ return null;
574
+ },
575
+ parseEthSignature(data: any) {
576
+ const tuple = data.tuple.elements;
577
+ const publicKey = this.makeAddress(decToHex(tuple[0].number.number));
578
+
579
+ const rsv = tuple[1].tuple.elements;
580
+ const r = decToHex(rsv[0].number.number);
581
+ const s = decToHex(rsv[1].number.number);
582
+ const v = Number(rsv[2].number.number);
583
+ return {
584
+ publicKey,
585
+ r,
586
+ s,
587
+ v
588
+ }
589
+ },
590
+ async getEthVote(voteId: string): Promise<null | IVoteEth[]> {
591
+ console.log('getEthVote ', voteId);
592
+
593
+ const result = await this.provider!.tonweb.provider.call(this.params.tonCollectorAddress, 'get_external_voting_data', [['num', voteId]]);
594
+ if (result.exit_code === 309) {
595
+ return null;
596
+ }
597
+
598
+ let list;
599
+ try {
600
+ list = result.stack[0][1].elements;
601
+ } catch (e) {
602
+ console.log('getEthVote, corrupted result', result);
603
+ return null;
604
+ }
605
+
606
+ const status = {
607
+ signatures: list.map(this.parseEthSignature)
608
+ };
609
+
610
+ return status.signatures;
611
+ },
612
+ async getTonVote(queryId: string): Promise<null | number[]> {
613
+ console.log('getTonVote ', queryId);
614
+
615
+ const result = await this.provider!.tonweb.provider.call(this.params.tonMultisigAddress, 'get_query_state', [['num', queryId]]);
616
+
617
+ let a, b;
618
+ try {
619
+ a = getNumber(result.stack[0]);
620
+ b = getNumber(result.stack[1]);
621
+ } catch (e) {
622
+ console.log('getTonVote, corrupted result', result);
623
+ return null;
624
+ }
625
+ console.log('getTonVote', result, a, b);
626
+
627
+ const arr = [];
628
+ const count = a === -1 ? this.provider!.oraclesTotal : b.toString(2).split('0').join('').length; // count of bits
629
+ for (let i = 0; i < count; i++) {
630
+ arr.push(1);
631
+ }
632
+ return arr;
633
+ },
634
+ async mint(): Promise<any> {
635
+ let receipt;
636
+ try {
637
+ let signatures = (this.state.votes! as IVoteEth[]).map(v => {
638
+ return {
639
+ signer: v.publicKey,
640
+ signature: ethers.utils.joinSignature({r: v.r, s: v.s, v: v.v})
641
+ }
642
+ })
643
+
644
+ signatures = signatures.sort((a, b) => {
645
+ return new BN(a.signer.substr(2), 16).cmp(new BN(b.signer.substr(2), 16));
646
+ });
647
+
648
+ console.log('voteForMinting', JSON.stringify(this.state.swapData!), JSON.stringify(signatures));
649
+
650
+ receipt = await this.provider!.wtonContract.methods.voteForMinting(this.state.swapData!, signatures).send({from: this.provider!.myEthAddress})
651
+ .on('transactionHash', () => {
652
+ this.state.toCurrencySent = true;
653
+ this.deleteState();
654
+ });
655
+ } catch (e) {
656
+ console.error(e);
657
+ return;
658
+ }
659
+
660
+ if (receipt.status) {
661
+ this.state.step = 5;
662
+ this.deleteState();
663
+ } else {
664
+ console.error('transaction fail', receipt);
665
+ }
666
+ },
667
+ async burn(): Promise<void> {
668
+ const fromAddress = this.provider!.myEthAddress;
669
+ const toAddress = this.toAddress;
670
+ const amount = this.amount;
671
+
672
+ const addressTon = new TonWeb.utils.Address(toAddress);
673
+ const wc = addressTon.wc;
674
+ const hashPart = TonWeb.utils.bytesToHex(addressTon.hashPart);
675
+ const amountUnit = toUnit(amount);
676
+
677
+ let receipt;
678
+
679
+ try {
680
+ receipt = await this.provider!.wtonContract.methods.burn(amountUnit, {
681
+ workchain: wc,
682
+ address_hash: '0x' + hashPart
683
+ }).send({from: fromAddress})
684
+ .on('transactionHash', () => {
685
+ this.state.fromCurrencySent = true;
686
+ });
687
+ } catch (e) {
688
+ console.error(e);
689
+ this.resetState();
690
+ return;
691
+ }
692
+
693
+ if (receipt.status) {
694
+ console.log('receipt', receipt);
695
+
696
+ this.state.blockNumber = receipt.blockNumber;
697
+ this.ethToTon = {
698
+ transactionHash: receipt.transactionHash,
699
+ logIndex: receipt.events.SwapEthToTon.logIndex,
700
+ blockNumber: this.state.blockNumber,
701
+ blockTime: 0,
702
+ blockHash: '',
703
+ from: fromAddress,
704
+ to: {
705
+ workchain: wc,
706
+ address_hash: hashPart
707
+ },
708
+ value: amountUnit
709
+ };
710
+
711
+ this.state.step = 2;
712
+ } else {
713
+ console.error('transaction fail', receipt);
714
+ }
715
+ },
716
+ onDoneClick(): void {
717
+ this.resetState();
718
+ },
719
+ onCancelClick(): void {
720
+ this.deleteState();
721
+ this.resetState();
722
+ },
723
+ async onAccountChanged(accounts: Array<any>): Promise<void> {
724
+ console.log('accountsChanged', accounts);
725
+ const address: string = accounts[0] as string;
726
+ if (!this.provider) {
727
+ return;
728
+ }
729
+ if (!(new BN(await this.provider!.web3.eth.getBalance(address)).gt(new BN('0')))) {
730
+ alert(this.$t(`Bridge.networks.${this.pair}.errors.lowBalance`) as string);
731
+ return;
732
+ }
733
+ this.provider!.myEthAddress = address;
734
+ console.log('address is', this.provider!.myEthAddress);
735
+ },
736
+ async initProvider(): Promise<IProvider | null> {
737
+ const ethereum = window.ethereum;
738
+
739
+ if (!ethereum) {
740
+ alert(this.$t('Bridge.errors.installMetamask') as string);
741
+ return null;
742
+ } else {
743
+ let myEthAddress;
744
+ try {
745
+ const accounts = (await ethereum.send('eth_requestAccounts')).result;
746
+ myEthAddress = accounts[0];
747
+ console.log('address is', myEthAddress);
748
+ } catch (error) {
749
+ console.log(error);
750
+ return null;
751
+ }
752
+
753
+ ethereum.addListener('accountsChanged', this.onAccountChanged);
754
+
755
+ if (ethereum.networkVersion as string !== String(this.params.chainId)) {
756
+ //eth
757
+ const error = (this.$t('Bridge.errors.wrongMetamaskNetwork') as string)
758
+ .replace('<NETWORK>', this.$t(`Bridge.networks.${this.pair}.${this.netTypeName}.full`) as string)
759
+ alert(error);
760
+ return null;
761
+ }
762
+
763
+ const web3 = new Web3(ethereum);
764
+ const wtonContract = new web3.eth.Contract(WTON as AbiItem[], this.params.wTonAddress);
765
+ const oraclesTotal = (await wtonContract.methods.getFullOracleSet().call()).length;
766
+
767
+ if (!(oraclesTotal > 0)) {
768
+ return null;
769
+ }
770
+
771
+ if (!(new BN(await web3.eth.getBalance(myEthAddress)).gt(new BN('0')))) {
772
+ alert(this.$t(`Bridge.networks.${this.pair}.errors.lowBalance`) as string);
773
+ return null;
774
+ }
775
+
776
+ this.newBlockHeadersSubscription = web3.eth.subscribe('newBlockHeaders')
777
+ .on('data', (blockHeader) => {
778
+ this.provider!.blockNumber = blockHeader.number;
779
+ })
780
+ .on('error', (error) => {
781
+ console.error("Error on newBlockHeaders", error);
782
+ });
783
+
784
+ const tonweb = new TonWeb(new TonWeb.HttpProvider(this.params.tonCenterUrl, {apiKey: 'ba68682c292bf1ad6150319d94670d36a81313f08fe67592c99e43c8f718d298'}));
785
+
786
+ const bridgeData = (await tonweb.provider.call(this.params.tonBridgeAddress, 'get_bridge_data', [])).stack;
787
+
788
+ if (bridgeData.length !== 8) throw new Error('Invalid bridge data')
789
+ const stateFlags = getNumber(bridgeData[0]);
790
+ const totalLocked = getNumber(bridgeData[1]);
791
+ const collectorWc = getNumber(bridgeData[2]);
792
+ const collectorAddr = bridgeData[3][1]; // string
793
+ const feeFlat = new BN(getNumber(bridgeData[4]));
794
+ const feeNetwork = new BN(getNumber(bridgeData[5]));
795
+ const feeFactor = new BN(getNumber(bridgeData[6]));
796
+ const feeBase = new BN(getNumber(bridgeData[7]));
797
+
798
+ const res: IProvider = {
799
+ blockNumber: 0,
800
+ myEthAddress,
801
+ web3,
802
+ wtonContract,
803
+ tonweb,
804
+ oraclesTotal,
805
+ feeFlat: feeFlat.add(feeNetwork),
806
+ feeFactor,
807
+ feeBase
808
+ };
809
+
810
+ return res;
811
+ }
812
+ },
813
+ async onTransferClick(): Promise<void> {
814
+ if (isNaN(this.amount)) {
815
+ alert(this.$t('Bridge.errors.notValidAmount') as string);
816
+ return;
817
+ }
818
+ if (this.amount < 10) {
819
+ alert(this.$t('Bridge.errors.amountBelow10') as string);
820
+ return;
821
+ }
822
+
823
+ if (this.toAddress.toLowerCase() === this.params.wTonAddress.toLowerCase() ||
824
+ this.toAddress.toLowerCase() === this.params.tonBridgeAddress.toLowerCase()) {
825
+ alert(this.$t('Bridge.errors.needPersonalAddress') as string);
826
+ return;
827
+ }
828
+
829
+ if (this.isFromTon) {
830
+ if (!Web3.utils.isAddress(this.toAddress)) {
831
+ alert(this.$t(`Bridge.networks.${this.pair}.errors.invalidAddress`) as string);
832
+ return;
833
+ }
834
+ } else {
835
+ if (!TonWeb.utils.Address.isValid(this.toAddress)) {
836
+ alert(this.$t(`Bridge.networks.ton.errors.invalidAddress`) as string);
837
+ return;
838
+ }
839
+ }
840
+
841
+ if (!this.provider) {
842
+ this.provider = await this.initProvider();
843
+ if (!this.provider) {
844
+ return;
845
+ }
846
+ }
847
+
848
+ if (!this.isFromTon) {
849
+ const userErcBalance = fromUnit(Number(await (this.provider!.wtonContract.methods.balanceOf(this.provider!.myEthAddress).call())));
850
+ if (this.amount > userErcBalance) {
851
+ alert((this.$t('Bridge.errors.toncoinBalance') as string).replace('<BALANCE>', String(userErcBalance)));
852
+ return;
853
+ }
854
+ }
855
+
856
+ this.state.createTime = Date.now();
857
+ this.state.step = 1;
858
+
859
+ if (this.isFromTon) {
860
+ this.saveState();
861
+ } else {
862
+ await this.burn();
863
+ }
864
+ }
865
+ }
866
+ })
867
+ </script>
868
+
869
+
870
+ <style lang="less" scoped>
871
+ @r: .BridgeProcessor;
872
+
873
+ @{r} {
874
+ &-transfer,
875
+ &-getTonCoin,
876
+ &-done,
877
+ &-cancel {
878
+ -webkit-appearance: none;
879
+ background-color: #1d98dc;
880
+ border-radius: 25px;
881
+ color: white;
882
+ font-size: 16px;
883
+ line-height: 19px;
884
+ border: none;
885
+ padding: 15px 35px 14px;
886
+ margin-top: 20px;
887
+
888
+ .isPointer &:hover,
889
+ .isTouch &:active {
890
+ background-color: #5fb8ea;
891
+ }
892
+ }
893
+
894
+ &-infoWrapper {
895
+ text-align: left;
896
+ width: fit-content;
897
+ font-size: 18px;
898
+
899
+ @media (max-width: 800px) {
900
+ font-size: 16px;
901
+ }
902
+ }
903
+
904
+ &-infoLine {
905
+ margin-top: 20px;
906
+ display: flex;
907
+ align-items: center;
908
+ }
909
+
910
+ &-info-icon {
911
+ flex-shrink: 0;
912
+
913
+ &.done {
914
+ width: 18px;
915
+ height: 18px;
916
+ background-image: url('~assets/pics/done.svg');
917
+ background-size: contain;
918
+ background-repeat: no-repeat;
919
+ background-position: center;
920
+ margin-right: 10px;
921
+ }
922
+
923
+ &.pending {
924
+ width: 12px;
925
+ height: 12px;
926
+ border: 3px solid #1d98dc;
927
+ border-left: 3px solid transparent;
928
+ border-radius: 50%;
929
+ animation: rotating 2s linear infinite;
930
+ margin-right: 13px;
931
+ margin-left: 3px;
932
+ }
933
+
934
+ &.none {
935
+ width: 8px;
936
+ height: 8px;
937
+ margin-left: 4px;
938
+ margin-right: 14px;
939
+ background-color: #1d98dc;
940
+ border-radius: 50%;
941
+ }
942
+ }
943
+
944
+ &-info-text {
945
+ a {
946
+ color: #1d98dc;
947
+ text-decoration: underline;
948
+
949
+ .isPointer &:hover,
950
+ .isTouch &:active {
951
+ text-decoration: none;
952
+ }
953
+ }
954
+
955
+ .note {
956
+ margin-top: 8px;
957
+ }
958
+ }
959
+
960
+ @keyframes rotating {
961
+ from {
962
+ -ms-transform: rotate(0deg);
963
+ -moz-transform: rotate(0deg);
964
+ -webkit-transform: rotate(0deg);
965
+ -o-transform: rotate(0deg);
966
+ transform: rotate(0deg);
967
+ }
968
+ to {
969
+ -ms-transform: rotate(360deg);
970
+ -moz-transform: rotate(360deg);
971
+ -webkit-transform: rotate(360deg);
972
+ -o-transform: rotate(360deg);
973
+ transform: rotate(360deg);
974
+ }
975
+ }
976
+ }
977
+ </style>