diff --git a/.env.dev b/.env.dev
index 32a7b57..c8d3fe2 100644
--- a/.env.dev
+++ b/.env.dev
@@ -1,4 +1,9 @@
VUE_APP_BASE_API='https://market.cebg.games'
VUE_APP_BASE_API2='https://invitation.counterfire.games'
//VUE_APP_BASE_API2='http://192.168.100.83:3000/'
-VUE_APP_GPAL_API='https://game2006api.cebggame.com/'
\ No newline at end of file
+VUE_APP_GPAL_API='https://game2006api.cebggame.com/'
+VUE_APP_PASSPORT_PUBLISHABLE_KEY=pk_imapik-test-8c2FAlWxWAoRITk1v9rH
+VUE_APP_PASSPORT_REDIRECT_URI=http://localhost:4000/redirect
+VUE_APP_PASSPORT_LOGOUT_URI=http://localhost:4000/
+VUE_APP_PASSPORT_CLIENT_ID=eTmUah69p7ZdRhRYzBta6lZRKXXeXDYj
+VUE_APP_PASSPORT_MARKET_ADDRESS=0x7d117aA8BD6D31c4fa91722f246388f38ab1942c
\ No newline at end of file
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 0000000..f268596
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,5 @@
+node_modules
+artifacts
+cache
+coverage*
+gasReporterOutput.json
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..32f2e64
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,8 @@
+{
+ "printWidth": 120,
+ "tabWidth": 2,
+ "useTabs": false,
+ "singleQuote": false,
+ "bracketSpacing": false,
+ "explicitTypes": "always"
+}
diff --git a/jsconfig.json b/jsconfig.json
new file mode 100644
index 0000000..64fea22
--- /dev/null
+++ b/jsconfig.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "target": "es5",
+ "module": "esnext",
+ "baseUrl": "./",
+ "moduleResolution": "node",
+ "allowJs": true,
+ "paths": {
+ "@/*": [
+ "src/*"
+ ]
+ },
+ "lib": [
+ "esnext",
+ "dom",
+ "dom.iterable",
+ "scripthost"
+ ]
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/package.json b/package.json
index 4cfb846..274a5b6 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"ant-design-vue": "^4.2.3",
"axios": "^1.3.3",
"buffer": "^6.0.3",
+ "ethers": "^5.7.2",
"gsap": "^3.11.5",
"js-cookie": "^3.0.1",
"normalize.css": "^8.0.1",
diff --git a/src/components/layout/NavBar.vue b/src/components/layout/NavBar.vue
index 01c17e0..c4c6eaa 100644
--- a/src/components/layout/NavBar.vue
+++ b/src/components/layout/NavBar.vue
@@ -66,6 +66,7 @@
+
@@ -78,7 +79,7 @@ import { hasMetamask } from "@/utils/chain.util";
import { useRouter, useRoute } from "vue-router";
import ChainModel from "@/components/home/ChainModel.vue";
import { useCopyToClipboard } from "./../../hooks/useCopyToClipboard";
-import { getConnect } from "@/wallet/passPort"
+import { PassportWallet } from "@/wallet/passPort"
const AppModule = useAppStore();
const router = useRouter();
@@ -226,8 +227,13 @@ watchEffect(() => {
const immuTableLogin = async () => {
// console.log('----')
try{
- const walletLogin = await getConnect()
+ const walletLogin = await new PassportWallet().connect()
console.log(walletLogin)
+ // const list = await new PassportWallet().beginSellERC721({
+ // contractAddress: '',
+ // tokenId: ''
+ // });
+ // console.log(list)
} catch (e) {
console.log(e);
}
diff --git a/src/utils/singleton.js b/src/utils/singleton.js
new file mode 100644
index 0000000..21d2cd9
--- /dev/null
+++ b/src/utils/singleton.js
@@ -0,0 +1,16 @@
+export const createSingleton = (Class) => {
+ let instance;
+
+ return new Proxy(Class, {
+ construct(target, args, newTarget) {
+ // Skip proxy for children
+ if (target.prototype !== newTarget.prototype) {
+ return Reflect.construct(target, argumentsList, newTarget)
+ }
+ if (!instance) {
+ instance = Reflect.construct(target, args, newTarget);
+ }
+ return instance;
+ },
+ });
+};
diff --git a/src/wallet/passPort.js b/src/wallet/passPort.js
index 98fd987..0f1853a 100644
--- a/src/wallet/passPort.js
+++ b/src/wallet/passPort.js
@@ -1,28 +1,274 @@
-import { config, passport } from '@imtbl/sdk';
+import { config, passport, orderbook, checkout } from '@imtbl/sdk';
+import { createSingleton } from '@/utils/singleton';
+import { providers } from 'ethers';
-const passportInstance = new passport.Passport({
- baseConfig: {
- environment: config.Environment.SANDBOX, // or Environment.PRODUCTION
- publishableKey: 'wc:b348768c53d32b69062adcc7f67b263f771564c39edc06acc4cb3e9ee561d415@2?relay-protocol=irn&symKey=b8302aed25de3f9e3485eb8601d27c52779ab28cd8b1398312aa18be01cebbe8', // replace with your publishable API key from Hub
- },
- clientId: '', // replace with your client ID from Hub
- redirectUri: 'http://192.168.100.216:4000/', // replace with one of your redirect URIs from Hub
- logoutRedirectUri: 'http://192.168.100.216:4000/', // replace with one of your logout URIs from Hub
- audience: 'platform_api',
- scope: 'openid offline_access email transact',
- popupOverlayOptions: {
- disableGenericPopupOverlay: false, // Set to true to disable the generic pop-up overlay
- disableBlockedPopupOverlay: false, // Set to true to disable the blocked pop-up overlay
+const environment = process.env.NODE_ENV === 'production' ? config.Environment.PRODUCTION : config.Environment.SANDBOX;
+const publishableKey = import.meta.env.VUE_APP_PASSPORT_PUBLISHABLE_KEY
+const clientId = import.meta.env.VUE_APP_PASSPORT_CLIENT_ID
+const redirectUri = import.meta.env.VUE_APP_PASSPORT_REDIRECT_URI
+const logoutRedirectUri = import.meta.env.VUE_APP_PASSPORT_LOGOUT_URI
+const marketAddress = import.meta.env.VUE_APP_PASSPORT_MARKET_ADDRESS
+
+const NATIVE = 'NATIVE'
+const ERC20 = 'ERC20'
+
+const baseConfig = { environment, publishableKey }
+class LPassportWallet {
+ constructor() {
+ this.passportInstance = new passport.Passport({
+ baseConfig,
+ clientId, // replace with your client ID from Hub
+ redirectUri, // replace with one of your redirect URIs from Hub
+ logoutRedirectUri, // replace with one of your logout URIs from Hub
+ audience: 'platform_api',
+ scope: 'openid offline_access email transact',
+ popupOverlayOptions: {
+ disableGenericPopupOverlay: false, // Set to true to disable the generic pop-up overlay
+ disableBlockedPopupOverlay: false, // Set to true to disable the blocked pop-up overlay
+ }
+ });
+ this.passportInstance.loginCallback();
+ this.client = new orderbook.Orderbook({ baseConfig });
}
-});
+ async initWidget() {
+ const checkoutSDK = new checkout.Checkout({
+ baseConfig,
+ passport: this.passportInstance,
+ bridge: { enable: true },
+ swap: { enable: true },
+ onRamp: { enable: true }
+ });
+
+ const widgets = await checkoutSDK.widgets({
+ config: { theme: checkout.WidgetTheme.DARK },
+ });
-// 链接Wallet Connect钱包
-export async function getConnect() {
- // let result = await passportInstance.loginCallback()
- const profile = await passportInstance.login();
- console.log(profile,'-----------------------------------------------------------------')
- return
- // let result = profile.passport.UserProfile | null = await passportInstance.login()
- // let result = await passportInstance.request({ method: 'eth_requestAccounts' })
- return result
-}
\ No newline at end of file
+ // RECOMMENDED - create all of the widgets once at the start of your application
+ // use the created widgets throughout your application to mount and unmount in specific parts of your application
+ const connect = widgets.create(checkout.WidgetType.CONNECT);
+ const wallet = widgets.create(checkout.WidgetType.WALLET); // you can optionally pass in additional config per widget
+ const swap = widgets.create(checkout.WidgetType.SWAP);
+ const bridge = widgets.create(checkout.WidgetType.BRIDGE);
+ const onramp = widgets.create(checkout.WidgetType.ONRAMP);
+
+ // Mount the wallet widget passing the element id of where to mount the widget
+ connect.mount('wallet');
+ }
+ async connect() {
+ const profile = await this.passportInstance.login();
+ console.log(profile,'-----------------------------------------------------------------')
+ const passportProvider = this.passportInstance.connectEvm();
+ this.web3Provider = new providers.Web3Provider(passportProvider);
+ this.signer = this.web3Provider.getSigner();
+ const accounts = await passportProvider.request({ method: "eth_requestAccounts" })
+ const accessToken = await this.passportInstance.getAccessToken();
+ console.log('accessToken:', accessToken)
+ // const idToken = await passportInstance.getIdToken();
+ // console.log('idToken:', idToken)
+ return {profile, accounts, accessToken}
+ }
+
+ async logout() {
+ await this.passportInstance.logout();
+ }
+ /**
+ * 查询某个nft的所有挂单
+ * @param {*} contractAddress
+ * @returns
+ */
+ async listListings(contractAddress){
+ const listOfListings = await this.client.listListings({
+ sellItemContractAddress: contractAddress,
+ status: orderbook.OrderStatusName.ACTIVE,
+ pageSize: 50,
+ });
+ return listOfListings;
+ };
+
+ /**
+ * 准备一个ERC721的挂单
+ * @returns
+ */
+ async _prepareERC721Listing({ contractAddress, tokenId, type = 'ERC721', currencyAddress, currencyAmount}){
+ const offerer = await this.signer.getAddress();
+ const buyData = {
+ amount: currencyAmount,
+ }
+ if (currencyAddress == NATIVE) {
+ buyData.type = NATIVE
+ } else {
+ buyData.type = ERC20
+ buyData.contractAddress = currencyAddress
+ }
+ const preparedListing = await this.client.prepareListing({
+ makerAddress: offerer,
+ buy: buyData,
+ sell: {
+ contractAddress,
+ tokenId,
+ type,
+ },
+ });
+
+ let orderSignature = ''
+ for (const action of preparedListing.actions) {
+ // If the user hasn't yet approved the Immutable Seaport contract to transfer assets from this
+ // collection on their behalf they'll need to do so before they create an order
+ if (action.type === orderbook.ActionType.TRANSACTION) {
+ const builtTx = await action.buildTransaction()
+ console.log(`Submitting ${action.purpose} transaction`)
+ await signer.sendTransaction(builtTx);
+ }
+
+ // For an order to be created (and subsequently filled), Immutable needs a valid signature for the order data.
+ // This signature is stored off-chain and is later provided to any user wishing to fulfil the open order.
+ // The signature only allows the order to be fulfilled if it meets the conditions specified by the user that created the listing.
+ if (action.type === orderbook.ActionType.SIGNABLE) {
+ orderSignature = await signer._signTypedData(
+ action.message.domain,
+ action.message.types,
+ action.message.value,
+ )
+ }
+ }
+
+ return { preparedListing, orderSignature }
+ }
+ /**
+ * 创建一个挂单
+ * @param {*} preparedListing
+ * @param {*} orderSignature
+ */
+ async _createListing(
+ preparedListing,
+ orderSignature,
+ currencyAmount
+ ){
+ const amount = (BigInt(currencyAmount) * 2n / 100n).toString()
+ const order = await this.client.createListing({
+ orderComponents: preparedListing.orderComponents,
+ orderHash: preparedListing.orderHash,
+ orderSignature,
+ // Optional maker marketplace fee
+ makerFees: [{
+ amount,
+ recipientAddress: marketAddress, // Replace address with your own marketplace address
+ }],
+ });
+ console.log('order:', order);
+ return order
+ };
+
+ /**
+ * 出售一个ERC721的NFT
+ * doc: https://docs.immutable.com/docs/zkEVM/products/orderbook/create-listing
+ * @param {string} contractAddress NFT的合约地址
+ * @param {string} tokenId NFT的tokenId
+ * @param {string} currencyAddress NATIVE 或者 ERC20的合约地址
+ * @param {string} currencyAmount 出售价格, 单位 wei
+ */
+ async beginSellERC721({contractAddress, tokenId, currencyAddress, currencyAmount}) {
+ const { preparedListing, orderSignature } =
+ await this._prepareERC721Listing({contractAddress, tokenId, currencyAddress, currencyAmount});
+ const order = await this._createListing(preparedListing, orderSignature, currencyAmount);
+ return order
+ }
+ /**
+ * 开始购买
+ * doc: https://docs.immutable.com/docs/zkEVM/products/orderbook/fill
+ * @param {*} listingId
+ */
+ async beginBuy(listingId) {
+ const fulfiller = await this.signer.getAddress();
+ const { actions, expiration, order } = await this.client.fulfillOrder(
+ listingId,
+ fulfiller,
+ []
+ );
+ console.log(`Fulfilling listing ${order}, transaction expiry ${expiration}`);
+ for (const action of actions) {
+ if (action.type === orderbook.ActionType.TRANSACTION) {
+ const builtTx = await action.buildTransaction();
+ console.log(`Submitting ${action.purpose} transaction`);
+ await signer.sendTransaction(builtTx);
+ }
+ }
+ }
+ /**
+ * 批量购买
+ * doc: https://docs.immutable.com/docs/zkEVM/products/orderbook/fill-bulk
+ * @param { string[] } listingIds: listingId列表
+ */
+ async batchBuy(listingIds) {
+ const fulfiller = await this.signer.getAddress();
+ try {
+ const fulfillResponse = await this.client.fulfillBulkOrders(
+ listingIds.map((listingId) => ({
+ listingId,
+ // you could have up to 2 marketplace fees
+ takerFees: [],
+ })),
+ fulfiller
+ );
+
+ if (fulfillResponse.sufficientBalance) {
+ const { actions, expiration, fulfillableOrders, unfulfillableOrders } = fulfillResponse;
+ // depending on the application, we can either throw an error if some orders are not fulfillable
+ // or we can ignore these unfulfillable orders and proceed with fulfillment
+ if (unfulfillableOrders.length > 0) {
+ throw new Error(
+ `Not all orders are fulfillable - unfulfillable orders: ${unfulfillableOrders}`
+ );
+ }
+ for (const action of actions) {
+ if (action.type === orderbook.ActionType.TRANSACTION) {
+ const builtTx = await action.buildTransaction();
+ console.log(`Submitting ${action.purpose} transaction`);
+ await signer.sendTransaction(builtTx);
+ }
+ }
+ console.log(
+ `Fulfilling listings ${fulfillableOrders}, transaction expiry ${expiration}`
+ );
+ }
+ } catch (e) {
+ console.error(`Fulfill bulk orders request failed with ${e}`);
+ throw e;
+ }
+ }
+ /**
+ * 取消交易
+ * doc: https://docs.immutable.com/docs/zkEVM/products/orderbook/cancel
+ * @param {*} listingIds
+ * @returns
+ */
+ async cancelOrder(listingIds) {
+ const account = await this.signer.getAddress();
+ const { signableAction } = await this.client.prepareOrderCancellations(listingIds);
+ const cancellationSignature = await this.signer._signTypedData(
+ signableAction.message.domain,
+ signableAction.message.types,
+ signableAction.message.value,
+ )
+ return this.client.cancelOrders(listingIds, account, cancellationSignature)
+ }
+ /**
+ * 取消交易, onChain
+ * doc: https://docs.immutable.com/docs/zkEVM/products/orderbook/cancel
+ * @param {*} listingIds
+ * @returns
+ */
+ async cancelOrdersOnChain(listingIds) {
+ const offerer = await this.signer.getAddress();
+ const { cancellationAction } = await this.client.cancelOrdersOnChain(
+ listingIds,
+ offerer
+ );
+
+ const unsignedCancelOrderTransaction = await cancellationAction.buildTransaction();
+ const receipt = await this.signer.sendTransaction(unsignedCancelOrderTransaction);
+ return receipt;
+ }
+}
+
+export const PassportWallet = createSingleton(LPassportWallet)
\ No newline at end of file