opennft-clientOpenNFT 瀏覽器插件
opennft-client 需要谷歌瀏覽器或 Chromium 內(nèi)核
前期準(zhǔn)備
注冊(cè)百度賬號(hào)以及獲取私鑰
充值百度開(kāi)放網(wǎng)絡(luò)
Tip:用戶(hù)地址下需要有百度開(kāi)放網(wǎng)絡(luò)余額才能使用轉(zhuǎn)移資產(chǎn),查詢(xún)余額等功能。建議在百度開(kāi)放網(wǎng)絡(luò)充值0.1元。充值鏈接:https://xuper.baidu.com/n/console#/finance/wallet/recharge
插件使用
使用幫助二維碼
插件安裝
插件已放置根目錄下()
國(guó)內(nèi)加速下載 https://gitee.com/shengjian-tech/opennft-client/raw/master/MakerONE.zip
1,瀏覽器選擇管理擴(kuò)展程序
2,首先打開(kāi)開(kāi)發(fā)者模式,然后解壓下載的壓縮包并選擇加載,此時(shí)您可以看到瀏覽器已經(jīng)安裝好該插件了
3,您可以選擇插件常駐
插件登錄
下載私鑰到本地之后,打開(kāi)瀏覽器插件進(jìn)入登錄頁(yè),選擇本地私鑰,輸入安全碼登錄:
用戶(hù)首頁(yè)
登錄之后跳轉(zhuǎn)到首頁(yè),顯示目前處于百度開(kāi)放網(wǎng)絡(luò),用戶(hù)百度開(kāi)放網(wǎng)絡(luò)地址,余額。以及功能等。
查詢(xún)資產(chǎn)余額:
轉(zhuǎn)移資產(chǎn):
查詢(xún)交易:
整體設(shè)計(jì):
登錄頁(yè)
- 本地私鑰路徑:需要用戶(hù)選擇本地私鑰存放路徑,讀取私鑰內(nèi)容。
- 安全碼(可選)(6 位數(shù)):根據(jù)超級(jí)鏈生成賬戶(hù)的方式可選。開(kāi)放網(wǎng)絡(luò)必填。
js 代碼:
轉(zhuǎn)換開(kāi)放網(wǎng)絡(luò)地址為 EVM 地址的工具類(lèi) addressUtils.js
import base58 from 'bs58' import { sha256 } from 'js-sha256' export function XchainAddrToEvm(addr) { var result = '' try { // 判斷是否是合約賬號(hào);判斷合約賬戶(hù)地址僅支持 XC11111111111@xuper, xuper后綴,go-sdk有相同問(wèn)題。 if (determineContractAccount(addr)) { result = contractAccountToEVMAddress(addr) } else if (determineContractName(addr)) { result = contractNameToEVMAddress(addr) } else { result = xchainAKToEVMAddress(addr) } return result } catch (err) { console.log(err) } } // 判斷合約賬戶(hù)地址僅支持 XC11111111111@xuper, xuper后綴,go-sdk有相同問(wèn)題。 function determineContractAccount(xchainAddr) { if (isAccount(xchainAddr) != 1) { return false } return xchainAddr.indexOf('@xuper') != -1 } const accountPrefix = 'XC' function isAccount(name) { if (name == '') { return -1 } if (name.indexOf(accountPrefix) != 0) { return 0 } var prefix = name.split('@')[0] prefix = prefix.substr(accountPrefix.length) if (!validRawAccount(prefix)) { return 0 } return 1 } const accountSize = 16 function validRawAccount(accountName) { if (accountName == '') { return false } if (accountName.length != accountSize) { return false } for (var i = 0; i < accountSize; i++) { if (accountName[i] >= '0' && accountName[i] <= '9') { continue } else { return false } } return true } const contractAccountPrefixs = '1112' const Word160Length = 20 function contractAccountToEVMAddress(contractAccount) { var contractAccountValid = contractAccount.slice(2, 18) var str = contractAccountPrefixs.concat(contractAccountValid) if (str.length != Word160Length) { throw new Error('slice passed as address shou have 20 byte length') } return Buffer.from(str).toString('hex').toUpperCase() } const contractNameMaxSize = 16 const contractNameMinSize = 4 const contractNameRegex = /^[a-zA-Z_]{1}[0-9a-zA-Z_.]+[0-9a-zA-Z_]$/ function determineContractName(xchainAddr) { var contractSize = xchainAddr.length if ( contractSize > contractNameMaxSize || contractSize < contractNameMinSize ) { return false } if (!contractNameRegex.test(xchainAddr)) { return false } return true } const evmAddressFiller = '-' const contractNamePrefixs = '1111' function contractNameToEVMAddress(contractName) { var contractNameLength = contractName.length var prefixStr = '' for (var i = 0; i < Word160Length - contractNameLength - 4; i++) { prefixStr += evmAddressFiller } contractName = prefixStr + contractName contractName = contractNamePrefixs + contractName if (contractName.length != Word160Length) { throw new Error('slice passed as address shou have 20 byte length') } return Buffer.from(contractName).toString('hex').toUpperCase() } function xchainAKToEVMAddress(xchainAddr) { var rawAddr = base58.decode(xchainAddr) if (rawAddr.length < 21) { throw new Error('bad address') } rawAddr = rawAddr.slice(1, 21) return Buffer.from(rawAddr, '').toString('hex').toUpperCase() } export function EvmToXchainAddr(addr) { // return addr, addrType, nil var result = '' try { var bs = Buffer.from(addr, 'hex').toString('ascii') if (bs.length != Word160Length) { throw new Error('slice passed as address shou have 20 byte length') } var evmAddrStrWithPrefix = bs // 合約賬號(hào) if (evmAddrStrWithPrefix.slice(0, 4) == contractAccountPrefixs) { result = evmAddressToContractAccount(bs) } else if (evmAddrStrWithPrefix.slice(0, 4) == contractNamePrefixs) { result = evmAddressToContractName(bs) } else { var buffer = Buffer.from(addr, 'hex') result = evmAddressToXchain(buffer) } return result } catch (err) { console.log(err) } } function evmAddressToContractAccount(addr) { return accountPrefix + addr.slice(4) + '@xuper' } function evmAddressToContractName(addr) { var index = addr.lastIndexOf(evmAddressFiller) return addr.slice(index + 1) } function evmAddressToXchain(addr) { var addTyepe = [] var addrArray = new Uint8Array(addr) addTyepe.push(1) for (var i = 0; i < addrArray.length; i++) { addTyepe.push(addrArray[i]) } var checkCode = DoubleSha256(addTyepe) var simpleCheckCode = checkCode.slice(0, 4) for (var i = 0; i < simpleCheckCode.length; i++) { addTyepe.push(simpleCheckCode[i]) } return base58.encode(addTyepe) } // DoubleSha256 執(zhí)行2次SHA256,這是為了防止SHA256算法被攻破。 function DoubleSha256(data) { return UsingSha256(UsingSha256(data)) } // UsingSha256 get the hash result of data using SHA256 function UsingSha256(data) { return sha256.array(data) }
// 需要引入 XuperSDK 和上面的地址轉(zhuǎn)換工具類(lèi) import XuperSDK, { Endorsement } from '@xuperchain/xuper-sdk' import { XchainAddrToEvm } from './addressUtils' // TODO 需要通過(guò)用戶(hù)選擇的私鑰路徑,讀取出來(lái)私鑰的內(nèi)容 const private = '' // 安全碼 const password = '' // 登錄后 生成 賬戶(hù)對(duì)象 const acc = xsdk.import(password, private)
首頁(yè)
-
默認(rèn)首頁(yè)用戶(hù)處于開(kāi)放網(wǎng)絡(luò)。 開(kāi)放網(wǎng)絡(luò)為下拉框,默認(rèn)一個(gè)位開(kāi)放網(wǎng)絡(luò)。用戶(hù)可以添加網(wǎng)絡(luò)。添加網(wǎng)絡(luò)可以彈框,需要參數(shù):網(wǎng)絡(luò)名(用戶(hù)自己輸入就行),節(jié)點(diǎn) IP(例如:39.156.69.83:37100),鏈名:(例如 xuperchain), Vue 里面開(kāi)放網(wǎng)絡(luò)的 node 需要設(shè)置為https://xuper.baidu.com/nodeapi
// 默認(rèn)就有的開(kāi)放網(wǎng)絡(luò),默認(rèn)開(kāi)放網(wǎng)絡(luò)的鏈名 const node = '39.156.69.83:37100' // vue里面設(shè)置為 https://xuper.baidu.com/nodeapi const chain = 'xuper' // 連接開(kāi)放網(wǎng)絡(luò)時(shí)需要加載背書(shū)服務(wù), const params = { server: '39.156.69.83:37100', // ip, port // vue里面設(shè)置為 https://xuper.baidu.com/nodeapi fee: '400', // fee endorseServiceCheckAddr: 'jknGxa6eyum1JrATWvSJKW3thJ9GKHA9n', // sign address endorseServiceFeeAddr: 'aB2hpHnTBDxko3UoP2BpBZRujwhdcAFoT', // fee address } // 默認(rèn)的開(kāi)放網(wǎng)絡(luò) SDK client, 后續(xù)查詢(xún)余額,調(diào)用合約使用。 const xsdk = new XuperSDK({ node, chain, plugins: [ Endorsement({ transfer: params, makeTransaction: params, }), ], }) // 用戶(hù)新增網(wǎng)絡(luò)可以生成新的sdk client并記錄,切換網(wǎng)絡(luò)即切換SDK,新增網(wǎng)絡(luò)暫時(shí)不考慮背書(shū)插件,僅傳入IP和鏈名即可 const xsdk = new XuperSDK({ node, chain, })
-
顯示用戶(hù) Address
// 用戶(hù)登錄時(shí)生成的acc, acc.address即為用的address地址 const addr = acc.address
-
顯示用戶(hù)余額
// 查詢(xún)登錄用戶(hù)余額 // 用戶(hù)超級(jí)鏈賬戶(hù)余額 address 直接傳入上面 acc.address即可。 const balance = async (adress) => { try { const result = await xsdk.getBalance(adress) debug(result.bcs[0].balance) } catch (err) { throw err } }
-
默認(rèn)提供三個(gè) Action 功能,轉(zhuǎn)移 NFT。查詢(xún) NFT 數(shù)量,查詢(xún)交易。并提供新增 Action 操作
-
點(diǎn)擊功能,跳轉(zhuǎn)到功能頁(yè)。最上面為下拉框,用戶(hù)可以選擇功能,默認(rèn)為上述三個(gè),并且有一個(gè)新增操作。
-
默認(rèn)三個(gè)功能 轉(zhuǎn)移,查詢(xún),查詢(xún)交易
// 默認(rèn)合約名 const contractName = 'opennft' // 默認(rèn)方法名 const methodName = 'safeTransferFrom' // 默認(rèn)參數(shù) // nft 轉(zhuǎn)移發(fā)起者,默認(rèn)取值 acc.address,acc.address需要轉(zhuǎn)換,下述為轉(zhuǎn)換好的。 // acc.address 格式為 ULuqhymLPGidfihUb683i2TH4qtaqZ2Dz 需要用工具類(lèi)轉(zhuǎn)換為evm的地址2BEF68690AE24553824BA37C003C2B9067665F81 const from = XchainAddrToEvm(acc.address) // 下述三個(gè)參數(shù),需要用戶(hù)在頁(yè)面輸入,提供三個(gè)輸入框即可。 // nft 轉(zhuǎn)移接受者 前端輸入 例如 cRsoDDnDX1NjhzJtNKLS3GHudBsgyRouQ 也需要轉(zhuǎn)換 const to = XchainAddrToEvm('cRsoDDnDX1NjhzJtNKLS3GHudBsgyRouQ') // 轉(zhuǎn)移的 nft token id const nftTokenID = '6' // 轉(zhuǎn)移數(shù)量 const amount = '1' // 轉(zhuǎn)移NFT 轉(zhuǎn)移操作前先查詢(xún)一下賬戶(hù)余額。是0的話(huà)讓他去充值。建議最少充值一元。 // 咱們默認(rèn)提供的NFT 轉(zhuǎn)移,只需要轉(zhuǎn)移著的地址,tokenid,數(shù)量。給用戶(hù)返回交易ID const TransferNFTEvm = async (toAddr, TokenID, Amount) => { try { const contractName = contractName const methodName = methodName const demo = await xsdk.invokeSolidityContarct( contractName, methodName, 'evm', { from: from, to: to, id: nftTokenID, amount: amount, data: '', }, '0', acc ) // 352cd3f829dded7ad1da7ab3a0c3a8776cd3ec545c617ad499abb2d29459c6ee // 交易ID 返回給用戶(hù) debug(xsdk.transactionIdToHex(demo.transaction.txid)) const result = await xsdk.postTransaction(demo.transaction, acc) // TODO 調(diào)用轉(zhuǎn)移操作成功后 // TODO 調(diào)用坤那邊的服務(wù)端接口解析交易 只傳輸txid,調(diào)用接口即可,后續(xù)不管。最后給用戶(hù)返回上面的交易ID就行。 debug(result) // err 是空 證明轉(zhuǎn)移成功,不是 就是執(zhí)行失敗。 } catch (err) { console.log(err) } }
-
查詢(xún)自己的 NFT 余額
// 查詢(xún) NFT 余額 前端用戶(hù)選擇查詢(xún)資產(chǎn)余額,需要輸入NFT token ID 就行,然后返回余額數(shù)量就OK。 const queryNFTBalance = async (tokenID) => { try { const contractName = 'opennft' const methodName = 'balanceOf' const args = { account: XchainAddrToEvm(acc.address), id: tokenID, } const demo = await xsdk.invokeSolidityContarct( contractName, methodName, 'evm', args, '0', acc ) // 判斷 demo.preExecutionTransaction.response.responses的長(zhǎng)度是否大于0, 大于0 取demo.preExecutionTransaction.response.responses[length - 1] const len = demo.preExecutionTransaction.response.responses.length if (len > 0) { const str = demo.preExecutionTransaction.response.responses[len - 1].body const result = Buffer.from(str, 'base64').toString('ascii') // [{\"0\":\"10\"}] result 即為 [{\"0\":\"10\"}] 10即為想要的結(jié)果,即對(duì)應(yīng)nft 的余額 debug(result) } } catch (err) { console.log(err) } }
-
查詢(xún)交易信息
// 查詢(xún)交易 前端用戶(hù)選擇查詢(xún)交易,讓用戶(hù)輸入交易ID,即可,返回交易信息。目前交易信息很少 // 查詢(xún)交易 const GetTxDetail = async (txID) => { try { const demo = await xsdk.queryTransaction( Buffer.from(txID, 'hex').toString('base64') ) if (demo.tx == undefined) { // 證明此交易鏈上沒(méi)有 直接報(bào)錯(cuò) throw new Error('this tx undefined') } // 交易ID var txID = Buffer.from(demo.tx.txid, 'base64').toString('hex') var txReqJson = JSON.parse( Buffer.from( demo.tx.contract_requests[1].args.input, 'base64' ).toString() ) var from = '' var to = '' var tokenID = '' var amount = '' if (demo.tx.contract_requests[1].method_name == 'safeTransferFrom') { from = EvmToXchainAddr(txReqJson.from) to = EvmToXchainAddr(txReqJson.to) tokenID = txReqJson.id amount = txReqJson.amount } else { from = demo.tx.initiator tokenID = txReqJson._id amount = txReqJson._initialSupply } // 根據(jù) tokenID 查詢(xún)token id的圖片路徑 供瀏覽器跳轉(zhuǎn) const contractName = 'opennft' const methodName = 'getTokenBytes' const args = { _id: tokenID, } const res = await xsdk.invokeSolidityContarct( contractName, methodName, 'evm', args, '0', acc ) const len = res.preExecutionTransaction.response.responses.length if (len > 0) { var result = res.preExecutionTransaction.response.responses[len - 1].body var response = JSON.parse(Buffer.from(result, 'base64').toString()) var base64Addr = response[0]._response var data = Buffer.from(base64Addr, 'base64').toString() var dataJson = JSON.parse(data) } var timestamp = parseInt(demo.tx.timestamp / 1000) // 用戶(hù)查看交易詳情,前端顯示下述txDetail信息。 var txDetail = { txID: txID, from: from, to: to, id: tokenID, amount: amount, timestamp: timestamp, } var nftDetail = { link: dataJson.link, name: dataJson.name, hash: dataJson.hash, } // 前端展示數(shù)據(jù) console.log(txDetail) console.log(nftDetail) return txDetail, nftDetail } catch (err) { console.log(err) } }
-
用戶(hù)新增 action 操作。不用再首頁(yè)展示,只在第三張圖的下拉框顯示即可。用戶(hù)新增功能需要傳入?yún)?shù)較多(TODO)
// 下述,所有參數(shù)可設(shè)置默認(rèn)值。用戶(hù)新增功能時(shí),用戶(hù)可以設(shè)置是否用戶(hù)輸入。,前端不顯示,不設(shè)置即前端需要輸入。 // 需要傳入?yún)?shù) const contractName = '合約名' const method = '方法名' // 調(diào)用方法的參數(shù)。(前端可選多個(gè)) const from =''; const to = ''; ...
-
切換賬號(hào)功能用戶(hù)點(diǎn)擊頭像,可以新增賬戶(hù),即新增一個(gè) acc 對(duì)象,切換賬戶(hù),即切換 acc 對(duì)象。新增賬戶(hù)也需要指定私鑰,安全碼(非必須)。(TODO)
// TODO 需要通過(guò)用戶(hù)選擇的私鑰路徑,讀取出來(lái)私鑰的內(nèi)容 const private = '' // 安全碼 const password = '' // 登錄后 生成 賬戶(hù)對(duì)象 const acc = xsdk.import(password, private)
