UNPKG

anchor-sdk

Version:

TypeScript SDK for interacting with Anchor ecosystem - badge minting, payment processing, and ERC1155 token management

1,163 lines (1,162 loc) 66.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AnchorERC1155Client = exports.AnchorApiClientV2 = exports.AnchorPayClient = exports.AnchorSDK = void 0; const viem_1 = require("viem"); const AnchorPayClient_1 = require("./AnchorPayClient"); const AnchorERC1155Client_1 = require("./AnchorERC1155Client"); const AnchorApiClientV2_1 = require("./AnchorApiClientV2"); const constants_1 = require("./constants"); const AnchorPay_json_1 = __importDefault(require("./abi/AnchorPay.json")); const AnchorERC1155_1 = require("./abi/AnchorERC1155"); /** * Anchor SDK * 用于与 AnchorPay 和 AnchorERC1155 合约交互的 SDK */ class AnchorSDK { /** * 创建 Anchor SDK 实例 * @param config SDK 配置 */ constructor(config) { this.config = config; this.contracts = {}; // 检查是否使用旧版配置(向后兼容) if ("publicClient" in config && config.publicClient) { // 使用旧版配置 this.publicClient = config.publicClient; this.walletClient = config.walletClient; this.account = this.walletClient?.account; this.network = this.createChainFromNetwork(config.network); } else { // 使用新版配置 // Convert network to viem Chain this.network = this.createChainFromNetwork(config.network); // Initialize viem clients this.publicClient = this.initializePublicClient(config.provider); // 如果没有提供 signer,则使用 provider 作为 signer const effectiveSigner = config.signer || config.provider; this.walletClient = effectiveSigner ? this.initializeWalletClient(effectiveSigner) : undefined; this.account = this.walletClient?.account || this.getAccountFromSigner(effectiveSigner); } // 如果用户直接提供了合约地址,优先使用用户提供的地址 if (config.anchorPayAddress) { this.contracts.anchorPay = config.anchorPayAddress; } if (config.anchorERC1155Address) { this.contracts.anchorERC1155 = config.anchorERC1155Address; } // 创建客户端 this.anchorPay = new AnchorPayClient_1.AnchorPayClient(this.publicClient, this.network, this.walletClient, this.account, this.contracts); this.anchorERC1155 = new AnchorERC1155Client_1.AnchorERC1155Client(this.publicClient, this.network, this.walletClient, this.account, this.contracts); // 处理环境配置 let apiBaseUrl = config.apiBaseUrl; // 如果指定了环境,先尝试从环境配置中获取 API URL 和合约地址 if (config.env && constants_1.ENVIRONMENTS[config.env]) { // 如果没有指定 API URL,使用环境配置中的 URL if (!apiBaseUrl) { apiBaseUrl = constants_1.ENVIRONMENTS[config.env].apiBaseUrl; } // 从环境配置中获取当前网络的合约地址(仅当用户没有直接提供时) if (this.network.id && constants_1.ENVIRONMENTS[config.env].contracts[this.network.id]) { const networkContracts = constants_1.ENVIRONMENTS[config.env].contracts[this.network.id]; // 只有当用户没有直接提供合约地址时,才使用环境配置中的地址 if (networkContracts.anchorPay && !this.contracts.anchorPay) { this.contracts.anchorPay = networkContracts.anchorPay; } if (networkContracts.anchorERC1155 && !this.contracts.anchorERC1155) { this.contracts.anchorERC1155 = networkContracts.anchorERC1155; } // 重新初始化客户端 this.anchorPay = new AnchorPayClient_1.AnchorPayClient(this.publicClient, this.network, this.walletClient, this.account, this.contracts); this.anchorERC1155 = new AnchorERC1155Client_1.AnchorERC1155Client(this.publicClient, this.network, this.walletClient, this.account, this.contracts); } } // 如果有 API URL,创建 Anchor API 客户端 if (apiBaseUrl) { this.anchorApi = new AnchorApiClientV2_1.AnchorApiClientV2({ apiBaseUrl: apiBaseUrl, authToken: config.authToken, projectId: config.projectId, network: this.network, onTokenExpired: config.onTokenExpired, }); } } /** * 设置 Token 过期回调 * @param callback Token 过期回调函数 */ setTokenExpiredCallback(callback) { if (this.anchorApi) { this.anchorApi.setTokenExpiredCallback(callback); } } /** * 设置钱包客户端 * 在 SDK 初始化后动态更新钱包客户端和账户 * @param walletClient 新的钱包客户端 */ setWalletClient(signerOrWalletClient) { // 检查是否是 viem WalletClient(向后兼容) if (signerOrWalletClient && "account" in signerOrWalletClient) { // 使用旧版接口 this.walletClient = signerOrWalletClient; this.account = signerOrWalletClient.account; } else { // 使用新版接口 // 如果没有提供有效的签名者,则使用当前的 provider const effectiveSigner = signerOrWalletClient || this.config.provider; this.walletClient = this.initializeWalletClient(effectiveSigner); this.account = this.walletClient?.account || this.getAccountFromSigner(effectiveSigner); } // 重新初始化客户端 this.anchorPay = new AnchorPayClient_1.AnchorPayClient(this.publicClient, this.network, this.walletClient, this.account, this.contracts); this.anchorERC1155 = new AnchorERC1155Client_1.AnchorERC1155Client(this.publicClient, this.network, this.walletClient, this.account, this.contracts); console.log("SDK wallet client updated:", this.account); return this; } /** * 初始化公共客户端 * @param provider 提供者(URL或Web3提供者) * @returns 公共客户端 */ initializePublicClient(provider) { // If provider is a string URL if (typeof provider === "string") { return (0, viem_1.createPublicClient)({ chain: this.network, transport: (0, viem_1.http)(provider), }); } // If provider is a Web3 Provider (like window.ethereum) if (provider && typeof provider === "object") { return (0, viem_1.createPublicClient)({ chain: this.network, transport: (0, viem_1.custom)(provider), }); } throw new Error("Invalid provider. Please provide a valid RPC URL or Web3 Provider."); } /** * 初始化钱包客户端 * @param signer 签名者 * @returns 钱包客户端 */ initializeWalletClient(signer) { if (!signer) return undefined; // If signer is a Web3 Provider with accounts if (signer && typeof signer === "object") { // For providers like window.ethereum that have accounts if (signer.selectedAddress) { return (0, viem_1.createWalletClient)({ chain: this.network, transport: (0, viem_1.custom)(signer), account: signer.selectedAddress, }); } // For signers with an address property if (signer.address) { return (0, viem_1.createWalletClient)({ chain: this.network, transport: (0, viem_1.custom)(signer.provider || signer), account: signer.address, }); } } return undefined; } /** * 从签名者获取账户 * @param signer 签名者 * @returns 账户 */ getAccountFromSigner(signer) { if (!signer) return undefined; // If signer has selectedAddress (like window.ethereum) if (signer.selectedAddress) { return { address: signer.selectedAddress, type: "json-rpc", }; } // If signer has address property if (signer.address) { return { address: signer.address, type: "json-rpc", }; } return undefined; } /** * 从网络信息创建Chain对象 * @param network 网络信息 * @returns Chain对象 */ createChainFromNetwork(network) { // Create a basic Chain object from the provided network info return { ...network, nativeCurrency: network.nativeCurrency || { name: "Ether", symbol: "ETH", decimals: 18, }, rpcUrls: { default: { http: [ typeof this.config.provider === "string" ? this.config.provider : "", ], }, public: { http: [ typeof this.config.provider === "string" ? this.config.provider : "", ], }, }, }; } /** * 购买 Badge(使用 ETH,直接使用签名请求) * @param signedRequest 签名铸造请求 * @param receiptAddress 接收地址,如果提供,将作为 ERC20 转账的接收地址使用 * @param options 支付选项 * @returns 如果直接发送交易,返回交易收据;否则返回交易数据 */ async buyBadgeWithETHWithSignedRequest(signedRequest, receiptAddress, options) { // 校验 signedRequest if (!signedRequest || !signedRequest.request || !signedRequest.signature) { throw new Error("signedRequest is required and must contain request and signature"); } // 校验 receiptAddress if (!receiptAddress || !(0, viem_1.isAddress)(receiptAddress)) { throw new Error("receiptAddress is required and must be a valid Ethereum address"); } // 如果需要发送交易,但没有钱包客户端或账户 if (options?.sendTransaction !== false && (!this.walletClient || !this.account)) { throw new Error("Wallet client and account are required to send transactions"); } // 检查请求是否有效 this.validateMintRequest(signedRequest.request); // 检查货币是否为 ETH if (signedRequest.request.currency !== constants_1.NATIVE_TOKEN_ADDRESS) { throw new Error("Currency must be ETH"); } // 计算总价 const totalPrice = BigInt(signedRequest.request.price) * BigInt(signedRequest.request.quantity); // 编码数据 const data = this.anchorERC1155.encodeTokenReceivedData(signedRequest); // 发送交易 const result = await this.anchorPay.sendETH(receiptAddress, totalPrice, data, options); // 如果是交易收据并且有 transactionHash,尝试加快交易处理 // 根据 viem 文档,TransactionReceipt 中的交易哈希字段是 transactionHash,而不是 hash if (options?.sendTransaction !== false && result && typeof result === "object" && "transactionHash" in result) { try { await this.processTransactionHash(result.transactionHash); } catch (error) { // 忽略错误,不影响主流程 console.warn(`Failed to process transaction hash: ${error instanceof Error ? error.message : String(error)}`); } } return result; } /** * 购买 Badge(使用 ERC20 代币,直接使用签名请求) * @param signedRequest 签名铸造请求 * @param receiptAddress 接收地址,如果提供,将作为接收地址使用 * @param options 支付选项 * @returns 如果直接发送交易,返回交易收据;否则返回交易数据 */ async buyBadgeWithERC20WithSignedRequest(signedRequest, receiptAddress, options) { // 校验 signedRequest if (!signedRequest || !signedRequest.request || !signedRequest.signature) { throw new Error("signedRequest is required and must contain request and signature"); } // 校验 receiptAddress if (!receiptAddress || !(0, viem_1.isAddress)(receiptAddress)) { throw new Error("receiptAddress is required and must be a valid Ethereum address"); } // 如果需要发送交易,但没有钱包客户端或账户 if (options?.sendTransaction !== false && (!this.walletClient || !this.account)) { throw new Error("Wallet client and account are required to send transactions"); } // 检查请求是否有效 this.validateMintRequest(signedRequest.request); // 检查货币是否为 ERC20 if (signedRequest.request.currency === constants_1.NATIVE_TOKEN_ADDRESS) { throw new Error("Currency cannot be native token"); } // 计算总价 const totalPrice = BigInt(signedRequest.request.price) * BigInt(signedRequest.request.quantity); // 编码数据 const data = this.anchorERC1155.encodeTokenReceivedData(signedRequest); // 发送交易 const result = await this.anchorPay.sendERC20(receiptAddress, signedRequest.request.currency, totalPrice, data, options); // 如果是交易收据并且有 transactionHash,尝试加快交易处理 // 根据 viem 文档,TransactionReceipt 中的交易哈希字段是 transactionHash,而不是 hash if (options?.sendTransaction !== false && result && typeof result === "object" && "transactionHash" in result) { try { await this.processTransactionHash(result.transactionHash); } catch (error) { // 忽略错误,不影响主流程 console.warn(`Failed to process transaction hash: ${error instanceof Error ? error.message : String(error)}`); } } return result; } /** * 使用 ETH 购买徽章 * @param customerAddress 客户地址 * @param contractAddress NFT 地址 * @param tokenId 代币 ID * @param quantity 数量 * @param receiptAddress 接收地址,如果提供,将作为接收地址使用 * @param options 支付选项 * @returns 如果直接发送交易,返回交易收据;否则返回交易数据 */ async buyBadgeWithETH(customerAddress, contractAddress, tokenId, quantity = 1, receiptAddress, options) { // 校验地址参数 if (!customerAddress || !(0, viem_1.isAddress)(customerAddress)) { throw new Error("customerAddress is required and must be a valid Ethereum address"); } if (!contractAddress || !(0, viem_1.isAddress)(contractAddress)) { throw new Error("contractAddress is required and must be a valid Ethereum address"); } if (!receiptAddress || !(0, viem_1.isAddress)(receiptAddress)) { throw new Error("receiptAddress is required and must be a valid Ethereum address"); } // 校验 tokenId 和 quantity if (tokenId === undefined || tokenId === "") { throw new Error("tokenId is required"); } const quantityBigInt = BigInt(quantity.toString()); if (quantityBigInt <= 0n) { throw new Error("quantity must be greater than 0"); } if (!this.anchorApi) { throw new Error("Anchor API client not initialized, please check apiBaseUrl configuration"); } // 如果需要发送交易,但没有钱包客户端或账户 if (options?.sendTransaction !== false && (!this.walletClient || !this.account)) { throw new Error("Wallet client and account are required to send transactions"); } // 使用 claimableIds 来标识要购买的徽章 const claimableIds = [`${tokenId.toString()}`]; // 从后端获取铸造请求 const mintResponse = await this.anchorApi.getBadgeClaimSignatures({ customerAddress, badgeIds: claimableIds, }); if (mintResponse.code !== "0000" || !mintResponse.obj || !mintResponse.obj.signatures || mintResponse.obj.signatures.length === 0) { throw new Error(mintResponse.code || "Failed to get mint request from backend"); } const mintRequest = mintResponse.obj.signatures[0]; // 检查必要字段 if (!mintRequest.badgeId || !mintRequest.signature || !mintRequest.validityStartTimestamp || !mintRequest.validityEndTimestamp) { throw new Error("Mint request missing required fields"); } // 创建签名铸造请求 const signedRequest = { request: { to: customerAddress, tokenId: BigInt(mintRequest.badgeId || tokenId), quantity: BigInt(mintRequest.quantity || quantity), price: BigInt(mintRequest.price || 0), // 从签名中获取价格信息 currency: mintRequest.currency, // 从签名中获取货币信息 validityStartTimestamp: BigInt(mintRequest.validityStartTimestamp || Math.floor(Date.now() / 1000)), // 立即生效 validityEndTimestamp: BigInt(mintRequest.validityEndTimestamp || Math.floor(Date.now() / 1000) + 3600), // 1小时后过期 uid: mintRequest.uid, // 从签名中获取 uid chainId: BigInt(mintRequest.chainId || this.network.id), // 从签名中获取链 ID projectHandle: mintRequest.projectHandle, // 从签名中获取项目句柄 }, signature: mintRequest.signature, }; // 使用签名请求购买徽章(支持 AA 和 EOA 模式) return await this.buyBadgeWithETHWithSignedRequest(signedRequest, receiptAddress, options); } /** * 使用自定义签名数据购买徽章(ETH) * @param signedRequest 用户自己组装的签名铸造请求 * @param receiptAddress 接收地址,如果提供,将作为接收地址使用 * @param options 支付选项 * @returns 如果直接发送交易,返回交易收据;否则返回交易数据 */ async buyBadgeWithETHWithCustomSignature(signedRequest, receiptAddress, options) { // 校验 signedRequest if (!signedRequest) { throw new Error("signedRequest is required"); } // 校验必要字段 if (!signedRequest.to || !(0, viem_1.isAddress)(signedRequest.to)) { throw new Error("signedRequest.to is required and must be a valid Ethereum address"); } if (signedRequest.tokenId === undefined || signedRequest.tokenId === "") { throw new Error("signedRequest.tokenId is required"); } if (signedRequest.quantity === undefined || BigInt(signedRequest.quantity.toString()) <= 0n) { throw new Error("signedRequest.quantity is required and must be greater than 0"); } if (signedRequest.price === undefined) { throw new Error("signedRequest.price is required"); } if (!signedRequest.uid) { throw new Error("signedRequest.uid is required"); } if (!signedRequest.signature) { throw new Error("signedRequest.signature is required"); } if (!signedRequest.chainId) { throw new Error("signedRequest.chainId is required"); } // 校验 receiptAddress if (!receiptAddress || !(0, viem_1.isAddress)(receiptAddress)) { throw new Error("receiptAddress is required and must be a valid Ethereum address"); } // 将用户提供的数据转换为标准格式 const formattedRequest = { request: { to: signedRequest.to, tokenId: BigInt(signedRequest.tokenId), quantity: BigInt(signedRequest.quantity), price: BigInt(signedRequest.price), currency: signedRequest.currency, validityStartTimestamp: BigInt(signedRequest.validityStartTimestamp || Math.floor(Date.now() / 1000) + 3600), validityEndTimestamp: BigInt(signedRequest.validityEndTimestamp || Math.floor(Date.now() / 1000) + 3600), uid: signedRequest.uid, chainId: BigInt(signedRequest.chainId || this.network.id), projectHandle: signedRequest.projectHandle, }, signature: signedRequest.signature.startsWith("0x") ? signedRequest.signature : `0x${signedRequest.signature}`, }; // 使用标准方法处理 return await this.buyBadgeWithETHWithSignedRequest(formattedRequest, receiptAddress, options); } /** * 使用 ERC20 代币购买徽章 * @param customerAddress 客户地址 * @param contractAddress NFT 地址 * @param tokenId 代币 ID * @param quantity 数量 * @param currency ERC20 代币地址 * @param receiptAddress 接收地址,如果提供,将作为接收地址使用 * @param options 支付选项 * @returns 如果直接发送交易,返回交易收据;否则返回交易数据 */ async buyBadgeWithERC20(customerAddress, contractAddress, tokenId, quantity = 1, currency, receiptAddress, options) { if (!this.anchorApi) { throw new Error("Anchor API client not initialized, please check apiBaseUrl configuration"); } // 如果需要发送交易,但没有钱包客户端或账户 if (options?.sendTransaction !== false && (!this.walletClient || !this.account)) { throw new Error("Wallet client and account are required to send transactions"); } // 校验地址参数 if (!customerAddress || !(0, viem_1.isAddress)(customerAddress)) { throw new Error("customerAddress is required and must be a valid Ethereum address"); } if (!contractAddress || !(0, viem_1.isAddress)(contractAddress)) { throw new Error("contractAddress is required and must be a valid Ethereum address"); } if (!receiptAddress || !(0, viem_1.isAddress)(receiptAddress)) { throw new Error("receiptAddress is required and must be a valid Ethereum address"); } if (!currency || !(0, viem_1.isAddress)(currency)) { throw new Error("currency is required and must be a valid ERC20 token address"); } // 校验 tokenId 和 quantity if (tokenId === undefined || tokenId === "") { throw new Error("tokenId is required"); } const quantityBigInt = BigInt(quantity.toString()); if (quantityBigInt <= 0n) { throw new Error("quantity must be greater than 0"); } // 使用 claimableIds 来标识要购买的徽章 const claimableIds = [`${tokenId.toString()}`]; // 从后端获取铸造请求 const mintResponse = await this.anchorApi.getBadgeClaimSignatures({ customerAddress, badgeIds: claimableIds, }); if (mintResponse.code !== "0000" || !mintResponse.obj || !mintResponse.obj.signatures || mintResponse.obj.signatures.length === 0) { throw new Error(mintResponse.code || "Failed to get mint request from backend"); } const mintRequest = mintResponse.obj.signatures[0]; // 检查必要字段 if (!mintRequest.badgeId || !mintRequest.signature || !mintRequest.validityStartTimestamp || !mintRequest.validityEndTimestamp) { throw new Error("Mint request missing required fields"); } // 创建签名铸造请求 const signedRequest = { request: { to: customerAddress, tokenId: BigInt(mintRequest.badgeId || tokenId), quantity: BigInt(mintRequest.quantity || quantity), price: BigInt(mintRequest.price || 0), // 从签名中获取价格信息 currency: mintRequest.currency, // 从签名中获取货币信息 validityStartTimestamp: BigInt(mintRequest.validityStartTimestamp || Math.floor(Date.now() / 1000)), // 立即生效 validityEndTimestamp: BigInt(mintRequest.validityEndTimestamp || Math.floor(Date.now() / 1000) + 3600), // 1小时后过期 uid: mintRequest.uid, // 从签名中获取 uid chainId: BigInt(mintRequest.chainId || this.network.id), // 从签名中获取链 ID projectHandle: mintRequest.projectHandle, // 从签名中获取项目句柄 }, signature: mintRequest.signature, }; // 使用签名请求购买徽章(支持 AA 和 EOA 模式) return await this.buyBadgeWithERC20WithSignedRequest(signedRequest, receiptAddress, options); } /** * 使用自定义签名数据购买徽章(ERC20) * @param signedRequest 用户自己组装的签名铸造请求 * @param receiptAddress 接收地址,如果提供,将作为 ERC20 转账的接收地址使用 * @param options 支付选项 * @returns 如果直接发送交易,返回交易收据;否则返回交易数据 */ async buyBadgeWithERC20WithCustomSignature(signedRequest, receiptAddress, options) { // 校验 signedRequest if (!signedRequest) { throw new Error("signedRequest is required"); } // 校验必要字段 if (!signedRequest.to || !(0, viem_1.isAddress)(signedRequest.to)) { throw new Error("signedRequest.to is required and must be a valid Ethereum address"); } if (signedRequest.tokenId === undefined || signedRequest.tokenId === "") { throw new Error("signedRequest.tokenId is required"); } if (signedRequest.quantity === undefined || BigInt(signedRequest.quantity.toString()) <= 0n) { throw new Error("signedRequest.quantity is required and must be greater than 0"); } if (signedRequest.price === undefined) { throw new Error("signedRequest.price is required"); } if (!signedRequest.uid) { throw new Error("signedRequest.uid is required"); } if (!signedRequest.signature) { throw new Error("signedRequest.signature is required"); } if (!signedRequest.chainId) { throw new Error("signedRequest.chainId is required"); } // 校验 receiptAddress if (!receiptAddress || !(0, viem_1.isAddress)(receiptAddress)) { throw new Error("receiptAddress is required and must be a valid Ethereum address"); } // 将用户提供的数据转换为标准格式 const formattedRequest = { request: { to: signedRequest.to, tokenId: BigInt(signedRequest.tokenId), quantity: BigInt(signedRequest.quantity), price: BigInt(signedRequest.price), currency: signedRequest.currency, validityStartTimestamp: BigInt(signedRequest.validityStartTimestamp || Math.floor(Date.now() / 1000) + 3600), validityEndTimestamp: BigInt(signedRequest.validityEndTimestamp || Math.floor(Date.now() / 1000) + 3600), uid: signedRequest.uid, chainId: BigInt(signedRequest.chainId || this.network.id), projectHandle: signedRequest.projectHandle, }, signature: signedRequest.signature.startsWith("0x") ? signedRequest.signature : `0x${signedRequest.signature}`, }; // 使用标准方法处理 return await this.buyBadgeWithERC20WithSignedRequest(formattedRequest, receiptAddress, options); } /** * 支付方法,使用 ETH 进行支付 * @deprecated 请使用 sendPayment 方法代替。sendPayment 提供了更灵活的支付选项和更好的类型支持。 * @param orderId 订单 ID * @param amount 支付金额 * @param receiptAddress 接收地址,如果提供,将作为接收地址使用 * @param options 支付选项 * @returns 如果直接发送交易,返回交易收据;否则返回交易数据 */ async pay(orderId, amount, receiptAddress, options) { // 校验 orderId if (!orderId || orderId.trim() === "") { throw new Error("orderId is required and cannot be empty"); } // 校验 receiptAddress if (!receiptAddress || !(0, viem_1.isAddress)(receiptAddress)) { throw new Error("receiptAddress is required and must be a valid Ethereum address"); } // 如果需要发送交易,但没有钱包客户端或账户 if (options?.sendTransaction !== false && (!this.walletClient || !this.account)) { throw new Error("Wallet client and account are required to send transactions"); } // 将 ETH 金额转换为 Wei const paymentAmount = typeof amount === "string" || typeof amount === "number" ? (0, viem_1.parseEther)(amount.toString()) : amount; // 使用 orderId 作为 userData const data = this.encodeUserData(orderId); const calldata = (0, viem_1.encodeFunctionData)({ abi: AnchorPay_json_1.default, functionName: "send", args: [receiptAddress, constants_1.NATIVE_TOKEN_ADDRESS, paymentAmount, data], }); // 如果不需要发送交易,只返回交易数据(AA 模式) if (options?.sendTransaction === false) { return { to: this.contracts?.anchorPay || "", data: calldata, value: paymentAmount, }; } // 发送交易(EOA 模式) const txHash = await this.walletClient.sendTransaction({ account: this.account, to: this.contracts?.anchorPay || "", data: calldata, value: paymentAmount, }); // 等待交易确认并返回收据 const result = await this.publicClient.waitForTransactionReceipt({ hash: txHash, }); // 如果是交易收据并且有 transactionHash,尝试加快交易处理 if (result && typeof result === "object" && "transactionHash" in result) { try { await this.processTransactionHash(result.transactionHash); } catch (error) { // 忽略错误,不影响主流程 console.warn(`Failed to process transaction hash: ${error instanceof Error ? error.message : String(error)}`); } } return result; } /** * 支付方法,使用 ETH 进行支付 * @param params 支付参数 * @param params.callData 自定义数据 * @param params.amount 支付金额 * @param params.receiptAddress 接收地址 * @param params.contractName 合约名称(可选) * @param params.options 支付选项(可选) * @returns 如果直接发送交易,返回交易收据;否则返回交易数据 */ async sendPaymentWithNativeToken(params) { const { callData, amount, receiptAddress, contractName, options } = params; if (!callData || callData.trim() === "") { throw new Error("callData is required and cannot be empty"); } // 校验 receiptAddress if (!receiptAddress || !(0, viem_1.isAddress)(receiptAddress)) { throw new Error("receiptAddress is required and must be a valid Ethereum address"); } // 如果需要发送交易,但没有钱包客户端或账户 if (options?.sendTransaction !== false && (!this.walletClient || !this.account)) { throw new Error("Wallet client and account are required to send transactions"); } // 将 ETH 金额转换为 Wei const paymentAmount = typeof amount === "string" || typeof amount === "number" ? (0, viem_1.parseEther)(amount.toString()) : amount; const interfaceHash = (0, viem_1.keccak256)((0, viem_1.stringToHex)(contractName || "")); const data = (0, viem_1.concat)([interfaceHash, callData]); const calldata = (0, viem_1.encodeFunctionData)({ abi: AnchorPay_json_1.default, functionName: "send", args: [receiptAddress, constants_1.NATIVE_TOKEN_ADDRESS, paymentAmount, data], }); // 如果不需要发送交易,只返回交易数据(AA 模式) if (options?.sendTransaction === false) { return { to: this.contracts?.anchorPay || "", data: calldata, value: paymentAmount, }; } // 发送交易(EOA 模式) const txHash = await this.walletClient.sendTransaction({ account: this.account, to: this.contracts?.anchorPay || "", data: calldata, value: paymentAmount, }); // 等待交易确认并返回收据 const result = await this.publicClient.waitForTransactionReceipt({ hash: txHash, }); // 如果是交易收据并且有 transactionHash,尝试加快交易处理 if (result && typeof result === "object" && "transactionHash" in result) { try { await this.processTransactionHash(result.transactionHash); } catch (error) { // 忽略错误,不影响主流程 console.warn(`Failed to process transaction hash: ${error instanceof Error ? error.message : String(error)}`); } } return result; } /** * 支付方法,使用 ERC20 代币进行支付 * @param params 支付参数 * @param params.callData 自定义数据 * @param params.amount 支付金额 * @param params.receiptAddress 接收地址 * @param params.tokenAddress ERC20 代币地址 * @param params.contractName 合约名称(可选) * @param params.options 支付选项(可选) * @returns 如果直接发送交易,返回交易收据;否则返回交易数据数组 */ async sendPaymentWithERC20(params) { const { callData, amount, receiptAddress, tokenAddress, contractName, options, } = params; if (!callData || callData.trim() === "") { throw new Error("callData is required and cannot be empty"); } // 校验 receiptAddress if (!receiptAddress || !(0, viem_1.isAddress)(receiptAddress)) { throw new Error("receiptAddress is required and must be a valid Ethereum address"); } // 校验 tokenAddress if (!tokenAddress || !(0, viem_1.isAddress)(tokenAddress)) { throw new Error("tokenAddress is required and must be a valid Ethereum address"); } // 获取代币的小数位数 const decimals = (await this.publicClient.readContract({ address: tokenAddress, abi: constants_1.ERC20_ABI, functionName: "decimals", })); // 根据代币的小数位数进行转换 const paymentAmount = typeof amount === "bigint" ? amount : BigInt(Math.floor(Number(amount) * 10 ** decimals)); const interfaceHash = (0, viem_1.keccak256)((0, viem_1.stringToHex)(contractName || "")); console.log(`Interface hash: ${interfaceHash}`); const data = (0, viem_1.concat)([interfaceHash, callData]); console.log(`callData: ${callData}`); console.log(`Data: ${data}`); // 使用 AnchorPayClient 的 sendERC20 方法 const result = await this.anchorPay.sendERC20(receiptAddress, tokenAddress, paymentAmount, data, options); // 如果是交易收据并且有 transactionHash,尝试加快交易处理 if (result && typeof result === "object" && "transactionHash" in result) { try { await this.processTransactionHash(result.transactionHash); } catch (error) { console.warn(`Failed to process transaction hash ${result.transactionHash} with Anchor API: ${error}`); } } return result; } /** * 编码用户数据 * @param orderId 订单 ID * @returns 编码后的数据 */ encodeUserData(orderId) { // 使用 keccak256 哈希函数计算接口哈希 const interfaceHash = (0, viem_1.keccak256)((0, viem_1.stringToHex)("")); console.log(interfaceHash, "interfaceHash"); // 编码订单 ID const encodedOrderId = (0, viem_1.stringToHex)(orderId); // 返回完整的编码数据 console.log((0, viem_1.concat)([interfaceHash, encodedOrderId]), "concat"); return (0, viem_1.concat)([interfaceHash, encodedOrderId]); } /** * 铸造免费 Badge * 从后端获取签名铸造请求,然后调用合约铸造 * @param customerAddress 客户地址 * @param contractAddress NFT 地址 * @param tokenIds badge 的 tokenId 数组 * @param options 选项,可以控制是否直接发送交易 * @returns 如果直接发送交易,返回交易收据;否则返回交易数据 */ async mintFreeBadge(customerAddress, contractAddress, tokenIds, options) { // 校验地址参数 if (!customerAddress || !(0, viem_1.isAddress)(customerAddress)) { throw new Error("customerAddress is required and must be a valid Ethereum address"); } if (!contractAddress || !(0, viem_1.isAddress)(contractAddress)) { throw new Error("contractAddress is required and must be a valid Ethereum address"); } // 校验 tokenIds if (!tokenIds || !Array.isArray(tokenIds) || tokenIds.length === 0) { throw new Error("tokenIds is required and must be a non-empty array"); } for (const id of tokenIds) { if (!id || typeof id !== "string") { throw new Error("Each tokenId must be a non-empty string"); } } if (!this.anchorApi) { throw new Error("Anchor API client not initialized, please check apiBaseUrl configuration"); } // 如果需要发送交易,但没有钱包客户端或账户 if (options?.sendTransaction !== false && (!this.walletClient || !this.account)) { throw new Error("Wallet client and account are required to send transactions"); } // 步骤 1: 从后端获取铸造请求 const mintResponse = await this.anchorApi.getBadgeClaimSignatures({ customerAddress, badgeIds: tokenIds, }); console.log(JSON.stringify(mintResponse)); if (mintResponse.code !== "0000" || !mintResponse.obj || !mintResponse.obj.signatures || mintResponse.obj.signatures.length === 0) { throw new Error(mintResponse.code || "Failed to get mint request from backend"); } if (options?.sendTransaction === false) { const mintRequests = mintResponse.obj.signatures; // 获取所有签名 // 创建批量签名铸造请求数组 const batchUserOp = await Promise.all(mintRequests.map(async (mintRequest) => { // 检查必要字段 if (!mintRequest.badgeId || !mintRequest.signature || !mintRequest.validityStartTimestamp || !mintRequest.validityEndTimestamp) { throw new Error("Mint request missing required fields"); } // 创建签名铸造请求 const signedRequest = { request: { to: customerAddress, tokenId: BigInt(mintRequest.badgeId), quantity: BigInt(mintRequest.quantity || 1), price: BigInt(mintRequest.price || 0), // 从签名中获取价格信息 currency: mintRequest.currency, // ETH validityStartTimestamp: BigInt(mintRequest.validityStartTimestamp), validityEndTimestamp: BigInt(mintRequest.validityEndTimestamp), uid: mintRequest.uid, chainId: BigInt(mintRequest.chainId || this.network.id), projectHandle: mintRequest.projectHandle, }, signature: mintRequest.signature, }; // 检查请求是否有效 this.validateMintRequest(signedRequest.request); // 检查价格是否为 0 if (BigInt(signedRequest.request.price) !== 0n) { throw new Error("Free Badge price must be 0"); } // 返回单个交易数据 return (await this.anchorERC1155.mintWithSignature(signedRequest, contractAddress, { sendTransaction: false })); })); // 返回批量操作数组 return batchUserOp; } // 处理所有铸造请求 let lastReceipt = null; for (const mintRequest of mintResponse.obj.signatures) { // 检查必要字段 if (!mintRequest.badgeId || !mintRequest.signature || !mintRequest.validityStartTimestamp || !mintRequest.validityEndTimestamp) { console.warn("铸造请求缺失必要字段", mintRequest); continue; } // 创建签名铸造请求 const signedRequest = { request: { to: customerAddress, tokenId: BigInt(mintRequest.badgeId), quantity: BigInt(mintRequest.quantity || 1), price: BigInt(mintRequest.price || 0), // 从签名中获取价格信息 currency: mintRequest.currency, // 从签名中获取货币信息 validityStartTimestamp: BigInt(mintRequest.validityStartTimestamp), validityEndTimestamp: BigInt(mintRequest.validityEndTimestamp), uid: mintRequest.uid, // 从签名中获取 uid chainId: BigInt(mintRequest.chainId || this.network.id), // 从签名中获取链 ID projectHandle: mintRequest.projectHandle, // 从签名中获取项目句柄 }, signature: mintRequest.signature, }; // 检查请求是否有效 this.validateMintRequest(signedRequest.request); // 检查价格是否为 0 if (BigInt(signedRequest.request.price) !== 0n) { throw new Error("Free Badge price must be 0"); } console.log(signedRequest, "signedRequest"); // 调用合约铸造(始终发送交易) const result = await this.anchorERC1155.mintWithSignature(signedRequest, contractAddress, { sendTransaction: true }); // 由于指定了 sendTransaction: true,返回的应该是交易收据 if ("transactionHash" in result) { lastReceipt = result; // 尝试加快交易处理 try { await this.processTransactionHash(result.transactionHash); } catch (error) { // 忽略错误,不影响主流程 console.warn(`Failed to process transaction hash: ${error instanceof Error ? error.message : String(error)}`); } } else { throw new Error("Expected transaction receipt, but received transaction data"); } } // 返回最后一个交易收据,或者抛出错误 if (!lastReceipt) { throw new Error("No successful mint transaction"); } return lastReceipt; } /** * 铸造免费 Badge(直接使用签名请求) * @param signedRequest 签名铸造请求 * @param options 选项,可以控制是否直接发送交易 * @returns 如果直接发送交易,返回交易收据;否则返回交易数据 */ async mintFreeBadgeWithSignedRequest(signedRequest, options) { // 检查请求是否有效 this.validateMintRequest(signedRequest.request); // 检查价格是否为 0 if (BigInt(signedRequest.request.price) !== 0n) { throw new Error("Free Badge price must be 0"); } // 调用 mintWithSignature,并传递选项 const result = await this.anchorERC1155.mintWithSignature(signedRequest, signedRequest?.request?.contractAddress, options); // 如果是交易收据并且有 transactionHash,尝试加快交易处理 if (options?.sendTransaction !== false && result && typeof result === "object" && "transactionHash" in result) { try { await this.processTransactionHash(result.transactionHash); } catch (error) { // 忽略错误,不影响主流程 console.warn(`Failed to process transaction hash: ${error instanceof Error ? error.message : String(error)}`); } } return result; } /** * 获取 ERC20 代币批准的交易数据(用于 AA 模式) * @param tokenAddress ERC20 代币地址 * @param amount 批准金额 * @param spender 花费者地址(默认为 AnchorPay 合约地址) * @returns 交易数据对象,包含 to、data 和 value 字段 */ getERC20ApprovalData(tokenAddress, amount, spender) { return this.anchorPay.getERC20ApprovalData(tokenAddress, amount, spender); } /** * 验证铸造请求 * @param request 铸造请求 */ validateMintRequest(request) { // 检查接收者地址 if (!(0, viem_1.isAddress)(request.to)) { throw new Error("Invalid recipient address"); } // 检查有效期 const now = Math.floor(Date.now() / 1000); const endTimestamp = request.validityEndTimestamp; if (endTimestamp && BigInt(endTimestamp) < BigInt(now)) { throw new Error("Mint request expired"); } // 检查数量 if (BigInt(request.quantity) <= 0n) { throw new Error("Quantity must be greater than 0"); } } /** * 处理交易哈希 * 在交易确认后调用该方法可加快交易处理 * @param txHash 交易哈希 * @returns 处理结果或 undefined(如果 API 客户端不可用) */ async processTransactionHash(txHash) { try { if (this.anchorApi) { const result = await this.anchorApi.transactionHashProcess(txHash); console.log(`Transaction processing accelerated: ${txHash}`); return result; } } catch (error) { console.warn(`Failed to accelerate transaction processing: ${error instanceof Error ? error.message : String(error)}`); // 不影响主流程,只记录日志 } return undefined; } /** * 批量铸造徽章(V2) * @param customerAddress 客户地址 * @param contractAddress NFT 合约地址 * @param tokenIds 代币 ID 列表 * @param options 选项,可以控制是否直接发送交易 * @returns 如果直接发送交易,返回最后一个交易收据;AA 模式返回所有交易数据数组 */ async batchMintBadge(customerAddress, contractAddress, tokenIds, options) { if (!this.anchorApi) { throw new Error("Anchor API client not initialized, please check apiBaseUrl configuration"); } // 参数校验 if (!customerAddress || !(0, viem_1.isAddress)(customerAddress)) { throw new Error("customerAddress is required and must be a valid Ethereum address"); } if (!contractAddress || !(0, viem_1.isAddress)(contractAddress)) { throw new Error("contractAddress is required and must be a valid Ethereum address"); } if (!Array.isArray(tokenIds) || tokenIds.length === 0) { throw new Error("tokenIds is required and must be a non-empty array"); } for (const id of tokenIds) { if (!id || typeof id !== "string") { throw new Error("Each tokenId must be a non-empty string"); } } // 如果需要发送交易,但没有钱包客户端或账户 if (options?.sendTransaction !== false && (!this.walletClient || !this.account)) { throw new Error("Wallet client and account are required to send transactions"); } // 步骤 1: 从后端获取批量铸造请求 // 使用新的 API 设计,一次性获取所有徽章的签名 const batchMintResponse = await this.anchorApi.getBadgeClaimSignatures({ customerAddress, badgeIds: tokenIds, }); if (batchMintResponse.code !== "0000" || !batchMintResponse.obj || !batchMintResponse.obj.signatures || batchMintResponse.obj.signatures.length === 0) { throw new Error(batchMintResponse.code || "Failed to get batch mint request from backend"); } const mintRequests = batchMintResponse.obj.signatures; // AA 模式:只返回所有交易数据 if (options?.sendTransaction === false) { const batchUserOp = await Promise.all(mintRequests.map(async (mintRequest) => { // 检查必要字段 if (!mintRequest.badgeId || !mintRequest.signature || !mintRequest.validityStartTimestamp || !mintRequest.validityEndTimestamp) { throw new Error("Mint request missing required fields"); } // 创建签名铸造请求 const signedRequest = { request: { to: customerAddress, tokenId: BigInt(mintRequest.badgeId), quantity: BigInt(mintRequest.quantity || 1), // 默认数量为1 price: BigInt(mintRequest.price || 0), // 从签名中获取价格信息 currency: mintRequest.currency, // 从签名中获取货币信息 validityStartTimestamp: BigInt(mintRequest.validityStartTimestamp), validityEndTimestamp: BigInt(mintRequest.validityEndTimestamp), uid: mintRequest.uid, // 从签名中获取 uid chainId: BigInt(mintRequest.chainId || this.network.id), // 从签名中获取链 ID projectHandle: mintRequest.projectHandle, // 从签名中获取项目句柄 }, signature: mintRequest.signature, }; this.validateMintRequest(signedRequest.request); return (await this.anchorERC1155.mintWithSignature(signedRequest, contractAddress, { sendTransaction: false })); })); return batchUserOp; } // EOA 模式:链上批量 mint,返回最后一个收据 let lastReceipt = null; for (const mintRequest of mintRequests) { if (!mintRequest.badgeId || !mintRequest.signature || !mintRequest.validityStartTimestamp || !mintRequest.validityEndTimestamp) { console.warn("铸造请求缺失必要字段", mintRequest); continue; } const signedRequest = { request: { to: customerAddress, tokenId: BigInt(mintRequest.badgeId), quantity: BigInt(mintRequest.quantity || 1), // 默认数量为1 price: BigInt(mintRequest.price || 0), // 从签名中获取价格信息 currency: mintRequest.currency, // 从签名中获取货币信息 validityStartTimestamp: BigInt(mintRequest.validityStartTimestamp), validityEndTimestamp: BigInt(mintRequest.validityEndTimestamp), uid: mintRequest.uid, // 从签名中获取 uid chainId: BigInt(mintRequest.chainId || this.network.id), // 从签名中获取链 ID projectHandle: mintRequest.projectHandle, // 从签名中获取项目句柄 }, signature: mintRequest.signature, }; this.validateMintRequest(signedRequest.request); const result = await this.anchorERC1155.mintWithSignature(signedRequest, contractAddress, { sendTransaction: true }); if ("transactionHash" in result) { lastReceipt = result; try {