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
JavaScript
"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 {