完善上链流程

This commit is contained in:
CounterFire2023 2024-06-26 14:11:12 +08:00
parent 577cb37723
commit 18c398ce4d
10 changed files with 234 additions and 36 deletions

View File

@ -4,10 +4,12 @@ import { OkxWallet } from '@/components/chain/wallet/OkxWallet';
import {walletStore} from "@/store/wallet"; import {walletStore} from "@/store/wallet";
import WalletSelectModel from "@/components/chain/WalletSelectModel.vue"; import WalletSelectModel from "@/components/chain/WalletSelectModel.vue";
import {createModal} from "@/utils/model.util"; import {createModal} from "@/utils/model.util";
import {isTokenExpired, genRefreshToken, cfgChainId, switchEthereumChain} from "@/components/chain/utils"
import {ImtblMarket} from "@/components/chain/Market"; import {ImtblMarket} from "@/components/chain/Market";
import { ALL_PROVIDERS } from "@/configs/configchain"; import { ALL_PROVIDERS } from "@/configs/configchain";
import {Locker} from "@/components/chain/contract/Locker"; import {Locker} from "@/components/chain/contract/Locker";
export const allProviders = { export const allProviders = {
1: MetaMaskWallet, 1: MetaMaskWallet,
2: OkxWallet, 2: OkxWallet,
@ -43,16 +45,26 @@ export class BlockChain {
new PassportWallet(); new PassportWallet();
} }
async updateInfo({provider, accounts, token}) { async updateInfo({provider, accounts}) {
this.provider = provider this.web3Provider = provider
if (!token && !this.store.token) { if (!this.store.token) {
token = await this.wallet.getAccessToken(); const {token, refreshToken}= await this.wallet.getAccessToken();
} else if (!token && this.store.token){ this.store.token = token
token = this.store.token; this.store.refreshToken = refreshToken
} else {
if (isTokenExpired(3600, this.store.token)) {
if (this.store.refreshToken && !isTokenExpired(300, this.store.refreshToken)) {
const {token, refreshToken} = await genRefreshToken(this.store.refreshToken);
this.store.token = token;
this.store.refreshToken = refreshToken;
} else {
const {token, refreshToken}= await this.wallet.getAccessToken();
this.store.token = token
this.store.refreshToken = refreshToken
}
}
} }
this.store.address = accounts[0]; this.store.address = accounts[0];
this.token = token
this.store.token = token;
this.store.$persist(); this.store.$persist();
this.market.updateProvider(provider); this.market.updateProvider(provider);
return provider; return provider;
@ -60,8 +72,8 @@ export class BlockChain {
async restoreWallet(walletType) { async restoreWallet(walletType) {
this.wallet = new allProviders[walletType](); this.wallet = new allProviders[walletType]();
let { provider, accounts, token } = await this.wallet.web3Provider(); const { provider, accounts } = await this.wallet.web3Provider();
await this.updateInfo({provider, accounts, token}) await this.updateInfo({provider, accounts })
return provider; return provider;
} }
@ -71,8 +83,8 @@ export class BlockChain {
const walletType = ALL_PROVIDERS[0].id const walletType = ALL_PROVIDERS[0].id
this.wallet = new allProviders[walletType](); this.wallet = new allProviders[walletType]();
this.store.walletType = walletType; this.store.walletType = walletType;
const { provider, accounts, token } = await this.wallet.web3Provider(); const { provider, accounts } = await this.wallet.web3Provider();
await this.updateInfo({ provider, accounts, token }) await this.updateInfo({ provider, accounts })
return provider; return provider;
} }
const rewardModal = createModal(WalletSelectModel, {}); const rewardModal = createModal(WalletSelectModel, {});
@ -88,10 +100,30 @@ export class BlockChain {
} }
} }
get token() {
const suffix = (this.store.walletType == 2 || this.store.walletType == 1) ? '.cf' : ''
return this.store.token+suffix
}
async logout() { async logout() {
this.token = '';
this.store.reset(); this.store.reset();
this.store.$persist(); this.store.$persist();
await this.wallet.logout(); await this.wallet.logout();
} }
async getChainId() {
return this.wallet.getChainId();
}
/**
* 检查并切换到目标链, 各上链前须调用该方法
*/
async checkAndChangeChain() {
let chainId = await this.getChainId();
if (chainId !== cfgChainId) {
console.log(`current chain: ${chainId}, want: ${cfgChainId}`)
chainId = await switchEthereumChain(this.web3Provider.provider, cfgChainId);
}
}
} }

View File

