diff --git a/package.json b/package.json
index 7f98f2da3..63fc85ef4 100644
--- a/package.json
+++ b/package.json
@@ -31,8 +31,13 @@
"@ledgerhq/hw-transport-u2f": "5.22.0",
"@ledgerhq/hw-transport-webhid": "^5.51.1",
"@ledgerhq/hw-transport-webusb": "5.22.0",
- "@obsidiansystems/hw-app-avalanche": "0.2.2",
"@openzeppelin/contracts": "^3.4.2",
+ "@secux/app-eth": "^2.1.6",
+ "@secux/hw-app-avalanche": "https://github.com/jshuo/hw-app-avalanche.git#249c5b5dafaff7a58ba16d5e1a0cfad49a483e98",
+ "@secux/protocol-device": "^2.1.1",
+ "@secux/protocol-transaction": "^2.1.3",
+ "@secux/transport-webble": "^2.1.2",
+ "@secux/transport-webusb": "^2.1.2",
"@types/big.js": "4.0.5",
"@types/create-hash": "1.2.2",
"@types/crypto-js": "^4.0.2",
@@ -134,7 +139,7 @@
"sass": "^1.24.4",
"sass-loader": "^8.0.2",
"ts-jest": "^26.1.1",
- "typescript": "~3.9.5",
+ "typescript": "^3.9.10",
"vue-cli-plugin-vuetify": "^2.0.3",
"vue-jest": "^3.0.5",
"vue-template-compiler": "2.6.12",
diff --git a/public/img/access_icons/day/secux.svg b/public/img/access_icons/day/secux.svg
new file mode 100644
index 000000000..0b0504748
--- /dev/null
+++ b/public/img/access_icons/day/secux.svg
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/public/img/access_icons/night/secux.svg b/public/img/access_icons/night/secux.svg
new file mode 100644
index 000000000..9c23c9847
--- /dev/null
+++ b/public/img/access_icons/night/secux.svg
@@ -0,0 +1,11 @@
+
diff --git a/src/components/Secux/SecuXButton.vue b/src/components/Secux/SecuXButton.vue
new file mode 100644
index 000000000..1c1fcad2e
--- /dev/null
+++ b/src/components/Secux/SecuXButton.vue
@@ -0,0 +1,177 @@
+
+
+
+
+
diff --git a/src/components/Secux/SecuXButtonBle.vue b/src/components/Secux/SecuXButtonBle.vue
new file mode 100644
index 000000000..464638522
--- /dev/null
+++ b/src/components/Secux/SecuXButtonBle.vue
@@ -0,0 +1,176 @@
+
+
+
+
+
diff --git a/src/components/misc/UrlBanner.vue b/src/components/misc/UrlBanner.vue
index 5b15afc8f..45ced0c16 100644
--- a/src/components/misc/UrlBanner.vue
+++ b/src/components/misc/UrlBanner.vue
@@ -3,7 +3,7 @@
Make sure the URL is
- wallet.avax.network
+ secux-avax.netlify.app
diff --git a/src/components/modals/HdDerivationList/HDDerivationList.vue b/src/components/modals/HdDerivationList/HDDerivationList.vue
index f7220114c..af0ac5c9d 100644
--- a/src/components/modals/HdDerivationList/HDDerivationList.vue
+++ b/src/components/modals/HdDerivationList/HDDerivationList.vue
@@ -46,6 +46,7 @@ import Big from 'big.js'
import AvaAsset from '@/js/AvaAsset'
import { DerivationListBalanceDict } from '@/components/modals/HdDerivationList/types'
import { LedgerWallet } from '../../../js/wallets/LedgerWallet'
+import { SecuXWallet } from '../../../js/wallets/SecuXWallet'
import { bnToBig } from '@/helpers/helper'
import { BN } from 'avalanche'
import HdChainTable from '@/components/modals/HdDerivationList/HdChainTable.vue'
@@ -56,7 +57,7 @@ import HdChainTable from '@/components/modals/HdDerivationList/HdChainTable.vue'
},
})
export default class HDDerivationList extends Vue {
- @Prop() wallet!: MnemonicWallet | LedgerWallet
+ @Prop() wallet!: MnemonicWallet | LedgerWallet | SecuXWallet
addrsExternal: string[] = []
addrsInternal: string[] = []
diff --git a/src/components/modals/HdDerivationList/HdChainTable.vue b/src/components/modals/HdDerivationList/HdChainTable.vue
index 370705113..4a9ce8549 100644
--- a/src/components/modals/HdDerivationList/HdChainTable.vue
+++ b/src/components/modals/HdDerivationList/HdChainTable.vue
@@ -40,6 +40,7 @@
import { Vue, Component, Prop } from 'vue-property-decorator'
import MnemonicWallet from '@/js/wallets/MnemonicWallet'
import { LedgerWallet } from '@/js/wallets/LedgerWallet'
+import { SecuXWallet } from '@/js/wallets/SecuXWallet'
import { DerivationListBalanceDict } from '@/components/modals/HdDerivationList/types'
import HdDerivationListRow from '@/components/modals/HdDerivationList/HdDerivationListRow.vue'
import HdEmptyAddressRow from '@/components/modals/HdDerivationList/HdEmptyAddressRow.vue'
@@ -48,7 +49,7 @@ import { HdHelper } from '@/js/HdHelper'
components: { HdEmptyAddressRow, HdDerivationListRow },
})
export default class HdChainTable extends Vue {
- @Prop() wallet!: MnemonicWallet | LedgerWallet
+ @Prop() wallet!: MnemonicWallet | LedgerWallet | SecuXWallet
@Prop() addresses!: string[]
@Prop() balanceDict!: DerivationListBalanceDict[]
@Prop() path!: number
diff --git a/src/components/modals/HdDerivationList/HdDerivationListRow.vue b/src/components/modals/HdDerivationList/HdDerivationListRow.vue
index b1f7ffb9d..cd612d029 100644
--- a/src/components/modals/HdDerivationList/HdDerivationListRow.vue
+++ b/src/components/modals/HdDerivationList/HdDerivationListRow.vue
@@ -25,6 +25,7 @@ import { Vue, Component, Prop } from 'vue-property-decorator'
import Big from 'big.js'
import { DerivationListBalanceDict } from '@/components/modals/HdDerivationList/types'
import { LedgerWallet } from '@/js/wallets/LedgerWallet'
+import { SecuXWallet } from '@/js/wallets/SecuXWallet'
import { WalletType } from '@/js/wallets/types'
import { ava } from '@/AVA'
diff --git a/src/components/wallet/TopCards/AddressCard/AddressCard.vue b/src/components/wallet/TopCards/AddressCard/AddressCard.vue
index 518ec9607..19ad094fd 100644
--- a/src/components/wallet/TopCards/AddressCard/AddressCard.vue
+++ b/src/components/wallet/TopCards/AddressCard/AddressCard.vue
@@ -64,6 +64,7 @@ import MnemonicWallet, {
LEDGER_ETH_ACCOUNT_PATH,
} from '@/js/wallets/MnemonicWallet'
import { LedgerWallet } from '@/js/wallets/LedgerWallet'
+import { SecuXWallet } from '@/js/wallets/SecuXWallet'
import ChainSelect from '@/components/wallet/TopCards/AddressCard/ChainSelect.vue'
import { ChainIdType } from '@/constants'
diff --git a/src/components/wallet/advanced/SignMessage/SearchAddress.vue b/src/components/wallet/advanced/SignMessage/SearchAddress.vue
index 7fdca0cc8..9d8be922b 100644
--- a/src/components/wallet/advanced/SignMessage/SearchAddress.vue
+++ b/src/components/wallet/advanced/SignMessage/SearchAddress.vue
@@ -30,11 +30,12 @@
import { Vue, Component, Prop, Model } from 'vue-property-decorator'
import MnemonicWallet from '@/js/wallets/MnemonicWallet'
import { LedgerWallet } from '@/js/wallets/LedgerWallet'
+import { SecuXWallet } from '@/js/wallets/SecuXWallet'
@Component
export default class SearchAddress extends Vue {
@Model('change', { type: String }) readonly selectedAddress!: string | null
- @Prop() wallet!: MnemonicWallet | LedgerWallet
+ @Prop() wallet!: MnemonicWallet | LedgerWallet | SecuXWallet
address: string = ''
matchingAddrs: string[] = []
diff --git a/src/js/wallets/MnemonicWallet.ts b/src/js/wallets/MnemonicWallet.ts
index 0dd1bf57a..9aed38831 100644
--- a/src/js/wallets/MnemonicWallet.ts
+++ b/src/js/wallets/MnemonicWallet.ts
@@ -55,6 +55,7 @@ const AVA_TOKEN_INDEX: string = '9000'
export const AVA_ACCOUNT_PATH: string = `m/44'/${AVA_TOKEN_INDEX}'/0'` // Change and index left out
export const ETH_ACCOUNT_PATH: string = `m/44'/60'/0'`
export const LEDGER_ETH_ACCOUNT_PATH = ETH_ACCOUNT_PATH + '/0/0'
+export const SECUX_ETH_ACCOUNT_PATH = ETH_ACCOUNT_PATH + '/0/0'
const INDEX_RANGE: number = 20 // a gap of at least 20 indexes is needed to claim an index unused
const SCAN_SIZE: number = 70 // the total number of utxos to look at initially to calculate last index
diff --git a/src/js/wallets/SecuXWallet.ts b/src/js/wallets/SecuXWallet.ts
new file mode 100644
index 000000000..857b7e65a
--- /dev/null
+++ b/src/js/wallets/SecuXWallet.ts
@@ -0,0 +1,965 @@
+// import AppBtc from "@ledgerhq/hw-app-btc";
+//@ts-ignore
+import AppAvax from '@secux/hw-app-avalanche'
+//@ts-ignore
+
+// import { SecuxTransactionTool } from "@secux/protocol-transaction";
+
+import EthereumjsCommon from '@ethereumjs/common'
+import { Transaction } from '@ethereumjs/tx'
+
+import moment from 'moment'
+import { Buffer, BN } from 'avalanche'
+import HDKey from 'hdkey'
+import { ava, avm, bintools, cChain, pChain } from '@/AVA'
+const bippath = require('bip32-path')
+import createHash from 'create-hash'
+import store from '@/store'
+import { importPublic, publicToAddress, bnToRlp, bnToHex, rlp } from 'ethereumjs-util'
+
+import { UTXO as AVMUTXO, UTXO, UTXOSet as AVMUTXOSet } from 'avalanche/dist/apis/avm/utxos'
+import { AvaWalletCore } from '@/js/wallets/types'
+import { ITransaction } from '@/components/wallet/transfer/types'
+import {
+ AVMConstants,
+ OperationTx,
+ SelectCredentialClass as AVMSelectCredentialClass,
+ TransferableOperation,
+ Tx as AVMTx,
+ UnsignedTx as AVMUnsignedTx,
+ ImportTx as AVMImportTx,
+} from 'avalanche/dist/apis/avm'
+
+import {
+ ImportTx as PlatformImportTx,
+ ExportTx as PlatformExportTx,
+ Tx as PlatformTx,
+ UTXO as PlatformUTXO,
+ UnsignedTx as PlatformUnsignedTx,
+ PlatformVMConstants,
+ SelectCredentialClass as PlatformSelectCredentialClass,
+ AddDelegatorTx,
+ AddValidatorTx,
+} from 'avalanche/dist/apis/platformvm'
+
+import {
+ UnsignedTx as EVMUnsignedTx,
+ ImportTx as EVMImportTx,
+ ExportTx as EVMExportTx,
+ Tx as EvmTx,
+ EVMConstants,
+ EVMInput,
+ SelectCredentialClass as EVMSelectCredentialClass,
+} from 'avalanche/dist/apis/evm'
+
+import { Credential, SigIdx, Signature, UTXOResponse, Address } from 'avalanche/dist/common'
+import { getPreferredHRP, PayloadBase } from 'avalanche/dist/utils'
+import { HdWalletCore } from '@/js/wallets/HdWalletCore'
+import { ISecuXConfig } from '@/store/types'
+import { WalletNameType } from '@/js/wallets/types'
+import { bnToBig, digestMessage } from '@/helpers/helper'
+import { abiDecoder, web3 } from '@/evm'
+import { AVA_ACCOUNT_PATH, ETH_ACCOUNT_PATH, SECUX_ETH_ACCOUNT_PATH } from './MnemonicWallet'
+import { ChainIdType } from '@/constants'
+import { ParseableAvmTxEnum, ParseablePlatformEnum, ParseableEvmTxEnum } from '../TxHelper'
+import { ISecuXBlockMessage } from '../../store/modules/secux/types'
+import Erc20Token from '@/js/Erc20Token'
+import { WalletHelper } from '@/helpers/wallet_helper'
+import { Utils, NetworkHelper, Network } from '@avalabs/avalanche-wallet-sdk'
+
+export const MIN_MCU_FW_SUPPORT_V = '2.16'
+
+class SecuXWallet extends HdWalletCore implements AvaWalletCore {
+ type: WalletNameType = 'SecuX'
+ ethAddress: string
+ ethBalance: BN
+
+ constructor(
+ public app: AppAvax,
+ public transport: any,
+ public hdkey: HDKey,
+ public config: ISecuXConfig,
+ public hdEth: HDKey,
+ public eth: any
+ ) {
+ super(hdkey, hdEth)
+ this.type = 'SecuX'
+
+ if (hdEth) {
+ const ethKey = hdEth
+ const ethPublic = importPublic(ethKey.publicKey)
+ this.ethAddress = publicToAddress(ethPublic).toString('hex')
+ this.ethBalance = new BN(0)
+ } else {
+ this.ethAddress = ''
+ this.ethBalance = new BN(0)
+ }
+ }
+
+ static async fromApp(app: any, eth: any, transport: any, config: ISecuXConfig) {
+ let ethRes = await transport.getXPublickey(SECUX_ETH_ACCOUNT_PATH, false)
+ let res = await transport.getXPublickey(AVA_ACCOUNT_PATH, false)
+
+ let hd = new HDKey()
+ hd.publicKey = res.publicKey
+ hd.chainCode = res.chainCode
+ let hdEth = new HDKey()
+ // @ts-ignore
+ hdEth.publicKey = ethRes.publicKey
+ // @ts-ignore
+ hdEth.chainCode = ethRes.chainCode
+ // @ts-ignore
+ return new SecuXWallet(app, transport, hd, config, hdEth, eth)
+ }
+
+ // Returns an array of derivation paths that need to sign this transaction
+ // Used with signTransactionHash and signTransactionParsable
+ getTransactionPaths(
+ unsignedTx: UnsignedTx,
+ chainId: ChainIdType
+ ): { paths: string[]; isAvaxOnly: boolean } {
+ // TODO: This is a nasty fix. Remove when AJS is updated.
+ unsignedTx.toBuffer()
+ let tx = unsignedTx.getTransaction()
+ let txType = tx.getTxType()
+
+ let ins = tx.getIns()
+ let operations: TransferableOperation[] = []
+
+ // Try to get operations, it will fail if there are none, ignore and continue
+ try {
+ operations = (tx as OperationTx).getOperations()
+ } catch (e) {
+ console.log(e)
+ }
+
+ let items = ins
+ if (
+ (txType === AVMConstants.IMPORTTX && chainId === 'X') ||
+ (txType === PlatformVMConstants.IMPORTTX && chainId === 'P')
+ ) {
+ items = ((tx as AVMImportTx) || PlatformImportTx).getImportInputs()
+ }
+
+ let hrp = getPreferredHRP(ava.getNetworkID())
+ let paths: string[] = []
+
+ let isAvaxOnly = true
+
+ // Collect derivation paths for source addresses
+ for (let i = 0; i < items.length; i++) {
+ let item = items[i]
+
+ let assetId = bintools.cb58Encode(item.getAssetID())
+ // @ts-ignore
+ if (assetId !== store.state.Assets.AVA_ASSET_ID) {
+ isAvaxOnly = false
+ }
+
+ let sigidxs: SigIdx[] = item.getInput().getSigIdxs()
+ let sources = sigidxs.map((sigidx) => sigidx.getSource())
+ let addrs: string[] = sources.map((source) => {
+ return bintools.addressToString(hrp, chainId, source)
+ })
+
+ for (let j = 0; j < addrs.length; j++) {
+ let srcAddr = addrs[j]
+ let pathStr = this.getPathFromAddress(srcAddr) // returns change/index
+
+ paths.push(pathStr)
+ }
+ }
+
+ // Do the Same for operational inputs, if there are any...
+ for (let i = 0; i < operations.length; i++) {
+ let op = operations[i]
+ let sigidxs: SigIdx[] = op.getOperation().getSigIdxs()
+ let sources = sigidxs.map((sigidx) => sigidx.getSource())
+ let addrs: string[] = sources.map((source) => {
+ return bintools.addressToString(hrp, chainId, source)
+ })
+
+ for (let j = 0; j < addrs.length; j++) {
+ let srcAddr = addrs[j]
+ let pathStr = this.getPathFromAddress(srcAddr) // returns change/index
+
+ paths.push(pathStr)
+ }
+ }
+
+ return { paths, isAvaxOnly }
+ }
+
+ pathsToUniqueBipPaths(paths: string[]) {
+ let uniquePaths = paths.filter((val: any, i: number) => {
+ return paths.indexOf(val) === i
+ })
+
+ let bip32Paths = uniquePaths.map((path) => {
+ return bippath.fromString(path, false)
+ })
+
+ return bip32Paths
+ }
+
+ getChangeBipPath(
+ unsignedTx: UnsignedTx,
+ chainId: ChainIdType
+ ) {
+ if (chainId === 'C') {
+ return null
+ }
+
+ let tx = unsignedTx.getTransaction()
+ let txType = tx.getTxType()
+
+ const chainChangePath = this.getChangePath(chainId).split('m/')[1]
+ let changeIdx = this.getChangeIndex(chainId)
+ // If change and destination paths are the same
+ // it can cause secux to not display the destination amt.
+ // Since platform helper does not have internal/external
+ // path for change (it uses the next address)
+ // there can be an address collisions.
+ if (
+ (txType === PlatformVMConstants.IMPORTTX || txType === PlatformVMConstants.EXPORTTX) &&
+ this.platformHelper.hdIndex === this.externalHelper.hdIndex
+ ) {
+ return null
+ } else if (
+ txType === PlatformVMConstants.ADDVALIDATORTX ||
+ txType === PlatformVMConstants.ADDDELEGATORTX
+ ) {
+ changeIdx = this.platformHelper.getFirstAvailableIndex()
+ }
+
+ return bippath.fromString(`${AVA_ACCOUNT_PATH}/${chainChangePath}/${changeIdx}`)
+ }
+
+ getCredentials(
+ unsignedTx: UnsignedTx,
+ paths: string[],
+ sigMap: any,
+ chainId: ChainIdType
+ ): Credential[] {
+ let creds: Credential[] = []
+ let tx = unsignedTx.getTransaction()
+ let txType = tx.getTxType()
+
+ // @ts-ignore
+ let ins = tx.getIns ? tx.getIns() : []
+ let operations: TransferableOperation[] = []
+ let evmInputs: EVMInput[] = []
+
+ let items = ins
+ if (
+ (txType === AVMConstants.IMPORTTX && chainId === 'X') ||
+ (txType === PlatformVMConstants.IMPORTTX && chainId === 'P') ||
+ (txType === EVMConstants.IMPORTTX && chainId === 'C')
+ ) {
+ items = ((tx as AVMImportTx) || PlatformImportTx || EVMImportTx).getImportInputs()
+ }
+
+ // Try to get operations, it will fail if there are none, ignore and continue
+ try {
+ operations = (tx as OperationTx).getOperations()
+ } catch (e) {
+ console.error(e)
+ }
+
+ let CredentialClass
+ if (chainId === 'X') {
+ CredentialClass = AVMSelectCredentialClass
+ } else if (chainId === 'P') {
+ CredentialClass = PlatformSelectCredentialClass
+ } else {
+ CredentialClass = EVMSelectCredentialClass
+ }
+
+ // Try to get evm inputs, it will fail if there are none, ignore and continue
+ try {
+ evmInputs = (tx as EVMExportTx).getInputs()
+ } catch (e) {
+ console.error(e)
+ }
+
+ for (let i = 0; i < items.length; i++) {
+ const sigidxs: SigIdx[] = items[i].getInput().getSigIdxs()
+ const cred: Credential = CredentialClass(items[i].getInput().getCredentialID())
+
+ for (let j = 0; j < sigidxs.length; j++) {
+ let pathIndex = i + j
+ let pathStr = paths[pathIndex]
+
+ let sigRaw = sigMap.get(pathStr)
+ let sigBuff = Buffer.from(sigRaw)
+ const sig: Signature = new Signature()
+ sig.fromBuffer(sigBuff)
+ cred.addSignature(sig)
+ }
+ creds.push(cred)
+ }
+
+ for (let i = 0; i < operations.length; i++) {
+ let op = operations[i].getOperation()
+ const sigidxs: SigIdx[] = op.getSigIdxs()
+ const cred: Credential = CredentialClass(op.getCredentialID())
+
+ for (let j = 0; j < sigidxs.length; j++) {
+ let pathIndex = items.length + i + j
+ let pathStr = paths[pathIndex]
+
+ let sigRaw = sigMap.get(pathStr)
+ let sigBuff = Buffer.from(sigRaw)
+ const sig: Signature = new Signature()
+ sig.fromBuffer(sigBuff)
+ cred.addSignature(sig)
+ }
+ creds.push(cred)
+ }
+
+ for (let i = 0; i < evmInputs.length; i++) {
+ let evmInput = evmInputs[i]
+ const sigidxs: SigIdx[] = evmInput.getSigIdxs()
+ const cred: Credential = CredentialClass(evmInput.getCredentialID())
+
+ for (let j = 0; j < sigidxs.length; j++) {
+ let pathIndex = items.length + i + j
+ let pathStr = paths[pathIndex]
+
+ let sigRaw = sigMap.get(pathStr)
+ let sigBuff = Buffer.from(sigRaw)
+ const sig: Signature = new Signature()
+ sig.fromBuffer(sigBuff)
+ cred.addSignature(sig)
+ }
+ creds.push(cred)
+ }
+
+ return creds
+ }
+
+ // Used for non parsable transactions.
+ // Ideally we wont use this function at all, but secux is not ready yet.
+ async signTransactionHash<
+ UnsignedTx extends AVMUnsignedTx | PlatformUnsignedTx | EVMUnsignedTx,
+ SignedTx extends AVMTx | PlatformTx | EvmTx
+ >(unsignedTx: UnsignedTx, paths: string[], chainId: ChainIdType): Promise {
+ let txbuff = unsignedTx.toBuffer()
+ const msg: Buffer = Buffer.from(createHash('sha256').update(txbuff).digest())
+
+ try {
+ store.commit('SecuX/openModal', {
+ title: 'Sign Hash',
+ messages: [],
+ info: msg.toString('hex').toUpperCase(),
+ })
+
+ let bip32Paths = this.pathsToUniqueBipPaths(paths)
+
+ // Sign the msg with secux
+ const accountPathSource = chainId === 'C' ? ETH_ACCOUNT_PATH : AVA_ACCOUNT_PATH
+ const accountPath = bippath.fromString(`${accountPathSource}`)
+ let sigMap = await await this.app.signHash(accountPath, bip32Paths, msg)
+ store.commit('SecuX/closeModal')
+
+ let creds: Credential[] = this.getCredentials(
+ unsignedTx,
+ paths,
+ sigMap,
+ chainId
+ )
+
+ let signedTx
+ switch (chainId) {
+ case 'X':
+ signedTx = new AVMTx(unsignedTx as AVMUnsignedTx, creds)
+ break
+ case 'P':
+ signedTx = new PlatformTx(unsignedTx as PlatformUnsignedTx, creds)
+ break
+ case 'C':
+ signedTx = new EvmTx(unsignedTx as EVMUnsignedTx, creds)
+ break
+ }
+
+ return signedTx as SignedTx
+ } catch (e) {
+ store.commit('SecuX/closeModal')
+ console.error(e)
+ throw e
+ }
+ }
+
+ // Used for signing transactions that are parsable
+ async signTransactionParsable<
+ UnsignedTx extends AVMUnsignedTx | PlatformUnsignedTx | EVMUnsignedTx,
+ SignedTx extends AVMTx | PlatformTx | EvmTx
+ >(unsignedTx: UnsignedTx, paths: string[], chainId: ChainIdType): Promise {
+ let tx = unsignedTx.getTransaction()
+ let txType = tx.getTxType()
+ let parseableTxs = {
+ X: ParseableAvmTxEnum,
+ P: ParseablePlatformEnum,
+ C: ParseableEvmTxEnum,
+ }[chainId]
+
+ let title = `Sign ${parseableTxs[txType]}`
+
+ let bip32Paths = this.pathsToUniqueBipPaths(paths)
+ const accountPathSource = chainId === 'C' ? ETH_ACCOUNT_PATH : AVA_ACCOUNT_PATH
+ let txbuff = unsignedTx.toBuffer()
+ let changePath = this.getChangeBipPath(unsignedTx, chainId)
+ let messages = this.getTransactionMessages(unsignedTx, chainId, changePath)
+
+ try {
+ store.commit('SecuX/openModal', {
+ title: title,
+ messages: messages,
+ info: null,
+ })
+
+ let SecuXSignedTx = await this.app.signTransaction(accountPathSource, paths, txbuff)
+
+ let sigMap = SecuXSignedTx.signatures
+ let creds = this.getCredentials(unsignedTx, paths, sigMap, chainId)
+
+ let signedTx
+ switch (chainId) {
+ case 'X':
+ signedTx = new AVMTx(unsignedTx as AVMUnsignedTx, creds)
+ break
+ case 'P':
+ signedTx = new PlatformTx(unsignedTx as PlatformUnsignedTx, creds)
+ break
+ case 'C':
+ signedTx = new EvmTx(unsignedTx as EVMUnsignedTx, creds)
+ break
+ }
+
+ return signedTx as SignedTx
+ } catch (e) {
+ store.commit('SecuX/closeModal')
+ console.error(e)
+ throw e
+ }
+ }
+
+ getOutputMsgs(
+ unsignedTx: UnsignedTx,
+ chainId: ChainIdType,
+ changePath: null | { toPathArray: () => number[] }
+ ): ISecuXBlockMessage[] {
+ let messages: ISecuXBlockMessage[] = []
+ let hrp = getPreferredHRP(ava.getNetworkID())
+ let tx = unsignedTx.getTransaction()
+ let txType = tx.getTxType()
+
+ // @ts-ignore
+ let outs
+ if (
+ (txType === AVMConstants.EXPORTTX && chainId === 'X') ||
+ (txType === PlatformVMConstants.EXPORTTX && chainId === 'P')
+ ) {
+ outs = (tx as PlatformExportTx).getExportOutputs()
+ } else if (txType === EVMConstants.EXPORTTX && chainId === 'C') {
+ outs = (tx as EVMExportTx).getExportedOutputs()
+ } else {
+ outs = (tx as PlatformExportTx).getOuts()
+ }
+
+ let destinationChain = chainId
+ if (chainId === 'C' && txType === EVMConstants.EXPORTTX) destinationChain = 'X'
+
+ if (destinationChain === 'C') {
+ for (let i = 0; i < outs.length; i++) {
+ // @ts-ignore
+ const value = outs[i].getAddress()
+ const addr = bintools.addressToString(hrp, chainId, value)
+ // @ts-ignore
+ const amt = bnToBig(outs[i].getAmount(), 9)
+
+ messages.push({
+ title: 'Output',
+ value: `${addr} - ${amt.toString()} AVAX`,
+ })
+ }
+ } else {
+ let changeIdx = changePath?.toPathArray()[changePath?.toPathArray().length - 1]
+ let changeAddr = this.getChangeFromIndex(changeIdx, destinationChain)
+
+ for (let i = 0; i < outs.length; i++) {
+ outs[i]
+ .getOutput()
+ .getAddresses()
+ .forEach((value) => {
+ const addr = bintools.addressToString(hrp, chainId, value)
+ // @ts-ignore
+ const amt = bnToBig(outs[i].getOutput().getAmount(), 9)
+
+ if (!changePath || changeAddr !== addr)
+ messages.push({
+ title: 'Output',
+ value: `${addr} - ${amt.toString()} AVAX`,
+ })
+ })
+ }
+ }
+
+ return messages
+ }
+
+ getValidateDelegateMsgs(
+ unsignedTx: UnsignedTx,
+ chainId: ChainIdType
+ ): ISecuXBlockMessage[] {
+ let tx =
+ ((unsignedTx as
+ | AVMUnsignedTx
+ | PlatformUnsignedTx).getTransaction() as AddValidatorTx) || AddDelegatorTx
+ let txType = tx.getTxType()
+ let messages: ISecuXBlockMessage[] = []
+
+ if (
+ (txType === PlatformVMConstants.ADDDELEGATORTX && chainId === 'P') ||
+ (txType === PlatformVMConstants.ADDVALIDATORTX && chainId === 'P')
+ ) {
+ const format = 'YYYY-MM-DD H:mm:ss UTC'
+
+ const nodeID = bintools.cb58Encode(tx.getNodeID())
+ const startTime = moment(tx.getStartTime().toNumber() * 1000)
+ .utc()
+ .format(format)
+
+ const endTime = moment(tx.getEndTime().toNumber() * 1000)
+ .utc()
+ .format(format)
+
+ const stakeAmt = bnToBig(tx.getStakeAmount(), 9)
+
+ const rewardOwners = tx.getRewardOwners()
+ let hrp = ava.getHRP()
+ const rewardAddrs = rewardOwners
+ .getOutput()
+ .getAddresses()
+ .map((addr) => {
+ return bintools.addressToString(hrp, chainId, addr)
+ })
+
+ messages.push({ title: 'NodeID', value: nodeID })
+ messages.push({ title: 'Start Time', value: startTime })
+ messages.push({ title: 'End Time', value: endTime })
+ messages.push({ title: 'Total Stake', value: `${stakeAmt} AVAX` })
+ messages.push({
+ title: 'Stake',
+ value: `${stakeAmt} to ${this.platformHelper.getCurrentAddress()}`,
+ })
+ messages.push({
+ title: 'Reward to',
+ value: `${rewardAddrs.join('\n')}`,
+ })
+ // @ts-ignore
+ if (tx.delegationFee) {
+ // @ts-ignore
+ messages.push({ title: 'Delegation Fee', value: `${tx.delegationFee}%` })
+ }
+ messages.push({ title: 'Fee', value: '0' })
+ }
+
+ return messages
+ }
+
+ getFeeMsgs(
+ unsignedTx: UnsignedTx,
+ chainId: ChainIdType
+ ): ISecuXBlockMessage[] {
+ let tx = unsignedTx.getTransaction()
+ let txType = tx.getTxType()
+ let messages = []
+
+ if (
+ (txType === AVMConstants.BASETX && chainId === 'X') ||
+ (txType === AVMConstants.EXPORTTX && chainId === 'X') ||
+ (txType === AVMConstants.IMPORTTX && chainId === 'X') ||
+ (txType === PlatformVMConstants.EXPORTTX && chainId === 'P') ||
+ (txType === PlatformVMConstants.IMPORTTX && chainId === 'P') ||
+ (txType === EVMConstants.EXPORTTX && chainId === 'C') ||
+ (txType === EVMConstants.IMPORTTX && chainId === 'C')
+ ) {
+ messages.push({ title: 'Fee', value: `${0.001} AVAX` })
+ }
+
+ return messages
+ }
+
+ // Given the unsigned transaction returns an array of messages that will be displayed on ledgegr window
+ getTransactionMessages(
+ unsignedTx: UnsignedTx,
+ chainId: ChainIdType,
+ changePath: null | { toPathArray: () => number[] }
+ ): ISecuXBlockMessage[] {
+ let messages: ISecuXBlockMessage[] = []
+
+ const outputMessages = this.getOutputMsgs(unsignedTx, chainId, changePath)
+ messages.push(...outputMessages)
+
+ const validateDelegateMessages = this.getValidateDelegateMsgs(
+ unsignedTx as AVMUnsignedTx | PlatformUnsignedTx,
+ chainId
+ )
+ messages.push(...validateDelegateMessages)
+
+ const feeMessages = this.getFeeMsgs(unsignedTx, chainId)
+ messages.push(...feeMessages)
+
+ return messages
+ }
+
+ getEvmTransactionMessages(tx: Transaction): ISecuXBlockMessage[] {
+ let gasPrice = tx.gasPrice
+ let gasLimit = tx.gasLimit
+ let totFee = gasPrice.mul(new BN(gasLimit))
+ let feeNano = Utils.bnToBig(totFee, 9)
+
+ let msgs: ISecuXBlockMessage[] = []
+ try {
+ let test = '0x' + tx.data.toString('hex')
+ let data = abiDecoder.decodeMethod(test)
+
+ let callMsg: ISecuXBlockMessage = {
+ title: 'Contract Call',
+ value: data.name,
+ }
+ let paramMsgs: ISecuXBlockMessage[] = data.params.map((param: any) => {
+ return {
+ title: param.name,
+ value: param.value,
+ }
+ })
+
+ let feeMsg: ISecuXBlockMessage = {
+ title: 'Fee',
+ value: feeNano.toLocaleString() + ' nAVAX',
+ }
+
+ msgs = [callMsg, ...paramMsgs, feeMsg]
+ } catch (e) {
+ console.log(e)
+ }
+ return msgs
+ }
+
+ async signX(unsignedTx: AVMUnsignedTx): Promise {
+ let tx = unsignedTx.getTransaction()
+ let txType = tx.getTxType()
+ let chainId: ChainIdType = 'X'
+
+ let parseableTxs = ParseableAvmTxEnum
+ let { paths, isAvaxOnly } = this.getTransactionPaths(unsignedTx, chainId)
+
+ // If SecuX doesnt support parsing, sign hash
+ let canSecuXParse = this.config.mcuFwVersion >= MIN_MCU_FW_SUPPORT_V
+ let isParsableType = txType in parseableTxs && isAvaxOnly
+
+ let signedTx
+ if (canSecuXParse && isParsableType) {
+ signedTx = await this.signTransactionParsable(
+ unsignedTx,
+ paths,
+ chainId
+ )
+ } else {
+ signedTx = await this.signTransactionHash(
+ unsignedTx,
+ paths,
+ chainId
+ )
+ }
+
+ store.commit('SecuX/closeModal')
+ return signedTx
+ }
+
+ async signP(unsignedTx: PlatformUnsignedTx): Promise {
+ let tx = unsignedTx.getTransaction()
+ let txType = tx.getTxType()
+ let chainId: ChainIdType = 'P'
+ let parseableTxs = ParseablePlatformEnum
+
+ let { paths, isAvaxOnly } = this.getTransactionPaths(
+ unsignedTx,
+ chainId
+ )
+ // If SecuX doesnt support parsing, sign hash
+ let canSecuXParse = this.config.mcuFwVersion >= MIN_MCU_FW_SUPPORT_V
+ let isParsableType = txType in parseableTxs && isAvaxOnly
+
+ // TODO: Remove after SecuX is fixed
+ // If UTXOS contain lockedStakeable funds always use sign hash
+ let txIns = unsignedTx.getTransaction().getIns()
+ for (var i = 0; i < txIns.length; i++) {
+ let typeID = txIns[i].getInput().getTypeID()
+ if (typeID === PlatformVMConstants.STAKEABLELOCKINID) {
+ canSecuXParse = false
+ break
+ }
+ }
+ let signedTx
+ if (canSecuXParse) {
+ signedTx = await this.signTransactionParsable(
+ unsignedTx,
+ paths,
+ chainId
+ )
+ } else {
+ signedTx = await this.signTransactionHash(
+ unsignedTx,
+ paths,
+ chainId
+ )
+ }
+ store.commit('SecuX/closeModal')
+ return signedTx
+ }
+
+ async signC(unsignedTx: EVMUnsignedTx): Promise {
+ // TODO: Might need to upgrade paths array to:
+ // paths = Array(utxoSet.getAllUTXOs().length).fill('0/0'),
+ let tx = unsignedTx.getTransaction()
+ let typeId = tx.getTxType()
+
+ let canSecuXParse = true
+
+ let paths = ['0/0']
+ if (typeId === EVMConstants.EXPORTTX) {
+ let ins = (tx as EVMExportTx).getInputs()
+ paths = ins.map((input) => '0/0')
+ } else if (typeId === EVMConstants.IMPORTTX) {
+ let ins = (tx as EVMImportTx).getImportInputs()
+ paths = ins.map((input) => '0/0')
+ }
+
+ let txSigned
+ if (canSecuXParse) {
+ txSigned = (await this.signTransactionParsable(unsignedTx, paths, 'C')) as EvmTx
+ } else {
+ txSigned = (await this.signTransactionHash(unsignedTx, paths, 'C')) as EvmTx
+ }
+ store.commit('SecuX/closeModal')
+ return txSigned
+ }
+
+ async signEvm(tx: Transaction) {
+ const rawUnsignedTx = rlp.encode([
+ bnToRlp(tx.nonce),
+ bnToRlp(tx.gasPrice),
+ bnToRlp(tx.gasLimit),
+ tx.to !== undefined ? tx.to.buf : Buffer.from([]),
+ bnToRlp(tx.value),
+ tx.data,
+ bnToRlp(new BN(tx.getChainId())),
+ Buffer.from([]),
+ Buffer.from([]),
+ ])
+
+ try {
+ let msgs = this.getEvmTransactionMessages(tx)
+
+ // Open Modal Prompt
+ store.commit('SecuX/openModal', {
+ title: 'Transfer',
+ messages: msgs,
+ info: null,
+ })
+
+ const chainId = await web3.eth.getChainId()
+ const response = await this.eth.signTransaction(
+ this.transport,
+ SECUX_ETH_ACCOUNT_PATH,
+ {
+ chainId: chainId,
+ nonce: tx.nonce.toNumber(),
+ gasPrice: tx.gasPrice.toNumber(),
+ gasLimit: tx.gasLimit.toNumber(),
+ to: tx.to?.toString(),
+ value: bnToHex(tx.value),
+ }
+ )
+ store.commit('SecuX/closeModal')
+
+ const signatureBN = {
+ v: new BN(response.signature.slice(64), 16),
+ r: new BN(response.signature.slice(0, 32), 16),
+ s: new BN(response.signature.slice(32, 64), 16),
+ }
+ const networkId = await web3.eth.net.getId()
+ const chainParams = {
+ common: EthereumjsCommon.forCustomChain(
+ 'mainnet',
+ { networkId, chainId },
+ 'istanbul'
+ ),
+ }
+
+ const signedTx = Transaction.fromTxData(
+ {
+ nonce: tx.nonce,
+ gasPrice: tx.gasPrice,
+ gasLimit: tx.gasLimit,
+ to: tx.to,
+ value: tx.value,
+ data: tx.data,
+ ...signatureBN,
+ },
+ chainParams
+ )
+ return signedTx
+ } catch (e) {
+ store.commit('SecuX/closeModal')
+ console.error(e)
+ throw e
+ }
+ }
+
+ getEvmAddress(): string {
+ return this.ethAddress
+ }
+
+ async getStake(): Promise {
+ this.stakeAmount = await WalletHelper.getStake(this)
+ return this.stakeAmount
+ }
+
+ async getEthBalance() {
+ let bal = await WalletHelper.getEthBalance(this)
+ this.ethBalance = bal
+ return bal
+ }
+
+ async getUTXOs(): Promise {
+ // TODO: Move to shared file
+ this.isFetchUtxos = true
+ // If we are waiting for helpers to initialize delay the call
+ let isInit =
+ this.externalHelper.isInit && this.internalHelper.isInit && this.platformHelper.isInit
+ if (!isInit) {
+ setTimeout(() => {
+ this.getUTXOs()
+ }, 1000)
+ return
+ }
+
+ super.getUTXOs()
+ this.getStake()
+ this.getEthBalance()
+ return
+ }
+
+ getPathFromAddress(address: string) {
+ let externalAddrs = this.externalHelper.getExtendedAddresses()
+ let internalAddrs = this.internalHelper.getExtendedAddresses()
+ let platformAddrs = this.platformHelper.getExtendedAddresses()
+
+ let extIndex = externalAddrs.indexOf(address)
+ let intIndex = internalAddrs.indexOf(address)
+ let platformIndex = platformAddrs.indexOf(address)
+
+ if (extIndex >= 0) {
+ return `0/${extIndex}`
+ } else if (intIndex >= 0) {
+ return `1/${intIndex}`
+ } else if (platformIndex >= 0) {
+ return `0/${platformIndex}`
+ } else if (address[0] === 'C') {
+ return '0/0'
+ } else {
+ throw 'Unable to find source address.'
+ }
+ }
+
+ async issueBatchTx(
+ orders: (ITransaction | AVMUTXO)[],
+ addr: string,
+ memo: Buffer | undefined
+ ): Promise {
+ return await WalletHelper.issueBatchTx(this, orders, addr, memo)
+ }
+
+ async delegate(
+ nodeID: string,
+ amt: BN,
+ start: Date,
+ end: Date,
+ rewardAddress?: string,
+ utxos?: PlatformUTXO[]
+ ): Promise {
+ return await WalletHelper.delegate(this, nodeID, amt, start, end, rewardAddress, utxos)
+ }
+
+ async validate(
+ nodeID: string,
+ amt: BN,
+ start: Date,
+ end: Date,
+ delegationFee: number,
+ rewardAddress?: string,
+ utxos?: PlatformUTXO[]
+ ): Promise {
+ return await WalletHelper.validate(
+ this,
+ nodeID,
+ amt,
+ start,
+ end,
+ delegationFee,
+ rewardAddress,
+ utxos
+ )
+ }
+
+ async signHashByExternalIndex(index: number, hash: Buffer) {
+ let pathStr = `0/${index}`
+ store.commit('SecuX/openModal', {
+ title: `Sign Hash`,
+ info: hash.toString('hex').toUpperCase(),
+ })
+
+ try {
+ let sigMap = await this.app.signHash(AVA_ACCOUNT_PATH, [pathStr], hash)
+ store.commit('SecuX/closeModal')
+ let signed = sigMap.signatures.get(pathStr)
+ return bintools.cb58Encode(signed)
+ } catch (e) {
+ store.commit('SecuX/closeModal')
+ throw e
+ }
+ }
+
+ async createNftFamily(name: string, symbol: string, groupNum: number) {
+ return await WalletHelper.createNftFamily(this, name, symbol, groupNum)
+ }
+
+ async mintNft(mintUtxo: AVMUTXO, payload: PayloadBase, quantity: number) {
+ return await WalletHelper.mintNft(this, mintUtxo, payload, quantity)
+ }
+
+ async sendEth(to: string, amount: BN, gasPrice: BN, gasLimit: number) {
+ return await WalletHelper.sendEth(this, to, amount, gasPrice, gasLimit)
+ }
+
+ async estimateGas(to: string, amount: BN, token: Erc20Token): Promise {
+ return await WalletHelper.estimateGas(this, to, amount, token)
+ }
+
+ async sendERC20(
+ to: string,
+ amount: BN,
+ gasPrice: BN,
+ gasLimit: number,
+ token: Erc20Token
+ ): Promise {
+ // throw 'Not Implemented'
+ return await WalletHelper.sendErc20(this, to, amount, gasPrice, gasLimit, token)
+ }
+}
+
+export { SecuXWallet }
diff --git a/src/js/wallets/types.ts b/src/js/wallets/types.ts
index 2d9e2d361..0560cf2de 100644
--- a/src/js/wallets/types.ts
+++ b/src/js/wallets/types.ts
@@ -30,6 +30,7 @@ import Erc20Token from '@/js/Erc20Token'
import { Transaction } from '@ethereumjs/tx'
import MnemonicWallet from '@/js/wallets/MnemonicWallet'
import { LedgerWallet } from '@/js/wallets/LedgerWallet'
+import { SecuXWallet } from '@/js/wallets/SecuXWallet'
import { SingletonWallet } from '@/js/wallets/SingletonWallet'
import { ExportChainsC, ExportChainsP, ExportChainsX } from '@avalabs/avalanche-wallet-sdk'
import { UTXOSet as EVMUTXOSet } from 'avalanche/dist/apis/evm/utxos'
@@ -42,8 +43,8 @@ export type ChainAlias = 'X' | 'P'
export type AvmImportChainType = 'P' | 'C'
export type AvmExportChainType = 'P' | 'C'
-export type WalletNameType = 'mnemonic' | 'ledger' | 'singleton'
-export type WalletType = MnemonicWallet | LedgerWallet | SingletonWallet
+export type WalletNameType = 'mnemonic' | 'ledger' | 'singleton' | 'SecuX'
+export type WalletType = MnemonicWallet | LedgerWallet | SingletonWallet | SecuXWallet
interface IAddressManager {
getCurrentAddressAvm(): string
diff --git a/src/store/index.ts b/src/store/index.ts
index eaf834cde..c819968a9 100644
--- a/src/store/index.ts
+++ b/src/store/index.ts
@@ -7,6 +7,7 @@ import Notifications from './modules/notifications/notifications'
import History from './modules/history/history'
import Platform from './modules/platform/platform'
import Ledger from './modules/ledger/ledger'
+import SecuX from './modules/secux/secux'
import Accounts from './modules/accounts/accounts'
import {
@@ -34,6 +35,7 @@ import {
readKeyFile,
} from '@/js/Keystore'
import { LedgerWallet } from '@/js/wallets/LedgerWallet'
+import { SecuXWallet } from '@/js/wallets/SecuXWallet'
import { SingletonWallet } from '@/js/wallets/SingletonWallet'
import { Buffer } from 'avalanche'
import { privateToAddress } from 'ethereumjs-util'
@@ -48,6 +50,7 @@ export default new Vuex.Store({
History,
Platform,
Ledger,
+ SecuX,
Accounts,
},
state: {
@@ -128,6 +131,14 @@ export default new Vuex.Store({
dispatch('onAccess')
},
+ async accessWalletSecuX({ state, dispatch }, wallet: SecuXWallet) {
+ state.wallets = [wallet]
+
+ await dispatch('activateWallet', wallet)
+
+ dispatch('onAccess')
+ },
+
async accessWalletSingleton({ state, dispatch }, key: string) {
let wallet = await dispatch('addWalletSingleton', key)
await dispatch('activateWallet', wallet)
diff --git a/src/store/modules/secux/secux.ts b/src/store/modules/secux/secux.ts
new file mode 100644
index 000000000..b93086023
--- /dev/null
+++ b/src/store/modules/secux/secux.ts
@@ -0,0 +1,38 @@
+import { Module } from 'vuex'
+import { RootState } from '@/store/types'
+import { SecuXState } from '@/store/modules/secux/types'
+
+const secux_module: Module = {
+ namespaced: true,
+ state: {
+ isBlock: false, // if true a modal blocks the window
+ isPrompt: false,
+ isUpgradeRequired: false,
+ isWalletLoading: false,
+ messages: [],
+ title: 'title',
+ info: `info'`,
+ },
+ mutations: {
+ openModal(state, input) {
+ state.title = input.title
+ state.info = input.info
+ state.messages = input.messages
+ state.isPrompt = input.isPrompt
+ state.isBlock = true
+ },
+ closeModal(state) {
+ state.messages = []
+ state.isBlock = false
+ },
+ setIsUpgradeRequired(state, val) {
+ state.isUpgradeRequired = val
+ },
+ setIsWalletLoading(state, val) {
+ state.isWalletLoading = val
+ },
+ },
+ actions: {},
+}
+
+export default secux_module
diff --git a/src/store/modules/secux/types.ts b/src/store/modules/secux/types.ts
new file mode 100644
index 000000000..0e2ab46b3
--- /dev/null
+++ b/src/store/modules/secux/types.ts
@@ -0,0 +1,16 @@
+export interface ISecuXBlockMessage {
+ title: string
+ value: string
+}
+
+export interface SecuXState {
+ isBlock: boolean
+ isPrompt: boolean
+ isUpgradeRequired: boolean
+ isWalletLoading: boolean
+ messages: ISecuXBlockMessage[]
+ title: string
+ info: string
+}
+
+export const LEDGER_EXCHANGE_TIMEOUT = 90_000
diff --git a/src/store/types.ts b/src/store/types.ts
index b047a95eb..473af0e9a 100644
--- a/src/store/types.ts
+++ b/src/store/types.ts
@@ -27,6 +27,13 @@ export interface ILedgerAppConfig {
name: 'Avalanche'
}
+export interface ISecuXConfig {
+ transportVersion: number;
+ seFwVersion: string;
+ mcuFwVersion: string;
+ bootloaderVersion: string;
+}
+
export interface priceDict {
usd: number
}
diff --git a/src/views/access/Menu.vue b/src/views/access/Menu.vue
index d414bf994..4f6ed546b 100644
--- a/src/views/access/Menu.vue
+++ b/src/views/access/Menu.vue
@@ -27,6 +27,7 @@
>
+
@@ -39,6 +40,8 @@