bridge_ui: store, stepper, eth redeem
Change-Id: I0afddb5b066f1454d1c7b07bbdf81642b9216207
This commit is contained in:
parent
b035ebc438
commit
b4ca77497a
|
@ -13,6 +13,7 @@
|
|||
"@material-ui/core": "^4.12.2",
|
||||
"@metamask/detect-provider": "^1.2.0",
|
||||
"@project-serum/sol-wallet-adapter": "^0.2.5",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@solana/spl-token": "^0.1.6",
|
||||
"@solana/wallet-base": "^0.0.1",
|
||||
"@solana/web3.js": "^1.22.0",
|
||||
|
@ -21,7 +22,9 @@
|
|||
"ethers": "^5.4.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-scripts": "4.0.3",
|
||||
"redux": "^3.7.2",
|
||||
"token-bridge": "file:rust_modules\\token"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -5250,6 +5253,46 @@
|
|||
"integrity": "sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.6.1.tgz",
|
||||
"integrity": "sha512-pa3nqclCJaZPAyBhruQtiRwtTjottRrVJqziVZcWzI73i6L3miLTtUyWfauwv08HWtiXLx1xGyGt+yLFfW/d0A==",
|
||||
"dependencies": {
|
||||
"immer": "^9.0.1",
|
||||
"redux": "^4.1.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.14.0 || ^17.0.0",
|
||||
"react-redux": "^7.2.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-redux": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit/node_modules/immer": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.5.tgz",
|
||||
"integrity": "sha512-2WuIehr2y4lmYz9gaQzetPR2ECniCifk4ORaQbU3g5EalLt+0IVTosEPJ5BoYl/75ky2mivzdRzV8wWgQGOSYQ==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/immer"
|
||||
}
|
||||
},
|
||||
"node_modules/@reduxjs/toolkit/node_modules/redux": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz",
|
||||
"integrity": "sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@repeaterjs/repeater": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.4.tgz",
|
||||
|
@ -7072,6 +7115,15 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/html-minifier-terser": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz",
|
||||
|
@ -7234,6 +7286,25 @@
|
|||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-redux": {
|
||||
"version": "7.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz",
|
||||
"integrity": "sha512-9iwAsPyJ9DLTRH+OFeIrm9cAbIj1i2ANL3sKQFATqnPWRbg+jEFXyZOKHiQK/N86pNRXbb4HRxAxo0SIX1XwzQ==",
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"redux": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-redux/node_modules/redux": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz",
|
||||
"integrity": "sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.2.tgz",
|
||||
|
@ -23346,8 +23417,7 @@
|
|||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"node_modules/lodash._reinterpolate": {
|
||||
"version": "3.0.0",
|
||||
|
@ -30092,6 +30162,35 @@
|
|||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz",
|
||||
"integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"@types/react-redux": "^7.1.16",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.13.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.3 || ^17"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
},
|
||||
"react-native": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-redux/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/react-refresh": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
|
||||
|
@ -30400,7 +30499,6 @@
|
|||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
|
||||
"integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash": "^4.2.1",
|
||||
"lodash-es": "^4.2.1",
|
||||
|
@ -30469,11 +30567,15 @@
|
|||
"@redux-saga/core": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
|
||||
"integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
|
||||
},
|
||||
"node_modules/redux/node_modules/symbol-observable": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
|
||||
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
@ -30934,8 +31036,7 @@
|
|||
"node_modules/reselect": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
|
||||
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
|
||||
},
|
||||
"node_modules/reselect-tree": {
|
||||
"version": "1.3.4",
|
||||
|
@ -42420,6 +42521,32 @@
|
|||
"integrity": "sha512-afmTuJrylUU/0OtqzaRkbyYFFNgCF73Bvel/sw90pvGrWIZ+vyoIJqA6eMSoA6+nb443kTmulmBtC9NerXboNg==",
|
||||
"dev": true
|
||||
},
|
||||
"@reduxjs/toolkit": {
|
||||
"version": "1.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.6.1.tgz",
|
||||
"integrity": "sha512-pa3nqclCJaZPAyBhruQtiRwtTjottRrVJqziVZcWzI73i6L3miLTtUyWfauwv08HWtiXLx1xGyGt+yLFfW/d0A==",
|
||||
"requires": {
|
||||
"immer": "^9.0.1",
|
||||
"redux": "^4.1.0",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"reselect": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"immer": {
|
||||
"version": "9.0.5",
|
||||
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.5.tgz",
|
||||
"integrity": "sha512-2WuIehr2y4lmYz9gaQzetPR2ECniCifk4ORaQbU3g5EalLt+0IVTosEPJ5BoYl/75ky2mivzdRzV8wWgQGOSYQ=="
|
||||
},
|
||||
"redux": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz",
|
||||
"integrity": "sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@repeaterjs/repeater": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@repeaterjs/repeater/-/repeater-3.0.4.tgz",
|
||||
|
@ -44019,6 +44146,15 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"requires": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"@types/html-minifier-terser": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz",
|
||||
|
@ -44188,6 +44324,27 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@types/react-redux": {
|
||||
"version": "7.1.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.18.tgz",
|
||||
"integrity": "sha512-9iwAsPyJ9DLTRH+OFeIrm9cAbIj1i2ANL3sKQFATqnPWRbg+jEFXyZOKHiQK/N86pNRXbb4HRxAxo0SIX1XwzQ==",
|
||||
"requires": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"redux": "^4.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"redux": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-4.1.1.tgz",
|
||||
"integrity": "sha512-hZQZdDEM25UY2P493kPYuKqviVwZ58lEmGQNeQ+gXa+U0gYPUBf7NKYazbe3m+bs/DzM/ahN12DbF+NG8i0CWw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.9.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/react-transition-group": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.2.tgz",
|
||||
|
@ -57155,8 +57312,7 @@
|
|||
"lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"lodash._reinterpolate": {
|
||||
"version": "3.0.0",
|
||||
|
@ -62736,6 +62892,26 @@
|
|||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"react-redux": {
|
||||
"version": "7.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.4.tgz",
|
||||
"integrity": "sha512-hOQ5eOSkEJEXdpIKbnRyl04LhaWabkDPV+Ix97wqQX3T3d2NQ8DUblNXXtNMavc7DpswyQM6xfaN4HQDKNY2JA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"@types/react-redux": "^7.1.16",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.13.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-refresh": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
|
||||
|
@ -62984,7 +63160,6 @@
|
|||
"version": "3.7.2",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz",
|
||||
"integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.2.1",
|
||||
"lodash-es": "^4.2.1",
|
||||
|
@ -62995,8 +63170,7 @@
|
|||
"symbol-observable": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
|
||||
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -63057,6 +63231,11 @@
|
|||
"@redux-saga/core": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"redux-thunk": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
|
||||
"integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw=="
|
||||
},
|
||||
"regenerate": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||
|
@ -63419,8 +63598,7 @@
|
|||
"reselect": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-4.0.0.tgz",
|
||||
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA=="
|
||||
},
|
||||
"reselect-tree": {
|
||||
"version": "1.3.4",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"@material-ui/core": "^4.12.2",
|
||||
"@metamask/detect-provider": "^1.2.0",
|
||||
"@project-serum/sol-wallet-adapter": "^0.2.5",
|
||||
"@reduxjs/toolkit": "^1.6.1",
|
||||
"@solana/spl-token": "^0.1.6",
|
||||
"@solana/wallet-base": "^0.0.1",
|
||||
"@solana/web3.js": "^1.22.0",
|
||||
|
@ -15,7 +16,9 @@
|
|||
"ethers": "^5.4.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-scripts": "4.0.3",
|
||||
"redux": "^3.7.2",
|
||||
"token-bridge": "file:rust_modules\\token"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AppBar, Link, makeStyles, Toolbar } from "@material-ui/core";
|
||||
import { AppBar, makeStyles, Toolbar } from "@material-ui/core";
|
||||
import Transfer from "./components/Transfer";
|
||||
import wormholeLogo from "./icons/wormhole.svg";
|
||||
|
||||
|
@ -6,8 +6,8 @@ const useStyles = makeStyles((theme) => ({
|
|||
appBar: {
|
||||
borderBottom: `.5px solid ${theme.palette.divider}`,
|
||||
"& > .MuiToolbar-root": {
|
||||
height: 82,
|
||||
margin: "auto",
|
||||
width: "100%",
|
||||
maxWidth: 1100,
|
||||
},
|
||||
},
|
||||
|
@ -19,18 +19,13 @@ const useStyles = makeStyles((theme) => ({
|
|||
color: theme.palette.text.primary,
|
||||
marginLeft: theme.spacing(6),
|
||||
},
|
||||
sideBar: {
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: 733,
|
||||
maxHeight: "80vh",
|
||||
width: 50,
|
||||
borderRight: `.5px solid ${theme.palette.divider}`,
|
||||
borderBottom: `.5px solid ${theme.palette.divider}`,
|
||||
},
|
||||
content: {
|
||||
margin: theme.spacing(10.5, 8),
|
||||
[theme.breakpoints.up("sm")]: {
|
||||
margin: theme.spacing(2, 0),
|
||||
},
|
||||
[theme.breakpoints.up("md")]: {
|
||||
margin: theme.spacing(4, 0),
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
|
@ -42,12 +37,8 @@ function App() {
|
|||
<Toolbar>
|
||||
<img src={wormholeLogo} alt="Wormhole Logo" />
|
||||
<div className={classes.spacer} />
|
||||
<Link className={classes.link}>Placeholder</Link>
|
||||
<Link className={classes.link}>Placeholder</Link>
|
||||
<Link className={classes.link}>Placeholder</Link>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<div className={classes.sideBar}></div>
|
||||
<div className={classes.content}>
|
||||
<Transfer />
|
||||
</div>
|
||||
|
|
|
@ -1,40 +1,18 @@
|
|||
import { Button, Tooltip, Typography } from "@material-ui/core";
|
||||
import { Typography } from "@material-ui/core";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import ToggleConnectedButton from "./ToggleConnectedButton";
|
||||
|
||||
const EthereumSignerKey = () => {
|
||||
const { connect, disconnect, signerAddress, providerError } =
|
||||
useEthereumProvider();
|
||||
return (
|
||||
<>
|
||||
{signerAddress ? (
|
||||
<>
|
||||
<Tooltip title={signerAddress}>
|
||||
<Typography>
|
||||
{signerAddress.substring(0, 6)}...
|
||||
{signerAddress.substr(signerAddress.length - 4)}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Button
|
||||
color="secondary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={disconnect}
|
||||
style={{ width: "100%", textTransform: "none" }}
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={connect}
|
||||
style={{ width: "100%", textTransform: "none" }}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
)}
|
||||
<ToggleConnectedButton
|
||||
connect={connect}
|
||||
disconnect={disconnect}
|
||||
connected={!!signerAddress}
|
||||
pk={signerAddress || ""}
|
||||
/>
|
||||
{providerError ? (
|
||||
<Typography variant="body2" color="error">
|
||||
{providerError}
|
||||
|
|
|
@ -1,40 +1,16 @@
|
|||
import { Button, Tooltip, Typography } from "@material-ui/core";
|
||||
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
||||
import ToggleConnectedButton from "./ToggleConnectedButton";
|
||||
|
||||
const SolanaWalletKey = () => {
|
||||
const { connect, disconnect, connected, wallet } = useSolanaWallet();
|
||||
const pk = wallet?.publicKey?.toString() || "";
|
||||
return (
|
||||
<>
|
||||
{connected ? (
|
||||
<>
|
||||
<Tooltip title={pk}>
|
||||
<Typography>
|
||||
{pk.substring(0, 3)}...{pk.substr(pk.length - 3)}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Button
|
||||
color="secondary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={disconnect}
|
||||
style={{ width: "100%", textTransform: "none" }}
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={connect}
|
||||
style={{ width: "100%", textTransform: "none" }}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
<ToggleConnectedButton
|
||||
connect={connect}
|
||||
disconnect={disconnect}
|
||||
connected={connected}
|
||||
pk={pk}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
import { Button, makeStyles, Tooltip } from "@material-ui/core";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
button: {
|
||||
display: "block",
|
||||
margin: `${theme.spacing(1)}px auto`,
|
||||
width: "100%",
|
||||
maxWidth: 400,
|
||||
},
|
||||
}));
|
||||
|
||||
const ToggleConnectedButton = ({
|
||||
connect,
|
||||
disconnect,
|
||||
connected,
|
||||
pk,
|
||||
}: {
|
||||
connect(): any;
|
||||
disconnect(): any;
|
||||
connected: boolean;
|
||||
pk: string;
|
||||
}) => {
|
||||
const classes = useStyles();
|
||||
const is0x = pk.startsWith("0x");
|
||||
return connected ? (
|
||||
<Tooltip title={pk}>
|
||||
<Button
|
||||
color="secondary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={disconnect}
|
||||
className={classes.button}
|
||||
>
|
||||
Disconnect {pk.substring(0, is0x ? 6 : 3)}...
|
||||
{pk.substr(pk.length - (is0x ? 4 : 3))}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
size="small"
|
||||
onClick={connect}
|
||||
className={classes.button}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToggleConnectedButton;
|
|
@ -1,24 +1,41 @@
|
|||
import {
|
||||
Button,
|
||||
CircularProgress,
|
||||
Grid,
|
||||
Container,
|
||||
makeStyles,
|
||||
MenuItem,
|
||||
Step,
|
||||
StepButton,
|
||||
StepContent,
|
||||
Stepper,
|
||||
TextField,
|
||||
Typography,
|
||||
} from "@material-ui/core";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { useEthereumProvider } from "../contexts/EthereumProviderContext";
|
||||
import { useSolanaWallet } from "../contexts/SolanaWalletContext";
|
||||
import useEthereumBalance from "../hooks/useEthereumBalance";
|
||||
import useSolanaBalance from "../hooks/useSolanaBalance";
|
||||
import useWrappedAsset from "../hooks/useWrappedAsset";
|
||||
import {
|
||||
selectActiveStep,
|
||||
selectSignedVAA,
|
||||
selectSourceChain,
|
||||
selectTargetChain,
|
||||
} from "../store/selectors";
|
||||
import {
|
||||
incrementStep,
|
||||
setSignedVAA,
|
||||
setSourceChain,
|
||||
setStep,
|
||||
setTargetChain,
|
||||
} from "../store/transferSlice";
|
||||
import attestFrom, {
|
||||
attestFromEth,
|
||||
attestFromSolana,
|
||||
} from "../utils/attestFrom";
|
||||
import {
|
||||
ChainId,
|
||||
CHAINS,
|
||||
CHAINS_BY_ID,
|
||||
CHAIN_ID_ETH,
|
||||
|
@ -26,6 +43,7 @@ import {
|
|||
ETH_TEST_TOKEN_ADDRESS,
|
||||
SOL_TEST_TOKEN_ADDRESS,
|
||||
} from "../utils/consts";
|
||||
import redeemOn, { redeemOnEth } from "../utils/redeemOn";
|
||||
import transferFrom, {
|
||||
transferFromEth,
|
||||
transferFromSolana,
|
||||
|
@ -48,7 +66,7 @@ const useStyles = makeStyles((theme) => ({
|
|||
marginTop: theme.spacing(5),
|
||||
},
|
||||
transferButton: {
|
||||
marginTop: theme.spacing(7.5),
|
||||
marginTop: theme.spacing(2),
|
||||
textTransform: "none",
|
||||
width: "100%",
|
||||
},
|
||||
|
@ -61,14 +79,15 @@ const useStyles = makeStyles((theme) => ({
|
|||
|
||||
function Transfer() {
|
||||
const classes = useStyles();
|
||||
//TODO: don't attempt to connect to any wallets until the user clicks a connect button
|
||||
const [fromChain, setFromChain] = useState<ChainId>(CHAIN_ID_ETH);
|
||||
const [toChain, setToChain] = useState<ChainId>(CHAIN_ID_SOLANA);
|
||||
const [assetAddress, setAssetAddress] = useState(ETH_TEST_TOKEN_ADDRESS);
|
||||
const dispatch = useDispatch();
|
||||
const activeStep = useSelector(selectActiveStep);
|
||||
const fromChain = useSelector(selectSourceChain);
|
||||
const toChain = useSelector(selectTargetChain);
|
||||
const [assetAddress, setAssetAddress] = useState(SOL_TEST_TOKEN_ADDRESS);
|
||||
const [amount, setAmount] = useState("");
|
||||
const handleFromChange = useCallback(
|
||||
(event) => {
|
||||
setFromChain(event.target.value);
|
||||
dispatch(setSourceChain(event.target.value));
|
||||
// TODO: remove or check env - for testing purposes
|
||||
if (event.target.value === CHAIN_ID_ETH) {
|
||||
setAssetAddress(ETH_TEST_TOKEN_ADDRESS);
|
||||
|
@ -77,16 +96,16 @@ function Transfer() {
|
|||
setAssetAddress(SOL_TEST_TOKEN_ADDRESS);
|
||||
}
|
||||
if (toChain === event.target.value) {
|
||||
setToChain(fromChain);
|
||||
dispatch(setTargetChain(fromChain));
|
||||
}
|
||||
},
|
||||
[fromChain, toChain]
|
||||
[dispatch, fromChain, toChain]
|
||||
);
|
||||
const handleToChange = useCallback(
|
||||
(event) => {
|
||||
setToChain(event.target.value);
|
||||
dispatch(setTargetChain(event.target.value));
|
||||
if (fromChain === event.target.value) {
|
||||
setFromChain(toChain);
|
||||
dispatch(setSourceChain(toChain));
|
||||
// TODO: remove or check env - for testing purposes
|
||||
if (toChain === CHAIN_ID_ETH) {
|
||||
setAssetAddress(ETH_TEST_TOKEN_ADDRESS);
|
||||
|
@ -96,7 +115,7 @@ function Transfer() {
|
|||
}
|
||||
}
|
||||
},
|
||||
[fromChain, toChain]
|
||||
[dispatch, fromChain, toChain]
|
||||
);
|
||||
const handleAssetChange = useCallback((event) => {
|
||||
setAssetAddress(event.target.value);
|
||||
|
@ -121,9 +140,10 @@ function Transfer() {
|
|||
} = useSolanaBalance(assetAddress, solPK, fromChain === CHAIN_ID_SOLANA);
|
||||
const {
|
||||
isLoading: isCheckingWrapped,
|
||||
isWrapped,
|
||||
// isWrapped,
|
||||
wrappedAsset,
|
||||
} = useWrappedAsset(toChain, fromChain, assetAddress, provider);
|
||||
const isWrapped = true;
|
||||
console.log(isCheckingWrapped, isWrapped, wrappedAsset);
|
||||
const handleAttestClick = useCallback(() => {
|
||||
// TODO: more generic way of calling these
|
||||
|
@ -132,13 +152,26 @@ function Transfer() {
|
|||
fromChain === CHAIN_ID_ETH &&
|
||||
attestFrom[fromChain] === attestFromEth
|
||||
) {
|
||||
attestFromEth(provider, signer, assetAddress);
|
||||
//TODO: just for testing, this should eventually use the store to communicate between steps
|
||||
(async () => {
|
||||
const vaaBytes = await attestFromEth(provider, signer, assetAddress);
|
||||
console.log("bytes in transfer", vaaBytes);
|
||||
})();
|
||||
}
|
||||
if (
|
||||
fromChain === CHAIN_ID_SOLANA &&
|
||||
attestFrom[fromChain] === attestFromSolana
|
||||
) {
|
||||
attestFromSolana(wallet, solPK?.toString(), assetAddress, solDecimals);
|
||||
//TODO: just for testing, this should eventually use the store to communicate between steps
|
||||
(async () => {
|
||||
const vaaBytes = await attestFromSolana(
|
||||
wallet,
|
||||
solPK?.toString(),
|
||||
assetAddress,
|
||||
solDecimals
|
||||
);
|
||||
console.log("bytes in transfer", vaaBytes);
|
||||
})();
|
||||
}
|
||||
}
|
||||
}, [fromChain, provider, signer, wallet, solPK, assetAddress, solDecimals]);
|
||||
|
@ -150,33 +183,44 @@ function Transfer() {
|
|||
fromChain === CHAIN_ID_ETH &&
|
||||
transferFrom[fromChain] === transferFromEth
|
||||
) {
|
||||
transferFromEth(
|
||||
provider,
|
||||
signer,
|
||||
assetAddress,
|
||||
ethDecimals,
|
||||
amount,
|
||||
toChain,
|
||||
solPK?.toBytes()
|
||||
);
|
||||
//TODO: just for testing, this should eventually use the store to communicate between steps
|
||||
(async () => {
|
||||
const vaaBytes = await transferFromEth(
|
||||
provider,
|
||||
signer,
|
||||
assetAddress,
|
||||
ethDecimals,
|
||||
amount,
|
||||
toChain,
|
||||
solPK?.toBytes()
|
||||
);
|
||||
console.log("bytes in transfer", vaaBytes);
|
||||
vaaBytes && dispatch(setSignedVAA(vaaBytes));
|
||||
})();
|
||||
}
|
||||
if (
|
||||
fromChain === CHAIN_ID_SOLANA &&
|
||||
transferFrom[fromChain] === transferFromSolana
|
||||
) {
|
||||
transferFromSolana(
|
||||
wallet,
|
||||
solPK?.toString(),
|
||||
solTokenPK?.toString(),
|
||||
assetAddress,
|
||||
amount,
|
||||
solDecimals,
|
||||
signerAddress,
|
||||
toChain
|
||||
);
|
||||
//TODO: just for testing, this should eventually use the store to communicate between steps
|
||||
(async () => {
|
||||
const vaaBytes = await transferFromSolana(
|
||||
wallet,
|
||||
solPK?.toString(),
|
||||
solTokenPK?.toString(),
|
||||
assetAddress,
|
||||
amount,
|
||||
solDecimals,
|
||||
signerAddress,
|
||||
toChain
|
||||
);
|
||||
console.log("bytes in transfer", vaaBytes);
|
||||
vaaBytes && dispatch(setSignedVAA(vaaBytes));
|
||||
})();
|
||||
}
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
fromChain,
|
||||
provider,
|
||||
signer,
|
||||
|
@ -190,6 +234,16 @@ function Transfer() {
|
|||
solDecimals,
|
||||
toChain,
|
||||
]);
|
||||
const signedVAA = useSelector(selectSignedVAA);
|
||||
const handleRedeemClick = useCallback(() => {
|
||||
if (
|
||||
toChain === CHAIN_ID_ETH &&
|
||||
redeemOn[toChain] === redeemOnEth &&
|
||||
signedVAA
|
||||
) {
|
||||
redeemOnEth(provider, signer, signedVAA);
|
||||
}
|
||||
}, [toChain, provider, signer, signedVAA]);
|
||||
// update this as we develop, just setting expectations with the button state
|
||||
const balance = Number(ethBalance) || solBalance;
|
||||
const isAttestImplemented = !!attestFrom[fromChain];
|
||||
|
@ -211,134 +265,186 @@ function Transfer() {
|
|||
isAddressDefined &&
|
||||
isAmountPositive &&
|
||||
isBalanceAtLeastAmount;
|
||||
const handleNextClick = useCallback(() => {
|
||||
dispatch(incrementStep());
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<div className={classes.transferBox}>
|
||||
<Grid container>
|
||||
<Grid item xs={4}>
|
||||
<Typography>From</Typography>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
value={fromChain}
|
||||
onChange={handleFromChange}
|
||||
<Container maxWidth="md">
|
||||
<Stepper activeStep={activeStep} orientation="vertical">
|
||||
<Step>
|
||||
<StepButton onClick={() => dispatch(setStep(0))}>
|
||||
Select a source
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
value={fromChain}
|
||||
onChange={handleFromChange}
|
||||
>
|
||||
{CHAINS.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<KeyAndBalance chainId={fromChain} tokenAddress={assetAddress} />
|
||||
<TextField
|
||||
placeholder="Asset"
|
||||
fullWidth
|
||||
className={classes.transferField}
|
||||
value={assetAddress}
|
||||
onChange={handleAssetChange}
|
||||
/>
|
||||
<TextField
|
||||
placeholder="Amount"
|
||||
type="number"
|
||||
fullWidth
|
||||
className={classes.transferField}
|
||||
value={amount}
|
||||
onChange={handleAmountChange}
|
||||
/>
|
||||
<Button
|
||||
onClick={handleNextClick}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</StepContent>
|
||||
</Step>
|
||||
<Step>
|
||||
<StepButton onClick={() => dispatch(setStep(1))}>
|
||||
Select a target
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<TextField
|
||||
select
|
||||
fullWidth
|
||||
value={toChain}
|
||||
onChange={handleToChange}
|
||||
>
|
||||
{CHAINS.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
{/* TODO: determine "to" token address */}
|
||||
<KeyAndBalance chainId={toChain} />
|
||||
<Button
|
||||
onClick={handleNextClick}
|
||||
variant="contained"
|
||||
color="primary"
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</StepContent>
|
||||
</Step>
|
||||
<Step>
|
||||
<StepButton onClick={() => dispatch(setStep(2))}>
|
||||
Send tokens
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
{isWrapped ? (
|
||||
<>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
className={classes.transferButton}
|
||||
onClick={handleTransferClick}
|
||||
disabled={!canAttemptTransfer}
|
||||
>
|
||||
Transfer
|
||||
</Button>
|
||||
{canAttemptTransfer ? null : (
|
||||
<Typography variant="body2" color="error">
|
||||
{!isTransferImplemented
|
||||
? `Transfer is not yet implemented for ${CHAINS_BY_ID[fromChain].name}`
|
||||
: !isProviderConnected
|
||||
? "The source wallet is not connected"
|
||||
: !isRecipientAvailable
|
||||
? "The receiving wallet is not connected"
|
||||
: !isAddressDefined
|
||||
? "Please provide an asset address"
|
||||
: !isAmountPositive
|
||||
? "The amount must be positive"
|
||||
: !isBalanceAtLeastAmount
|
||||
? "The amount may not be greater than the balance"
|
||||
: ""}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div style={{ position: "relative" }}>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
disabled={isCheckingWrapped || !canAttemptAttest}
|
||||
onClick={handleAttestClick}
|
||||
className={classes.transferButton}
|
||||
>
|
||||
Attest
|
||||
</Button>
|
||||
{isCheckingWrapped ? (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
color="inherit"
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: "50%",
|
||||
marginLeft: -12,
|
||||
marginBottom: 6,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{isCheckingWrapped ? null : canAttemptAttest ? (
|
||||
<Typography variant="body2">
|
||||
<br />
|
||||
This token does not exist on {CHAINS_BY_ID[toChain].name}.
|
||||
Someone must attest the the token to the target chain before
|
||||
it can be transferred.
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography variant="body2" color="error">
|
||||
{!isAttestImplemented
|
||||
? `Transfer is not yet implemented for ${CHAINS_BY_ID[fromChain].name}`
|
||||
: !isProviderConnected
|
||||
? "The source wallet is not connected"
|
||||
: !isRecipientAvailable
|
||||
? "The receiving wallet is not connected"
|
||||
: !isAddressDefined
|
||||
? "Please provide an asset address"
|
||||
: ""}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</StepContent>
|
||||
</Step>
|
||||
<Step>
|
||||
<StepButton
|
||||
onClick={() => dispatch(setStep(3))}
|
||||
disabled={!signedVAA}
|
||||
>
|
||||
{CHAINS.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
<KeyAndBalance chainId={fromChain} tokenAddress={assetAddress} />
|
||||
</Grid>
|
||||
<Grid item xs={4} className={classes.arrow}>
|
||||
→
|
||||
</Grid>
|
||||
<Grid item xs={4}>
|
||||
<Typography>To</Typography>
|
||||
<TextField select fullWidth value={toChain} onChange={handleToChange}>
|
||||
{CHAINS.map(({ id, name }) => (
|
||||
<MenuItem key={id} value={id}>
|
||||
{name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>
|
||||
{/* TODO: determine "to" token address */}
|
||||
<KeyAndBalance chainId={toChain} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
<TextField
|
||||
placeholder="Asset"
|
||||
fullWidth
|
||||
className={classes.transferField}
|
||||
value={assetAddress}
|
||||
onChange={handleAssetChange}
|
||||
/>
|
||||
{isWrapped ? (
|
||||
<>
|
||||
<TextField
|
||||
placeholder="Amount"
|
||||
type="number"
|
||||
fullWidth
|
||||
className={classes.transferField}
|
||||
value={amount}
|
||||
onChange={handleAmountChange}
|
||||
/>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
className={classes.transferButton}
|
||||
onClick={handleTransferClick}
|
||||
disabled={!canAttemptTransfer}
|
||||
>
|
||||
Transfer
|
||||
</Button>
|
||||
{canAttemptTransfer ? null : (
|
||||
<Typography variant="body2" color="error">
|
||||
{!isTransferImplemented
|
||||
? `Transfer is not yet implemented for ${CHAINS_BY_ID[fromChain].name}`
|
||||
: !isProviderConnected
|
||||
? "The source wallet is not connected"
|
||||
: !isRecipientAvailable
|
||||
? "The receiving wallet is not connected"
|
||||
: !isAddressDefined
|
||||
? "Please provide an asset address"
|
||||
: !isAmountPositive
|
||||
? "The amount must be positive"
|
||||
: !isBalanceAtLeastAmount
|
||||
? "The amount may not be greater than the balance"
|
||||
: ""}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div style={{ position: "relative" }}>
|
||||
Redeem tokens
|
||||
</StepButton>
|
||||
<StepContent>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="contained"
|
||||
disabled={isCheckingWrapped || !canAttemptAttest}
|
||||
onClick={handleAttestClick}
|
||||
className={classes.transferButton}
|
||||
onClick={handleRedeemClick}
|
||||
>
|
||||
Attest
|
||||
Redeem
|
||||
</Button>
|
||||
{isCheckingWrapped ? (
|
||||
<CircularProgress
|
||||
size={24}
|
||||
color="inherit"
|
||||
style={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: "50%",
|
||||
marginLeft: -12,
|
||||
marginBottom: 6,
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{isCheckingWrapped ? null : canAttemptAttest ? (
|
||||
<Typography variant="body2">
|
||||
<br />
|
||||
This token does not exist on {CHAINS_BY_ID[toChain].name}. Someone
|
||||
must attest the the token to the target chain before it can be
|
||||
transferred.
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography variant="body2" color="error">
|
||||
{!isAttestImplemented
|
||||
? `Transfer is not yet implemented for ${CHAINS_BY_ID[fromChain].name}`
|
||||
: !isProviderConnected
|
||||
? "The source wallet is not connected"
|
||||
: !isRecipientAvailable
|
||||
? "The receiving wallet is not connected"
|
||||
: !isAddressDefined
|
||||
? "Please provide an asset address"
|
||||
: ""}
|
||||
</Typography>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</StepContent>
|
||||
</Step>
|
||||
</Stepper>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -41,13 +41,24 @@ function useWrappedAsset(
|
|||
}
|
||||
} else if (checkChain === CHAIN_ID_SOLANA) {
|
||||
setState({ isLoading: true, isWrapped: false, wrappedAsset: null });
|
||||
const asset = await getAttestedAssetSol(originChain, originAsset);
|
||||
if (!cancelled) {
|
||||
setState({
|
||||
isLoading: false,
|
||||
isWrapped: !!asset,
|
||||
wrappedAsset: asset,
|
||||
});
|
||||
try {
|
||||
const asset = await getAttestedAssetSol(originChain, originAsset);
|
||||
if (!cancelled) {
|
||||
setState({
|
||||
isLoading: false,
|
||||
isWrapped: !!asset,
|
||||
wrappedAsset: asset,
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
if (!cancelled) {
|
||||
// TODO: warning for this
|
||||
setState({
|
||||
isLoading: false,
|
||||
isWrapped: false,
|
||||
wrappedAsset: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setState({ isLoading: false, isWrapped: false, wrappedAsset: null });
|
||||
|
|
|
@ -1,19 +1,23 @@
|
|||
import { CssBaseline } from "@material-ui/core";
|
||||
import { ThemeProvider } from "@material-ui/core/styles";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import App from "./App";
|
||||
import { store } from "./store";
|
||||
import { EthereumProviderProvider } from "./contexts/EthereumProviderContext";
|
||||
import { SolanaWalletProvider } from "./contexts/SolanaWalletContext.tsx";
|
||||
import { theme } from "./muiTheme";
|
||||
|
||||
ReactDOM.render(
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<SolanaWalletProvider>
|
||||
<EthereumProviderProvider>
|
||||
<App />
|
||||
</EthereumProviderProvider>
|
||||
</SolanaWalletProvider>
|
||||
</ThemeProvider>,
|
||||
<Provider store={store}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<SolanaWalletProvider>
|
||||
<EthereumProviderProvider>
|
||||
<App />
|
||||
</EthereumProviderProvider>
|
||||
</SolanaWalletProvider>
|
||||
</ThemeProvider>
|
||||
</Provider>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
|
|
|
@ -10,13 +10,20 @@ export const theme = responsiveFontSizes(
|
|||
},
|
||||
divider: "#4e4e54",
|
||||
primary: {
|
||||
main: "#0074FF",
|
||||
main: "rgba(0, 116, 255, 0.8)", // #0074FF
|
||||
},
|
||||
secondary: {
|
||||
main: "rgb(0,239,216,0.8)", // #00EFD8
|
||||
},
|
||||
error: {
|
||||
main: "#FD3503",
|
||||
},
|
||||
},
|
||||
overrides: {
|
||||
MuiButton: {
|
||||
root: {
|
||||
borderRadius: 0,
|
||||
textTransform: "none",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { configureStore } from "@reduxjs/toolkit";
|
||||
import transferReducer from "./transferSlice";
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
transfer: transferReducer,
|
||||
},
|
||||
});
|
||||
|
||||
// Infer the `RootState` and `AppDispatch` types from the store itself
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
|
||||
export type AppDispatch = typeof store.dispatch;
|
|
@ -0,0 +1,8 @@
|
|||
import { RootState } from ".";
|
||||
|
||||
export const selectActiveStep = (state: RootState) => state.transfer.activeStep;
|
||||
export const selectSourceChain = (state: RootState) =>
|
||||
state.transfer.sourceChain;
|
||||
export const selectTargetChain = (state: RootState) =>
|
||||
state.transfer.targetChain;
|
||||
export const selectSignedVAA = (state: RootState) => state.transfer.signedVAA; //TODO: deserialize
|
|
@ -0,0 +1,57 @@
|
|||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { ChainId, CHAIN_ID_ETH, CHAIN_ID_SOLANA } from "../utils/consts";
|
||||
|
||||
const LAST_STEP = 3;
|
||||
|
||||
type Steps = 0 | 1 | 2 | 3;
|
||||
|
||||
export interface TransferState {
|
||||
activeStep: Steps;
|
||||
sourceChain: ChainId;
|
||||
targetChain: ChainId;
|
||||
signedVAA: Uint8Array | undefined;
|
||||
}
|
||||
|
||||
const initialState: TransferState = {
|
||||
activeStep: 0,
|
||||
sourceChain: CHAIN_ID_SOLANA,
|
||||
targetChain: CHAIN_ID_ETH,
|
||||
signedVAA: undefined,
|
||||
};
|
||||
|
||||
export const transferSlice = createSlice({
|
||||
name: "transfer",
|
||||
initialState,
|
||||
reducers: {
|
||||
incrementStep: (state) => {
|
||||
if (state.activeStep < LAST_STEP) state.activeStep++;
|
||||
},
|
||||
decrementStep: (state) => {
|
||||
if (state.activeStep > 0) state.activeStep--;
|
||||
},
|
||||
setStep: (state, action: PayloadAction<Steps>) => {
|
||||
state.activeStep = action.payload;
|
||||
},
|
||||
setSourceChain: (state, action: PayloadAction<ChainId>) => {
|
||||
state.sourceChain = action.payload;
|
||||
},
|
||||
setTargetChain: (state, action: PayloadAction<ChainId>) => {
|
||||
state.targetChain = action.payload;
|
||||
},
|
||||
setSignedVAA: (state, action: PayloadAction<Uint8Array>) => {
|
||||
state.signedVAA = action.payload; //TODO: serialize
|
||||
state.activeStep = 3;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
incrementStep,
|
||||
decrementStep,
|
||||
setStep,
|
||||
setSourceChain,
|
||||
setTargetChain,
|
||||
setSignedVAA,
|
||||
} = transferSlice.actions;
|
||||
|
||||
export default transferSlice.reducer;
|
|
@ -21,135 +21,134 @@ import {
|
|||
|
||||
// TODO: allow for / handle cancellation?
|
||||
// TODO: overall better input checking and error handling
|
||||
export function attestFromEth(
|
||||
export async function attestFromEth(
|
||||
provider: ethers.providers.Web3Provider | undefined,
|
||||
signer: ethers.Signer | undefined,
|
||||
tokenAddress: string
|
||||
) {
|
||||
if (!provider || !signer) return;
|
||||
//TODO: more catches
|
||||
(async () => {
|
||||
const signerAddress = await signer.getAddress();
|
||||
console.log("Signer:", signerAddress);
|
||||
console.log("Token:", tokenAddress);
|
||||
const nonceConst = Math.random() * 100000;
|
||||
const nonceBuffer = Buffer.alloc(4);
|
||||
nonceBuffer.writeUInt32LE(nonceConst, 0);
|
||||
console.log("Initiating attestation");
|
||||
console.log("Nonce:", nonceBuffer);
|
||||
const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer);
|
||||
const v = await bridge.attestToken(tokenAddress, nonceBuffer);
|
||||
const receipt = await v.wait();
|
||||
// TODO: dangerous!(?)
|
||||
const bridgeLog = receipt.logs.filter((l) => {
|
||||
console.log(l.address, ETH_BRIDGE_ADDRESS);
|
||||
return l.address === ETH_BRIDGE_ADDRESS;
|
||||
})[0];
|
||||
const {
|
||||
args: { sequence },
|
||||
} = Implementation__factory.createInterface().parseLog(bridgeLog);
|
||||
console.log("SEQ:", sequence);
|
||||
const emitterAddress = Buffer.from(
|
||||
zeroPad(arrayify(ETH_TOKEN_BRIDGE_ADDRESS), 32)
|
||||
).toString("hex");
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
console.log("SIGNED VAA:", vaaBytes);
|
||||
})();
|
||||
const signerAddress = await signer.getAddress();
|
||||
console.log("Signer:", signerAddress);
|
||||
console.log("Token:", tokenAddress);
|
||||
const nonceConst = Math.random() * 100000;
|
||||
const nonceBuffer = Buffer.alloc(4);
|
||||
nonceBuffer.writeUInt32LE(nonceConst, 0);
|
||||
console.log("Initiating attestation");
|
||||
console.log("Nonce:", nonceBuffer);
|
||||
const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer);
|
||||
const v = await bridge.attestToken(tokenAddress, nonceBuffer);
|
||||
const receipt = await v.wait();
|
||||
// TODO: log parsing should be part of a utility
|
||||
// TODO: dangerous!(?)
|
||||
const bridgeLog = receipt.logs.filter((l) => {
|
||||
console.log(l.address, ETH_BRIDGE_ADDRESS);
|
||||
return l.address === ETH_BRIDGE_ADDRESS;
|
||||
})[0];
|
||||
const {
|
||||
args: { sequence },
|
||||
} = Implementation__factory.createInterface().parseLog(bridgeLog);
|
||||
console.log("SEQ:", sequence);
|
||||
const emitterAddress = Buffer.from(
|
||||
zeroPad(arrayify(ETH_TOKEN_BRIDGE_ADDRESS), 32)
|
||||
).toString("hex");
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
console.log("SIGNED VAA:", vaaBytes);
|
||||
return vaaBytes;
|
||||
}
|
||||
|
||||
// TODO: need to check transfer native vs transfer wrapped
|
||||
// TODO: switch out targetProvider for generic address (this likely involves getting these in their respective contexts)
|
||||
export function attestFromSolana(
|
||||
export async function attestFromSolana(
|
||||
wallet: Wallet | undefined,
|
||||
payerAddress: string | undefined, //TODO: we may not need this since we have wallet
|
||||
mintAddress: string,
|
||||
decimals: number
|
||||
) {
|
||||
if (!wallet || !wallet.publicKey || !payerAddress) return;
|
||||
(async () => {
|
||||
const nonceConst = Math.random() * 100000;
|
||||
const nonceBuffer = Buffer.alloc(4);
|
||||
nonceBuffer.writeUInt32LE(nonceConst, 0);
|
||||
const nonce = nonceBuffer.readUInt32LE(0);
|
||||
console.log("program:", SOL_TOKEN_BRIDGE_ADDRESS);
|
||||
console.log("bridge:", SOL_BRIDGE_ADDRESS);
|
||||
console.log("payer:", payerAddress);
|
||||
console.log("token:", mintAddress);
|
||||
console.log("nonce:", nonce);
|
||||
const bridge = await import("bridge");
|
||||
const feeAccount = await bridge.fee_collector_address(SOL_BRIDGE_ADDRESS);
|
||||
const bridgeStatePK = new PublicKey(
|
||||
bridge.state_address(SOL_BRIDGE_ADDRESS)
|
||||
);
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const bridgeStateAccountInfo = await connection.getAccountInfo(
|
||||
bridgeStatePK
|
||||
);
|
||||
if (bridgeStateAccountInfo?.data === undefined) {
|
||||
throw new Error("bridge state not found");
|
||||
}
|
||||
const bridgeState = bridge.parse_state(
|
||||
new Uint8Array(bridgeStateAccountInfo?.data)
|
||||
);
|
||||
const transferIx = SystemProgram.transfer({
|
||||
fromPubkey: new PublicKey(payerAddress),
|
||||
toPubkey: new PublicKey(feeAccount),
|
||||
lamports: bridgeState.config.fee,
|
||||
});
|
||||
// TODO: pass in connection
|
||||
// Add transfer instruction to transaction
|
||||
const { attest_ix, emitter_address } = await import("token-bridge");
|
||||
const ix = ixFromRust(
|
||||
attest_ix(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
mintAddress,
|
||||
decimals,
|
||||
mintAddress, // TODO: automate on wasm side
|
||||
nonce
|
||||
)
|
||||
);
|
||||
const transaction = new Transaction().add(transferIx, ix);
|
||||
const { blockhash } = await connection.getRecentBlockhash();
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.feePayer = new PublicKey(payerAddress);
|
||||
// Sign transaction, broadcast, and confirm
|
||||
const signed = await wallet.signTransaction(transaction);
|
||||
console.log("SIGNED", signed);
|
||||
const txid = await connection.sendRawTransaction(signed.serialize());
|
||||
console.log("SENT", txid);
|
||||
const conf = await connection.confirmTransaction(txid);
|
||||
console.log("CONFIRMED", conf);
|
||||
const info = await connection.getTransaction(txid);
|
||||
console.log("INFO", info);
|
||||
// TODO: better parsing, safer
|
||||
const SEQ_LOG = "Program log: Sequence: ";
|
||||
const sequence = info?.meta?.logMessages
|
||||
?.filter((msg) => msg.startsWith(SEQ_LOG))[0]
|
||||
.replace(SEQ_LOG, "");
|
||||
if (!sequence) {
|
||||
throw new Error("sequence not found");
|
||||
}
|
||||
console.log("SEQ", sequence);
|
||||
const emitterAddress = Buffer.from(
|
||||
zeroPad(
|
||||
new PublicKey(emitter_address(SOL_TOKEN_BRIDGE_ADDRESS)).toBytes(),
|
||||
32
|
||||
)
|
||||
).toString("hex");
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
console.log("SIGNED VAA:", vaaBytes);
|
||||
})();
|
||||
const nonceConst = Math.random() * 100000;
|
||||
const nonceBuffer = Buffer.alloc(4);
|
||||
nonceBuffer.writeUInt32LE(nonceConst, 0);
|
||||
const nonce = nonceBuffer.readUInt32LE(0);
|
||||
console.log("program:", SOL_TOKEN_BRIDGE_ADDRESS);
|
||||
console.log("bridge:", SOL_BRIDGE_ADDRESS);
|
||||
console.log("payer:", payerAddress);
|
||||
console.log("token:", mintAddress);
|
||||
console.log("nonce:", nonce);
|
||||
const bridge = await import("bridge");
|
||||
const feeAccount = await bridge.fee_collector_address(SOL_BRIDGE_ADDRESS);
|
||||
const bridgeStatePK = new PublicKey(bridge.state_address(SOL_BRIDGE_ADDRESS));
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const bridgeStateAccountInfo = await connection.getAccountInfo(bridgeStatePK);
|
||||
if (bridgeStateAccountInfo?.data === undefined) {
|
||||
throw new Error("bridge state not found");
|
||||
}
|
||||
const bridgeState = bridge.parse_state(
|
||||
new Uint8Array(bridgeStateAccountInfo?.data)
|
||||
);
|
||||
const transferIx = SystemProgram.transfer({
|
||||
fromPubkey: new PublicKey(payerAddress),
|
||||
toPubkey: new PublicKey(feeAccount),
|
||||
lamports: bridgeState.config.fee,
|
||||
});
|
||||
// TODO: pass in connection
|
||||
// Add transfer instruction to transaction
|
||||
const { attest_ix, emitter_address } = await import("token-bridge");
|
||||
const ix = ixFromRust(
|
||||
attest_ix(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
mintAddress,
|
||||
decimals,
|
||||
mintAddress, // TODO: mint_metadata: what address is this supposed to be?
|
||||
mintAddress, // TODO: spl_metadata: what address is this supposed to be?
|
||||
"", // TODO: lookup symbol
|
||||
"", //: TODO: lookup name
|
||||
nonce
|
||||
)
|
||||
);
|
||||
const transaction = new Transaction().add(transferIx, ix);
|
||||
const { blockhash } = await connection.getRecentBlockhash();
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.feePayer = new PublicKey(payerAddress);
|
||||
// Sign transaction, broadcast, and confirm
|
||||
const signed = await wallet.signTransaction(transaction);
|
||||
console.log("SIGNED", signed);
|
||||
const txid = await connection.sendRawTransaction(signed.serialize());
|
||||
console.log("SENT", txid);
|
||||
const conf = await connection.confirmTransaction(txid);
|
||||
console.log("CONFIRMED", conf);
|
||||
const info = await connection.getTransaction(txid);
|
||||
console.log("INFO", info);
|
||||
// TODO: log parsing should be part of a utility
|
||||
// TODO: better parsing, safer
|
||||
const SEQ_LOG = "Program log: Sequence: ";
|
||||
const sequence = info?.meta?.logMessages
|
||||
?.filter((msg) => msg.startsWith(SEQ_LOG))[0]
|
||||
.replace(SEQ_LOG, "");
|
||||
if (!sequence) {
|
||||
throw new Error("sequence not found");
|
||||
}
|
||||
console.log("SEQ", sequence);
|
||||
const emitterAddress = Buffer.from(
|
||||
zeroPad(
|
||||
new PublicKey(emitter_address(SOL_TOKEN_BRIDGE_ADDRESS)).toBytes(),
|
||||
32
|
||||
)
|
||||
).toString("hex");
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
console.log("SIGNED VAA:", vaaBytes);
|
||||
return vaaBytes;
|
||||
}
|
||||
|
||||
const attestFrom = {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { ethers } from "ethers";
|
||||
import { Bridge__factory } from "../ethers-contracts";
|
||||
import { CHAIN_ID_ETH, ETH_TOKEN_BRIDGE_ADDRESS } from "./consts";
|
||||
|
||||
export async function redeemOnEth(
|
||||
provider: ethers.providers.Web3Provider | undefined,
|
||||
signer: ethers.Signer | undefined,
|
||||
signedVAA: Uint8Array
|
||||
) {
|
||||
console.log(provider, signer, signedVAA);
|
||||
if (!provider || !signer) return;
|
||||
console.log("completing transfer");
|
||||
const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer);
|
||||
const v = await bridge.completeTransfer(signedVAA);
|
||||
const receipt = await v.wait();
|
||||
console.log(receipt);
|
||||
}
|
||||
|
||||
const redeemOn = {
|
||||
[CHAIN_ID_ETH]: redeemOnEth,
|
||||
};
|
||||
|
||||
export default redeemOn;
|
|
@ -27,7 +27,7 @@ import {
|
|||
|
||||
// TODO: allow for / handle cancellation?
|
||||
// TODO: overall better input checking and error handling
|
||||
export function transferFromEth(
|
||||
export async function transferFromEth(
|
||||
provider: ethers.providers.Web3Provider | undefined,
|
||||
signer: ethers.Signer | undefined,
|
||||
tokenAddress: string,
|
||||
|
@ -41,65 +41,65 @@ export function transferFromEth(
|
|||
//TODO: don't hardcode, fetch decimals / share them with balance, how do we determine recipient chain?
|
||||
//TODO: more catches
|
||||
const amountParsed = parseUnits(amount, decimals);
|
||||
(async () => {
|
||||
const signerAddress = await signer.getAddress();
|
||||
console.log("Signer:", signerAddress);
|
||||
console.log("Token:", tokenAddress);
|
||||
const token = TokenImplementation__factory.connect(tokenAddress, signer);
|
||||
const allowance = await token.allowance(
|
||||
signerAddress,
|
||||
ETH_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
console.log("Allowance", allowance.toString()); //TODO: should we check that this is zero and warn if it isn't?
|
||||
const transaction = await token.approve(
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
amountParsed
|
||||
);
|
||||
console.log(transaction);
|
||||
const fee = 0; // for now, this won't do anything, we may add later
|
||||
const nonceConst = Math.random() * 100000;
|
||||
const nonceBuffer = Buffer.alloc(4);
|
||||
nonceBuffer.writeUInt32LE(nonceConst, 0);
|
||||
console.log("Initiating transfer");
|
||||
console.log("Amount:", formatUnits(amountParsed, decimals));
|
||||
console.log("To chain:", recipientChain);
|
||||
console.log("To address:", recipientAddress);
|
||||
console.log("Fees:", fee);
|
||||
console.log("Nonce:", nonceBuffer);
|
||||
const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer);
|
||||
const v = await bridge.transferTokens(
|
||||
tokenAddress,
|
||||
amountParsed,
|
||||
recipientChain,
|
||||
recipientAddress,
|
||||
fee,
|
||||
nonceBuffer
|
||||
);
|
||||
const receipt = await v.wait();
|
||||
// TODO: dangerous!(?)
|
||||
const bridgeLog = receipt.logs.filter((l) => {
|
||||
console.log(l.address, ETH_BRIDGE_ADDRESS);
|
||||
return l.address === ETH_BRIDGE_ADDRESS;
|
||||
})[0];
|
||||
const {
|
||||
args: { sender, sequence },
|
||||
} = Implementation__factory.createInterface().parseLog(bridgeLog);
|
||||
console.log(sender, sequence);
|
||||
const emitterAddress = Buffer.from(
|
||||
zeroPad(arrayify(ETH_TOKEN_BRIDGE_ADDRESS), 32)
|
||||
).toString("hex");
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
console.log("SIGNED VAA:", vaaBytes);
|
||||
})();
|
||||
const signerAddress = await signer.getAddress();
|
||||
console.log("Signer:", signerAddress);
|
||||
console.log("Token:", tokenAddress);
|
||||
const token = TokenImplementation__factory.connect(tokenAddress, signer);
|
||||
const allowance = await token.allowance(
|
||||
signerAddress,
|
||||
ETH_TOKEN_BRIDGE_ADDRESS
|
||||
);
|
||||
console.log("Allowance", allowance.toString()); //TODO: should we check that this is zero and warn if it isn't?
|
||||
const transaction = await token.approve(
|
||||
ETH_TOKEN_BRIDGE_ADDRESS,
|
||||
amountParsed
|
||||
);
|
||||
console.log(transaction);
|
||||
const fee = 0; // for now, this won't do anything, we may add later
|
||||
const nonceConst = Math.random() * 100000;
|
||||
const nonceBuffer = Buffer.alloc(4);
|
||||
nonceBuffer.writeUInt32LE(nonceConst, 0);
|
||||
console.log("Initiating transfer");
|
||||
console.log("Amount:", formatUnits(amountParsed, decimals));
|
||||
console.log("To chain:", recipientChain);
|
||||
console.log("To address:", recipientAddress);
|
||||
console.log("Fees:", fee);
|
||||
console.log("Nonce:", nonceBuffer);
|
||||
const bridge = Bridge__factory.connect(ETH_TOKEN_BRIDGE_ADDRESS, signer);
|
||||
const v = await bridge.transferTokens(
|
||||
tokenAddress,
|
||||
amountParsed,
|
||||
recipientChain,
|
||||
recipientAddress,
|
||||
fee,
|
||||
nonceBuffer
|
||||
);
|
||||
const receipt = await v.wait();
|
||||
// TODO: log parsing should be part of a utility
|
||||
// TODO: dangerous!(?)
|
||||
const bridgeLog = receipt.logs.filter((l) => {
|
||||
console.log(l.address, ETH_BRIDGE_ADDRESS);
|
||||
return l.address === ETH_BRIDGE_ADDRESS;
|
||||
})[0];
|
||||
const {
|
||||
args: { sender, sequence },
|
||||
} = Implementation__factory.createInterface().parseLog(bridgeLog);
|
||||
console.log(sender, sequence);
|
||||
const emitterAddress = Buffer.from(
|
||||
zeroPad(arrayify(ETH_TOKEN_BRIDGE_ADDRESS), 32)
|
||||
).toString("hex");
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
CHAIN_ID_ETH,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
console.log("SIGNED VAA:", vaaBytes);
|
||||
return vaaBytes;
|
||||
}
|
||||
|
||||
// TODO: need to check transfer native vs transfer wrapped
|
||||
// TODO: switch out targetProvider for generic address (this likely involves getting these in their respective contexts)
|
||||
export function transferFromSolana(
|
||||
export async function transferFromSolana(
|
||||
wallet: Wallet | undefined,
|
||||
payerAddress: string | undefined, //TODO: we may not need this since we have wallet
|
||||
fromAddress: string | undefined,
|
||||
|
@ -117,106 +117,102 @@ export function transferFromSolana(
|
|||
!targetAddressStr
|
||||
)
|
||||
return;
|
||||
(async () => {
|
||||
const targetAddress = zeroPad(arrayify(targetAddressStr), 32);
|
||||
const nonceConst = Math.random() * 100000;
|
||||
const nonceBuffer = Buffer.alloc(4);
|
||||
nonceBuffer.writeUInt32LE(nonceConst, 0);
|
||||
const nonce = nonceBuffer.readUInt32LE(0);
|
||||
const amountParsed = parseUnits(amount, decimals).toBigInt();
|
||||
const fee = BigInt(0); // for now, this won't do anything, we may add later
|
||||
console.log("program:", SOL_TOKEN_BRIDGE_ADDRESS);
|
||||
console.log("bridge:", SOL_BRIDGE_ADDRESS);
|
||||
console.log("payer:", payerAddress);
|
||||
console.log("from:", fromAddress);
|
||||
console.log("token:", mintAddress);
|
||||
console.log("nonce:", nonce);
|
||||
console.log("amount:", amountParsed);
|
||||
console.log("fee:", fee);
|
||||
console.log("target:", targetAddressStr, targetAddress);
|
||||
console.log("chain:", targetChain);
|
||||
const bridge = await import("bridge");
|
||||
const feeAccount = await bridge.fee_collector_address(SOL_BRIDGE_ADDRESS);
|
||||
const bridgeStatePK = new PublicKey(
|
||||
bridge.state_address(SOL_BRIDGE_ADDRESS)
|
||||
);
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const bridgeStateAccountInfo = await connection.getAccountInfo(
|
||||
bridgeStatePK
|
||||
);
|
||||
if (bridgeStateAccountInfo?.data === undefined) {
|
||||
throw new Error("bridge state not found");
|
||||
}
|
||||
const bridgeState = bridge.parse_state(
|
||||
new Uint8Array(bridgeStateAccountInfo?.data)
|
||||
);
|
||||
const transferIx = SystemProgram.transfer({
|
||||
fromPubkey: new PublicKey(payerAddress),
|
||||
toPubkey: new PublicKey(feeAccount),
|
||||
lamports: bridgeState.config.fee,
|
||||
});
|
||||
// TODO: pass in connection
|
||||
// Add transfer instruction to transaction
|
||||
const { transfer_native_ix, approval_authority_address, emitter_address } =
|
||||
await import("token-bridge");
|
||||
const approvalIx = Token.createApproveInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
new PublicKey(fromAddress),
|
||||
new PublicKey(approval_authority_address(SOL_TOKEN_BRIDGE_ADDRESS)),
|
||||
new PublicKey(payerAddress),
|
||||
[],
|
||||
Number(amountParsed)
|
||||
);
|
||||
const ix = ixFromRust(
|
||||
transfer_native_ix(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
fromAddress,
|
||||
mintAddress,
|
||||
nonce,
|
||||
amountParsed,
|
||||
fee,
|
||||
targetAddress,
|
||||
targetChain
|
||||
)
|
||||
);
|
||||
const transaction = new Transaction().add(transferIx, approvalIx, ix);
|
||||
const { blockhash } = await connection.getRecentBlockhash();
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.feePayer = new PublicKey(payerAddress);
|
||||
// Sign transaction, broadcast, and confirm
|
||||
const signed = await wallet.signTransaction(transaction);
|
||||
console.log("SIGNED", signed);
|
||||
const txid = await connection.sendRawTransaction(signed.serialize());
|
||||
console.log("SENT", txid);
|
||||
const conf = await connection.confirmTransaction(txid);
|
||||
console.log("CONFIRMED", conf);
|
||||
const info = await connection.getTransaction(txid);
|
||||
console.log("INFO", info);
|
||||
// TODO: better parsing, safer
|
||||
const SEQ_LOG = "Program log: Sequence: ";
|
||||
const sequence = info?.meta?.logMessages
|
||||
?.filter((msg) => msg.startsWith(SEQ_LOG))[0]
|
||||
.replace(SEQ_LOG, "");
|
||||
if (!sequence) {
|
||||
throw new Error("sequence not found");
|
||||
}
|
||||
console.log("SEQ", sequence);
|
||||
const emitterAddress = Buffer.from(
|
||||
zeroPad(
|
||||
new PublicKey(emitter_address(SOL_TOKEN_BRIDGE_ADDRESS)).toBytes(),
|
||||
32
|
||||
)
|
||||
).toString("hex");
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
console.log("SIGNED VAA:", vaaBytes);
|
||||
})();
|
||||
const targetAddress = zeroPad(arrayify(targetAddressStr), 32);
|
||||
const nonceConst = Math.random() * 100000;
|
||||
const nonceBuffer = Buffer.alloc(4);
|
||||
nonceBuffer.writeUInt32LE(nonceConst, 0);
|
||||
const nonce = nonceBuffer.readUInt32LE(0);
|
||||
const amountParsed = parseUnits(amount, decimals).toBigInt();
|
||||
const fee = BigInt(0); // for now, this won't do anything, we may add later
|
||||
console.log("program:", SOL_TOKEN_BRIDGE_ADDRESS);
|
||||
console.log("bridge:", SOL_BRIDGE_ADDRESS);
|
||||
console.log("payer:", payerAddress);
|
||||
console.log("from:", fromAddress);
|
||||
console.log("token:", mintAddress);
|
||||
console.log("nonce:", nonce);
|
||||
console.log("amount:", amountParsed);
|
||||
console.log("fee:", fee);
|
||||
console.log("target:", targetAddressStr, targetAddress);
|
||||
console.log("chain:", targetChain);
|
||||
const bridge = await import("bridge");
|
||||
const feeAccount = await bridge.fee_collector_address(SOL_BRIDGE_ADDRESS);
|
||||
const bridgeStatePK = new PublicKey(bridge.state_address(SOL_BRIDGE_ADDRESS));
|
||||
// TODO: share connection in context?
|
||||
const connection = new Connection(SOLANA_HOST, "confirmed");
|
||||
const bridgeStateAccountInfo = await connection.getAccountInfo(bridgeStatePK);
|
||||
if (bridgeStateAccountInfo?.data === undefined) {
|
||||
throw new Error("bridge state not found");
|
||||
}
|
||||
const bridgeState = bridge.parse_state(
|
||||
new Uint8Array(bridgeStateAccountInfo?.data)
|
||||
);
|
||||
const transferIx = SystemProgram.transfer({
|
||||
fromPubkey: new PublicKey(payerAddress),
|
||||
toPubkey: new PublicKey(feeAccount),
|
||||
lamports: bridgeState.config.fee,
|
||||
});
|
||||
// TODO: pass in connection
|
||||
// Add transfer instruction to transaction
|
||||
const { transfer_native_ix, approval_authority_address, emitter_address } =
|
||||
await import("token-bridge");
|
||||
const approvalIx = Token.createApproveInstruction(
|
||||
TOKEN_PROGRAM_ID,
|
||||
new PublicKey(fromAddress),
|
||||
new PublicKey(approval_authority_address(SOL_TOKEN_BRIDGE_ADDRESS)),
|
||||
new PublicKey(payerAddress),
|
||||
[],
|
||||
Number(amountParsed)
|
||||
);
|
||||
const ix = ixFromRust(
|
||||
transfer_native_ix(
|
||||
SOL_TOKEN_BRIDGE_ADDRESS,
|
||||
SOL_BRIDGE_ADDRESS,
|
||||
payerAddress,
|
||||
fromAddress,
|
||||
mintAddress,
|
||||
nonce,
|
||||
amountParsed,
|
||||
fee,
|
||||
targetAddress,
|
||||
targetChain
|
||||
)
|
||||
);
|
||||
const transaction = new Transaction().add(transferIx, approvalIx, ix);
|
||||
const { blockhash } = await connection.getRecentBlockhash();
|
||||
transaction.recentBlockhash = blockhash;
|
||||
transaction.feePayer = new PublicKey(payerAddress);
|
||||
// Sign transaction, broadcast, and confirm
|
||||
const signed = await wallet.signTransaction(transaction);
|
||||
console.log("SIGNED", signed);
|
||||
const txid = await connection.sendRawTransaction(signed.serialize());
|
||||
console.log("SENT", txid);
|
||||
const conf = await connection.confirmTransaction(txid);
|
||||
console.log("CONFIRMED", conf);
|
||||
const info = await connection.getTransaction(txid);
|
||||
console.log("INFO", info);
|
||||
// TODO: log parsing should be part of a utility
|
||||
// TODO: better parsing, safer
|
||||
const SEQ_LOG = "Program log: Sequence: ";
|
||||
const sequence = info?.meta?.logMessages
|
||||
?.filter((msg) => msg.startsWith(SEQ_LOG))[0]
|
||||
.replace(SEQ_LOG, "");
|
||||
if (!sequence) {
|
||||
throw new Error("sequence not found");
|
||||
}
|
||||
console.log("SEQ", sequence);
|
||||
const emitterAddress = Buffer.from(
|
||||
zeroPad(
|
||||
new PublicKey(emitter_address(SOL_TOKEN_BRIDGE_ADDRESS)).toBytes(),
|
||||
32
|
||||
)
|
||||
).toString("hex");
|
||||
const { vaaBytes } = await getSignedVAA(
|
||||
CHAIN_ID_SOLANA,
|
||||
emitterAddress,
|
||||
sequence
|
||||
);
|
||||
console.log("SIGNED VAA:", vaaBytes);
|
||||
return vaaBytes;
|
||||
}
|
||||
|
||||
const transferFrom = {
|
||||
|
|
Loading…
Reference in New Issue