├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── dist ├── css │ └── app.27e1f6bf.css ├── favicon.ico ├── img │ ├── arrow-disabled.0b5c1a41.svg │ ├── arrow.4c04916a.svg │ ├── check.75704e26.svg │ ├── close.f1b54995.svg │ ├── copy.06c68adf.svg │ ├── crystal.aabd46bc.svg │ ├── done.8796fcad.svg │ ├── dot.5a3261fd.svg │ ├── dropdown-active.e2c856a1.svg │ ├── dropdown.249360c6.svg │ ├── gem@large.0d6c241e.png │ ├── link.45d5f0f4.svg │ ├── loader.c1c4a3cb.svg │ ├── logo.ba3ba8c6.svg │ ├── metamask.397b9a5b.svg │ ├── swap.9363a68a.svg │ ├── walletConnect.2d540c60.svg │ └── walletlink.d118db5c.svg ├── index.html ├── js │ ├── 461.9de75747.js │ ├── 461.9de75747.js.map │ ├── 494.768525cc.js │ ├── 494.768525cc.js.map │ ├── 612.cbff4b65.js │ ├── 612.cbff4b65.js.map │ ├── 686.c70d4011.js │ ├── 686.c70d4011.js.map │ ├── 735.6c870ff0.js │ ├── 735.6c870ff0.js.map │ ├── 983.057ea28d.js │ ├── 983.057ea28d.js.map │ ├── app.7be21dbb.js │ ├── app.7be21dbb.js.map │ ├── chunk-vendors.eac6bc8b.js │ └── chunk-vendors.eac6bc8b.js.map ├── logo.png ├── robots.txt ├── tonconnect-manifest.json └── walletLink@2.4.2.js ├── docs ├── css │ └── app.27e1f6bf.css ├── favicon.ico ├── img │ ├── arrow-disabled.0b5c1a41.svg │ ├── arrow.4c04916a.svg │ ├── check.75704e26.svg │ ├── close.f1b54995.svg │ ├── copy.06c68adf.svg │ ├── crystal.aabd46bc.svg │ ├── done.8796fcad.svg │ ├── dot.5a3261fd.svg │ ├── dropdown-active.e2c856a1.svg │ ├── dropdown.249360c6.svg │ ├── gem@large.0d6c241e.png │ ├── link.45d5f0f4.svg │ ├── loader.c1c4a3cb.svg │ ├── logo.ba3ba8c6.svg │ ├── metamask.397b9a5b.svg │ ├── swap.9363a68a.svg │ ├── walletConnect.2d540c60.svg │ └── walletlink.d118db5c.svg ├── index.html ├── js │ ├── 461.9de75747.js │ ├── 461.9de75747.js.map │ ├── 494.768525cc.js │ ├── 494.768525cc.js.map │ ├── 612.cbff4b65.js │ ├── 612.cbff4b65.js.map │ ├── 686.c70d4011.js │ ├── 686.c70d4011.js.map │ ├── 735.6c870ff0.js │ ├── 735.6c870ff0.js.map │ ├── 983.057ea28d.js │ ├── 983.057ea28d.js.map │ ├── app.7be21dbb.js │ ├── app.7be21dbb.js.map │ ├── chunk-vendors.eac6bc8b.js │ └── chunk-vendors.eac6bc8b.js.map ├── logo.png ├── robots.txt ├── tonconnect-manifest.json └── walletLink@2.4.2.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo.png ├── robots.txt ├── tonconnect-manifest.json └── walletLink@2.4.2.js ├── src ├── @types │ ├── eth-lib.d.ts │ ├── global.d.ts │ └── params.d.ts ├── App.vue ├── api │ └── tonWallet.ts ├── assets │ ├── pics │ │ ├── arrow-disabled.svg │ │ ├── arrow.svg │ │ ├── check.svg │ │ ├── close.svg │ │ ├── copy.svg │ │ ├── crystal.svg │ │ ├── done.svg │ │ ├── dropdown-active.svg │ │ ├── dropdown.svg │ │ ├── gem@large.png │ │ ├── link.svg │ │ ├── logo.svg │ │ ├── progress │ │ │ ├── done.svg │ │ │ ├── dot.svg │ │ │ └── loader.svg │ │ ├── providers │ │ │ ├── metamask.svg │ │ │ ├── walletConnect.svg │ │ │ └── walletlink.svg │ │ └── swap.svg │ └── styles │ │ ├── _variables.less │ │ ├── global.less │ │ └── reboot.css ├── components │ ├── Alert │ │ ├── index.vue │ │ └── styles.less │ ├── BridgeProcessor │ │ ├── index.vue │ │ ├── styles.less │ │ └── types.ts │ ├── CustomInput │ │ ├── index.vue │ │ └── styles.less │ ├── Footer │ │ ├── index.vue │ │ └── styles.less │ ├── Header │ │ ├── index.vue │ │ └── styles.less │ ├── Layout │ │ ├── index.vue │ │ └── styles.less │ └── WalletsPopup │ │ ├── index.vue │ │ └── styles.less ├── i18n.ts ├── locales │ ├── en.json │ └── ru.json ├── main.ts ├── router │ └── index.ts ├── shims-vue.d.ts ├── store │ └── index.ts ├── ton-bridge-lib │ ├── BridgeCollector.ts │ ├── BridgeCommon.ts │ ├── BridgeEvmUtils.ts │ ├── BridgeJettonUtils.ts │ ├── BridgeMultisig.ts │ ├── BridgeTonUtils.ts │ ├── Paranoid.ts │ ├── TokenBridge.ts │ ├── ToncoinBridge.ts │ └── abi │ │ ├── ERC20.json │ │ ├── TokenBridge.json │ │ └── WTON.json ├── utils │ ├── constants.ts │ ├── helpers.ts │ ├── index.ts │ ├── providers │ │ ├── metamask │ │ │ └── index.ts │ │ ├── provider.ts │ │ ├── walletConnect │ │ │ └── index.ts │ │ └── walletLink │ │ │ └── index.ts │ └── services │ │ ├── Bridge.contract.ts │ │ └── ERC20.contract.ts └── views │ └── BridgePage │ ├── index.vue │ ├── styles.less │ └── types.ts ├── tsconfig.json └── vue.config.js /.env.example: -------------------------------------------------------------------------------- 1 | VUE_APP_I18N_LOCALE=en 2 | VUE_APP_I18N_FALLBACK_LOCALE=en 3 | BASE_URL=/bridge -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | # /dist 4 | 5 | 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | .env 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | pnpm-debug.log* 17 | 18 | # Editor directories and files 19 | .idea 20 | .vscode 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ton-token-bridge 2 | 3 | TON-EVM token bridge frontend. 4 | 5 | Developed by [RSquad](https://rsquad.io/) by order of TON Foundation. 6 | 7 | ## Project setup 8 | ``` 9 | npm install 10 | ``` 11 | 12 | ### Compiles and hot-reloads for development 13 | ``` 14 | npm run serve 15 | ``` 16 | 17 | ### Compiles and minifies for production 18 | ``` 19 | npm run build 20 | ``` 21 | 22 | ### Customize configuration 23 | See [Configuration Reference](https://cli.vuejs.org/config/). 24 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"], 3 | }; 4 | -------------------------------------------------------------------------------- /dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/token-bridge/36e80462f8ffa996b431996c080d2843e164f07e/dist/favicon.ico -------------------------------------------------------------------------------- /dist/img/arrow-disabled.0b5c1a41.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | change dir 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dist/img/arrow.4c04916a.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | change dir 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dist/img/check.75704e26.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | check 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dist/img/close.f1b54995.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/img/copy.06c68adf.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | copy 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dist/img/crystal.aabd46bc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | toncoin symbol 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /dist/img/done.8796fcad.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | done 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /dist/img/dot.5a3261fd.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | dot 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dist/img/dropdown-active.e2c856a1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | switcher dropdown arrow 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dist/img/dropdown.249360c6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | switcher dropdown arrow 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dist/img/gem@large.0d6c241e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/token-bridge/36e80462f8ffa996b431996c080d2843e164f07e/dist/img/gem@large.0d6c241e.png -------------------------------------------------------------------------------- /dist/img/link.45d5f0f4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | link 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /dist/img/loader.c1c4a3cb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | loader 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /dist/img/logo.ba3ba8c6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /dist/img/metamask.397b9a5b.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | MetaMask 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /dist/img/walletConnect.2d540c60.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | WalletConnect 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /dist/img/walletlink.d118db5c.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | WalletLink 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | TON Bridge
-------------------------------------------------------------------------------- /dist/js/686.c70d4011.js: -------------------------------------------------------------------------------- 1 | "use strict";(self["webpackChunkton_token_bridge"]=self["webpackChunkton_token_bridge"]||[]).push([[686],{1686:function(n,t,e){e.r(t),e.d(t,{WalletLink:function(){return r}});var i=e(2482),s=e(9432),o=e(1832),h=e.n(o),c=e(7450),a=e(7688),d=e(5941);class r{constructor(){(0,i.Z)(this,"name","walletLink"),(0,i.Z)(this,"title","WalletLink"),(0,i.Z)(this,"web3",null),(0,i.Z)(this,"myAddress",""),(0,i.Z)(this,"chainId",0),(0,i.Z)(this,"isConnected",!1),(0,i.Z)(this,"provider",null),(0,i.Z)(this,"emitter",void 0),this.emitter=(0,s.i)(),this.onAccountsChanged=this.onAccountsChanged.bind(this),this.onChainChanged=this.onChainChanged.bind(this),this.onDisconnect=this.onDisconnect.bind(this),this.onConnect=this.onConnect.bind(this)}on(n,t){return this.emitter.on(n,t)}async connect(n){if(window.ethereum&&!0===window.ethereum.isCoinbaseWallet)this.provider=window.ethereum;else{try{await(0,a.iM)("walletLink@2.4.2.js")}catch(e){return d.log(e.message),!1}const t=new window.WalletLinkBundle.default({appName:c.zK.appName,appLogoUrl:c.zK.appLogoUrl,darkMode:!1});this.provider=t.makeWeb3Provider(n.rpcEndpoint,n.chainId)}try{await this.provider.enable()}catch(i){if(d.log(i.message),"User denied account authorization"===i.message)return d.log(i.message),!1;throw new Error(i.message)}this.web3=new(h())(this.provider);const t=await this.web3.eth.getAccounts();return this.myAddress=t[0],this.chainId=(0,a.DQ)(await this.web3.eth.net.getId()),this.isConnected=!0,this.provider.on("accountsChanged",this.onAccountsChanged),this.provider.on("chainChanged",this.onChainChanged),this.provider.on("disconnect",this.onDisconnect),this.provider.on("connect",this.onConnect),!0}onAccountsChanged(n){d.log("account changed, old address: ",this.myAddress),n&&n.length?this.myAddress=n[0]:this.myAddress="",d.log("account changed, new address: ",this.myAddress)}onChainChanged(n){d.log("chain changed, old chain: ",this.chainId),this.chainId=(0,a.DQ)(n),d.log("chain changed, new chain: ",this.chainId)}onDisconnect(n,t){this.isConnected=!1,d.log("disconnected"),this.provider.off("accountsChanged",this.onAccountsChanged),this.provider.off("chainChanged",this.onChainChanged),this.provider.off("disconnect",this.onDisconnect),this.provider.off("connect",this.onConnect),this.emitter.emit("disconnect"),this.emitter.events={}}onConnect(){this.isConnected=!0,d.log("connected")}async switchChain(n){try{await this.provider.request({method:"wallet_switchEthereumChain",params:[{chainId:"0x"+n.toString(16)}]})}catch(t){return d.log(t.message),!1}return!0}disconnect(){this.provider.close()}}},9432:function(n,t,e){e.d(t,{i:function(){return i}});let i=()=>({events:{},emit(n,...t){let e=this.events[n]||[];for(let i=0,s=e.length;i{this.events[n]=this.events[n]?.filter((n=>t!==n))}}})}}]); 2 | //# sourceMappingURL=686.c70d4011.js.map -------------------------------------------------------------------------------- /dist/js/735.6c870ff0.js: -------------------------------------------------------------------------------- 1 | "use strict";(self["webpackChunkton_token_bridge"]=self["webpackChunkton_token_bridge"]||[]).push([[735],{9735:function(e,n,t){t.r(n),t.d(n,{Metamask:function(){return a}});var i=t(2482),s=t(6141),o=t(9432),h=t(1832),c=t.n(h),r=t(7688),d=t(5941);class a{constructor(){(0,i.Z)(this,"name","metamask"),(0,i.Z)(this,"title","Metamask"),(0,i.Z)(this,"web3",null),(0,i.Z)(this,"myAddress",""),(0,i.Z)(this,"chainId",0),(0,i.Z)(this,"isConnected",!1),(0,i.Z)(this,"emitter",void 0),(0,i.Z)(this,"provider",void 0),this.emitter=(0,o.i)(),this.onAccountsChanged=this.onAccountsChanged.bind(this),this.onChainChanged=this.onChainChanged.bind(this),this.onDisconnect=this.onDisconnect.bind(this),this.onConnect=this.onConnect.bind(this)}on(e,n){return this.emitter.on(e,n)}async connect(e){if(!window.ethereum)throw new Error("errors.installMetamask");try{await window.ethereum.request({method:"wallet_requestPermissions",params:[{eth_accounts:{}}]});const e=await window.ethereum.request({method:"eth_requestAccounts",params:[]});this.myAddress=e[0]}catch(n){if(4001===n.code)return d.log(n.message),!1;throw new Error(n.message)}return this.chainId=(0,r.DQ)(window.ethereum.networkVersion),this.isConnected=window.ethereum.isConnected(),this.web3=new(c())(window.ethereum),this.provider=new s.Q(this.web3?.currentProvider),window.ethereum.on("accountsChanged",this.onAccountsChanged),window.ethereum.on("chainChanged",this.onChainChanged),window.ethereum.on("disconnect",this.onDisconnect),window.ethereum.on("connect",this.onConnect),!0}onAccountsChanged(e){d.log("account changed, old address: ",this.myAddress),e&&e.length?this.myAddress=e[0]:this.myAddress="",d.log("account changed, new address: ",this.myAddress),this.onDisconnect(0,"")}onChainChanged(e){d.log("chain changed, old chain: ",this.chainId),this.chainId=(0,r.DQ)(e),d.log("chain changed, new chain: ",this.chainId),this.onDisconnect(0,"")}onDisconnect(e,n){this.isConnected=!1,d.log("disconnected"),window.ethereum.removeAllListeners(),this.emitter.emit("disconnect"),this.emitter.events={}}onConnect(){this.isConnected=!0,d.log("connected")}async switchChain(e){try{await window.ethereum.request({method:"wallet_switchEthereumChain",params:[{chainId:"0x"+e.toString(16)}]})}catch(n){return d.log(n.message),!1}return!0}disconnect(){this.onDisconnect(0,"")}}}}]); 2 | //# sourceMappingURL=735.6c870ff0.js.map -------------------------------------------------------------------------------- /dist/js/735.6c870ff0.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"js/735.6c870ff0.js","mappings":"sPAYM,MAAOA,EAUXC,eAAA,mBATc,aAAU,oBACT,aAAU,mBACE,OAAI,wBACZ,KAAE,sBACJ,IAAC,2BACG,IAAK,8DAKxBC,KAAKC,SAAUC,EAAAA,EAAAA,KACfF,KAAKG,kBAAoBH,KAAKG,kBAAkBC,KAAKJ,MACrDA,KAAKK,eAAiBL,KAAKK,eAAeD,KAAKJ,MAC/CA,KAAKM,aAAeN,KAAKM,aAAaF,KAAKJ,MAC3CA,KAAKO,UAAYP,KAAKO,UAAUH,KAAKJ,KACvC,CAEAQ,GAA2BC,EAAUC,GACnC,OAAOV,KAAKC,QAAQO,GAAGC,EAAOC,EAChC,CAEAC,cAAcC,GACZ,IAAKC,OAAOC,SACV,MAAM,IAAIC,MAAM,0BAGlB,UACQF,OAAOC,SAASE,QAAQ,CAC5BC,OAAQ,4BACRL,OAAQ,CACN,CACEM,aAAc,CAAC,MAKrB,MAAMC,QAAiBN,OAAOC,SAASE,QAAQ,CAC7C,OAAU,sBACV,OAAU,KAGZhB,KAAKoB,UAAYD,EAAS,E,CAC1B,MAAOE,GACP,GAAe,OAAXA,EAAEC,KAGJ,OADAC,EAAQC,IAAIH,EAAEI,UACP,EAET,MAAM,IAAIV,MAAMM,EAAEI,Q,CAmBpB,OAhBAzB,KAAK0B,SAAUC,EAAAA,EAAAA,IAAad,OAAOC,SAASc,gBAM5C5B,KAAK6B,YAAchB,OAAOC,SAASe,cACnC7B,KAAK8B,KAAO,IAAIC,IAAJ,CAASlB,OAAOC,UAE5Bd,KAAKgC,SAAW,IAAIC,EAAAA,EAAajC,KAAK8B,MAAMI,iBAE5CrB,OAAOC,SAASN,GAAG,kBAAmBR,KAAKG,mBAC3CU,OAAOC,SAASN,GAAG,eAAgBR,KAAKK,gBACxCQ,OAAOC,SAASN,GAAG,aAAcR,KAAKM,cACtCO,OAAOC,SAASN,GAAG,UAAWR,KAAKO,YAE5B,CACT,CAEAJ,kBAAkBgB,GAChBI,EAAQC,IAAI,iCAAkCxB,KAAKoB,WAC/CD,GAAYA,EAASgB,OACvBnC,KAAKoB,UAAYD,EAAS,GAE1BnB,KAAKoB,UAAY,GAEnBG,EAAQC,IAAI,iCAAkCxB,KAAKoB,WAIjDpB,KAAKM,aAAa,EAAG,GAEzB,CAEAD,eAAeqB,GACbH,EAAQC,IAAI,6BAA8BxB,KAAK0B,SAC/C1B,KAAK0B,SAAUC,EAAAA,EAAAA,IAAaD,GAC5BH,EAAQC,IAAI,6BAA8BxB,KAAK0B,SAC/C1B,KAAKM,aAAa,EAAG,GACvB,CAEAA,aAAagB,EAAcc,GACzBpC,KAAK6B,aAAc,EACnBN,EAAQC,IAAI,gBAEZX,OAAOC,SAASuB,qBAEhBrC,KAAKC,QAAQqC,KAAK,cAClBtC,KAAKC,QAAQsC,OAAS,CAAC,CACzB,CAEAhC,YACEP,KAAK6B,aAAc,EACnBN,EAAQC,IAAI,YACd,CAEAb,kBAAkBe,GAChB,UACQb,OAAOC,SAASE,QAAQ,CAC5BC,OAAQ,6BACRL,OAAQ,CAAC,CAAEc,QAAS,KAAOA,EAAQc,SAAS,O,CAE9C,MAAOnB,GAEP,OADAE,EAAQC,IAAIH,EAAEI,UACP,C,CAET,OAAO,CACT,CAEAgB,aAEEzC,KAAKM,aAAa,EAAG,GACvB,E","sources":["webpack://ton-token-bridge/./src/utils/providers/metamask/index.ts"],"sourcesContent":["import { Web3Provider } from \"@ethersproject/providers\";\nimport { createNanoEvents, Emitter } from \"nanoevents\";\nimport Web3 from \"web3\";\n\nimport { parseChainId } from \"@/utils/helpers\";\n\nimport { Provider } from \"../provider\";\n\ninterface Events {\n disconnect: () => void;\n}\n\nexport class Metamask implements Provider {\n public name = \"metamask\";\n public title = \"Metamask\";\n public web3: Web3 | null = null;\n public myAddress = \"\";\n public chainId = 0;\n public isConnected = false;\n private emitter: Emitter;\n public provider?: Web3Provider;\n\n constructor() {\n this.emitter = createNanoEvents();\n this.onAccountsChanged = this.onAccountsChanged.bind(this);\n this.onChainChanged = this.onChainChanged.bind(this);\n this.onDisconnect = this.onDisconnect.bind(this);\n this.onConnect = this.onConnect.bind(this);\n }\n\n on(event: E, callback: Events[E]) {\n return this.emitter.on(event, callback);\n }\n\n async connect(params: any): Promise {\n if (!window.ethereum) {\n throw new Error(\"errors.installMetamask\");\n }\n\n try {\n await window.ethereum.request({\n method: \"wallet_requestPermissions\",\n params: [\n {\n eth_accounts: {},\n },\n ],\n });\n\n const accounts = await window.ethereum.request({\n \"method\": \"eth_requestAccounts\",\n \"params\": []\n });\n\n this.myAddress = accounts[0];\n } catch (e: any) {\n if (e.code === 4001) {\n // soft error: User rejected the request.\n console.log(e.message);\n return false;\n }\n throw new Error(e.message);\n }\n\n this.chainId = parseChainId(window.ethereum.networkVersion);\n\n // if (this.chainId !== params.chainId) {\n // await this.switchChain(params.chainId);\n // }\n\n this.isConnected = window.ethereum.isConnected();\n this.web3 = new Web3(window.ethereum);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain\n this.provider = new Web3Provider(this.web3?.currentProvider! as any);\n\n window.ethereum.on(\"accountsChanged\", this.onAccountsChanged);\n window.ethereum.on(\"chainChanged\", this.onChainChanged);\n window.ethereum.on(\"disconnect\", this.onDisconnect);\n window.ethereum.on(\"connect\", this.onConnect);\n\n return true;\n }\n\n onAccountsChanged(accounts: Array) {\n console.log(\"account changed, old address: \", this.myAddress);\n if (accounts && accounts.length) {\n this.myAddress = accounts[0];\n } else {\n this.myAddress = \"\";\n }\n console.log(\"account changed, new address: \", this.myAddress);\n\n // metamask doesn't fire disconnect event if all accounts has been disconnected, so we need to do it explicitly\n // if (!this.myAddress) {\n this.onDisconnect(0, \"\");\n // }\n }\n\n onChainChanged(chainId: number | string) {\n console.log(\"chain changed, old chain: \", this.chainId);\n this.chainId = parseChainId(chainId);\n console.log(\"chain changed, new chain: \", this.chainId);\n this.onDisconnect(0, \"\");\n }\n\n onDisconnect(code: number, reason: string) {\n this.isConnected = false;\n console.log(\"disconnected\");\n\n window.ethereum.removeAllListeners();\n\n this.emitter.emit(\"disconnect\");\n this.emitter.events = {};\n }\n\n onConnect() {\n this.isConnected = true;\n console.log(\"connected\");\n }\n\n async switchChain(chainId: number): Promise {\n try {\n await window.ethereum.request({\n method: \"wallet_switchEthereumChain\",\n params: [{ chainId: \"0x\" + chainId.toString(16) }],\n });\n } catch (e: any) {\n console.log(e.message);\n return false;\n }\n return true;\n }\n\n disconnect() {\n // metamask has no disconnect method, strange, so simply report to FE about disconnect\n this.onDisconnect(0, \"\");\n }\n}\n"],"names":["Metamask","constructor","this","emitter","createNanoEvents","onAccountsChanged","bind","onChainChanged","onDisconnect","onConnect","on","event","callback","async","params","window","ethereum","Error","request","method","eth_accounts","accounts","myAddress","e","code","console","log","message","chainId","parseChainId","networkVersion","isConnected","web3","Web3","provider","Web3Provider","currentProvider","length","reason","removeAllListeners","emit","events","toString","disconnect"],"sourceRoot":""} -------------------------------------------------------------------------------- /dist/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/token-bridge/36e80462f8ffa996b431996c080d2843e164f07e/dist/logo.png -------------------------------------------------------------------------------- /dist/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /dist/tonconnect-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://bridge-v3.ton.org", 3 | "name": "TON Bridge", 4 | "iconUrl": "https://bridge-v3.ton.org/logo.png" 5 | } -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/token-bridge/36e80462f8ffa996b431996c080d2843e164f07e/docs/favicon.ico -------------------------------------------------------------------------------- /docs/img/arrow-disabled.0b5c1a41.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | change dir 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/img/arrow.4c04916a.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | change dir 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/img/check.75704e26.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | check 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/img/close.f1b54995.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/img/copy.06c68adf.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | copy 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/img/crystal.aabd46bc.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | toncoin symbol 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /docs/img/done.8796fcad.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | done 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/img/dot.5a3261fd.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | dot 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/img/dropdown-active.e2c856a1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | switcher dropdown arrow 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/img/dropdown.249360c6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | switcher dropdown arrow 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/img/gem@large.0d6c241e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/token-bridge/36e80462f8ffa996b431996c080d2843e164f07e/docs/img/gem@large.0d6c241e.png -------------------------------------------------------------------------------- /docs/img/link.45d5f0f4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | link 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/img/loader.c1c4a3cb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | loader 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/img/logo.ba3ba8c6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /docs/img/metamask.397b9a5b.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | MetaMask 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/img/walletConnect.2d540c60.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | WalletConnect 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/img/walletlink.d118db5c.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | WalletLink 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | TON Bridge
-------------------------------------------------------------------------------- /docs/js/686.c70d4011.js: -------------------------------------------------------------------------------- 1 | "use strict";(self["webpackChunkton_token_bridge"]=self["webpackChunkton_token_bridge"]||[]).push([[686],{1686:function(n,t,e){e.r(t),e.d(t,{WalletLink:function(){return r}});var i=e(2482),s=e(9432),o=e(1832),h=e.n(o),c=e(7450),a=e(7688),d=e(5941);class r{constructor(){(0,i.Z)(this,"name","walletLink"),(0,i.Z)(this,"title","WalletLink"),(0,i.Z)(this,"web3",null),(0,i.Z)(this,"myAddress",""),(0,i.Z)(this,"chainId",0),(0,i.Z)(this,"isConnected",!1),(0,i.Z)(this,"provider",null),(0,i.Z)(this,"emitter",void 0),this.emitter=(0,s.i)(),this.onAccountsChanged=this.onAccountsChanged.bind(this),this.onChainChanged=this.onChainChanged.bind(this),this.onDisconnect=this.onDisconnect.bind(this),this.onConnect=this.onConnect.bind(this)}on(n,t){return this.emitter.on(n,t)}async connect(n){if(window.ethereum&&!0===window.ethereum.isCoinbaseWallet)this.provider=window.ethereum;else{try{await(0,a.iM)("walletLink@2.4.2.js")}catch(e){return d.log(e.message),!1}const t=new window.WalletLinkBundle.default({appName:c.zK.appName,appLogoUrl:c.zK.appLogoUrl,darkMode:!1});this.provider=t.makeWeb3Provider(n.rpcEndpoint,n.chainId)}try{await this.provider.enable()}catch(i){if(d.log(i.message),"User denied account authorization"===i.message)return d.log(i.message),!1;throw new Error(i.message)}this.web3=new(h())(this.provider);const t=await this.web3.eth.getAccounts();return this.myAddress=t[0],this.chainId=(0,a.DQ)(await this.web3.eth.net.getId()),this.isConnected=!0,this.provider.on("accountsChanged",this.onAccountsChanged),this.provider.on("chainChanged",this.onChainChanged),this.provider.on("disconnect",this.onDisconnect),this.provider.on("connect",this.onConnect),!0}onAccountsChanged(n){d.log("account changed, old address: ",this.myAddress),n&&n.length?this.myAddress=n[0]:this.myAddress="",d.log("account changed, new address: ",this.myAddress)}onChainChanged(n){d.log("chain changed, old chain: ",this.chainId),this.chainId=(0,a.DQ)(n),d.log("chain changed, new chain: ",this.chainId)}onDisconnect(n,t){this.isConnected=!1,d.log("disconnected"),this.provider.off("accountsChanged",this.onAccountsChanged),this.provider.off("chainChanged",this.onChainChanged),this.provider.off("disconnect",this.onDisconnect),this.provider.off("connect",this.onConnect),this.emitter.emit("disconnect"),this.emitter.events={}}onConnect(){this.isConnected=!0,d.log("connected")}async switchChain(n){try{await this.provider.request({method:"wallet_switchEthereumChain",params:[{chainId:"0x"+n.toString(16)}]})}catch(t){return d.log(t.message),!1}return!0}disconnect(){this.provider.close()}}},9432:function(n,t,e){e.d(t,{i:function(){return i}});let i=()=>({events:{},emit(n,...t){let e=this.events[n]||[];for(let i=0,s=e.length;i{this.events[n]=this.events[n]?.filter((n=>t!==n))}}})}}]); 2 | //# sourceMappingURL=686.c70d4011.js.map -------------------------------------------------------------------------------- /docs/js/735.6c870ff0.js: -------------------------------------------------------------------------------- 1 | "use strict";(self["webpackChunkton_token_bridge"]=self["webpackChunkton_token_bridge"]||[]).push([[735],{9735:function(e,n,t){t.r(n),t.d(n,{Metamask:function(){return a}});var i=t(2482),s=t(6141),o=t(9432),h=t(1832),c=t.n(h),r=t(7688),d=t(5941);class a{constructor(){(0,i.Z)(this,"name","metamask"),(0,i.Z)(this,"title","Metamask"),(0,i.Z)(this,"web3",null),(0,i.Z)(this,"myAddress",""),(0,i.Z)(this,"chainId",0),(0,i.Z)(this,"isConnected",!1),(0,i.Z)(this,"emitter",void 0),(0,i.Z)(this,"provider",void 0),this.emitter=(0,o.i)(),this.onAccountsChanged=this.onAccountsChanged.bind(this),this.onChainChanged=this.onChainChanged.bind(this),this.onDisconnect=this.onDisconnect.bind(this),this.onConnect=this.onConnect.bind(this)}on(e,n){return this.emitter.on(e,n)}async connect(e){if(!window.ethereum)throw new Error("errors.installMetamask");try{await window.ethereum.request({method:"wallet_requestPermissions",params:[{eth_accounts:{}}]});const e=await window.ethereum.request({method:"eth_requestAccounts",params:[]});this.myAddress=e[0]}catch(n){if(4001===n.code)return d.log(n.message),!1;throw new Error(n.message)}return this.chainId=(0,r.DQ)(window.ethereum.networkVersion),this.isConnected=window.ethereum.isConnected(),this.web3=new(c())(window.ethereum),this.provider=new s.Q(this.web3?.currentProvider),window.ethereum.on("accountsChanged",this.onAccountsChanged),window.ethereum.on("chainChanged",this.onChainChanged),window.ethereum.on("disconnect",this.onDisconnect),window.ethereum.on("connect",this.onConnect),!0}onAccountsChanged(e){d.log("account changed, old address: ",this.myAddress),e&&e.length?this.myAddress=e[0]:this.myAddress="",d.log("account changed, new address: ",this.myAddress),this.onDisconnect(0,"")}onChainChanged(e){d.log("chain changed, old chain: ",this.chainId),this.chainId=(0,r.DQ)(e),d.log("chain changed, new chain: ",this.chainId),this.onDisconnect(0,"")}onDisconnect(e,n){this.isConnected=!1,d.log("disconnected"),window.ethereum.removeAllListeners(),this.emitter.emit("disconnect"),this.emitter.events={}}onConnect(){this.isConnected=!0,d.log("connected")}async switchChain(e){try{await window.ethereum.request({method:"wallet_switchEthereumChain",params:[{chainId:"0x"+e.toString(16)}]})}catch(n){return d.log(n.message),!1}return!0}disconnect(){this.onDisconnect(0,"")}}}}]); 2 | //# sourceMappingURL=735.6c870ff0.js.map -------------------------------------------------------------------------------- /docs/js/735.6c870ff0.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"js/735.6c870ff0.js","mappings":"sPAYM,MAAOA,EAUXC,eAAA,mBATc,aAAU,oBACT,aAAU,mBACE,OAAI,wBACZ,KAAE,sBACJ,IAAC,2BACG,IAAK,8DAKxBC,KAAKC,SAAUC,EAAAA,EAAAA,KACfF,KAAKG,kBAAoBH,KAAKG,kBAAkBC,KAAKJ,MACrDA,KAAKK,eAAiBL,KAAKK,eAAeD,KAAKJ,MAC/CA,KAAKM,aAAeN,KAAKM,aAAaF,KAAKJ,MAC3CA,KAAKO,UAAYP,KAAKO,UAAUH,KAAKJ,KACvC,CAEAQ,GAA2BC,EAAUC,GACnC,OAAOV,KAAKC,QAAQO,GAAGC,EAAOC,EAChC,CAEAC,cAAcC,GACZ,IAAKC,OAAOC,SACV,MAAM,IAAIC,MAAM,0BAGlB,UACQF,OAAOC,SAASE,QAAQ,CAC5BC,OAAQ,4BACRL,OAAQ,CACN,CACEM,aAAc,CAAC,MAKrB,MAAMC,QAAiBN,OAAOC,SAASE,QAAQ,CAC7C,OAAU,sBACV,OAAU,KAGZhB,KAAKoB,UAAYD,EAAS,E,CAC1B,MAAOE,GACP,GAAe,OAAXA,EAAEC,KAGJ,OADAC,EAAQC,IAAIH,EAAEI,UACP,EAET,MAAM,IAAIV,MAAMM,EAAEI,Q,CAmBpB,OAhBAzB,KAAK0B,SAAUC,EAAAA,EAAAA,IAAad,OAAOC,SAASc,gBAM5C5B,KAAK6B,YAAchB,OAAOC,SAASe,cACnC7B,KAAK8B,KAAO,IAAIC,IAAJ,CAASlB,OAAOC,UAE5Bd,KAAKgC,SAAW,IAAIC,EAAAA,EAAajC,KAAK8B,MAAMI,iBAE5CrB,OAAOC,SAASN,GAAG,kBAAmBR,KAAKG,mBAC3CU,OAAOC,SAASN,GAAG,eAAgBR,KAAKK,gBACxCQ,OAAOC,SAASN,GAAG,aAAcR,KAAKM,cACtCO,OAAOC,SAASN,GAAG,UAAWR,KAAKO,YAE5B,CACT,CAEAJ,kBAAkBgB,GAChBI,EAAQC,IAAI,iCAAkCxB,KAAKoB,WAC/CD,GAAYA,EAASgB,OACvBnC,KAAKoB,UAAYD,EAAS,GAE1BnB,KAAKoB,UAAY,GAEnBG,EAAQC,IAAI,iCAAkCxB,KAAKoB,WAIjDpB,KAAKM,aAAa,EAAG,GAEzB,CAEAD,eAAeqB,GACbH,EAAQC,IAAI,6BAA8BxB,KAAK0B,SAC/C1B,KAAK0B,SAAUC,EAAAA,EAAAA,IAAaD,GAC5BH,EAAQC,IAAI,6BAA8BxB,KAAK0B,SAC/C1B,KAAKM,aAAa,EAAG,GACvB,CAEAA,aAAagB,EAAcc,GACzBpC,KAAK6B,aAAc,EACnBN,EAAQC,IAAI,gBAEZX,OAAOC,SAASuB,qBAEhBrC,KAAKC,QAAQqC,KAAK,cAClBtC,KAAKC,QAAQsC,OAAS,CAAC,CACzB,CAEAhC,YACEP,KAAK6B,aAAc,EACnBN,EAAQC,IAAI,YACd,CAEAb,kBAAkBe,GAChB,UACQb,OAAOC,SAASE,QAAQ,CAC5BC,OAAQ,6BACRL,OAAQ,CAAC,CAAEc,QAAS,KAAOA,EAAQc,SAAS,O,CAE9C,MAAOnB,GAEP,OADAE,EAAQC,IAAIH,EAAEI,UACP,C,CAET,OAAO,CACT,CAEAgB,aAEEzC,KAAKM,aAAa,EAAG,GACvB,E","sources":["webpack://ton-token-bridge/./src/utils/providers/metamask/index.ts"],"sourcesContent":["import { Web3Provider } from \"@ethersproject/providers\";\nimport { createNanoEvents, Emitter } from \"nanoevents\";\nimport Web3 from \"web3\";\n\nimport { parseChainId } from \"@/utils/helpers\";\n\nimport { Provider } from \"../provider\";\n\ninterface Events {\n disconnect: () => void;\n}\n\nexport class Metamask implements Provider {\n public name = \"metamask\";\n public title = \"Metamask\";\n public web3: Web3 | null = null;\n public myAddress = \"\";\n public chainId = 0;\n public isConnected = false;\n private emitter: Emitter;\n public provider?: Web3Provider;\n\n constructor() {\n this.emitter = createNanoEvents();\n this.onAccountsChanged = this.onAccountsChanged.bind(this);\n this.onChainChanged = this.onChainChanged.bind(this);\n this.onDisconnect = this.onDisconnect.bind(this);\n this.onConnect = this.onConnect.bind(this);\n }\n\n on(event: E, callback: Events[E]) {\n return this.emitter.on(event, callback);\n }\n\n async connect(params: any): Promise {\n if (!window.ethereum) {\n throw new Error(\"errors.installMetamask\");\n }\n\n try {\n await window.ethereum.request({\n method: \"wallet_requestPermissions\",\n params: [\n {\n eth_accounts: {},\n },\n ],\n });\n\n const accounts = await window.ethereum.request({\n \"method\": \"eth_requestAccounts\",\n \"params\": []\n });\n\n this.myAddress = accounts[0];\n } catch (e: any) {\n if (e.code === 4001) {\n // soft error: User rejected the request.\n console.log(e.message);\n return false;\n }\n throw new Error(e.message);\n }\n\n this.chainId = parseChainId(window.ethereum.networkVersion);\n\n // if (this.chainId !== params.chainId) {\n // await this.switchChain(params.chainId);\n // }\n\n this.isConnected = window.ethereum.isConnected();\n this.web3 = new Web3(window.ethereum);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain\n this.provider = new Web3Provider(this.web3?.currentProvider! as any);\n\n window.ethereum.on(\"accountsChanged\", this.onAccountsChanged);\n window.ethereum.on(\"chainChanged\", this.onChainChanged);\n window.ethereum.on(\"disconnect\", this.onDisconnect);\n window.ethereum.on(\"connect\", this.onConnect);\n\n return true;\n }\n\n onAccountsChanged(accounts: Array) {\n console.log(\"account changed, old address: \", this.myAddress);\n if (accounts && accounts.length) {\n this.myAddress = accounts[0];\n } else {\n this.myAddress = \"\";\n }\n console.log(\"account changed, new address: \", this.myAddress);\n\n // metamask doesn't fire disconnect event if all accounts has been disconnected, so we need to do it explicitly\n // if (!this.myAddress) {\n this.onDisconnect(0, \"\");\n // }\n }\n\n onChainChanged(chainId: number | string) {\n console.log(\"chain changed, old chain: \", this.chainId);\n this.chainId = parseChainId(chainId);\n console.log(\"chain changed, new chain: \", this.chainId);\n this.onDisconnect(0, \"\");\n }\n\n onDisconnect(code: number, reason: string) {\n this.isConnected = false;\n console.log(\"disconnected\");\n\n window.ethereum.removeAllListeners();\n\n this.emitter.emit(\"disconnect\");\n this.emitter.events = {};\n }\n\n onConnect() {\n this.isConnected = true;\n console.log(\"connected\");\n }\n\n async switchChain(chainId: number): Promise {\n try {\n await window.ethereum.request({\n method: \"wallet_switchEthereumChain\",\n params: [{ chainId: \"0x\" + chainId.toString(16) }],\n });\n } catch (e: any) {\n console.log(e.message);\n return false;\n }\n return true;\n }\n\n disconnect() {\n // metamask has no disconnect method, strange, so simply report to FE about disconnect\n this.onDisconnect(0, \"\");\n }\n}\n"],"names":["Metamask","constructor","this","emitter","createNanoEvents","onAccountsChanged","bind","onChainChanged","onDisconnect","onConnect","on","event","callback","async","params","window","ethereum","Error","request","method","eth_accounts","accounts","myAddress","e","code","console","log","message","chainId","parseChainId","networkVersion","isConnected","web3","Web3","provider","Web3Provider","currentProvider","length","reason","removeAllListeners","emit","events","toString","disconnect"],"sourceRoot":""} -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/token-bridge/36e80462f8ffa996b431996c080d2843e164f07e/docs/logo.png -------------------------------------------------------------------------------- /docs/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /docs/tonconnect-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://bridge-v3.ton.org", 3 | "name": "TON Bridge", 4 | "iconUrl": "https://bridge-v3.ton.org/logo.png" 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ton-token-bridge", 3 | "version": "0.1.0", 4 | "private": true, 5 | "license": "GPL-3.0", 6 | "scripts": { 7 | "serve": "clear && vue-cli-service serve", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint", 10 | "lint:fix": "vue-cli-service lint -- --fix", 11 | "i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\"" 12 | }, 13 | "dependencies": { 14 | "@walletconnect/ethereum-provider": "2.9.2", 15 | "@walletconnect/modal": "2.6.1", 16 | "core-js": "3.26.0", 17 | "detect-it": "4.0.1", 18 | "ethers": "5.7.2", 19 | "lodash.debounce": "4.0.8", 20 | "nanoevents": "7.0.1", 21 | "node-polyfill-webpack-plugin": "2.0.1", 22 | "qr-code-styling": "1.6.0-rc.1", 23 | "tonweb": "0.0.60", 24 | "vue": "3.2.41", 25 | "vue-i18n": "9.2.2", 26 | "vue-router": "4.1.6", 27 | "vue-uuid": "3.0.0", 28 | "vuex": "4.1.0", 29 | "web3": "1.8.0", 30 | "@tonconnect/sdk": "3.0.0", 31 | "@tonconnect/ui": "2.0.0" 32 | }, 33 | "devDependencies": { 34 | "@intlify/vue-i18n-loader": "3.3.0", 35 | "@types/detect-it": "4.0.1", 36 | "@types/lodash.debounce": "4.0.7", 37 | "@vue/cli-plugin-babel": "5.0.8", 38 | "@vue/cli-plugin-router": "5.0.8", 39 | "@vue/cli-plugin-typescript": "5.0.8", 40 | "@vue/cli-plugin-vuex": "5.0.8", 41 | "@vue/cli-service": "5.0.8", 42 | "less": "4.1.3", 43 | "less-loader": "8.1.1", 44 | "typescript": "4.5.5", 45 | "vue-cli-plugin-i18n": "2.3.1" 46 | }, 47 | "browserslist": [ 48 | "> 1%", 49 | "last 2 versions", 50 | "not dead", 51 | "not ie 11" 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/token-bridge/36e80462f8ffa996b431996c080d2843e164f07e/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 19 | TON Bridge 20 | 21 | 22 | 29 |
30 |
31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/token-bridge/36e80462f8ffa996b431996c080d2843e164f07e/public/logo.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/tonconnect-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://bridge-v3.ton.org", 3 | "name": "TON Bridge", 4 | "iconUrl": "https://bridge-v3.ton.org/logo.png" 5 | } -------------------------------------------------------------------------------- /src/@types/eth-lib.d.ts: -------------------------------------------------------------------------------- 1 | declare module "eth-lib/lib/hash" { 2 | any; 3 | export default Hash; 4 | } 5 | -------------------------------------------------------------------------------- /src/@types/global.d.ts: -------------------------------------------------------------------------------- 1 | interface Window { 2 | ethereum: any; 3 | WalletLinkBundle: any; 4 | } 5 | -------------------------------------------------------------------------------- /src/@types/params.d.ts: -------------------------------------------------------------------------------- 1 | type ParamsNetwork = { 2 | getGasUrl: string; 3 | explorerUrl: string; 4 | wTonAddress: string; 5 | tonBridgeV2EVMAddress: string; 6 | tonBridgeAddress: string; 7 | tonCollectorAddress: string; 8 | tonMultisigAddress: string; 9 | tonBridgeAddressV2: string; 10 | tonCollectorAddressV2: string; 11 | tonMultisigAddressV2: string; 12 | tonCenterUrl: string; 13 | tonCenterApiKey: string; 14 | rpcEndpoint: string; 15 | chainId: number; 16 | blocksConfirmations: number; 17 | defaultGwei: number; 18 | toncoinTransferTo_gasPrice: number; 19 | toncoinTransferFrom_gasPrice: number; 20 | tokenTransferTo_gasPrice: number; 21 | tokenTransferFrom_gasPrice: number; 22 | }; 23 | 24 | type Params = { 25 | tonTransferUrl: string; 26 | tonExplorerUrl: { 27 | main: string; 28 | test: string; 29 | }; 30 | appName: string; 31 | appLogoUrl: string; 32 | networks: { 33 | [key: string]: { 34 | main: ParamsNetwork; 35 | test: ParamsNetwork; 36 | }; 37 | }; 38 | }; 39 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /src/api/tonWallet.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import TonWeb from "tonweb"; 3 | import {Address} from "tonweb/dist/types/utils/address"; 4 | import {decToBN} from "@/ton-bridge-lib/Paranoid"; 5 | import {TonConnectUI} from "@tonconnect/ui"; 6 | 7 | async function burnJetton({ 8 | tonConnect, 9 | destinationAddress, 10 | userTonAddress, 11 | jettonWalletAddress, 12 | jettonAmountWithDecimals, 13 | }: { 14 | tonConnect: TonConnectUI; 15 | destinationAddress: BN; 16 | userTonAddress: Address; 17 | jettonWalletAddress: Address | null; 18 | jettonAmountWithDecimals: BN; 19 | }) { 20 | if (!tonConnect.connected) { 21 | throw new Error('TON not connected'); 22 | } 23 | 24 | if (!jettonWalletAddress) { 25 | throw new Error('no jettonWalletAddress'); 26 | } 27 | 28 | const burnOP = 0x595f07bc; // burn op 29 | const queryId = new TonWeb.utils.BN(0); 30 | 31 | const burnPayload = new TonWeb.boc.Cell(); 32 | burnPayload.bits.writeUint(burnOP, 32); 33 | const customPayload = new TonWeb.boc.Cell(); 34 | customPayload.bits.writeUint(destinationAddress, 160); 35 | burnPayload.bits.writeUint(queryId, 64); 36 | burnPayload.bits.writeCoins(jettonAmountWithDecimals); 37 | burnPayload.bits.writeAddress(userTonAddress); 38 | burnPayload.bits.writeBit(1); 39 | burnPayload.refs.push(customPayload); 40 | 41 | const payloadBase64 = TonWeb.utils.bytesToBase64(await burnPayload.toBoc(false)); 42 | 43 | await tonConnect.sendTransaction( 44 | { 45 | validUntil: Math.floor(Date.now() / 1000) + 60, // 1 minute 46 | messages: [{ 47 | address: jettonWalletAddress.toString(true, true, true), 48 | amount: TonWeb.utils.toNano("1").toString(), 49 | payload: payloadBase64 50 | }] 51 | } 52 | ); 53 | } 54 | 55 | async function mintJetton({ 56 | tonConnect, 57 | queryId, 58 | bridgeTonAddress, 59 | }: { 60 | tonConnect: TonConnectUI; 61 | queryId: string; 62 | bridgeTonAddress: string; 63 | }) { 64 | if (!tonConnect.connected) { 65 | throw new Error('TON not connected'); 66 | } 67 | 68 | const mintOP = 8; 69 | 70 | const mintPayload = new TonWeb.boc.Cell(); 71 | mintPayload.bits.writeUint(mintOP, 32); 72 | mintPayload.bits.writeUint(decToBN(queryId), 64); 73 | mintPayload.bits.writeUint(decToBN(queryId), 256); 74 | 75 | const payloadBase64 = TonWeb.utils.bytesToBase64(await mintPayload.toBoc(false)); 76 | 77 | await tonConnect.sendTransaction( 78 | { 79 | validUntil: Math.floor(Date.now() / 1000) + 60, // 1 minute 80 | messages: [{ 81 | address: bridgeTonAddress, 82 | amount: TonWeb.utils.toNano("1").toString(), 83 | payload: payloadBase64 84 | }] 85 | } 86 | ); 87 | } 88 | 89 | export {burnJetton, mintJetton}; 90 | -------------------------------------------------------------------------------- /src/assets/pics/arrow-disabled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | change dir 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/pics/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | change dir 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/pics/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | check 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/pics/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/pics/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | copy 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/pics/crystal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | toncoin symbol 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/assets/pics/done.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/pics/dropdown-active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | switcher dropdown arrow 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/pics/dropdown.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | switcher dropdown arrow 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/pics/gem@large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/token-bridge/36e80462f8ffa996b431996c080d2843e164f07e/src/assets/pics/gem@large.png -------------------------------------------------------------------------------- /src/assets/pics/link.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | link 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/pics/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/pics/progress/done.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | done 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/pics/progress/dot.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | dot 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/pics/progress/loader.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | loader 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/pics/providers/metamask.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | MetaMask 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/assets/pics/providers/walletConnect.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | WalletConnect 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/assets/pics/providers/walletlink.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | WalletLink 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/assets/styles/_variables.less: -------------------------------------------------------------------------------- 1 | // Transition durations 2 | @d-hover: 0.15s; 3 | @d-modal: 0.1s; 4 | @d-header-collapse: 0.25s; 5 | @d-tooltip: 0.45s; 6 | @d-loader: 2s; 7 | 8 | // Z-indexes 9 | @z-input-dropdown: 1; 10 | @z-history: 2; 11 | @z-header: 3; 12 | @z-wallet-popup: 5; 13 | @z-alert: 6; 14 | 15 | // Colors 16 | @c-background: #ffffff; 17 | @c-overlay: rgba(black, 0.25); 18 | @c-panel-shadow: rgb(#303757, 0.12); 19 | @c-primary: #1d98dc; 20 | @c-error: #e53935; 21 | 22 | @c-text: #222222; 23 | @c-text-light: #303757; 24 | @c-label: #757575; 25 | @c-text-secondary: #858585; 26 | @c-text-secondary-light: #aaaaaa; 27 | @c-outline: #aaaaaa; 28 | 29 | @c-provider-border: #ced0d9; 30 | @c-provider-background: #edeef2; 31 | -------------------------------------------------------------------------------- /src/assets/styles/global.less: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, 6 | body, 7 | #app, 8 | .LayoutDefault { 9 | width: 100%; 10 | height: 100%; 11 | } 12 | 13 | body { 14 | -webkit-text-size-adjust: 100%; 15 | color: @c-text-light; 16 | } 17 | 18 | body, 19 | input, 20 | button { 21 | font-family: "Open Sans", sans-serif; 22 | font-weight: 600; 23 | } 24 | 25 | a, 26 | a:hover, 27 | a:active { 28 | text-decoration: none; 29 | // word-break: break-all; 30 | } 31 | 32 | #tonConnectButton { 33 | position: fixed; 34 | right: 220px; 35 | top: 20px; 36 | z-index: 4; 37 | 38 | @media (max-width: 560px) { 39 | top: 12px; 40 | right: 164px; 41 | transform: scale(0.8, 0.8); 42 | } 43 | } 44 | 45 | tc-root button { 46 | text-align: center !important; 47 | } 48 | -------------------------------------------------------------------------------- /src/assets/styles/reboot.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | vertical-align: baseline; 23 | } 24 | input, textarea{ 25 | margin: 0; 26 | } 27 | 28 | /* HTML5 display-role reset for older browsers */ 29 | article, aside, details, figcaption, figure, 30 | footer, header, hgroup, menu, nav, section { 31 | display: block; 32 | } 33 | 34 | blockquote, q { 35 | quotes: none; 36 | } 37 | 38 | blockquote:before, blockquote:after, 39 | q:before, q:after { 40 | content: ''; 41 | content: none; 42 | } 43 | 44 | table { 45 | border-collapse: collapse; 46 | border-spacing: 0; 47 | } 48 | 49 | 50 | *, 51 | *:after, 52 | *:before { 53 | -webkit-box-sizing: border-box; 54 | -moz-box-sizing: border-box; 55 | box-sizing: border-box; 56 | } 57 | 58 | @media (prefers-reduced-motion: no-preference) { 59 | :root { 60 | /*scroll-behavior: smooth;*/ 61 | } 62 | } 63 | 64 | html { 65 | -webkit-text-size-adjust: none; 66 | -webkit-tap-highlight-color: transparent; 67 | } 68 | 69 | body { 70 | -webkit-font-smoothing: antialiased; 71 | -moz-osx-font-smoothing: grayscale; 72 | text-rendering: optimizeLegibility; 73 | -webkit-text-size-adjust: 100%; 74 | -webkit-font-feature-settings: "kern"; 75 | -moz-font-feature-settings: "kern=1"; 76 | -moz-font-feature-settings: "kern"; 77 | -ms-font-feature-settings: "kern" 1; 78 | font-feature-settings: "kern"; 79 | font-variant-numeric: normal; 80 | word-wrap: break-word; 81 | } 82 | 83 | input::-webkit-contacts-auto-fill-button { 84 | visibility: hidden; 85 | display: none !important; 86 | pointer-events: none; 87 | position: absolute; 88 | right: 0; 89 | } 90 | 91 | ::-webkit-credentials-auto-fill-button { 92 | visibility: hidden; 93 | pointer-events: none; 94 | position: absolute; 95 | right: 0; 96 | } 97 | 98 | input::-ms-clear { 99 | display: none !important; 100 | } 101 | 102 | input::-ms-reveal { 103 | display: none !important; 104 | } 105 | 106 | input:focus::-webkit-input-placeholder, 107 | textarea:focus::-webkit-input-placeholder { 108 | color: transparent !important; 109 | } 110 | 111 | input:focus::-moz-placeholder, 112 | textarea:focus::-moz-placeholder { 113 | color: transparent !important; 114 | } 115 | 116 | input:focus:-moz-placeholder, 117 | textarea:focus:-moz-placeholder { 118 | color: transparent !important; 119 | } 120 | 121 | input:focus::placeholder, 122 | textarea:focus::placeholder { 123 | color: transparent !important; 124 | } 125 | 126 | button { 127 | border: none; 128 | margin: 0; 129 | padding: 0; 130 | width: auto; 131 | overflow: visible; 132 | background: transparent; 133 | color: inherit; 134 | font: inherit; 135 | text-align: inherit; 136 | outline: none; 137 | border-radius: 0; 138 | cursor: pointer; 139 | 140 | line-height: normal; 141 | 142 | -webkit-font-smoothing: inherit; 143 | -moz-osx-font-smoothing: inherit; 144 | -webkit-appearance: none; 145 | } 146 | 147 | a { 148 | outline: none; 149 | } 150 | 151 | button::-moz-focus-inner { 152 | border: 0; 153 | padding: 0; 154 | } 155 | 156 | 157 | svg { 158 | overflow: visible; 159 | } 160 | 161 | [tabindex="-1"]:focus:not(:focus-visible) { 162 | outline: 0 !important; 163 | } 164 | -------------------------------------------------------------------------------- /src/components/Alert/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 46 | 47 | 50 | -------------------------------------------------------------------------------- /src/components/Alert/styles.less: -------------------------------------------------------------------------------- 1 | @r: .Alert; 2 | 3 | @{r} { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | width: 100%; 8 | height: 100%; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | z-index: @z-alert; 13 | padding: 16px; 14 | 15 | &-panel { 16 | position: relative; 17 | background: @c-background; 18 | box-sizing: border-box; 19 | padding: 16px; 20 | margin: 32px auto; 21 | box-shadow: 0px 8px 24px @c-panel-shadow; 22 | border-radius: 12px; 23 | display: flex; 24 | flex-direction: column; 25 | //align-items: flex-start; 26 | justify-content: flex-start; 27 | max-width: 400px; 28 | 29 | h2 { 30 | font-size: 20px; 31 | text-align: left; 32 | margin-bottom: 16px; 33 | } 34 | 35 | p { 36 | font-size: 16px; 37 | text-align: left; 38 | margin-bottom: 16px; 39 | } 40 | 41 | button { 42 | align-self: flex-end; 43 | color: @c-primary; 44 | font-size: 16px; 45 | outline: none !important; 46 | display: flex; 47 | align-items: center; 48 | justify-content: center; 49 | border: 0; 50 | border-radius: 12px; 51 | background-color: transparent; 52 | background-size: cover; 53 | padding: 10px; 54 | line-height: 1.2; 55 | cursor: pointer; 56 | text-transform: uppercase; 57 | flex-shrink: 0; 58 | position: relative; 59 | overflow: hidden; 60 | transition: background-color @d-hover, color @d-hover; 61 | text-decoration: none !important; 62 | 63 | .isPointer &:hover, 64 | .isTouch &:active { 65 | background-color: rgba(@c-primary, 0.08); 66 | } 67 | } 68 | } 69 | 70 | &-overlay { 71 | position: absolute; 72 | left: 0; 73 | top: 0; 74 | width: 100%; 75 | height: 100%; 76 | background: @c-overlay; 77 | } 78 | 79 | &Transition-enter, 80 | &Transition-leave-to { 81 | opacity: 0; 82 | } 83 | &Transition-enter-active, 84 | &Transition-leave-active { 85 | transition: @d-modal opacity; 86 | } 87 | &Transition-enter-to, 88 | &Transition-leave { 89 | opacity: 1; 90 | } 91 | 92 | &-link { 93 | color: @c-primary; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/components/BridgeProcessor/styles.less: -------------------------------------------------------------------------------- 1 | @r: .BridgeProcessor; 2 | 3 | @{r} { 4 | &-transfer, 5 | &-getTonCoin, 6 | &-done, 7 | &-cancel, 8 | &-info-text-openWallet { 9 | -webkit-appearance: none; 10 | background-color: @c-primary; 11 | display: inline-block; 12 | border-radius: 25px; 13 | color: white; 14 | font-size: 15px; 15 | font-weight: 700; 16 | line-height: 19px; 17 | border: none; 18 | padding: 16px 35px 15px; 19 | white-space: nowrap; 20 | 21 | .isPointer &:hover, 22 | .isTouch &:active { 23 | background-color: rgba(@c-primary, 0.85); 24 | } 25 | 26 | &[disabled] { 27 | pointer-events: none; 28 | background-color: @c-text-secondary-light; 29 | } 30 | 31 | &.showLoader { 32 | pointer-events: none; 33 | } 34 | 35 | @media (max-width: 800px) { 36 | font-size: 14px; 37 | padding: 14px 32px 13px; 38 | } 39 | 40 | &.showLoader:after { 41 | content: ""; 42 | position: absolute; 43 | transform: translate(8px, 3px); 44 | width: 12px; 45 | height: 12px; 46 | border: 3px solid white; 47 | border-left: 3px solid transparent; 48 | border-radius: 50%; 49 | animation: loading-animation-spin @d-loader infinite linear; 50 | 51 | @keyframes loading-animation-spin { 52 | to { 53 | transform: translate(8px, 3px) rotate(1turn); 54 | } 55 | } 56 | } 57 | } 58 | 59 | &-cancel { 60 | margin-top: -10px; 61 | margin-bottom: 40px; 62 | background-color: @c-background; 63 | color: @c-error; 64 | box-shadow: inset 0 0 0 2px @c-error; 65 | 66 | .isPointer &:hover, 67 | .isTouch &:active { 68 | background-color: rgba(@c-error, 0.1); 69 | } 70 | } 71 | 72 | &-getTonCoin, 73 | &-done { 74 | margin-top: 30px; 75 | } 76 | 77 | &-infoWrapper { 78 | text-align: left; 79 | width: fit-content; 80 | font-size: 17px; 81 | line-height: 25px; 82 | 83 | @media (max-width: 800px) { 84 | font-size: 15px; 85 | line-height: 23px; 86 | } 87 | 88 | @media (max-width: 400px) { 89 | font-size: 14px; 90 | line-height: 21px; 91 | } 92 | } 93 | 94 | &-infoLine { 95 | position: relative; 96 | margin-top: 28px; 97 | 98 | &:first-child { 99 | margin-top: 0; 100 | } 101 | 102 | @media (max-width: 550px) { 103 | padding-left: 28px; 104 | margin-top: 20px; 105 | } 106 | } 107 | 108 | &-info-icon { 109 | position: absolute; 110 | left: -28px; 111 | margin-top: 4px; 112 | width: 18px; 113 | height: 18px; 114 | background-size: contain; 115 | background-repeat: no-repeat; 116 | background-position: center; 117 | 118 | @media (max-width: 550px) { 119 | left: 0; 120 | } 121 | 122 | &.done { 123 | background-image: url("~@/assets/pics/progress/done.svg"); 124 | } 125 | 126 | &.pending { 127 | background-image: url("~@/assets/pics/progress/loader.svg"); 128 | animation: rotating @d-loader linear infinite; 129 | } 130 | 131 | &.none { 132 | background-image: url("~@/assets/pics/progress/dot.svg"); 133 | } 134 | } 135 | 136 | &-info-text-send { 137 | display: flex; 138 | flex-direction: column; 139 | justify-content: flex-start; 140 | align-items: center; 141 | text-align: left; 142 | line-height: 27px; 143 | 144 | @media (max-width: 800px) { 145 | line-height: 25px; 146 | } 147 | 148 | @media (max-width: 400px) { 149 | line-height: 23px; 150 | } 151 | } 152 | 153 | &-info-text-send-buttons { 154 | display: flex; 155 | flex-direction: column; 156 | justify-content: flex-start; 157 | align-items: center; 158 | 159 | @media (max-width: 550px) { 160 | padding-right: 28px; 161 | } 162 | } 163 | 164 | &-info-text-openWallet { 165 | margin: 22px 0 20px; 166 | } 167 | 168 | &-info-text-copy, 169 | &-info-text-generateQRCode { 170 | color: @c-primary; 171 | text-decoration: underline; 172 | -webkit-appearance: none; 173 | background-color: transparent; 174 | border: none; 175 | padding: 0; 176 | margin: 0; 177 | letter-spacing: -0.3px; 178 | 179 | .isPointer &:hover, 180 | .isTouch &:active { 181 | text-decoration: none; 182 | } 183 | } 184 | 185 | &-info-text-generateQRCode, 186 | &-info-text-scanQRCode { 187 | font-size: 15px; 188 | 189 | @media (max-width: 800px) { 190 | font-size: 14px; 191 | } 192 | 193 | @media (max-width: 400px) { 194 | font-size: 13px; 195 | } 196 | } 197 | 198 | &-info-text-scanQRCode { 199 | margin-top: -3px; 200 | color: @c-text-light; 201 | } 202 | 203 | &-info-text-copy { 204 | position: relative; 205 | padding-left: 18px; 206 | word-break: break-word; 207 | 208 | &:before { 209 | content: ""; 210 | position: absolute; 211 | left: -3px; 212 | display: inline-block; 213 | height: 20px; 214 | width: 20px; 215 | background-image: url("~@/assets/pics/copy.svg"); 216 | background-repeat: no-repeat; 217 | background-size: contain; 218 | top: 1px; 219 | } 220 | 221 | &.success:after { 222 | content: "Copied"; 223 | } 224 | 225 | &.failure:after { 226 | background: @c-error; 227 | content: "Failed"; 228 | } 229 | 230 | &.success:after, 231 | &.failure:after { 232 | content: "Copied"; 233 | position: absolute; 234 | padding: 4px; 235 | font-size: 12px; 236 | color: white; 237 | background: @c-primary; 238 | border-radius: 4px; 239 | top: -22px; 240 | left: 7px; 241 | white-space: nowrap; 242 | animation: fade-tooltip @d-tooltip linear both; 243 | 244 | @keyframes fade-tooltip { 245 | 0% { 246 | transform: translate(-50%, 0); 247 | opacity: 0; 248 | } 249 | 20% { 250 | opacity: 1; 251 | } 252 | 60% { 253 | opacity: 1; 254 | } 255 | 100% { 256 | transform: translate(-50%, -5px); 257 | opacity: 0; 258 | } 259 | } 260 | } 261 | } 262 | 263 | &-info-text-QRCode { 264 | margin-top: 16px; 265 | width: 100%; 266 | height: 225px; 267 | display: flex; 268 | justify-content: center; 269 | align-items: center; 270 | margin-bottom: 7px; 271 | 272 | :deep(canvas) { 273 | width: 100%; 274 | height: 100%; 275 | object-fit: contain; 276 | } 277 | } 278 | 279 | @keyframes rotating { 280 | from { 281 | -ms-transform: rotate(0deg); 282 | -moz-transform: rotate(0deg); 283 | -webkit-transform: rotate(0deg); 284 | -o-transform: rotate(0deg); 285 | transform: rotate(0deg); 286 | } 287 | to { 288 | -ms-transform: rotate(360deg); 289 | -moz-transform: rotate(360deg); 290 | -webkit-transform: rotate(360deg); 291 | -o-transform: rotate(360deg); 292 | transform: rotate(360deg); 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /src/components/BridgeProcessor/types.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import TonWeb from "tonweb"; 3 | import { Contract } from "web3-eth-contract"; 4 | import {SwapEthToTonEvent, SwapTonToEth} from "@/ton-bridge-lib/ToncoinBridge"; 5 | import {EvmSignature} from "@/ton-bridge-lib/BridgeCollector"; 6 | import {BurnEvent} from "@/ton-bridge-lib/TokenBridge"; 7 | import {TonConnectUI} from "@tonconnect/ui"; 8 | 9 | type ProviderDataForToncoin = { 10 | oraclesTotal: number; 11 | blockNumber: number; 12 | wtonContract: Contract; 13 | tonweb: TonWeb; 14 | feeFlat: BN; 15 | feeFactor: BN; 16 | feeBase: BN; 17 | }; 18 | 19 | type ProviderDataForJettons = { 20 | oraclesTotal: number; 21 | blockNumber: number; 22 | bridgeContract: Contract; 23 | tonweb: TonWeb; 24 | myAddress: string; 25 | tonConnect: TonConnectUI; 26 | }; 27 | 28 | type State = { 29 | swapId: string; 30 | queryId: string; 31 | jettonEvmAddress: string; 32 | fromCurrencySent: boolean; 33 | toCurrencySent: boolean; 34 | step: number; 35 | votes: EvmSignature[] | number[] | null; 36 | swapData: SwapTonToEth | null; 37 | burnData: BurnEvent | null; 38 | createTime: number; 39 | blockNumber: number; 40 | }; 41 | 42 | type ComponentData = { 43 | updateStateIntervalForToncoin: null | ReturnType; 44 | updateStateIntervalForJettons: null | ReturnType; 45 | providerDataForToncoin: ProviderDataForToncoin | null; 46 | providerDataForJettons: ProviderDataForJettons | null; 47 | state: State; 48 | ethToTon: SwapEthToTonEvent | null; 49 | isInitInProgress: boolean; 50 | isMintingInProgress: boolean; 51 | isApprovingInProgress: boolean; 52 | isGetAllowanceInProcess: boolean; 53 | isGetAllowanceError: boolean; 54 | isBurningInProgress: boolean; 55 | isLockingInProgress: boolean; 56 | isQRCodeShown: boolean; 57 | hasApprove: boolean; 58 | }; 59 | 60 | export { 61 | ComponentData, 62 | ProviderDataForJettons, 63 | ProviderDataForToncoin, 64 | State, 65 | }; 66 | -------------------------------------------------------------------------------- /src/components/CustomInput/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 116 | 117 | 120 | -------------------------------------------------------------------------------- /src/components/CustomInput/styles.less: -------------------------------------------------------------------------------- 1 | @r: .CustomInput; 2 | 3 | @{r} { 4 | position: relative; 5 | margin-bottom: 30px; 6 | width: 100%; 7 | line-height: 1.5; 8 | 9 | @media (max-width: 800px) { 10 | margin-bottom: 28px; 11 | } 12 | 13 | @media (max-width: 400px) { 14 | margin-bottom: 26px; 15 | } 16 | 17 | &-labelWrapper { 18 | position: absolute; 19 | left: 0; 20 | top: -10px; 21 | right: 16px; 22 | bottom: 0; 23 | overflow: hidden; 24 | pointer-events: none; 25 | } 26 | 27 | label { 28 | display: block; 29 | padding: 0 8px; 30 | position: absolute; 31 | left: 12px; 32 | top: 22px; 33 | background-color: @c-background; 34 | font-size: 17px; 35 | color: @c-label; 36 | transition: transform @d-hover ease-out, color @d-hover ease-out; 37 | cursor: text; 38 | pointer-events: none; 39 | transform-origin: left center; 40 | white-space: nowrap; 41 | 42 | @media (max-width: 800px) { 43 | font-size: 15px; 44 | left: 8px; 45 | top: 21px; 46 | padding: 0 6px; 47 | } 48 | 49 | @media (max-width: 400px) { 50 | font-size: 14px; 51 | top: 22px; 52 | } 53 | } 54 | 55 | input { 56 | display: block; 57 | width: 100%; 58 | height: 50px; 59 | padding: 9px 18px; 60 | border: 1px solid @c-outline; 61 | border-radius: 8px; 62 | color: @c-text; 63 | background-color: @c-background; 64 | outline: none; 65 | transition: border-color @d-hover ease, 9999s background-color; // 9999s to skip browser styles for values 66 | word-break: break-word; 67 | -webkit-appearance: none; 68 | font-size: 17px; 69 | line-height: 20px; 70 | 71 | &[disabled] { 72 | color: @c-text-secondary; 73 | -webkit-text-fill-color: @c-text-secondary; 74 | } 75 | 76 | @media (max-width: 800px) { 77 | font-size: 15px; 78 | padding: 7px 14px; 79 | height: 46px; 80 | } 81 | 82 | @media (max-width: 400px) { 83 | font-size: 14px; 84 | padding: 7px 14px; 85 | height: 46px; 86 | } 87 | } 88 | 89 | &-arrow { 90 | position: absolute; 91 | top: 50%; 92 | bottom: 0; 93 | right: 22px; 94 | height: 0; 95 | width: 0; 96 | color: @c-text; 97 | border: solid currentColor; 98 | border-radius: 1px; 99 | border-width: 0 2px 2px 0; 100 | display: inline-block; 101 | padding: 5px; 102 | vertical-align: middle; 103 | margin-top: -9px; 104 | transform: rotate(45deg); 105 | transition: @d-hover all; 106 | pointer-events: none; 107 | 108 | @media (max-width: 800px) { 109 | right: 18px; 110 | } 111 | 112 | @media (max-width: 400px) { 113 | right: 16px; 114 | } 115 | } 116 | 117 | &.disabled &-arrow { 118 | color: @c-text-secondary; 119 | } 120 | 121 | &-dropdown { 122 | background: @c-background; 123 | border-radius: 16px; 124 | box-shadow: 0px 8px 24px @c-panel-shadow; 125 | box-sizing: border-box; 126 | color: @c-text-light; 127 | font-size: 17px; 128 | left: 0; 129 | line-height: 20px; 130 | list-style-type: none; 131 | margin: 0; 132 | padding: 12px 24px; 133 | position: absolute; 134 | right: 0; 135 | top: 100%; 136 | word-break: break-word; 137 | white-space: normal; 138 | z-index: @z-input-dropdown; 139 | text-align: left; 140 | 141 | transition: all 0 ease-in-out; 142 | opacity: 0; 143 | visibility: hidden; 144 | 145 | @media (max-width: 800px) { 146 | font-size: 15px; 147 | padding: 10px 20px; 148 | } 149 | 150 | @media (max-width: 400px) { 151 | font-size: 14px; 152 | } 153 | 154 | li { 155 | button { 156 | padding: 10px 0; 157 | color: @c-text-light; 158 | font-weight: 700; 159 | cursor: pointer; 160 | width: 100%; 161 | 162 | .isPointer &:hover, 163 | .isTouch &:active, 164 | &:focus { 165 | color: @c-primary; 166 | } 167 | 168 | @media (max-width: 800px) { 169 | padding: 8px 0; 170 | } 171 | 172 | @media (max-width: 400px) { 173 | padding: 6px 0; 174 | } 175 | } 176 | } 177 | } 178 | 179 | input::-webkit-outer-spin-button, 180 | input::-webkit-inner-spin-button { 181 | -webkit-appearance: none; 182 | margin: 0; 183 | } 184 | 185 | input[type="number"] { 186 | -moz-appearance: textfield; 187 | } 188 | 189 | &:not(.disabled) input:hover + &-labelWrapper label, 190 | &:not(.disabled) input:hover ~ &-arrow { 191 | color: @c-primary; 192 | } 193 | 194 | &:not(.disabled) input:hover { 195 | border-color: @c-primary; 196 | } 197 | 198 | &:not(.disabled) input:focus { 199 | border-color: @c-primary; 200 | box-shadow: inset 0 0 0 1px @c-primary; 201 | caret-color: @c-primary; 202 | } 203 | 204 | &.isDropdownOpened &-arrow { 205 | color: @c-primary; 206 | margin-top: -4px; 207 | transform: rotate(225deg); 208 | } 209 | 210 | &.isDropdownOpened &-dropdown { 211 | transition: opacity @d-hover ease-in-out; 212 | opacity: 1; 213 | visibility: inherit; 214 | } 215 | 216 | &.hasData label, 217 | &.error label, 218 | &:not(.disabled) input:focus + &-labelWrapper label { 219 | transform: scale(0.85) translate(3px, -31px); 220 | 221 | @media (max-width: 800px) { 222 | transform: scale(0.85) translate(3px, -27px); 223 | } 224 | 225 | @media (max-width: 400px) { 226 | } 227 | } 228 | 229 | &:not(.disabled) input:focus + &-labelWrapper label { 230 | color: @c-primary; 231 | } 232 | 233 | &:not(.disabled).hasData label { 234 | color: @c-label; 235 | } 236 | 237 | &:not(.disabled).error input { 238 | border-color: @c-error; 239 | box-shadow: inset 0 0 0 1px @c-error; 240 | caret-color: @c-error; 241 | } 242 | 243 | &:not(.disabled).error label { 244 | color: @c-error !important; 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /src/components/Footer/index.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 37 | 38 | 41 | -------------------------------------------------------------------------------- /src/components/Footer/styles.less: -------------------------------------------------------------------------------- 1 | @r: .Footer; 2 | 3 | @{r} { 4 | text-align: center; 5 | font-size: 12px; 6 | color: @c-label; 7 | padding-bottom: 10px; 8 | 9 | a { 10 | color: @c-label; 11 | text-decoration: underline; 12 | 13 | .isPointer &:hover, 14 | .isTouch &:active { 15 | text-decoration: none; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/components/Header/index.vue: -------------------------------------------------------------------------------- 1 | 70 | 71 | 184 | 185 | 188 | -------------------------------------------------------------------------------- /src/components/Layout/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 49 | 50 | 53 | -------------------------------------------------------------------------------- /src/components/Layout/styles.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ton-blockchain/token-bridge/36e80462f8ffa996b431996c080d2843e164f07e/src/components/Layout/styles.less -------------------------------------------------------------------------------- /src/components/WalletsPopup/index.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 128 | 129 | 132 | -------------------------------------------------------------------------------- /src/components/WalletsPopup/styles.less: -------------------------------------------------------------------------------- 1 | @r: .WalletsPopup; 2 | 3 | @{r} { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | width: 100%; 8 | height: 100%; 9 | display: flex; 10 | justify-content: center; 11 | align-items: center; 12 | z-index: @z-wallet-popup; 13 | 14 | &-panel { 15 | position: relative; 16 | background: @c-background; 17 | border-radius: 20px; 18 | box-shadow: 0px 8px 24px @c-panel-shadow; 19 | box-sizing: border-box; 20 | padding: 53px 16px 24px 16px; 21 | width: 260px; 22 | 23 | ul { 24 | list-style-type: none; 25 | margin: 0; 26 | word-break: break-word; 27 | white-space: normal; 28 | text-align: left; 29 | } 30 | 31 | li + li { 32 | margin-top: 10px; 33 | } 34 | 35 | li { 36 | button { 37 | position: relative; 38 | padding: 0 16px; 39 | color: @c-text-light; 40 | font-size: 15px; 41 | line-height: 56px; 42 | font-weight: 700; 43 | cursor: pointer; 44 | display: flex; 45 | justify-content: space-between; 46 | align-items: center; 47 | width: 100%; 48 | border: 1px solid @c-provider-border; 49 | background: @c-provider-background; 50 | border-radius: 16px; 51 | 52 | &[disabled] { 53 | pointer-events: none; 54 | } 55 | 56 | .isPointer &:hover, 57 | .isTouch &:active { 58 | color: @c-primary; 59 | } 60 | 61 | em { 62 | content: ""; 63 | position: relative; 64 | background-size: contain; 65 | background-repeat: no-repeat; 66 | background-position: center; 67 | width: 24px; 68 | height: 24px; 69 | } 70 | 71 | &[data-icon="metamask"] em { 72 | background-image: url("~@/assets/pics/providers/metamask.svg"); 73 | } 74 | 75 | &[data-icon="walletConnect"] em { 76 | background-image: url("~@/assets/pics/providers/walletConnect.svg"); 77 | } 78 | 79 | &[data-icon="walletLink"] em { 80 | background-image: url("~@/assets/pics/providers/walletlink.svg"); 81 | } 82 | 83 | &.showLoader:after { 84 | content: ""; 85 | position: absolute; 86 | right: 50px; 87 | margin-top: 1px; 88 | width: 12px; 89 | height: 12px; 90 | border: 3px solid @c-primary; 91 | border-left: 3px solid transparent; 92 | border-radius: 50%; 93 | animation: loading-animation-spin @d-loader infinite linear; 94 | 95 | @keyframes loading-animation-spin { 96 | to { 97 | transform: rotate(1turn); 98 | } 99 | } 100 | } 101 | } 102 | } 103 | 104 | &Close { 105 | position: absolute; 106 | right: 16px; 107 | top: 16px; 108 | background-image: url("~@/assets/pics/close.svg"); 109 | background-size: contain; 110 | background-repeat: no-repeat; 111 | background-position: center; 112 | width: 24px; 113 | height: 24px; 114 | } 115 | } 116 | 117 | &-overlay { 118 | position: absolute; 119 | left: 0; 120 | top: 0; 121 | width: 100%; 122 | height: 100%; 123 | background: @c-overlay; 124 | } 125 | 126 | &Transition-enter, 127 | &Transition-leave-to { 128 | opacity: 0; 129 | } 130 | &Transition-enter-active, 131 | &Transition-leave-active { 132 | transition: @d-modal opacity; 133 | } 134 | &Transition-enter-to, 135 | &Transition-leave { 136 | opacity: 1; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/i18n.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from "vue-i18n"; 2 | 3 | import en from "./locales/en.json"; 4 | import ru from "./locales/ru.json"; 5 | 6 | export default createI18n({ 7 | legacy: false, 8 | locale: 9 | navigator?.language?.split("-")?.[0] || 10 | process.env.VUE_APP_I18N_LOCALE || 11 | "en", 12 | fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || "en", 13 | messages: { en, ru }, 14 | }); 15 | -------------------------------------------------------------------------------- /src/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "logoLabel": "Bridge", 3 | "connectWallet": "Connect Wallet", 4 | "testnetMessage": "This is the testnet version. Dont send real coins!", 5 | "deprecated": "Learn about new cross-chain solutions", 6 | "read_more": "Read more", 7 | "providers": { 8 | "metamask": "Metamask", 9 | "walletConnect": "WalletConnect", 10 | "walletLink": "Coinbase Wallet" 11 | }, 12 | "networks": { 13 | "ton": { 14 | "transactionWait": "Please wait for the TON transaction to be processed", 15 | "transactionSend": "Please send TON transaction in {provider}", 16 | 17 | "currency": "Toncoin", 18 | "transactionCompleted": "TON transaction completed successfully", 19 | "transactionSend1": "Please send ", 20 | "transactionSend2": "Toncoin to the address", 21 | "transactionSendComment": "with the comment", 22 | "openWallet": "Open Wallet", 23 | "generateQRCode": "Generate QR Code", 24 | "scanQRCode": "or scan QR code below", 25 | "errors": { 26 | "invalidAddress": "Not valid TON address" 27 | }, 28 | "main": { 29 | "name": "The Open Network", 30 | "nameSwitcher": "The Open\nNetwork", 31 | "nameShort": "TON", 32 | "coin": "Toncoin", 33 | "coinShort": "Toncoin" 34 | }, 35 | "test": { 36 | "name": "TON Testnet", 37 | "nameSwitcher": "TON\nTestnet", 38 | "nameShort": "TON", 39 | "coin": "Toncoin", 40 | "coinShort": "Toncoin" 41 | } 42 | }, 43 | "eth": { 44 | "currency": "ETH", 45 | "pageTitle": "TON-ETH Bridge", 46 | "gasFee": "Ethereum gas fee ~ {fee} ETH", 47 | "transactionCompleted": "Ethereum transaction completed successfully", 48 | "transactionWait": "Please wait for the Ethereum transaction to be processed", 49 | "transactionSend": "Please send Ethereum transaction in {provider}", 50 | "blocksConfirmations": "Collected {count} Ethereum block confirmations", 51 | "errors": { 52 | "invalidAddress": "Not valid ETH address", 53 | "lowBalance": "You need to have ETH on your wallet to pay for gas in the Ethereum network" 54 | }, 55 | "main": { 56 | "name": "Ethereum Network", 57 | "nameSwitcher": "Ethereum\nNetwork", 58 | "nameShort": "ETH", 59 | "coin": "TONCOIN (ERC-20)", 60 | "coinShort": "TONCOIN" 61 | }, 62 | "test": { 63 | "name": "Ethereum Goerli", 64 | "nameSwitcher": "Ethereum\nGoerli", 65 | "nameShort": "ETH", 66 | "coin": "TONCOIN (ERC-20)", 67 | "coinShort": "TONCOIN" 68 | } 69 | }, 70 | "bsc": { 71 | "currency": "BNB", 72 | "pageTitle": "TON-BSC Bridge", 73 | "gasFee": "BSC gas fee ~ {fee} BNB", 74 | "transactionCompleted": "BSC transaction completed successfully", 75 | "transactionWait": "Please wait for the BSC transaction to be processed", 76 | "transactionSend": "Please send BSC transaction in {provider}", 77 | "blocksConfirmations": "Collected {count} BSC block confirmations", 78 | "errors": { 79 | "invalidAddress": "Not valid BSC address", 80 | "lowBalance": "You need to have BNB on your wallet to pay for gas in the BSC network" 81 | }, 82 | "main": { 83 | "name": "Binance Smart Chain", 84 | "nameSwitcher": "Binance\nSmart Chain", 85 | "nameShort": "BSC", 86 | "coin": "TONCOIN (BEP-20)", 87 | "coinShort": "TONCOIN" 88 | }, 89 | "test": { 90 | "name": "BSC Testnet", 91 | "nameSwitcher": "BSC\nTestnet", 92 | "nameShort": "BSC", 93 | "coin": "TONCOIN (BEP-20)", 94 | "coinShort": "TONCOIN" 95 | } 96 | } 97 | }, 98 | "tokenAddress": "Token address in {network} network", 99 | "sendToken": "Asset", 100 | "amountOfTon": "Amount {tokenSymbol}", 101 | "addressInputLabel": "Destination address in {network} network", 102 | "willReceive": "You will receive {fee} {coin}", 103 | "bridgeFeeAbove10": "Bridge fee - {fee} TON", 104 | "bridgeFeeBelow10": "Bridge fee - 5 TON + 0.25% of amount", 105 | "tokenBridgeFee": "Bridge fee - 1 TON", 106 | "tokenBridgeFeeToEvm": "Bridge fee - 1 TON + 0.1% of token amount", 107 | "sourceCode": "Source Code", 108 | "support": "Support", 109 | "howItWorks": "How it works", 110 | "documentation": "Documentation", 111 | 112 | "transferHistory": "History", 113 | "disconnectWallet": "Disconnect", 114 | 115 | "transfer": "Transfer", 116 | "getToncoin": "Get {coin}", 117 | "done": "Done", 118 | "cancel": "Cancel", 119 | 120 | "blocksConfirmationsCollected": "The confirmations of the blocks are collected", 121 | "blocksConfirmationsWaiting": "Wait a bit for the blocks confirmations", 122 | "oraclesConfirmations": "Collected {count} confirmations of oracles", 123 | "oraclesConfirmationsCollected": "The confirmations of the oracles are collected", 124 | "oraclesConfirmationsWaiting": "Wait a bit for the oracles to confirm the transfer", 125 | "getCoinsByProvider": "Get {toCoin}s by {provider}", 126 | "coinsSent": "{toCoin}s have been sent", 127 | "getCoins": "Get {toCoin}s in {toNetwork}", 128 | "confirmInWallet": "Confirm in {provider}", 129 | 130 | "otherTokens": "Other tokens", 131 | "tokens": "token", 132 | "approve": "Approve", 133 | 134 | "instruction": "Instruction", 135 | 136 | "errors": { 137 | "alertTitleError": "Error", 138 | "alertButtonClose": "Close", 139 | "notValidAmount": "Amount is not valid number", 140 | "amountBelow10": "Amount must be at least 10 TON", 141 | "needPersonalAddress": "Please enter YOUR address to receive", 142 | "toncoinBalance": "You have only {balance} {coin}", 143 | "installMetamask": "Please install MetaMask", 144 | "wrongProviderNetwork": "Set chain to {network} in {provider}", 145 | "cantGetAddress": "Can't get account address", 146 | "providerIsDisconnected": "{provider} is disconnected" 147 | }, 148 | 149 | "history": { 150 | "title": "Recent history", 151 | "recentHistory": "Recent history", 152 | "age": "Date", 153 | "amountFrom": "Amount / From (Network)", 154 | "amountTo": "Amount Received / To (Network)", 155 | "fee": "Gas / Bridge Fee", 156 | "status": "Status", 157 | "completed": "completed", 158 | "getToncoin": "Get {coin}", 159 | "connect": "Connect", 160 | "oracles": "{count} of oracles", 161 | "notFound": "No transfers found" 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/locales/ru.json: -------------------------------------------------------------------------------- 1 | { 2 | "logoLabel": "Бридж", 3 | "connectWallet": "Подключить Кошелек", 4 | "testnetMessage": "Работа в режиме тестовой сети. Не отправляйте реальные монеты!", 5 | "deprecated": "Узнайте про новые кроссчейн решения", 6 | "read_more": "Подробнее", 7 | "providers": { 8 | "metamask": "Metamask", 9 | "walletConnect": "WalletConnect", 10 | "walletLink": "Coinbase Wallet" 11 | }, 12 | "networks": { 13 | "ton": { 14 | "transactionWait": "Пожалуйста, подождите пока обрабатывается транзакция TON", 15 | "transactionSend": "Пожалуйста, подтвердите TON транзакцию в {provider}", 16 | 17 | "currency": "Toncoin", 18 | "transactionCompleted": "Транзакция TON успешно завершена", 19 | "transactionSend1": "Пожалуйста, отправьте ", 20 | "transactionSend2": "Toncoin на адрес", 21 | "transactionSendComment": "с комментарием", 22 | "openWallet": "Открыть Кошелек", 23 | "generateQRCode": "Сгенерировать QR-код", 24 | "scanQRCode": "или отсканируйте QR-код ниже", 25 | "errors": { 26 | "invalidAddress": "Не валидный TON адрес" 27 | }, 28 | "main": { 29 | "name": "The Open Network", 30 | "nameSwitcher": "The Open\nNetwork", 31 | "nameShort": "TON", 32 | "coin": "Toncoin", 33 | "coinShort": "Toncoin" 34 | }, 35 | "test": { 36 | "name": "TON Testnet", 37 | "nameSwitcher": "TON\nTestnet", 38 | "nameShort": "TON", 39 | "coin": "Toncoin", 40 | "coinShort": "Toncoin" 41 | } 42 | }, 43 | "eth": { 44 | "currency": "ETH", 45 | "pageTitle": "TON-ETH Бридж", 46 | "gasFee": "Комиссия Ethereum (газ) ~ {fee} ETH", 47 | "transactionCompleted": "Транзакция Ethereum успешно завершена", 48 | "transactionWait": "Пожалуйста, подождите пока обрабатывается транзакция Ethereum", 49 | "transactionSend": "Пожалуйста, подтвердите Ethereum транзакцию в {provider}", 50 | "blocksConfirmations": "Собрано {count} подтверждений блоков Ethereum", 51 | "errors": { 52 | "invalidAddress": "Не валидный ETH адрес", 53 | "lowBalance": "У вас должны быть ETH в кошельке чтобы оплатить газ в сети Ethereum" 54 | }, 55 | "main": { 56 | "name": "Ethereum Network", 57 | "nameSwitcher": "Ethereum\nNetwork", 58 | "nameShort": "ETH", 59 | "coin": "TONCOIN (ERC-20)", 60 | "coinShort": "TONCOIN" 61 | }, 62 | "test": { 63 | "name": "Ethereum Goerli", 64 | "nameSwitcher": "Ethereum\nGoerli", 65 | "nameShort": "ETH", 66 | "coin": "TONCOIN (ERC-20)", 67 | "coinShort": "TONCOIN" 68 | } 69 | }, 70 | "bsc": { 71 | "currency": "BNB", 72 | "pageTitle": "TON-BSC Бридж", 73 | "gasFee": "Комиссия BSC (газ) ~ {fee} BNB", 74 | "transactionCompleted": "Транзакция BSC успешно завершена", 75 | "transactionWait": "Пожалуйста, подождите пока обрабатывается транзакция BSC", 76 | "transactionSend": "Пожалуйста, подтвердите BSC транзакцию в {provider}", 77 | "blocksConfirmations": "Собрано {count} подтверждений блоков BSC", 78 | "errors": { 79 | "invalidAddress": "Не валидный BSC адрес", 80 | "lowBalance": "У вас должны быть BNB в кошельке чтобы оплатить газ в сети BSC" 81 | }, 82 | "main": { 83 | "name": "Binance Smart Chain", 84 | "nameSwitcher": "Binance\nSmart Chain", 85 | "nameShort": "BSC", 86 | "coin": "TONCOIN (BEP-20)", 87 | "coinShort": "TONCOIN" 88 | }, 89 | "test": { 90 | "name": "BSC Testnet", 91 | "nameSwitcher": "BSC\nTestnet", 92 | "nameShort": "BSC", 93 | "coin": "TONCOIN (BEP-20)", 94 | "coinShort": "TONCOIN" 95 | } 96 | } 97 | }, 98 | "sendToken": "Токен", 99 | "amountOfTon": "Количество {tokenSymbol}", 100 | "tokenAddress": "Адрес токена в сети {network}", 101 | "addressInputLabel": "Адрес получения в сети {network}", 102 | "willReceive": "Вы получите {fee} {coin}", 103 | "bridgeFeeAbove10": "Комиссия бриджа - {fee} TON", 104 | "bridgeFeeBelow10": "Комиссия бриджа - 5 TON + 0.25% от суммы", 105 | "tokenBridgeFee": "Комиссия бриджа - 1 TON", 106 | "tokenBridgeFeeToEvm": "Комиссия бриджа - 1 TON + 0.1% от суммы токенов", 107 | "sourceCode": "Исходный код", 108 | "support": "Тех. поддержка", 109 | "howItWorks": "Как это работает", 110 | "documentation": "Документация", 111 | 112 | "transferHistory": "История", 113 | "disconnectWallet": "Отключить", 114 | 115 | "transfer": "Перевод", 116 | "getToncoin": "Получить {coin}", 117 | "done": "Готово", 118 | "cancel": "Отменить", 119 | 120 | "blocksConfirmationsCollected": "Собраны подтверждения блоков", 121 | "blocksConfirmationsWaiting": "Пожалуйста, подождите подтверждения блоков", 122 | "oraclesConfirmations": "Собрано {count} подтверждений оракулов", 123 | "oraclesConfirmationsCollected": "Собраны подтверждения оракулов", 124 | "oraclesConfirmationsWaiting": "Пожалуйста, подождите подтверждения оракулов", 125 | "getCoinsByProvider": "Получите {toCoin} в {provider}", 126 | "coinsSent": "{toCoin} были отправлены", 127 | "getCoins": "Получите {toCoin} в {toNetwork}", 128 | "confirmInWallet": "Подтвердите в {provider}", 129 | 130 | "otherTokens": "Другие токены", 131 | "tokens": "токены", 132 | "approve": "Одобрить", 133 | 134 | "instruction": "Инструкция", 135 | 136 | "errors": { 137 | "alertTitleError": "Ошибка", 138 | "alertButtonClose": "Закрыть", 139 | "notValidAmount": "Введено не валидное число", 140 | "amountBelow10": "Количество должно быть не менее 10 TON", 141 | "needPersonalAddress": "Пожалуйста, введите СВОЙ адрес для получения", 142 | "toncoinBalance": "У вас всего {balance} {coin}", 143 | "installMetamask": "Пожалуйста, установите MetaMask", 144 | "wrongProviderNetwork": "Переключите сеть на {network} в {provider}", 145 | "cantGetAddress": "Не могу получить адрес аккаунта", 146 | "providerIsDisconnected": "{provider} отключился" 147 | }, 148 | 149 | "history": { 150 | "title": "История", 151 | "recentHistory": "История", 152 | "age": "Дата", 153 | "amountFrom": "Количество / Откуда (сеть)", 154 | "amountTo": "Получено / Куда (сеть)", 155 | "fee": "Газ / Бридж комиссии", 156 | "status": "Статус", 157 | "completed": "завершено", 158 | "getToncoin": "Получить {coin}", 159 | "connect": "Подключить", 160 | "oracles": "{count} оракулов", 161 | "notFound": "Переводы не найдены" 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import "@/assets/styles/reboot.css"; 2 | import "@/assets/styles/global.less"; 3 | 4 | import { createApp } from "vue"; 5 | import UUID from "vue-uuid"; 6 | 7 | import App from "./App.vue"; 8 | import i18n from "./i18n"; 9 | import router from "./router"; 10 | import store from "./store"; 11 | 12 | createApp(App).use(UUID).use(i18n).use(store).use(router).mount("#app"); 13 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from "vue-router"; 2 | 3 | import BridgePageVue from "@/views/BridgePage/index.vue"; 4 | 5 | const routes = [ 6 | { 7 | path: "/", 8 | name: "Bridge", 9 | component: BridgePageVue, 10 | }, 11 | ]; 12 | 13 | const router = createRouter({ 14 | history: createWebHistory(process.env.BASE_URL), 15 | routes, 16 | }); 17 | export default router; 18 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | declare module '*.vue' { 3 | import type { DefineComponent } from 'vue' 4 | const component: DefineComponent<{}, {}, any> 5 | export default component 6 | } 7 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from "vuex"; 2 | 3 | export default createStore({ 4 | state: () => ({ alert: null }), 5 | mutations: { 6 | setAlert(state, payload) { 7 | state.alert = payload; 8 | }, 9 | }, 10 | getters: { 11 | alert(state) { 12 | return state.alert; 13 | }, 14 | }, 15 | actions: {}, 16 | }); 17 | -------------------------------------------------------------------------------- /src/ton-bridge-lib/BridgeCollector.ts: -------------------------------------------------------------------------------- 1 | import TonWeb from "tonweb"; 2 | import {Bytes} from "@ethersproject/bytes/src.ts"; 3 | import {decToBN, decToHex, hexToBN, hexToDec} from "./Paranoid" 4 | import {splitSignature} from "ethers/lib/utils"; 5 | import {makeAddress, sendInternal} from "./BridgeTonUtils"; 6 | import {WalletContract} from "tonweb/dist/types/contract/wallet/wallet-contract"; 7 | import {ethers} from "ethers"; 8 | 9 | export interface EvmSignature { 10 | publicKey: string; // secp_key (ethereum oracle address) in hex, starts with 0x 11 | r: string; // 256bit in hex, starts with 0x 12 | s: string; // 256bit in hex, starts with 0x 13 | v: number; // 8bit 14 | } 15 | 16 | export interface EvmSignatures { 17 | signatures: EvmSignature[]; 18 | } 19 | 20 | const checkV = (v: number): void => { 21 | if (v !== 27 && v !== 28) throw new Error('invalid v'); 22 | } 23 | 24 | export const parseEvmSignature = (data: any): EvmSignature => { 25 | const tuple: any[] = data.tuple.elements; 26 | const publicKey: string = makeAddress(decToHex(tuple[0].number.number)); 27 | 28 | const rsv: any[] = tuple[1].tuple.elements; 29 | const r: string = decToHex(rsv[0].number.number); 30 | const s: string = decToHex(rsv[1].number.number); 31 | const v: number = Number(rsv[2].number.number); 32 | checkV(v); 33 | return { 34 | publicKey, 35 | r, 36 | s, 37 | v 38 | } 39 | } 40 | 41 | /** 42 | * @param voteId - hash of vote like '0xFF5856350817700B84368727835EEFE9BB10D8DB23FF4311F6936747ABC32308' 43 | */ 44 | export async function getEvmSignaturesFromCollector(tonweb: TonWeb, tonCollectorAddress: string, voteId: string): Promise { 45 | const intVoteId = hexToDec(voteId); 46 | console.log('getEvmSignaturesFromCollector ', voteId, intVoteId); 47 | 48 | const result: any = await tonweb.provider.call(tonCollectorAddress, 'get_external_voting_data', [['num', intVoteId]]); 49 | console.log('getEvmSignaturesFromCollector raw result', result); 50 | if (result.exit_code === 309) { 51 | console.log('getEvmSignaturesFromCollector 309 no found') 52 | return null; 53 | } 54 | const list: any[] = result.stack[0][1].elements; 55 | 56 | const status: EvmSignatures = { 57 | signatures: list.map(parseEvmSignature) 58 | }; 59 | console.log('getEvmSignaturesFromCollector parsed result', status); 60 | 61 | return status; 62 | } 63 | 64 | export async function sendToCollector(tonweb: TonWeb, wallet: WalletContract, secretKey: Uint8Array, tonCollectorAddress: string, voteId: string, signature: Bytes | string): Promise { 65 | const myAddress = await wallet.getAddress(); 66 | 67 | console.log('sendToCollector voteId=', voteId, 'signature=', signature, 'from ' + myAddress.toString(true)); 68 | 69 | const s = splitSignature(signature); 70 | checkV(s.v); 71 | 72 | const bits = new TonWeb.boc.BitString(32 + 64 + 256 + 256 + 256 + 8); 73 | bits.writeUint(decToBN(5), 32); // op 74 | bits.writeUint(decToBN(0), 64); // query_id 75 | bits.writeUint(hexToBN(voteId), 256); 76 | bits.writeUint(hexToBN(s.r), 256); 77 | bits.writeUint(hexToBN(s.s), 256); 78 | bits.writeUint(decToBN(s.v), 8); 79 | 80 | return sendInternal(tonweb, wallet, secretKey, bits.array, tonCollectorAddress); 81 | } 82 | 83 | /** 84 | * Prepare sorted signatures to send to EVM smart contract 85 | */ 86 | export function prepareEthSignatures(signatures: EvmSignature[]): { signer: string, signature: string }[] { 87 | return signatures 88 | .map(signature => { 89 | return { 90 | signer: signature.publicKey, 91 | signature: ethers.utils.joinSignature({r: signature.r, s: signature.s, v: signature.v}), 92 | }; 93 | }) 94 | .sort((a, b) => { 95 | return hexToBN(a.signer).cmp(hexToBN(b.signer)); 96 | }); 97 | } -------------------------------------------------------------------------------- /src/ton-bridge-lib/BridgeCommon.ts: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | import {calculateQueryId} from "./Paranoid"; 3 | 4 | export interface TonAddress { 5 | workchain: number; // int8 6 | address_hash: string; // bytes32 7 | } 8 | 9 | export interface TonTxID { 10 | address_: TonAddress; // sender user TON address 11 | tx_hash: string; // bytes32, transaction hash on TON bridge smart contract 12 | lt: string; // uint64, transaction LT (logical time) on TON bridge smart contract 13 | } 14 | 15 | export interface EvmTransaction { 16 | blockTime: number; 17 | blockHash: string; 18 | transactionHash: string; 19 | logIndex: number; 20 | } 21 | 22 | export interface TonTransaction { 23 | tx: TonTxID; 24 | } 25 | 26 | export type NewOracleSet = string[]; // address[] 27 | 28 | export interface Vote { 29 | id: string; 30 | data: string; // json 31 | status: number; 32 | statusTime: number; // in seconds 33 | } 34 | 35 | export function hash(data: string): string { 36 | const hash = Web3.utils.sha3(data) 37 | if (!hash) throw new Error('empty hash'); 38 | return hash; 39 | } 40 | 41 | export const MULTISIG_QUERY_TIMEOUT = 30 * 24 * 60 * 60; // 30 days 42 | 43 | export function getQueryId(evmTransaction: EvmTransaction) /* BN */ { 44 | const VERSION = 2; 45 | const timeout = evmTransaction.blockTime + MULTISIG_QUERY_TIMEOUT + VERSION; 46 | 47 | // web3@1.3.4 has an error in the algo for computing SHA 48 | // it doesn't strictly check input string for valid HEX relying only for 0x prefix 49 | // but the query string is formed that way: 0xBLOCKHASH + '_' + 0xTRANSACTIONHASH + '_' + LOGINDEX 50 | // the keccak algo splits string to pairs of symbols, and treats them as hex bytes 51 | // so _0 becames NaN, x7 becames NaN, d_ becames 13 (it only sees first d and skips invalid _) 52 | // web3@1.6.1 has this error fixed, but for our case this means that we've got different hashes for different web3 versions 53 | // 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 54 | // for example: 55 | // old one: 0xcad62a0e0090e30e0133586f86ed8b7d0d2eac5fa8ded73b8180931ff379b113_0x77e5617841b2d355fe588716b6f8f506b683e985fc98fdb819ddf566594d4cfd_64 56 | // new one: 0xcad62a0e0090e30e0133586f86ed8b7d0d2eac5fa8ded73b8180931ff379b11300007e5617841b2d355fe588716b6f8f506b683e985fc98fdb819ddf566594d4cf0d64 57 | // diff : ^^^^ ^^ 58 | function getLegacyQueryString(str: string): string { 59 | const strArr = str.split(''); 60 | strArr[66] = '0'; 61 | strArr[67] = '0'; 62 | strArr[68] = '0'; 63 | strArr[69] = '0'; 64 | strArr[133] = strArr[132]; 65 | strArr[132] = '0'; 66 | return strArr.join(''); 67 | } 68 | 69 | const queryId = hash( 70 | getLegacyQueryString( 71 | evmTransaction.blockHash + '_' + evmTransaction.transactionHash + '_' + String(evmTransaction.logIndex) 72 | ) 73 | ).substr(2, 8); // get first 32 bit 74 | 75 | return calculateQueryId(timeout, queryId); 76 | } -------------------------------------------------------------------------------- /src/ton-bridge-lib/BridgeEvmUtils.ts: -------------------------------------------------------------------------------- 1 | import {decToBN} from "./Paranoid"; 2 | 3 | export const TOKEN_TON_TO_EVM_PERCENT_FEE_START_TIME = 1687780800; // 26 June 2023, 12:00:00 UTC 4 | export const TOKEN_TON_TO_EVM_PERCENT_FEE_USDT_END_TIME = 1716552000; // 24 May 2024, 12:00:00 UTC 5 | 6 | export const USDT_ETHEREUM_ADDRESS = '0xdAC17F958D2ee523a2206206994597C13D831ec7'.toLowerCase(); 7 | 8 | export const BLOCK_CONFIRMATIONS_COUNT = 65; 9 | 10 | export function parseField(data: any, field: string | number): any { 11 | const x: any = data[field]; 12 | if (x === null || x === undefined) throw new Error('no "' + field + '"'); 13 | return x; 14 | } 15 | 16 | // 0xfe8af310e07838c114ad6c3227e4a08aa2c0ae31f6c88f3cdffcd493f15e7299 17 | export function parseEvmTxHash(data: any, field: string): string { 18 | const s: string = parseField(data, field); 19 | if (s.length !== 66 || !s.startsWith('0x')) throw new Error('invalid eth tx hash ' + s); 20 | return s.toLowerCase(); 21 | } 22 | 23 | // 0xef95f2f1ed3ca60b048b4bf67cde2195961e0bba6f70bcbea9a2c4e133e34b46 24 | export function parseEvmBlockHash(data: any, field: string): string { 25 | const s: string = parseField(data, field); 26 | if (s.length !== 66 || !s.startsWith('0x')) throw new Error('invalid eth block hash ' + s); 27 | return s.toLowerCase(); 28 | } 29 | 30 | // 0x1FF516E5ce789085CFF86d37fc27747dF852a80a 31 | export function parseEvmAddress(data: any, field: string): string { 32 | const s: string = parseField(data, field); 33 | if (s.length !== 42 || !s.startsWith('0x')) throw new Error('invalid eth address ' + s); 34 | return s.toLowerCase(); 35 | } 36 | 37 | export function parseNumber(data: any, field: string): number { 38 | const n: number = Number(parseField(data, field)); 39 | if (isNaN(n)) throw new Error('invalid number ' + n); 40 | if (n < 0) throw new Error('number is negative ' + n); 41 | return n; 42 | } 43 | 44 | // 0xE5BC34E7CD6D8669967B117CF7644A940571D169FB8DC9358C2D123A07DFB9A4 45 | export function parseTonAddress(data: any, field: string): string { 46 | const s: string = parseField(data, field); 47 | if (s.length !== 66 || !s.startsWith('0x')) throw new Error('invalid ton address ' + s); 48 | return s.slice(2).toLowerCase(); // slice '0x' 49 | } 50 | 51 | export function parseWc(data: any, field: string): number { 52 | const n: number = Number(parseField(data, field)); 53 | if (n !== 0 && n !== -1) throw new Error('invalid wc ' + n); 54 | return n; 55 | } 56 | 57 | export function parseDecimals(data: any, field: number): number { 58 | const n: number = Number(parseField(data, field)); 59 | if (!((n >= 0) && (n <= 255))) throw new Error('invalid decimals ' + n); 60 | return n; 61 | } 62 | 63 | export function parseBN(data: any, field: string | number): string { 64 | const s: string = parseField(data, field); 65 | const bn = decToBN(s); 66 | if (bn.lte(decToBN(0))) throw new Error('bn is negative ' + s); 67 | return s; 68 | } 69 | 70 | export async function throwIfSwapFinished(evmContract: any, swapId: string): Promise { 71 | const isFinished = await evmContract.methods.finishedVotings(swapId).call(); 72 | console.log({isFinished}); 73 | 74 | if ((isFinished !== true) && (isFinished !== false)) { 75 | alert('isFinished unexpected result'); 76 | throw new Error('isFinished unexpected result'); 77 | } 78 | 79 | if (isFinished) { 80 | alert('Already finished'); 81 | throw new Error('already finished'); 82 | } 83 | } 84 | 85 | export async function estimateGas(evmTransaction: any, fromEvmAddress: string): Promise { 86 | // https://web3js.readthedocs.io/en/v1.8.0/web3-eth-contract.html#methods-mymethod-estimategas 87 | const estimatedGas = await evmTransaction.estimateGas({from: fromEvmAddress}); 88 | console.log({estimatedGas}); 89 | 90 | let bn = null; 91 | try { 92 | bn = BigInt(estimatedGas); 93 | } catch (e) { 94 | } 95 | 96 | if (bn === null) { 97 | alert(`"${estimatedGas}" estimate gas wrong number`); 98 | throw new Error('estimate gas wrong number'); 99 | } 100 | 101 | return estimatedGas; 102 | } -------------------------------------------------------------------------------- /src/ton-bridge-lib/BridgeMultisig.ts: -------------------------------------------------------------------------------- 1 | import {EvmTransaction, getQueryId} from "./BridgeCommon"; 2 | import TonWeb from "tonweb"; 3 | import {sendInternal, getNumber} from "./BridgeTonUtils"; 4 | import {WalletContract} from "tonweb/dist/types/contract/wallet/wallet-contract"; 5 | import {decToBN} from "./Paranoid"; 6 | 7 | export function getMultisigWalletId(chainId: number): number { 8 | switch (chainId) { 9 | case 56: // BSC mainnet 10 | return 156; 11 | case 97: // BSC testnet 12 | return 97; 13 | case 1: // ETH mainnet 14 | return 101; 15 | case 5: // ETH testnet Goerli 16 | return 5; 17 | } 18 | throw new Error('unknown chainId ' + chainId); 19 | } 20 | 21 | export async function getVotesInMultisig(tonweb: TonWeb, tonMultisigAddress: string, queryId: string, oraclesTotal: number): Promise { 22 | console.log("getVotesInMultisig ", queryId); 23 | 24 | const result: any = await tonweb.provider.call(tonMultisigAddress, 'get_query_state', [["num", queryId]]); 25 | 26 | const a = getNumber(result.stack[0]); 27 | const b = getNumber(result.stack[1]); 28 | 29 | const arr: number[] = []; 30 | 31 | const count = a === -1 ? oraclesTotal : b.toString(2).split('0').join('').length; // count of bits 32 | 33 | for (let i = 0; i < count; i++) { 34 | arr.push(1); 35 | } 36 | return arr; 37 | } 38 | 39 | export async function hasMyVoteInMultisig(tonweb: TonWeb, tonMultisigAddress: string, tonMultisigIndex: number, ethToTon: EvmTransaction): Promise { 40 | const queryId = getQueryId(ethToTon).toString(); 41 | 42 | console.log('hasMyVoteInMultisig ', queryId); 43 | 44 | const result: any = await tonweb.provider.call(tonMultisigAddress, 'message_signed_by_id?', [['num', tonMultisigIndex], ['num', queryId]]); 45 | console.log('hasMyVoteInMultisig raw result', result); 46 | const stack: any[] = result.stack; 47 | 48 | return getNumber(stack[0]) === -1; 49 | } 50 | 51 | export async function sendToMultisig(tonweb: TonWeb, wallet: WalletContract, secretKey: Uint8Array, tonMultisigAddress: string, tonMultisigIndex: number, chainId: number, tonBridgeAddress: string, ethToTon: EvmTransaction, payload: any /* Cell */): Promise { 52 | const myAddress = await wallet.getAddress(); 53 | const queryId = getQueryId(ethToTon); 54 | console.log('sendToMultisig', ethToTon, 'from ' + myAddress.toString(true) + ' with query id ' + queryId.toString() + ' and index ' + tonMultisigIndex + ' to ' + tonMultisigAddress); 55 | 56 | const WALLET_ID = getMultisigWalletId(chainId); 57 | 58 | const orderHeader = TonWeb.Contract.createInternalMessageHeader(new TonWeb.utils.Address(tonBridgeAddress), TonWeb.utils.toNano('0.5')); 59 | const msgToBridge = TonWeb.Contract.createCommonMsgInfo(orderHeader, undefined, payload); 60 | 61 | const cell = new TonWeb.boc.Cell(); 62 | cell.bits.writeUint(decToBN(tonMultisigIndex), 8); 63 | cell.bits.writeBit(0); // null signatures dict 64 | cell.bits.writeUint(decToBN(WALLET_ID), 32); 65 | cell.bits.writeUint(queryId, 64); 66 | cell.bits.writeUint(decToBN(3), 8); // send mode 3 67 | cell.refs.push(msgToBridge); 68 | 69 | const rootHash: Uint8Array = await cell.hash(); 70 | const rootSignature: Uint8Array = TonWeb.utils.nacl.sign.detached(rootHash, secretKey); 71 | 72 | const body = new TonWeb.boc.Cell(); 73 | body.bits.writeBytes(rootSignature); 74 | body.writeCell(cell); 75 | return sendInternal(tonweb, wallet, secretKey, body, tonMultisigAddress); 76 | } -------------------------------------------------------------------------------- /src/ton-bridge-lib/BridgeTonUtils.ts: -------------------------------------------------------------------------------- 1 | import TonWeb from "tonweb"; 2 | import {WalletContract} from "tonweb/dist/types/contract/wallet/wallet-contract"; 3 | import {hexToBN, base64ToBytes} from "./Paranoid"; 4 | 5 | export const getNumber = (pair: string[]): number => parseInt(pair[1], 16); 6 | 7 | export const getBN = (pair: string[]) /* TonWeb.utils.BN */ => hexToBN(pair[1]); 8 | 9 | export const findLogOutMsg = (outMessages?: any[]): any => { 10 | if (!outMessages) return null; 11 | for (const outMsg of outMessages) { 12 | if (outMsg.destination === '') return outMsg; 13 | } 14 | return null; 15 | } 16 | 17 | export const getRawMessageBytes = (logMsg: any): Uint8Array | null => { 18 | if (!logMsg.message.endsWith('\n')) throw new Error('need \\n'); 19 | const message = logMsg.message.substr(0, logMsg.message.length - 1); // remove '\n' from end 20 | return base64ToBytes(message); 21 | } 22 | 23 | export const getTextMessageBytes = (logMsg: any): Uint8Array | null => { 24 | const message = logMsg.msg_data?.text; 25 | const textBytes = base64ToBytes(message); 26 | const bytes = new Uint8Array(textBytes.length + 4); 27 | bytes.set(textBytes, 4); 28 | return bytes; 29 | } 30 | 31 | export const getMessageBytes = (logMsg: any): Uint8Array | null => { 32 | const msgType = logMsg.msg_data['@type']; 33 | if (msgType === 'msg.dataText') { 34 | return getTextMessageBytes(logMsg); 35 | } else if (msgType === 'msg.dataRaw') { 36 | return getRawMessageBytes(logMsg); 37 | } else { 38 | console.error('Unknown log msg type ' + msgType); 39 | return null; 40 | } 41 | } 42 | 43 | export const makeAddress = (address: string): string => { 44 | if (!address.startsWith('0x')) throw new Error('Invalid address ' + address); 45 | let hex = address.substr(2); 46 | while (hex.length < 40) { 47 | hex = '0' + hex; 48 | } 49 | return '0x' + hex; 50 | } 51 | 52 | export async function sendInternal(tonweb: TonWeb, wallet: WalletContract, secretKey: Uint8Array, byteArray: any /* Uint8Array | TonWeb.boc.Cell */, toAddress: string): Promise { 53 | let seqno = await wallet.methods.seqno().call(); 54 | if (!seqno) { 55 | seqno = 0; 56 | } 57 | const query = await wallet.methods.transfer({ 58 | secretKey: secretKey, 59 | toAddress: toAddress, 60 | amount: TonWeb.utils.toNano('5'), // на газ, остаток вернется 61 | seqno: seqno, 62 | payload: byteArray, 63 | sendMode: 3 64 | }); 65 | 66 | const sendResponse = await query.send(); 67 | if (sendResponse["@type"] === "ok") { 68 | await wait(4000); // дадим выполниться чтобы далее получить верный seqno 69 | return true; 70 | } else { 71 | console.error(sendResponse); 72 | return false; 73 | } 74 | } 75 | 76 | function wait(ms: number): Promise { 77 | return new Promise(resolve => { 78 | setTimeout(() => resolve(), ms); 79 | }); 80 | } -------------------------------------------------------------------------------- /src/ton-bridge-lib/Paranoid.ts: -------------------------------------------------------------------------------- 1 | import TonWeb from "tonweb"; 2 | 3 | export const checkNull = (a: any): void => { 4 | if (a === null || a === undefined || a === '' || a === '0x') throw new Error('checkNull'); 5 | if ((typeof a === 'number') && isNaN(a)) throw new Error('checkNaN'); 6 | } 7 | 8 | const removeHexPrefix = (hex: string): string => { 9 | if (hex.startsWith('0x')) { 10 | hex = hex.substring(2); 11 | } 12 | if (hex.indexOf('0x') > -1) throw new Error('removeHexPrefix'); 13 | return hex; 14 | } 15 | 16 | /** 17 | * @returns hex WITHOUT 0x in lowercase 18 | */ 19 | export const bytesToHex = (bytes: Uint8Array): string => { 20 | checkNull(bytes); 21 | const a = Buffer.from(bytes).toString('hex'); 22 | const b = TonWeb.utils.bytesToHex(bytes); 23 | if (a !== b) throw new Error('bytesToHex'); 24 | return a; 25 | } 26 | 27 | export const bytesToBase64 = (bytes: Uint8Array): string => { 28 | checkNull(bytes); 29 | const a = Buffer.from(bytes).toString('base64'); 30 | const b = TonWeb.utils.bytesToBase64(bytes); 31 | if (a !== b) throw new Error('bytesToBase64'); 32 | return a; 33 | } 34 | 35 | /** 36 | * @returns hex with or without 0x 37 | */ 38 | export const hexToBytes = (hex: string): Uint8Array => { 39 | checkNull(hex); 40 | hex = removeHexPrefix(hex); 41 | const a = Uint8Array.from(Buffer.from(hex, 'hex')); 42 | const b = TonWeb.utils.hexToBytes(hex); 43 | if (a.join(',') !== b.join(',')) throw new Error('hexToBytes'); 44 | return a; 45 | } 46 | 47 | export const base64ToBytes = (base64: string): Uint8Array => { 48 | checkNull(base64); 49 | const a = Uint8Array.from(Buffer.from(base64, 'base64')); 50 | const b = TonWeb.utils.base64ToBytes(base64); 51 | if (a.join(',') !== b.join(',')) throw new Error('base64ToBytes'); 52 | return a; 53 | } 54 | 55 | const checkBN = (bn: any, bigInt: any): void => { 56 | if (bn.toString(2) !== bigInt.toString(2)) throw new Error('checkBN'); 57 | if (bn.toString(10) !== bigInt.toString(10)) throw new Error('checkBN'); 58 | if (bn.toString(16) !== bigInt.toString(16)) throw new Error('checkBN'); 59 | } 60 | 61 | export const decToBN = (dec: string | number) /* BN */ => { 62 | checkNull(dec); 63 | const bn = new TonWeb.utils.BN(dec); 64 | const bigInt = BigInt(dec); 65 | checkBN(bn, bigInt); 66 | return bn; 67 | } 68 | 69 | /** 70 | * @returns hex with or without 0x 71 | */ 72 | export const hexToBN = (hex: string) /* BN */ => { 73 | checkNull(hex); 74 | hex = removeHexPrefix(hex); 75 | 76 | const bn = new TonWeb.utils.BN(hex, 16); 77 | const bigInt = BigInt('0x' + hex); 78 | checkBN(bn, bigInt); 79 | return bn; 80 | } 81 | 82 | /** 83 | * @return hex starts with 0x in lowercase 84 | */ 85 | export const decToHex = (dec: string | number): string => { 86 | checkNull(dec); 87 | const bn = new TonWeb.utils.BN(dec); 88 | const bigInt = BigInt(dec); 89 | const a = bn.toString(16); 90 | const b = bigInt.toString(16); 91 | if (a !== b) throw new Error('decToHex'); 92 | return '0x' + a; 93 | } 94 | 95 | /** 96 | * @returns hex with or without 0x 97 | */ 98 | export const hexToDec = (hex: string): string => { 99 | checkNull(hex); 100 | hex = removeHexPrefix(hex); 101 | 102 | const bn = new TonWeb.utils.BN(hex, 16); 103 | const bigInt = BigInt('0x' + hex); 104 | const a = bn.toString(10); 105 | const b = bigInt.toString(10); 106 | if (a !== b) throw new Error('hexToDec'); 107 | return a; 108 | } 109 | 110 | export const calculateQueryId = (timeout: number, queryId: string) /* BN */ => { 111 | checkNull(timeout); 112 | checkNull(queryId); 113 | queryId = removeHexPrefix(queryId); 114 | const bn = decToBN(timeout).mul(decToBN(4294967296)).add(hexToBN(queryId)) 115 | const bigInt = BigInt(timeout) * BigInt(4294967296) + BigInt('0x' + queryId) 116 | checkBN(bn, bigInt); 117 | return bn; 118 | } 119 | 120 | export const calculateToncoinFee = (amount: any /* BN */) /* BN */ => { 121 | checkNull(amount); 122 | 123 | const feeFlat = decToBN(5000000000); 124 | const feeFactor = decToBN(25); 125 | const feeBase = decToBN(10000); 126 | 127 | const rest = amount.sub(feeFlat); 128 | const percentFee = rest.mul(feeFactor).div(feeBase); 129 | const bn = feeFlat.add(percentFee); 130 | 131 | const _feeFlat = BigInt(5000000000); 132 | const _feeFactor = BigInt(25); 133 | const _feeBase = BigInt(10000); 134 | const _rest = BigInt(amount.toString()) - _feeFlat; 135 | const _percentFee = (_rest * _feeFactor) / _feeBase; 136 | const bigInt = _feeFlat + _percentFee; 137 | 138 | checkBN(bn, bigInt); 139 | 140 | return bn; 141 | } 142 | 143 | export const getTokenAmountAfterFee = (amount: any /* BN */) /* BN */ => { 144 | checkNull(amount); 145 | const bn = amount.sub(amount.div(new TonWeb.utils.BN(1000))); // 0.1% fee of token amount 146 | 147 | const amountBigInt = BigInt(amount.toString()); 148 | const bigInt = amountBigInt - (amountBigInt / BigInt(1000)); 149 | if (bigInt > amountBigInt) { 150 | throw new Error('') 151 | } 152 | checkBN(bn, bigInt); 153 | 154 | return bn; 155 | } -------------------------------------------------------------------------------- /src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | function getScript(src: string): Promise { 2 | return new Promise((resolve, reject) => { 3 | const script: HTMLScriptElement = document.createElement("script"); 4 | const prior = document.getElementsByTagName("script")[0]; 5 | script.async = true; 6 | 7 | script.onload = () => { 8 | script.onload = null; 9 | script.onerror = null; 10 | setTimeout(resolve, 0); 11 | }; 12 | 13 | script.onerror = () => { 14 | script.onload = null; 15 | script.onerror = null; 16 | setTimeout(reject, 0); 17 | }; 18 | 19 | script.src = src; 20 | prior.parentNode!.insertBefore(script, prior); 21 | }); 22 | } 23 | 24 | function supportsLocalStorage(): boolean { 25 | try { 26 | return "localStorage" in window && window["localStorage"] !== null; 27 | } catch (e) { 28 | return false; 29 | } 30 | } 31 | 32 | function parseChainId(chainId: string | number): number { 33 | if (typeof chainId === "number") { 34 | return chainId; 35 | } 36 | if (typeof chainId === "string") { 37 | return parseInt(chainId, chainId.toLowerCase().startsWith("0x") ? 16 : 10); 38 | } else { 39 | return 0; 40 | } 41 | } 42 | 43 | export { 44 | getScript, 45 | parseChainId, 46 | supportsLocalStorage 47 | }; 48 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | function onCopyClick(e: MouseEvent) { 2 | const target = e.target as any; 3 | 4 | let timeout1: ReturnType; 5 | let timeout2: ReturnType; 6 | 7 | const triggerClass = (className: string) => { 8 | if (target) { 9 | target.classList.remove(className); 10 | clearTimeout(timeout1); 11 | clearTimeout(timeout2); 12 | 13 | timeout1 = setTimeout(() => { 14 | target.classList.add(className); 15 | }, 17); 16 | 17 | timeout2 = setTimeout(() => { 18 | target.classList.remove(className); 19 | }, 500); 20 | } 21 | }; 22 | 23 | if (navigator && navigator.clipboard && navigator.clipboard.writeText) { 24 | navigator.clipboard.writeText(target.innerText).then( 25 | function () { 26 | triggerClass("success"); 27 | }, 28 | function () { 29 | triggerClass("failure"); 30 | } 31 | ); 32 | } else { 33 | triggerClass("failure"); 34 | } 35 | } 36 | 37 | export { onCopyClick }; 38 | -------------------------------------------------------------------------------- /src/utils/providers/metamask/index.ts: -------------------------------------------------------------------------------- 1 | import { Web3Provider } from "@ethersproject/providers"; 2 | import { createNanoEvents, Emitter } from "nanoevents"; 3 | import Web3 from "web3"; 4 | 5 | import { parseChainId } from "@/utils/helpers"; 6 | 7 | import { Provider } from "../provider"; 8 | 9 | interface Events { 10 | disconnect: () => void; 11 | } 12 | 13 | export class Metamask implements Provider { 14 | public name = "metamask"; 15 | public title = "Metamask"; 16 | public web3: Web3 | null = null; 17 | public myAddress = ""; 18 | public chainId = 0; 19 | public isConnected = false; 20 | private emitter: Emitter; 21 | public provider?: Web3Provider; 22 | 23 | constructor() { 24 | this.emitter = createNanoEvents(); 25 | this.onAccountsChanged = this.onAccountsChanged.bind(this); 26 | this.onChainChanged = this.onChainChanged.bind(this); 27 | this.onDisconnect = this.onDisconnect.bind(this); 28 | this.onConnect = this.onConnect.bind(this); 29 | } 30 | 31 | on(event: E, callback: Events[E]) { 32 | return this.emitter.on(event, callback); 33 | } 34 | 35 | async connect(params: any): Promise { 36 | if (!window.ethereum) { 37 | throw new Error("errors.installMetamask"); 38 | } 39 | 40 | try { 41 | await window.ethereum.request({ 42 | method: "wallet_requestPermissions", 43 | params: [ 44 | { 45 | eth_accounts: {}, 46 | }, 47 | ], 48 | }); 49 | 50 | const accounts = await window.ethereum.request({ 51 | "method": "eth_requestAccounts", 52 | "params": [] 53 | }); 54 | 55 | this.myAddress = accounts[0]; 56 | } catch (e: any) { 57 | if (e.code === 4001) { 58 | // soft error: User rejected the request. 59 | console.log(e.message); 60 | return false; 61 | } 62 | throw new Error(e.message); 63 | } 64 | 65 | this.chainId = parseChainId(window.ethereum.networkVersion); 66 | 67 | // if (this.chainId !== params.chainId) { 68 | // await this.switchChain(params.chainId); 69 | // } 70 | 71 | this.isConnected = window.ethereum.isConnected(); 72 | this.web3 = new Web3(window.ethereum); 73 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain 74 | this.provider = new Web3Provider(this.web3?.currentProvider! as any); 75 | 76 | window.ethereum.on("accountsChanged", this.onAccountsChanged); 77 | window.ethereum.on("chainChanged", this.onChainChanged); 78 | window.ethereum.on("disconnect", this.onDisconnect); 79 | window.ethereum.on("connect", this.onConnect); 80 | 81 | return true; 82 | } 83 | 84 | onAccountsChanged(accounts: Array) { 85 | console.log("account changed, old address: ", this.myAddress); 86 | if (accounts && accounts.length) { 87 | this.myAddress = accounts[0]; 88 | } else { 89 | this.myAddress = ""; 90 | } 91 | console.log("account changed, new address: ", this.myAddress); 92 | 93 | // metamask doesn't fire disconnect event if all accounts has been disconnected, so we need to do it explicitly 94 | // if (!this.myAddress) { 95 | this.onDisconnect(0, ""); 96 | // } 97 | } 98 | 99 | onChainChanged(chainId: number | string) { 100 | console.log("chain changed, old chain: ", this.chainId); 101 | this.chainId = parseChainId(chainId); 102 | console.log("chain changed, new chain: ", this.chainId); 103 | this.onDisconnect(0, ""); 104 | } 105 | 106 | onDisconnect(code: number, reason: string) { 107 | this.isConnected = false; 108 | console.log("disconnected"); 109 | 110 | window.ethereum.removeAllListeners(); 111 | 112 | this.emitter.emit("disconnect"); 113 | this.emitter.events = {}; 114 | } 115 | 116 | onConnect() { 117 | this.isConnected = true; 118 | console.log("connected"); 119 | } 120 | 121 | async switchChain(chainId: number): Promise { 122 | try { 123 | await window.ethereum.request({ 124 | method: "wallet_switchEthereumChain", 125 | params: [{ chainId: "0x" + chainId.toString(16) }], 126 | }); 127 | } catch (e: any) { 128 | console.log(e.message); 129 | return false; 130 | } 131 | return true; 132 | } 133 | 134 | disconnect() { 135 | // metamask has no disconnect method, strange, so simply report to FE about disconnect 136 | this.onDisconnect(0, ""); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/utils/providers/provider.ts: -------------------------------------------------------------------------------- 1 | import Web3 from "web3"; 2 | 3 | interface Events { 4 | disconnect: () => void; 5 | } 6 | 7 | export interface Provider { 8 | name: string; 9 | title: string; 10 | web3: Web3 | null; 11 | myAddress: string; 12 | chainId: number; 13 | isConnected: boolean; 14 | 15 | connect(params: any): Promise; 16 | onAccountsChanged(accounts: Array): void; 17 | onChainChanged(chainId: number | string): void; 18 | onDisconnect(code: number, reason: string): void; 19 | onConnect(connectInfo: any): void; 20 | switchChain(chainId: number): Promise; 21 | disconnect(): void; 22 | on(event: E, callback: Events[E]): void; 23 | } 24 | -------------------------------------------------------------------------------- /src/utils/providers/walletConnect/index.ts: -------------------------------------------------------------------------------- 1 | import {EthereumProvider} from "@walletconnect/ethereum-provider"; 2 | import {createNanoEvents, Emitter} from "nanoevents"; 3 | import Web3 from "web3"; 4 | import {parseChainId} from "@/utils/helpers"; 5 | 6 | import {Provider} from "../provider"; 7 | import {Web3Provider} from "@ethersproject/providers"; 8 | 9 | interface Events { 10 | disconnect: () => void; 11 | } 12 | 13 | export class WalletConnect implements Provider { 14 | public name = "walletConnect"; 15 | public title = "WalletConnect"; 16 | public web3: Web3 | null = null; 17 | public myAddress = ""; 18 | public chainId = 0; 19 | public isConnected = false; 20 | private walletConnect?: any; 21 | public provider?: Web3Provider; 22 | private emitter: Emitter; 23 | 24 | constructor() { 25 | this.emitter = createNanoEvents(); 26 | this.onAccountsChanged = this.onAccountsChanged.bind(this); 27 | this.onChainChanged = this.onChainChanged.bind(this); 28 | this.onDisconnect = this.onDisconnect.bind(this); 29 | this.onConnect = this.onConnect.bind(this); 30 | } 31 | 32 | on(event: E, callback: Events[E]) { 33 | return this.emitter.on(event, callback); 34 | } 35 | 36 | async connect(params: ParamsNetwork): Promise { 37 | this.walletConnect = await EthereumProvider.init({ 38 | projectId: '94099fc4aa88338ac0e67f25b47da521', 39 | chains: [1, 56], 40 | showQrModal: true, 41 | rpcMap: { 42 | '1' : 'https://bridge.ton.org/mainnet/', 43 | '56': 'https://bsc-dataseed.binance.org/' 44 | } 45 | }); 46 | 47 | this.walletConnect!.on("connect", this.onConnect); 48 | 49 | try { 50 | await this.walletConnect!.enable(); 51 | } catch (e: any) { 52 | if (e.message === "User closed modal") { 53 | // soft error: User rejected the request. 54 | console.log(e.message); 55 | return false; 56 | } 57 | throw new Error(e.message); 58 | } 59 | 60 | this.web3 = new Web3(this.walletConnect!); 61 | 62 | const accounts = await this.web3.eth.getAccounts(); 63 | this.myAddress = accounts[0]; 64 | 65 | this.chainId = parseChainId(await this.web3.eth.net.getId()); 66 | 67 | // if (this.chainId !== params.chainId) { 68 | // this.disconnect(); 69 | // throw new Error('errors.invalidChain') 70 | // } 71 | 72 | this.provider = new Web3Provider(this.web3.currentProvider! as any); 73 | 74 | this.isConnected = true; 75 | 76 | this.walletConnect!.on("accountsChanged", this.onAccountsChanged); 77 | this.walletConnect!.on("chainChanged", this.onChainChanged); 78 | this.walletConnect!.on("disconnect", this.onDisconnect); 79 | 80 | return true; 81 | } 82 | 83 | onAccountsChanged(accounts: Array) { 84 | console.log("account changed, old address: ", this.myAddress); 85 | if (accounts && accounts.length) { 86 | this.myAddress = accounts[0]; 87 | } else { 88 | this.myAddress = ""; 89 | } 90 | console.log("account changed, new address: ", this.myAddress); 91 | 92 | this.onDisconnect(0, ""); 93 | } 94 | 95 | onChainChanged(chainId: number | string) { 96 | console.log("chain changed, old chain: ", this.chainId); 97 | this.chainId = parseChainId(chainId); 98 | console.log("chain changed, new chain: ", this.chainId); 99 | } 100 | 101 | removeListeners() { 102 | this.walletConnect!.off("accountsChanged", this.onAccountsChanged); 103 | this.walletConnect!.off("chainChanged", this.onChainChanged); 104 | this.walletConnect!.off("disconnect", this.onDisconnect); 105 | this.walletConnect!.off("connect", this.onConnect); 106 | } 107 | 108 | onDisconnect(code: number, reason: string) { 109 | this.isConnected = false; 110 | console.log("disconnected"); 111 | 112 | this.removeListeners(); 113 | 114 | this.emitter.emit("disconnect"); 115 | this.emitter.events = {}; 116 | } 117 | 118 | onConnect(connectInfo: any) { 119 | this.isConnected = true; 120 | console.log("connected"); 121 | } 122 | 123 | async switchChain(chainId: number): Promise { 124 | try { 125 | await this.walletConnect.switchEthereumChain(chainId); 126 | } catch (e) { 127 | console.error(e); 128 | return false; 129 | } 130 | // `this.chainId` will set be set on `onChainChanged` 131 | return true; 132 | } 133 | 134 | disconnect() { 135 | this.walletConnect!.disconnect(); 136 | this.onDisconnect(0, ''); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/utils/providers/walletLink/index.ts: -------------------------------------------------------------------------------- 1 | import { createNanoEvents, Emitter } from "nanoevents"; 2 | import Web3 from "web3"; 3 | 4 | // import { WalletLink as WalletLinkProvider } from 'walletlink'; 5 | import { PARAMS } from "@/utils/constants"; 6 | import { parseChainId } from "@/utils/helpers"; 7 | import { getScript } from "@/utils/helpers"; 8 | 9 | import { Provider } from "../provider"; 10 | 11 | interface Events { 12 | disconnect: () => void; 13 | } 14 | 15 | export class WalletLink implements Provider { 16 | public name = "walletLink"; 17 | public title = "WalletLink"; 18 | public web3: Web3 | null = null; 19 | public myAddress = ""; 20 | public chainId = 0; 21 | public isConnected = false; 22 | private provider: any = null; 23 | private emitter: Emitter; 24 | 25 | constructor() { 26 | this.emitter = createNanoEvents(); 27 | this.onAccountsChanged = this.onAccountsChanged.bind(this); 28 | this.onChainChanged = this.onChainChanged.bind(this); 29 | this.onDisconnect = this.onDisconnect.bind(this); 30 | this.onConnect = this.onConnect.bind(this); 31 | } 32 | 33 | on(event: E, callback: Events[E]) { 34 | return this.emitter.on(event, callback); 35 | } 36 | 37 | async connect(params: any): Promise { 38 | if (window.ethereum && window.ethereum.isCoinbaseWallet === true) { 39 | this.provider = window.ethereum; 40 | } else { 41 | try { 42 | await getScript("walletLink@2.4.2.js"); 43 | } catch (err: any) { 44 | console.log(err.message); 45 | return false; 46 | } 47 | 48 | const walletLink = new window.WalletLinkBundle.default({ 49 | appName: PARAMS.appName, 50 | appLogoUrl: PARAMS.appLogoUrl, 51 | darkMode: false, 52 | }); 53 | this.provider = walletLink.makeWeb3Provider( 54 | params.rpcEndpoint, 55 | params.chainId 56 | ); 57 | } 58 | 59 | try { 60 | await this.provider!.enable(); 61 | } catch (e: any) { 62 | console.log(e.message); 63 | if (e.message === "User denied account authorization") { 64 | // soft error: User rejected the request. 65 | console.log(e.message); 66 | return false; 67 | } 68 | throw new Error(e.message); 69 | } 70 | 71 | this.web3 = new Web3(this.provider!); 72 | 73 | const accounts = await this.web3.eth.getAccounts(); 74 | this.myAddress = accounts[0]; 75 | 76 | this.chainId = parseChainId(await this.web3.eth.net.getId()); 77 | 78 | // if (this.chainId !== params.chainId) { 79 | // await this.switchChain(params.chainId); 80 | // } 81 | 82 | this.isConnected = true; 83 | 84 | this.provider!.on("accountsChanged", this.onAccountsChanged); 85 | this.provider!.on("chainChanged", this.onChainChanged); 86 | this.provider!.on("disconnect", this.onDisconnect); 87 | this.provider!.on("connect", this.onConnect); 88 | 89 | return true; 90 | } 91 | 92 | onAccountsChanged(accounts: Array) { 93 | console.log("account changed, old address: ", this.myAddress); 94 | if (accounts && accounts.length) { 95 | this.myAddress = accounts[0]; 96 | } else { 97 | this.myAddress = ""; 98 | } 99 | console.log("account changed, new address: ", this.myAddress); 100 | } 101 | 102 | onChainChanged(chainId: number | string) { 103 | console.log("chain changed, old chain: ", this.chainId); 104 | this.chainId = parseChainId(chainId); 105 | console.log("chain changed, new chain: ", this.chainId); 106 | } 107 | 108 | onDisconnect(code: number, reason: string) { 109 | this.isConnected = false; 110 | console.log("disconnected"); 111 | 112 | this.provider!.off("accountsChanged", this.onAccountsChanged); 113 | this.provider!.off("chainChanged", this.onChainChanged); 114 | this.provider!.off("disconnect", this.onDisconnect); 115 | this.provider!.off("connect", this.onConnect); 116 | 117 | this.emitter.emit("disconnect"); 118 | this.emitter.events = {}; 119 | } 120 | 121 | onConnect() { 122 | this.isConnected = true; 123 | console.log("connected"); 124 | } 125 | 126 | async switchChain(chainId: number): Promise { 127 | try { 128 | await this.provider.request({ 129 | method: "wallet_switchEthereumChain", 130 | params: [{ chainId: "0x" + chainId.toString(16) }], 131 | }); 132 | } catch (e: any) { 133 | console.log(e.message); 134 | return false; 135 | } 136 | return true; 137 | } 138 | 139 | disconnect() { 140 | this.provider.close(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/utils/services/Bridge.contract.ts: -------------------------------------------------------------------------------- 1 | import { Contract } from "ethers"; 2 | 3 | import BRIDGE from "@/ton-bridge-lib/abi/TokenBridge.json"; 4 | 5 | import { Provider } from "../providers/provider"; 6 | import {TonAddress, TonTxID} from "@/ton-bridge-lib/BridgeCommon"; 7 | export class BridgeContract { 8 | contracts: { [key: string]: Contract } = {}; 9 | 10 | private provider: Provider; 11 | 12 | constructor(provider: Provider) { 13 | this.provider = provider; 14 | } 15 | 16 | _getContract(address: string) { 17 | if (!Object.keys(this.contracts).includes(address)) { 18 | return this._registerContract(address); 19 | } 20 | if (!this.contracts[address].provider) { 21 | return this._registerContract(address); 22 | } 23 | return this.contracts[address]; 24 | } 25 | 26 | _registerContract(address: string) { 27 | // const contract = new provider.web3!.eth.Contract(ERC20.abi as any[], address); 28 | const contract = new Contract( 29 | address, 30 | BRIDGE.abi, 31 | (this.provider as any).provider 32 | ); 33 | 34 | this.contracts[address] = contract; 35 | return contract; 36 | } 37 | 38 | symbol(address: string): string { 39 | const contract = this._getContract(address); 40 | return contract.symbol(); 41 | } 42 | 43 | lock({ 44 | address, 45 | token, 46 | amount, 47 | to_address_hash 48 | }: { 49 | address: string; 50 | token: string; 51 | amount: string; 52 | to_address_hash: string; 53 | }): Promise { 54 | const contract = this._getContract(address); 55 | return contract 56 | .connect((this.provider as any).provider.getSigner()!) 57 | .lock(token, amount, to_address_hash); 58 | } 59 | 60 | unlock({ 61 | bridgeAddress, 62 | ethReceiver, 63 | token, 64 | jettonAmount, 65 | tx, 66 | signatures, 67 | }: { 68 | bridgeAddress: string; 69 | ethReceiver: string; // address 70 | token: string; // address 71 | jettonAmount: string; // uint64 72 | tx: TonTxID; 73 | signatures: any; 74 | }): Promise { 75 | const contract = this._getContract(bridgeAddress); 76 | const resp = contract 77 | .connect((this.provider as any).provider.getSigner()!) 78 | .unlock( 79 | { 80 | receiver: ethReceiver, // address 81 | token, 82 | amount: jettonAmount, 83 | tx: { 84 | address_hash: tx.address_.address_hash, 85 | tx_hash: tx.tx_hash, 86 | lt: tx.lt 87 | } 88 | }, 89 | signatures 90 | ); 91 | return resp; 92 | } 93 | } 94 | 95 | // const erc20Contract = new ERC20Contract(); 96 | 97 | // export default erc20Contract; 98 | -------------------------------------------------------------------------------- /src/utils/services/ERC20.contract.ts: -------------------------------------------------------------------------------- 1 | import BN from "bn.js"; 2 | import { BigNumber, Contract } from "ethers"; 3 | 4 | import ERC20 from "@/ton-bridge-lib/abi/ERC20.json"; 5 | 6 | import { Provider } from "../providers/provider"; 7 | 8 | export class ERC20Contract { 9 | contracts: { [key: string]: Contract } = {}; 10 | 11 | private provider: Provider; 12 | 13 | constructor(provider: Provider) { 14 | this.provider = provider; 15 | } 16 | 17 | _getContract(address: string) { 18 | if (!Object.keys(this.contracts).includes(address)) { 19 | return this._registerContract(address); 20 | } 21 | if (!this.contracts[address].provider) { 22 | return this._registerContract(address); 23 | } 24 | return this.contracts[address]; 25 | } 26 | 27 | _registerContract(address: string) { 28 | // const contract = new provider.web3!.eth.Contract(ERC20.abi as any[], address); 29 | const contract = new Contract( 30 | address, 31 | ERC20.abi, 32 | (this.provider as any).provider 33 | ); 34 | this.contracts[address] = contract; 35 | return contract; 36 | } 37 | 38 | symbol(address: string): string { 39 | const contract = this._getContract(address); 40 | return contract.symbol(); 41 | } 42 | 43 | decimals({ address }: { address: string }) { 44 | const contract = this._getContract(address); 45 | return contract.decimals(); 46 | } 47 | 48 | balanceOf({ 49 | address, 50 | account, 51 | }: { 52 | address: string; 53 | account: string; 54 | }): Promise { 55 | const contract = this._getContract(address); 56 | return contract.balanceOf(account); 57 | } 58 | 59 | allowance({ 60 | address, 61 | owner, 62 | spender, 63 | }: { 64 | address: string; 65 | owner: string; 66 | spender: string; 67 | }): Promise { 68 | const contract = this._getContract(address); 69 | return contract.allowance(owner, spender); 70 | } 71 | 72 | approve({ 73 | address, 74 | spender, 75 | amount, 76 | }: { 77 | address: string; 78 | spender: string; 79 | amount: string; 80 | }): Promise { 81 | const contract = this._getContract(address); 82 | return contract 83 | .connect((this.provider as any).provider.getSigner()!) 84 | .approve(spender, amount); 85 | } 86 | } 87 | 88 | // const erc20Contract = new ERC20Contract(); 89 | 90 | // export default erc20Contract; 91 | -------------------------------------------------------------------------------- /src/views/BridgePage/types.ts: -------------------------------------------------------------------------------- 1 | import { Provider } from "@/utils/providers/provider"; 2 | import {TonConnectUI} from "@tonconnect/ui"; 3 | 4 | type ComponentData = { 5 | getPairGasFee__debounced: () => void; 6 | gasPrice: number; 7 | 8 | isTestnet: boolean; 9 | isRecover: boolean; 10 | lt: number; 11 | hash: string; 12 | evmHash: string; 13 | 14 | tokenSymbol: string; 15 | 16 | isFromTon: boolean; 17 | pair: string; 18 | token: string; 19 | amountInput: string; 20 | toAddress: string; 21 | tokenAddress: string; 22 | ethereumProvider: Provider | null; 23 | tonConnect: TonConnectUI | null; 24 | 25 | isTransferInProgress: boolean; 26 | isEthereumWalletConnected: boolean; 27 | walletsPopupState: string; 28 | bridgeProcessorIsLoading: boolean; 29 | errors: { 30 | amount: string; 31 | toAddress: string; 32 | tokenAddress: string; 33 | }; 34 | }; 35 | 36 | export { ComponentData }; 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "jsx": "preserve", 6 | "moduleResolution": "node", 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "useDefineForClassFields": true, 12 | "resolveJsonModule": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "types": ["webpack-env"], 16 | "allowJs": true, 17 | "paths": { 18 | "@/*": ["src/*"] 19 | }, 20 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 21 | }, 22 | "include": [ 23 | "src/**/*.ts", 24 | "src/**/*.tsx", 25 | "src/**/*.vue", 26 | "tests/**/*.ts", 27 | "tests/**/*.tsx" 28 | ], 29 | "exclude": ["node_modules"] 30 | } 31 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require("@vue/cli-service"); 2 | const NodePolyfillPlugin = require("node-polyfill-webpack-plugin"); 3 | 4 | module.exports = defineConfig({ 5 | publicPath: process.env.BASE_URL, 6 | transpileDependencies: true, 7 | configureWebpack: { 8 | plugins: [new NodePolyfillPlugin()], 9 | optimization: { 10 | splitChunks: { 11 | chunks: "all", 12 | }, 13 | }, 14 | resolve: { 15 | mainFiles: ["index"], 16 | }, 17 | }, 18 | 19 | pluginOptions: { 20 | i18n: { 21 | locale: "en", 22 | fallbackLocale: "en", 23 | localeDir: "locales", 24 | enableLegacy: false, 25 | runtimeOnly: false, 26 | compositionOnly: false, 27 | fullInstall: true, 28 | }, 29 | }, 30 | css: { 31 | loaderOptions: { 32 | less: { 33 | additionalData: ` 34 | @import "@/assets/styles/_variables.less"; 35 | `, 36 | }, 37 | }, 38 | }, 39 | }); 40 | --------------------------------------------------------------------------------