├── .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 |
--------------------------------------------------------------------------------
/dist/img/arrow.4c04916a.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dist/img/check.75704e26.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dist/img/close.f1b54995.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/dist/img/copy.06c68adf.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dist/img/crystal.aabd46bc.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dist/img/done.8796fcad.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dist/img/dot.5a3261fd.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dist/img/dropdown-active.e2c856a1.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dist/img/dropdown.249360c6.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/dist/img/loader.c1c4a3cb.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dist/img/logo.ba3ba8c6.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/dist/img/metamask.397b9a5b.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dist/img/walletConnect.2d540c60.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/dist/img/walletlink.d118db5c.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/docs/img/arrow.4c04916a.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/img/check.75704e26.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/img/close.f1b54995.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/img/copy.06c68adf.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/img/crystal.aabd46bc.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/img/done.8796fcad.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/img/dot.5a3261fd.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/img/dropdown-active.e2c856a1.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/img/dropdown.249360c6.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/docs/img/loader.c1c4a3cb.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/img/logo.ba3ba8c6.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/docs/img/metamask.397b9a5b.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/img/walletConnect.2d540c60.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/img/walletlink.d118db5c.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
2 |
3 |
4 |
5 |
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 |
--------------------------------------------------------------------------------
/src/assets/pics/arrow.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/pics/check.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/pics/close.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/pics/copy.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/pics/crystal.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/pics/done.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
--------------------------------------------------------------------------------
/src/assets/pics/dropdown-active.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/pics/dropdown.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/assets/pics/logo.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/pics/progress/done.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/pics/progress/dot.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/pics/progress/loader.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/pics/providers/metamask.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/pics/providers/walletConnect.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/pics/providers/walletlink.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
2 |
3 |
12 |
13 |
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 |
2 |
31 |
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 |
2 |
23 |
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 |
2 |
69 |
70 |
71 |
184 |
185 |
188 |
--------------------------------------------------------------------------------
/src/components/Layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
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 |
2 |
3 |
29 |
30 |
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 |
--------------------------------------------------------------------------------