import { config, passport, orderbook, checkout } from '@imtbl/sdk'; import { createSingleton } from '@/utils/singleton'; import { providers } from 'ethers'; 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 }, }); // 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)