@ -6,8 +6,9 @@ const NATIVE = 'NATIVE'
const ERC20 = 'ERC20' const ERC20 = 'ERC20'
export class ImtblMarket { export class ImtblMarket {
constructor() { constructor(_chainInstance) {
this.client = new orderbook.Orderbook({ baseConfig }); this.client = new orderbook.Orderbook({ baseConfig });
this.bc = _chainInstance
} }
updateProvider(provider) { updateProvider(provider) {
@ -21,6 +22,7 @@ export class ImtblMarket {
* @returns * @returns
*/ */
async listListings(contractAddress){ async listListings(contractAddress){
await this.bc.checkAndChangeChain();
const listOfListings = await this.client.listListings({ const listOfListings = await this.client.listListings({
sellItemContractAddress: contractAddress, sellItemContractAddress: contractAddress,
status: orderbook.OrderStatusName.ACTIVE, status: orderbook.OrderStatusName.ACTIVE,
@ -114,6 +116,7 @@ export class ImtblMarket {
* @param {string} currencyAmount 出售价格, 单位 wei * @param {string} currencyAmount 出售价格, 单位 wei
*/ */
async beginSellERC721({contractAddress, tokenId, currencyAddress, currencyAmount, orderExpiry}) { async beginSellERC721({contractAddress, tokenId, currencyAddress, currencyAmount, orderExpiry}) {
await this.bc.checkAndChangeChain();
const { preparedListing, orderSignature } = const { preparedListing, orderSignature } =
await this._prepareERC721Listing({contractAddress, tokenId, currencyAddress, currencyAmount, orderExpiry}); await this._prepareERC721Listing({contractAddress, tokenId, currencyAddress, currencyAmount, orderExpiry});
const order = await this._createListing(preparedListing, orderSignature, currencyAmount); const order = await this._createListing(preparedListing, orderSignature, currencyAmount);
@ -125,6 +128,7 @@ export class ImtblMarket {
* @param {*} listingId * @param {*} listingId
*/ */
async beginBuy(listingId) { async beginBuy(listingId) {
await this.bc.checkAndChangeChain();
const fulfiller = await this.signer.getAddress(); const fulfiller = await this.signer.getAddress();
// const fulfiller = marketAddress // const fulfiller = marketAddress
console.log(listingId,fulfiller) console.log(listingId,fulfiller)
@ -149,6 +153,7 @@ export class ImtblMarket {
* @param { string[] } listingIds: listingId列表 * @param { string[] } listingIds: listingId列表
*/ */
async batchBuy(listingIds) { async batchBuy(listingIds) {
await this.bc.checkAndChangeChain();
const fulfiller = await this.signer.getAddress(); const fulfiller = await this.signer.getAddress();
// console.log(listingIds, marketAddress,'---') // console.log(listingIds, marketAddress,'---')
// return // return
@ -211,6 +216,7 @@ export class ImtblMarket {
* @returns * @returns
*/ */
async cancelOrdersOnChain(listingIds) { async cancelOrdersOnChain(listingIds) {
await this.bc.checkAndChangeChain();
const offerer = await this.signer.getAddress(); const offerer = await this.signer.getAddress();
const { cancellationAction } = await this.client.cancelOrdersOnChain( const { cancellationAction } = await this.client.cancelOrdersOnChain(
listingIds, listingIds,

View File

@ -35,10 +35,6 @@ import { computed } from "vue";
import { ALL_PROVIDERS } from "@/configs/configchain"; import { ALL_PROVIDERS } from "@/configs/configchain";
import { allProviders } from "@/components/chain/BlockChain" import { allProviders } from "@/components/chain/BlockChain"
import {walletStore} from "@/store/wallet";
const localWalletStore = walletStore()
const props = defineProps({ const props = defineProps({
visible: Boolean, visible: Boolean,
close: Function, close: Function,

View File

@ -21,18 +21,19 @@ export class Locker {
async lock(nft, tokenIds) { async lock(nft, tokenIds) {
// call single method with abi and address // call single method with abi and address
console.log('lock nft', nft, tokenIds) console.log('lock nft', nft, tokenIds)
const nftContract = new ethers.Contract(nft, erc721Abi, this.bc.provider.getSigner()) await this.bc.checkAndChangeChain();
const nftContract = new ethers.Contract(nft, erc721Abi, this.bc.web3Provider.getSigner())
for (let tokenId of tokenIds) { for (let tokenId of tokenIds) {
const addressApproval = await nftContract.getApproved(tokenId) const addressApproval = await nftContract.getApproved(tokenId)
if ((addressApproval || "").toLowerCase() != lockAddress.toLowerCase()) { if ((addressApproval || "").toLowerCase() != lockAddress.toLowerCase()) {
const resApproval = await nftContract.approve(lockAddress, tokenId); const resApproval = await nftContract.approve(lockAddress, tokenId);
await this.bc.provider.waitForTransaction(resApproval.hash) await this.bc.web3Provider.waitForTransaction(resApproval.hash)
console.debug('approve', resApproval.hash) console.debug('approve', resApproval.hash)
} }
} }
const contract = new ethers.Contract(lockAddress, lockAbi, this.bc.provider.getSigner()) const contract = new ethers.Contract(lockAddress, lockAbi, this.bc.web3Provider.getSigner())
const res = await contract.lock(nft, tokenIds) const res = await contract.lock(nft, tokenIds)
await this.bc.provider.waitForTransaction(res.hash) await this.bc.web3Provider.waitForTransaction(res.hash)
return res.hash return res.hash
} }
} }

View File

@ -3,14 +3,27 @@ export const WALLET_API_HOST_RELEASE = 'https://wallet.cebggame.com';
const apiBase = process.env.NODE_ENV === 'production' ? WALLET_API_HOST_RELEASE : WALLET_API_HOST_TEST; const apiBase = process.env.NODE_ENV === 'production' ? WALLET_API_HOST_RELEASE : WALLET_API_HOST_TEST;
import { SiweMessage } from './common/SiweMessage'; import { SiweMessage } from './common/SiweMessage';
import { ethers } from 'ethers'; import { ethers } from 'ethers';
import assert from 'assert'
import { AllChains } from "@/configs/allchain";
import { Deferred } from '@/utils/promise.util';
const loginWithSignature = async(message, signature) => { export const cfgChainId = parseInt(import.meta.env.VUE_APP_NET_ID);
const url = `${apiBase}/wallet/login/general`;
const data = { assert(cfgChainId, 'VUE_APP_NET_ID not configured');
channel: 13,
code: signature, let chainCfg;
message for (const d of AllChains) {
if (d.id === cfgChainId) {
chainCfg = d;
break;
} }
}
assert(chainCfg, 'chain config not found');
export const currentChainCfg = chainCfg;
const request = async(url, data, method = 'POST') => {
let headers = { let headers = {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'api_version': 2, 'api_version': 2,
@ -18,12 +31,33 @@ const loginWithSignature = async(message, signature) => {
'api_env': process.env.NODE_ENV === 'production' ? 'release' : 'dev' 'api_env': process.env.NODE_ENV === 'production' ? 'release' : 'dev'
}; };
return fetch(url, { return fetch(url, {
method: "POST", method,
body: JSON.stringify(data), body: JSON.stringify(data),
headers headers
}).then(res => res.json()); }).then(res => res.json());
} }
const loginWithSignature = async(message, signature) => {
const url = `${apiBase}/wallet/login/general`;
const data = {
channel: 13,
code: signature,
message,
nb: 1
}
return request(url, data);
}
export const genRefreshToken = async(refreshToken) => {
const url = `${apiBase}/wallet/refresh_token`;
const data = { refreshToken }
const res = await request(url, data);
if (res.errcode) {
throw new Error(res.errmsg);
}
return res.data;
}
const utf8ToHex = (str) => { const utf8ToHex = (str) => {
return '0x' + Buffer.from(str).toString('hex'); return '0x' + Buffer.from(str).toString('hex');
} }
@ -55,5 +89,100 @@ export const signLogin = async (provider, address) => {
if (res.errcode) { if (res.errcode) {
throw new Error(res.errmsg); throw new Error(res.errmsg);
} }
return res.data?.token; return res.data;
}
export function parseTokenData(token) {
if (!token) {
return {};
}
let datas = token.split(".");
if (datas.length < 2) {
return {};
}
try {
return JSON.parse(window.atob(datas[1]));
} catch (err) {
return {};
}
}
/**
* check if token expired
* @param token jwt token string
* @param fixed fixed seconds
* @returns
*/
export function isTokenExpired(fixed, token) {
if (!token) {
return true;
}
let data = parseTokenData(token);
if (!data.exp) {
return true;
}
let now = Date.now() / 1000 | 0;
return data.exp < now - fixed;
}
/**
* number to hex string
* @param {number} chainId
* @return {string}
*/
export function toHexChainId(chainId) {
return '0x' + chainId.toString(16)
}
export const switchEthereumChain = async (provider, targetChainId) => {
const hexChainId = toHexChainId(targetChainId)
const deferred = new Deferred();
const onChainChange = (chainId) => {
const chainIdNum = parseInt(chainId)
console.log('switchEthereumChain: ', chainIdNum)
provider.removeListener('chainChanged', onChainChange)
if (chainIdNum !== targetChainId) {
deferred.reject(new Error('switch chain failed'))
return
}
deferred.resolve(chainIdNum)
}
provider.on('chainChanged', onChainChange)
try {
await provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: hexChainId }]
})
console.log('success send switch chain request')
} catch (e) {
console.log('error switch chain: ', e)
if (e.code === 4902 || e.message.indexOf('Unrecognized chain ID') >= 0) {
try {
const data = chainCfg
await provider.request({
method: 'wallet_addEthereumChain',
params: [
{
chainId: hexChainId,
chainName: data.name,
nativeCurrency: {
name: data.symbol,
symbol: data.symbol,
decimals: data.decimals || 18
},
blockExplorerUrls: [data.explorerurl],
rpcUrls: [data.rpc]
}
]
})
console.log('success send add chain request')
} catch (addError) {
console.error('error add chain: ', addError)
provider.removeListener('chainChanged', onChainChange)
deferred.reject(addError)
}
}
}
return deferred.promise
} }

View File

@ -21,8 +21,8 @@ export class MetaMaskWallet{
async getAccessToken() { async getAccessToken() {
const accounts = await this.nativeProvider.request({ method: "eth_requestAccounts" }); const accounts = await this.nativeProvider.request({ method: "eth_requestAccounts" });
const token = await signLogin(this.nativeProvider, accounts[0]); const { token, refreshToken } = await signLogin(this.nativeProvider, accounts[0]);
return token return { token, refreshToken }
} }
async logout() { async logout() {
@ -35,4 +35,9 @@ export class MetaMaskWallet{
] ]
}); });
} }
async getChainId() {
const chainId = await this.nativeProvider.request({ method: "eth_chainId" });
return parseInt(chainId);
}
} }

View File

@ -21,11 +21,16 @@ export class OkxWallet{
async getAccessToken() { async getAccessToken() {
const accounts = await this.nativeProvider.request({ method: "eth_requestAccounts" }); const accounts = await this.nativeProvider.request({ method: "eth_requestAccounts" });
const token = await signLogin(this.nativeProvider, accounts[0]); const { token, refreshToken } = await signLogin(this.nativeProvider, accounts[0]);
return token return { token, refreshToken }
} }
async logout() { async logout() {
await this.nativeProvider.request({ method: 'wallet_disconnect' }); await this.nativeProvider.request({ method: 'wallet_disconnect' });
} }
async getChainId() {
const chainId = await this.nativeProvider.request({ method: "eth_chainId" });
return parseInt(chainId);
}
} }

View File

@ -1,5 +1,6 @@
import { config, passport, orderbook, checkout } from '@imtbl/sdk'; import { config, passport, orderbook, checkout } from '@imtbl/sdk';
import { providers } from 'ethers'; import { providers } from 'ethers';
import { cfgChainId } from '@/components/chain/utils.js';
const environment = process.env.NODE_ENV === 'production' ? config.Environment.PRODUCTION : config.Environment.SANDBOX; const environment = process.env.NODE_ENV === 'production' ? config.Environment.PRODUCTION : config.Environment.SANDBOX;
const publishableKey = import.meta.env.VUE_APP_PASSPORT_PUBLISHABLE_KEY const publishableKey = import.meta.env.VUE_APP_PASSPORT_PUBLISHABLE_KEY
@ -69,12 +70,16 @@ export class PassportWallet {
async getAccessToken() { async getAccessToken() {
return await this.passportInstance.getAccessToken(); const token = await this.passportInstance.getAccessToken();
return { token }
} }
async logout() { async logout() {
await this.passportInstance.logout(); await this.passportInstance.logout();
await this.passportInstance.logoutSilentCallback(logoutRedirectUri); await this.passportInstance.logoutSilentCallback(logoutRedirectUri);
} }
async getChainId() {
return Promise.resolve(cfgChainId)
}
} }

View File

@ -282,5 +282,21 @@ export const AllChains = [
id: 1666700000, id: 1666700000,
symbol: 'ONE', symbol: 'ONE',
explorerurl: 'https://explorer.harmony.one' explorerurl: 'https://explorer.harmony.one'
},
{
name: 'Immutable zkEVM',
type: 'Mainnet',
rpc: 'https://rpc.immutable.com',
id: 13371,
symbol: 'IMX',
explorerurl: 'https://explorer.immutable.com'
},
{
name: 'Immutable zkEVM Testnet',
type: 'Testnet',
rpc: 'https://rpc.testnet.immutable.com',
id: 13473,
symbol: 'tIMX',
explorerurl: 'https://explorer.testnet.immutable.com'
} }
] ]

View File

@ -8,6 +8,7 @@ export const walletStore = defineStore(
const address = ref(); const address = ref();
const chainId = ref(); const chainId = ref();
const token = ref(); const token = ref();
const refreshToken = ref();
const showAddress = computed(() => { const showAddress = computed(() => {
if (address.value.length > 10) { if (address.value.length > 10) {
@ -21,12 +22,14 @@ export const walletStore = defineStore(
address.value = ''; address.value = '';
chainId.value = ''; chainId.value = '';
token.value = ''; token.value = '';
refreshToken.value = '';
} }
return { return {
walletType, walletType,
address, address,
chainId, chainId,
token, token,
refreshToken,
showAddress, showAddress,
reset, reset,
}; };