UNPKG

@spheron/protocol-sdk

Version:
1,668 lines (1,447 loc) 112 kB
import { ethers } from 'ethers'; import { NexusClient } from '@biconomy/abstractjs'; import { BundlerClient } from 'viem/_types/account-abstraction'; interface RpcUrls { http: string; websocket: string; } type NetworkType = 'testnet' | 'mainnet'; type gaslessOptions = { type: 'biconomy' | 'coinbase'; bundlerUrl: string; paymasterUrl: string; }; interface ProviderDetails { name: string; region: string; attributes: string; hostUri: string; certificate: string; paymentsAccepted: string[]; status: string; trust: number; timestamp: number; } declare enum TransactionStatus { SUCCESS = 'success', FAILURE = 'failure', } interface TransactionData { rewardWallet: string; tokenAddress: string; amount: number; decimals: number; onSuccessCallback?: (data: unknown) => void; onFailureCallback?: (data: unknown) => void; } interface DepositData { token: string; amount: number; onSuccessCallback?: (data: unknown) => void; onFailureCallback?: (data: unknown) => void; } interface WithdrawData extends DepositData { operator?: string; } interface TokenDetails { name: string; symbol: string; decimal: number; } interface UserBalance { lockedBalance: string; unlockedBalance: string; token: TokenDetails; } interface WithdrawEarningsData { providerAddress: string; fizzId: string; token: string; amount: number; isFizz: boolean; } interface DepositForOperatorData extends DepositData { operatorAddresses: string[]; } type SmartWalletBundlerClient = BundlerClient | NexusClient; declare class EscrowModule { private provider: ethers.Provider; private wallet: ethers.Wallet | undefined; private networkType: NetworkType; constructor( provider: ethers.Provider, wallet?: ethers.Wallet, networkType: NetworkType = 'mainnet', private smartWalletBundlerClientPromise?: Promise<SmartWalletBundlerClient> ) { this.provider = provider; this.wallet = wallet; this.networkType = networkType; this.smartWalletBundlerClientPromise = smartWalletBundlerClientPromise; } async getUserBalance(token: string, walletAddress?: string, isOperator: boolean = false) { const contractAbi = abiMap[this.networkType].escrow; try { const contractAddress = contractAddresses[this.networkType].escrow; const contract = new ethers.Contract(contractAddress, contractAbi, this.provider); const tokenDetails = tokenMap[this.networkType].find( (eachToken) => eachToken.symbol.toLowerCase() === token.toLowerCase() ); if (!tokenDetails) { throw new Error('Provided token symbol is invalid.'); } const tokenAddress: string = tokenDetails?.address || ethers.ZeroAddress; let userWalletAddress; if (walletAddress) { userWalletAddress = walletAddress; } else { if (this.wallet) { userWalletAddress = await this.wallet.getAddress(); } else { throw new Error('No wallet address provided'); } } const response = await contract.getUserData(userWalletAddress, tokenAddress, isOperator); const userData: UserBalance = { lockedBalance: response[0].toString(), unlockedBalance: response[1].toString(), token: { name: tokenDetails?.name, symbol: tokenDetails?.symbol, decimal: tokenDetails?.decimal, }, }; return userData; } catch (error) { const errorMessage = handleContractError(error, contractAbi); throw errorMessage; } } async getSmartWalletDetails(): Promise<{ accountAddress: string; balance: string }> { if (this.smartWalletBundlerClientPromise) { const smartWalletBundlerClient = await this.smartWalletBundlerClientPromise; const accountAddress = await smartWalletBundlerClient?.account?.address; if (!accountAddress) { throw new Error('Smart wallet account address not found'); } const uSponToken = tokenMap[this.networkType].find( (token) => token.symbol.toLowerCase() === 'uspon' ); if (!uSponToken) { throw new Error('uSPON token not found'); } const tokenContract = new ethers.Contract( uSponToken.address, abiMap[this.networkType].testToken, this.provider ); const balance = await tokenContract.balanceOf(accountAddress); return { accountAddress, balance: balance.toString(), }; } else { throw new Error('Gasless options not provided'); } } async depositBalance({ token, amount, onSuccessCallback, onFailureCallback }: DepositData) { const contractABI = abiMap[this.networkType].escrow; try { if (this.smartWalletBundlerClientPromise) { return await this.depositBalanceGasless({ token, amount, onSuccessCallback, onFailureCallback, }); } const { signer } = await initializeSigner({ wallet: this.wallet }); const contractAddress = contractAddresses[this.networkType].escrow; const tokenABI = abiMap[this.networkType].testToken; const tokenDetails = tokenMap[this.networkType].find( (eachToken) => eachToken.symbol.toLowerCase() === token.toLowerCase() ); if (!tokenDetails) { throw new Error('Provided token symbol is invalid.'); } const decimals = tokenDetails?.decimal ?? 18; const tokenAddress: string = tokenDetails?.address; const contract = new ethers.Contract(contractAddress, contractABI, signer); const tokenContract = new ethers.Contract(tokenAddress, tokenABI, signer); const finalAmount = Number(amount.toString()); const depositAmount = ethers.parseUnits(finalAmount.toFixed(decimals), decimals); const approvalTxn = await tokenContract.approve(contractAddress, depositAmount); await approvalTxn.wait(); const result = await contract.deposit(tokenAddress, depositAmount); const receipt = await result.wait(); if (onSuccessCallback) onSuccessCallback(receipt); return receipt; } catch (error) { if (onFailureCallback) onFailureCallback(error); const errorMessage = handleContractError(error, contractABI); throw errorMessage; } } private async depositBalanceGasless({ token, amount, onSuccessCallback, onFailureCallback, }: DepositData) { const contractABI = abiMap[this.networkType].escrow; try { const contractAddress = contractAddresses[this.networkType].escrow; const network = await this.provider.getNetwork(); const chainId = network.chainId; const { signer } = await initializeSigner({ wallet: this.wallet }); const signerAddress = signer.address; // Get the current nonce for the signer const contract = new ethers.Contract(contractAddress, contractABI, signer); const nonce = await contract.nonces(signerAddress); const tokenDetails = tokenMap[this.networkType].find( (eachToken) => eachToken.symbol.toLowerCase() === token.toLowerCase() ); if (!tokenDetails) { throw new Error('Provided token symbol is invalid.'); } const decimals = tokenDetails?.decimal ?? 18; const tokenAddress: string = tokenDetails?.address; const finalAmount = Number(amount.toString()); const depositAmount = ethers.parseUnits(finalAmount.toFixed(decimals), decimals); const deadline = Math.floor(Date.now() / 1000) + SIGNATURE_DEADLINE; const domain = { name: 'Spheron', version: '1', chainId, verifyingContract: contractAddress, }; const types = { Deposit: [ { name: 'token', type: 'address' }, { name: 'amount', type: 'uint256' }, { name: 'nonce', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, ], }; const value = { token: tokenAddress, amount: depositAmount, nonce, deadline, }; // Sign the typed data using EIP-712 const signature = await signer.signTypedData(domain, types, value); const smartWalletBundlerClient = await this.smartWalletBundlerClientPromise; const tokenABI = abiMap[this.networkType].testToken; const approveTxnHash = await smartWalletBundlerClient?.sendUserOperation({ calls: [ { abi: tokenABI, functionName: 'approve', to: tokenAddress as `0x${string}`, args: [contractAddress, depositAmount], }, ], }); await smartWalletBundlerClient?.waitForUserOperationReceipt({ hash: approveTxnHash!, }); const txHash = await smartWalletBundlerClient?.sendUserOperation({ calls: [ { abi: contractABI, functionName: 'depositWithSignature', to: contractAddress as `0x${string}`, args: [tokenAddress, depositAmount, signerAddress, signature, nonce, deadline], }, ], }); const txReceipt = await smartWalletBundlerClient?.waitForUserOperationReceipt({ hash: txHash!, }); if (onSuccessCallback) onSuccessCallback(txReceipt?.receipt); return txReceipt?.receipt; } catch (error) { if (onFailureCallback) onFailureCallback(error); const errorMessage = handleContractError(error, contractABI); throw errorMessage; } } async withdrawBalance({ token, amount, operator = ethers.ZeroAddress, onSuccessCallback, onFailureCallback, }: WithdrawData) { const contractABI = abiMap[this.networkType].escrow; try { if (this.smartWalletBundlerClientPromise) { return await this.withdrawBalanceGasless({ token, amount, operator, onSuccessCallback, onFailureCallback, }); } const { signer } = await initializeSigner({ wallet: this.wallet }); const contractAddress = contractAddresses[this.networkType].escrow; const tokenDetails = tokenMap[this.networkType].find( (eachToken) => eachToken.symbol.toLowerCase() === token.toLowerCase() ); if (!tokenDetails) { throw new Error('Provided token symbol is invalid.'); } const decimals = tokenDetails?.decimal ?? 18; const tokenAddress: string = tokenDetails?.address; const contract = new ethers.Contract(contractAddress, contractABI, signer); const finalAmount = Number(amount.toString()); const withdrawAmount = ethers.parseUnits(finalAmount.toFixed(decimals), decimals); const result = await contract.withdraw(tokenAddress, withdrawAmount, operator); const receipt = await result.wait(); if (onSuccessCallback) onSuccessCallback(receipt); return receipt; } catch (error) { if (onFailureCallback) onFailureCallback(error); const errorMessage = handleContractError(error, contractABI); throw errorMessage; } } private async withdrawBalanceGasless({ token, amount, operator = ethers.ZeroAddress, onSuccessCallback, onFailureCallback, }: WithdrawData) { const contractABI = abiMap[this.networkType].escrow; try { const network = await this.provider.getNetwork(); const chainId = network.chainId; const { signer } = await initializeSigner({ wallet: this.wallet }); const signerAddress = signer.address; const tokenDetails = tokenMap[this.networkType].find( (eachToken) => eachToken.symbol.toLowerCase() === token.toLowerCase() ); if (!tokenDetails) { throw new Error('Provided token symbol is invalid.'); } const decimals = tokenDetails?.decimal ?? 18; const tokenAddress: string = tokenDetails?.address; const finalAmount = Number(amount.toString()); const withdrawAmount = ethers.parseUnits(finalAmount.toFixed(decimals), decimals); const contractAddress = contractAddresses[this.networkType].escrow; const contract = new ethers.Contract(contractAddress, contractABI, signer); const deadline = Math.floor(Date.now() / 1000 + SIGNATURE_DEADLINE); // Get the current nonce for the signer const nonce = await contract.nonces(signerAddress); const domain = { name: 'Spheron', version: '1', chainId, verifyingContract: contractAddress, }; const types = { Withdraw: [ { name: 'token', type: 'address' }, { name: 'amount', type: 'uint256' }, { name: 'operator', type: 'address' }, { name: 'nonce', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, ], }; const value = { token: tokenAddress, amount: withdrawAmount, operator, nonce, deadline, }; // Sign the typed data using EIP-712 const signature = await signer.signTypedData(domain, types, value); const smartWalletBundlerClient = await this.smartWalletBundlerClientPromise; const txHash = await smartWalletBundlerClient?.sendUserOperation({ calls: [ { abi: contractABI, functionName: 'withdrawWithSignature', to: contractAddress as `0x${string}`, args: [ tokenAddress, withdrawAmount, operator, signerAddress, signature, nonce, deadline, ], }, ], }); const txReceipt = await smartWalletBundlerClient?.waitForUserOperationReceipt({ hash: txHash!, }); if (onSuccessCallback) onSuccessCallback(txReceipt?.receipt); return txReceipt?.receipt; } catch (error) { if (onFailureCallback) onFailureCallback(error); const errorMessage = handleContractError(error, contractABI); throw errorMessage; } } async getProviderEarnings(providerAddress: string, tokenAddress: string) { const contractABI = abiMap[this.networkType].escrowProtocol; try { const contractAddress = contractAddresses[this.networkType].escrowProtocol; const contract = new ethers.Contract(contractAddress, contractABI, this.provider); const response = await contract.getProviderEarnings(providerAddress, tokenAddress); const providerEarnings: { earned: string; withdrawn: string; balance: string } = { earned: response[0].toString(), withdrawn: response[1].toString(), balance: response[2].toString(), }; return providerEarnings; } catch (error) { const errorMessage = handleContractError(error, contractABI); throw errorMessage; } } async getFizzEarnings(fizzAddress: string, tokenAddress: string) { const contractABI = abiMap[this.networkType].escrowProtocol; try { const contractAddress = contractAddresses[this.networkType].escrowProtocol; const contract = new ethers.Contract(contractAddress, contractABI, this.provider); const response = await contract.getFizzNodeEarnings(fizzAddress, tokenAddress); const fizzEarnings: { earned: string; withdrawn: string; balance: string } = { earned: response[0].toString(), withdrawn: response[1].toString(), balance: response[2].toString(), }; return fizzEarnings; } catch (error) { const errorMessage = handleContractError(error, contractABI); throw errorMessage; } } async withdrawEarnings({ providerAddress, fizzId = '0', token, amount }: WithdrawEarningsData) { const contractABI = abiMap[this.networkType].escrowProtocol; try { const { signer } = await initializeSigner({ wallet: this.wallet }); const contractAddress = contractAddresses[this.networkType].escrowProtocol; const tokenDetails = tokenMap[this.networkType].find( (eachToken) => eachToken.symbol.toLowerCase() === token.toLowerCase() ); if (!tokenDetails) { throw new Error('Provided token Symbol is invalid.'); } const tokenAddress: string = tokenDetails?.address; const contract = new ethers.Contract(contractAddress, contractABI, signer); const result = await contract.withdrawEarnings(providerAddress, fizzId, tokenAddress, amount); const receipt = await result.wait(); return receipt; } catch (error) { const errorMessage = handleContractError(error, contractABI); throw errorMessage; } } async depositForOperators({ token, amount, operatorAddresses, onSuccessCallback, onFailureCallback, }: DepositForOperatorData) { const contractABI = abiMap[this.networkType].escrow; try { if (this.smartWalletBundlerClientPromise) { return await this.depositForOperatorsGasless({ token, amount, operatorAddresses, onSuccessCallback, onFailureCallback, }); } const { signer } = await initializeSigner({ wallet: this.wallet }); const contractAddress = contractAddresses[this.networkType].escrow; const contract = new ethers.Contract(contractAddress, contractABI, signer); const tokenABI = abiMap[this.networkType].testToken; const tokenDetails = tokenMap[this.networkType].find( (eachToken) => eachToken.symbol.toLowerCase() === token.toLowerCase() ); if (!tokenDetails) { throw new Error('Provided token Symbol is invalid.'); } const decimals = tokenDetails?.decimal ?? 18; const tokenAddress: string = tokenDetails?.address; const tokenContract = new ethers.Contract(tokenAddress, tokenABI, signer); const finalAmount = Number(amount.toString()); const depositAmount = ethers.parseUnits(finalAmount.toFixed(decimals), decimals); const approvalTxn = await tokenContract.approve(contractAddress, depositAmount); await approvalTxn.wait(); const result = await contract.depositForOperators(tokenAddress, amount, operatorAddresses); const receipt = await result.wait(); if (onSuccessCallback) onSuccessCallback(receipt); return receipt; } catch (error) { if (onFailureCallback) onFailureCallback(error); const errorMessage = handleContractError(error, contractABI); throw errorMessage; } } private async depositForOperatorsGasless({ token, amount, operatorAddresses, onFailureCallback, onSuccessCallback, }: DepositForOperatorData) { const contractAbi = abiMap[this.networkType].escrow; try { const contractAddress = contractAddresses[this.networkType].escrow; const network = await this.provider.getNetwork(); const chainId = network.chainId; const { signer } = await initializeSigner({ wallet: this.wallet }); const signerAddress = signer.address; const tokenABI = abiMap[this.networkType].testToken; const contract = new ethers.Contract(contractAddress, contractAbi, signer); const nonce = await contract.nonces(signerAddress); const tokenDetails = tokenMap[this.networkType].find( (eachToken) => eachToken.symbol.toLowerCase() === token.toLowerCase() ); if (!tokenDetails) { throw new Error('Provided token Symbol is invalid.'); } const decimals = tokenDetails?.decimal ?? 18; const tokenAddress: string = tokenDetails?.address; const finalAmount = Number(amount.toString()); const depositAmount = ethers.parseUnits(finalAmount.toString(), decimals); const deadline = Math.floor(Date.now() / 1000) + SIGNATURE_DEADLINE * 1000; const smartWalletBundlerClient = await this.smartWalletBundlerClientPromise; const approveTxnHash = await smartWalletBundlerClient?.sendUserOperation({ calls: [ { abi: tokenABI, functionName: 'approve', to: tokenAddress as `0x${string}`, args: [contractAddress, depositAmount], }, ], }); await smartWalletBundlerClient?.waitForUserOperationReceipt({ hash: approveTxnHash!, }); const domain = { name: 'Spheron', version: '1', chainId, verifyingContract: contractAddress, }; const types = { Deposit: [ { name: 'token', type: 'address' }, { name: 'amount', type: 'uint256' }, { name: 'nonce', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, ], }; const value = { token: tokenAddress, amount: depositAmount, nonce, deadline, }; const signature = await signer.signTypedData(domain, types, value); const txHash = await smartWalletBundlerClient?.sendUserOperation({ calls: [ { abi: contractAbi, functionName: 'depositForOperatorsWithSignature', to: contractAddress as `0x${string}`, args: [ tokenAddress, depositAmount, operatorAddresses, signerAddress, signature, nonce, deadline, ], }, ], }); const txReceipt = await smartWalletBundlerClient?.waitForUserOperationReceipt({ hash: txHash!, }); if (onSuccessCallback) onSuccessCallback(txReceipt?.receipt); return txReceipt?.receipt; } catch (error) { if (onFailureCallback) onFailureCallback(error); const errorMessage = handleContractError(error, contractAbi); throw errorMessage; } } } declare enum Tier { One, Two, Three, Four, Five, Six, Seven, } declare enum Mode { Fizz, Provider, } interface OrderDetails { maxPrice: bigint; numOfBlocks: bigint; token: string; spec: string; version: number | bigint; mode: Mode; tier: Tier[]; } declare enum OrderState { OPEN = 'open', PROVISIONED = 'provisioned', CLOSED = 'closed', MATCHED = 'matched', } interface OrderSpecs { specs: string; version: string; mode: string; tier: Tier[]; } interface InitialOrder { id: number; name: string; region: string; maxPrice: number; numOfBlocks: number; token?: { symbol?: string; decimal?: number; address: string; }; creator: string; state: OrderState; specs: OrderSpecs; } interface OrderMatchedEvent { leaseId: string; providerAddress: string; fizzId: string | number | bigint; providerId: string | number | bigint; acceptedPrice: string | number | bigint; creatorAddress: string; } interface OrderUpdatedEvent { leaseId: string; providerAddress: string; tenantAddress: string; acceptedPrice: string | number | bigint; } interface OrderUpdateAcceptedEvent { leaseId: string; providerAddress: string; } declare class OrderModule { private provider: ethers.Provider; private createTimeoutId: NodeJS.Timeout | null; private updateTimeoutId: NodeJS.Timeout | null; private wallet: ethers.Wallet | undefined; private networkType: NetworkType | undefined; private rpcUrls: RpcUrls | undefined; constructor( provider: ethers.Provider, wallet?: ethers.Wallet, networkType?: NetworkType, private smartWalletBundlerClientPromise?: Promise<SmartWalletBundlerClient>, rpcUrls?: RpcUrls ) { this.provider = provider; this.createTimeoutId = null; this.updateTimeoutId = null; this.wallet = wallet; this.networkType = networkType; this.smartWalletBundlerClientPromise = smartWalletBundlerClientPromise; this.rpcUrls = rpcUrls; } async createOrder(orderDetails: OrderDetails): Promise<string | null> { const contractAbi = abiMap[this.networkType as NetworkType].orderRequest; try { if (this.smartWalletBundlerClientPromise) { return await this.createOrderWithPaymaster(orderDetails); } const { signer } = await initializeSigner({ wallet: this.wallet }); const contractAddress = contractAddresses[this.networkType as NetworkType].orderRequest; const contractAbi = abiMap[this.networkType as NetworkType].orderRequest; const contract = new ethers.Contract(contractAddress, contractAbi, signer); const tx: ethers.ContractTransactionResponse = await contract.createOrder(orderDetails); const receipt: ethers.ContractTransactionReceipt | null = await tx.wait(); return receipt?.hash || null; } catch (error) { if (this.smartWalletBundlerClientPromise) { throw error; } const errorMessage = handleContractError(error, contractAbi); throw errorMessage; } } async createOrderWithPaymaster(orderDetails: OrderDetails): Promise<string | null> { const network = await this.provider.getNetwork(); const chainId = network.chainId; const { signer } = await initializeSigner({ wallet: this.wallet }); const claimedSigner = signer.address; const contractAddress = contractAddresses[this.networkType as NetworkType].orderRequest; const contractAbi = abiMap[this.networkType as NetworkType].orderRequest; const contract = new ethers.Contract(contractAddress, contractAbi, signer); const nonce = await contract.nonces(claimedSigner); const deadline = Math.floor(Date.now() / 1000 + SIGNATURE_DEADLINE); const domain = { name: 'Spheron', version: '1', chainId, verifyingContract: contractAddress, }; const types = { CreateOrder: [ { name: 'maxPrice', type: 'uint256' }, { name: 'numOfBlocks', type: 'uint64' }, { name: 'token', type: 'address' }, { name: 'nonce', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, ], }; const value = { maxPrice: orderDetails.maxPrice, numOfBlocks: orderDetails.numOfBlocks, token: orderDetails.token, nonce, deadline, }; // Sign the typed data using EIP-712 const signature = await signer.signTypedData(domain, types, value); const smartWalletBundlerClient = await this.smartWalletBundlerClientPromise; try { const txHash = await smartWalletBundlerClient?.sendUserOperation({ calls: [ { abi: contractAbi, functionName: 'createOrderWithSignature', to: contractAddress as `0x${string}`, args: [orderDetails, claimedSigner, signature, nonce, deadline], }, ], }); const txReceipt = await smartWalletBundlerClient?.waitForUserOperationReceipt({ hash: txHash!, }); return txReceipt?.receipt.transactionHash || null; } catch (error) { throw error; } } async updateOrder(orderId: string, orderDetails: OrderDetails): Promise<string | null> { const contractAbi = abiMap[this.networkType as NetworkType].orderRequest; try { if (this.smartWalletBundlerClientPromise) { return await this.updateOrderWithPaymaster(orderId, orderDetails); } const { signer } = await initializeSigner({ wallet: this.wallet }); const contractAddress = contractAddresses[this.networkType as NetworkType].orderRequest; const contract = new ethers.Contract(contractAddress, contractAbi, signer); const tx: ethers.ContractTransactionResponse = await contract.updateInitialOrder( orderId, orderDetails ); const receipt: ethers.ContractTransactionReceipt | null = await tx.wait(); return receipt?.hash || null; } catch (error) { if (this.smartWalletBundlerClientPromise) { throw error; } const errorMessage = handleContractError(error, contractAbi); throw errorMessage; } } async updateOrderWithPaymaster( orderId: string, orderDetails: OrderDetails ): Promise<string | null> { const network = await this.provider.getNetwork(); const chainId = network.chainId; const { signer } = await initializeSigner({ wallet: this.wallet }); const claimedSigner = signer.address; const contractAddress = contractAddresses[this.networkType as NetworkType].orderRequest; const contractAbi = abiMap[this.networkType as NetworkType].orderRequest; const contract = new ethers.Contract(contractAddress, contractAbi, signer); const nonce = await contract.nonces(claimedSigner); const deadline = Math.floor(Date.now() / 1000 + SIGNATURE_DEADLINE); const domain = { name: 'Spheron', version: '1', chainId, verifyingContract: contractAddress, }; const types = { UpdateInitialOrder: [ { name: 'orderId', type: 'uint64' }, { name: 'maxPrice', type: 'uint256' }, { name: 'numOfBlocks', type: 'uint64' }, { name: 'token', type: 'address' }, { name: 'nonce', type: 'uint256' }, { name: 'deadline', type: 'uint256' }, ], }; const value = { orderId: orderId, maxPrice: orderDetails.maxPrice, numOfBlocks: orderDetails.numOfBlocks, token: orderDetails.token, nonce, deadline, }; // Sign the typed data using EIP-712 const signature = await signer.signTypedData(domain, types, value); const smartWalletBundlerClient = await this.smartWalletBundlerClientPromise; try { const txHash = await smartWalletBundlerClient?.sendUserOperation({ calls: [ { abi: contractAbi, functionName: 'updateInitialOrderWithSignature', to: contractAddress as `0x${string}`, args: [orderId, orderDetails, claimedSigner, signature, nonce, deadline], }, ], }); const txReceipt = await smartWalletBundlerClient?.waitForUserOperationReceipt({ hash: txHash!, }); return txReceipt?.receipt.transactionHash || null; } catch (error) { throw error; } } async getOrderDetails(leaseId: string): Promise<InitialOrder> { const contractAbi = abiMap[this.networkType as NetworkType].orderRequest; const contractAddress = contractAddresses[this.networkType as NetworkType].orderRequest; const contract = new ethers.Contract(contractAddress, contractAbi, this.provider); const response = await contract.getOrderById(leaseId); const specs = { specs: response.specs.specs, version: response.specs.version, mode: response.specs.mode, tier: response.specs.tier.map((t: bigint) => Number(t)) as Tier[], }; const tokenDetails = getTokenDetails(response.token, this.networkType as NetworkType); const token = { symbol: tokenDetails?.symbol, decimal: tokenDetails?.decimal, address: tokenDetails?.address, }; return { id: response.id.toString(), maxPrice: Number(response.maxPrice), numOfBlocks: Number(response.numOfBlocks), token, creator: response.creator, state: getOrderStateAsString(response.state), specs, } as InitialOrder; } async listenToOrderCreated( timeoutTime = 60000, onSuccessCallback: ( leaseId: string, providerAddress: string, fizzId: string | number | bigint, providerId: string | number | bigint, acceptedPrice: string | number | bigint, creatorAddress: string ) => void, onFailureCallback: () => void ): Promise<OrderMatchedEvent> { let orderWssProvider: WebSocketProvider | null = null; if (this.rpcUrls?.websocket) { orderWssProvider = new ethers.WebSocketProvider(this.rpcUrls?.websocket); } if (!orderWssProvider) { throw new Error('Order WSS provider not created'); } const { signer } = await initializeSigner({ wallet: this.wallet }); const account = await signer.getAddress(); const contractAbi = abiMap[this.networkType as NetworkType].bid; const contractAddress = contractAddresses[this.networkType as NetworkType].bid; const contract = new ethers.Contract(contractAddress, contractAbi, orderWssProvider); return new Promise((resolve, reject) => { this.createTimeoutId = setTimeout(() => { contract.off('OrderMatched'); orderWssProvider.destroy(); onFailureCallback(); reject({ error: true, msg: 'Order not matched within timeout' }); }, timeoutTime); contract.on( 'OrderMatched', ( leaseId: string, providerAddress: string, fizzId: string | number | bigint, providerId: string | number | bigint, acceptedPrice: string | number | bigint, creatorAddress: string ) => { if (creatorAddress.toString().toLowerCase() === account.toString().toLowerCase()) { onSuccessCallback( leaseId, providerAddress, fizzId, providerId, acceptedPrice, creatorAddress ); contract.off('OrderMatched'); orderWssProvider.destroy(); clearTimeout(this.createTimeoutId as NodeJS.Timeout); resolve({ leaseId, providerAddress, fizzId, providerId, acceptedPrice, creatorAddress, }); } } ); }); } async listenToOrderUpdated( timeoutTime = 60000, onSuccessCallback: ( leaseId: string, providerAddress: string, tenantAddress?: string, acceptedPrice?: string ) => void, onFailureCallback: () => void ): Promise<OrderUpdatedEvent> { let orderWssProvider: WebSocketProvider | null = null; if (this.rpcUrls?.websocket) { orderWssProvider = new ethers.WebSocketProvider(this.rpcUrls?.websocket); } if (!orderWssProvider) { throw new Error('Order WSS provider not created'); } const { signer } = await initializeSigner({ wallet: this.wallet }); const account = await signer.getAddress(); const contractAbi = abiMap[this.networkType as NetworkType].bid; const contractAddress = contractAddresses[this.networkType as NetworkType].bid; const contract = new ethers.Contract(contractAddress, contractAbi, orderWssProvider); return new Promise((resolve, reject) => { this.updateTimeoutId = setTimeout(() => { contract.off('LeaseUpdated'); orderWssProvider.destroy(); onFailureCallback(); reject({ error: true, msg: 'Order update failed' }); }, timeoutTime); contract.on('LeaseUpdated', (leaseId, providerAddress, tenantAddress, acceptedPrice) => { if (tenantAddress.toString().toLowerCase() === account.toString().toLowerCase()) { onSuccessCallback(leaseId, providerAddress, tenantAddress, acceptedPrice?.toString()); contract.off('LeaseUpdated'); orderWssProvider.destroy(); clearTimeout(this.updateTimeoutId as NodeJS.Timeout); resolve({ leaseId, providerAddress, tenantAddress, acceptedPrice }); } }); }); } async listenToOrderUpdateAccepted( timeoutTime = 60000, onSuccessCallback: (leaseId: string, providerAddress: string) => void, onFailureCallback: () => void ): Promise<OrderUpdateAcceptedEvent> { let orderWssProvider: WebSocketProvider | null = null; if (this.rpcUrls?.websocket) { orderWssProvider = new ethers.WebSocketProvider(this.rpcUrls?.websocket); } if (!orderWssProvider) { throw new Error('Order WSS provider not created'); } const { signer } = await initializeSigner({ wallet: this.wallet }); const account = await signer.getAddress(); const contractAbi = abiMap[this.networkType as NetworkType].bid; const contractAddress = contractAddresses[this.networkType as NetworkType].bid; const contract = new ethers.Contract(contractAddress, contractAbi, orderWssProvider); return new Promise((resolve, reject) => { this.updateTimeoutId = setTimeout(() => { contract.off('UpdateRequestAccepted'); orderWssProvider.destroy(); onFailureCallback(); reject({ error: true, msg: 'Order not accepted within timeout' }); }, timeoutTime); contract.on('UpdateRequestAccepted', async (leaseId, providerAddress, tenantAddress) => { if (tenantAddress.toString().toLowerCase() === account.toString().toLowerCase()) { await onSuccessCallback(leaseId, providerAddress); contract.off('UpdateRequestAccepted'); orderWssProvider.destroy(); clearTimeout(this.updateTimeoutId as NodeJS.Timeout); resolve({ leaseId, providerAddress }); } }); }); } } declare enum LeaseState { ACTIVE = 'active', TERMINATED = 'terminated', } interface Lease { leaseId: string; fizzId: string; requestId: string; acceptedPrice: number; leaseHourlyCost: number; providerAddress: string; tenantAddress: string; startBlock: string; startTime: number; endTime: number; state: LeaseState; } interface LeaseWithOrderDetails extends Lease { name: string; region: string; tier: Tier[]; token: { symbol?: string; decimal?: number; }; } interface FizzParams { providerId: bigint; spec: string; walletAddress: string; paymentsAccepted: string[]; rewardWallet: string; } type RawFizzNode = [ bigint, // fizzId bigint, // providerId string, // spec string, // walletAddress string[], // paymentsAccepted bigint, // status bigint, // joinTimestamp string // rewardWallet ]; interface FizzNode { fizzId: bigint; providerId: bigint; region: string; spec: string; walletAddress: string; paymentsAccepted: string[]; status: number; joinTimestamp: bigint; rewardWallet: string; } interface FizzDetails { region: string; providerId: bigint; spec: string; walletAddress: string; paymentsAccepted: string[]; status: bigint; joinTimestamp: bigint; rewardWallet: string; } interface ResourceCategory { name: string; registry: string; baseReward: bigint; } interface Resource { name: string; tier: string; multiplier: bigint; } interface ResourceAttributes { cpuUnits: bigint; cpuAttributes: string[]; ramUnits: bigint; ramAttributes: string[]; gpuUnits: bigint; gpuAttributes: string[]; endpointsKind: number; endpointsSequenceNumber: number; } interface FizzLease { leaseId: bigint; fizzId: bigint; requestId: bigint; resourceAttribute: ResourceAttributes; acceptedPrice: bigint; providerAddress: string; tenantAddress: string; startBlock: bigint; startTime: bigint; endTime: bigint; state: string; } declare enum FizzProviderStatus { Unregistered, Registered, Active, Maintenance, Suspended, Deactivated, } declare enum FizzProviderTrustTier { One, Two, Three, Four, Five, Six, Seven, } interface FizzProvider { providerId: bigint; name: string; region: string; walletAddress: string; paymentsAccepted: string[]; spec: string; hostUri: string; certificate: string; status: FizzProviderStatus; tier: FizzProviderTrustTier; joinTimestamp: bigint; rewardWallet: string; } interface FizzAttribute { id: bigint | string; units: bigint | string; } type RawFizzAttribute = [id: string, units: string]; interface FizzStatusResponse { name: string; allocatable: { cpu: number; gpu: number; gpu_infos: | null | { vendor: string; name: string; model_id: string; interface: string; memory_size: string; vram_available_percentage: number; }[]; memory: number; storage_ephemeral: number; }; available: { cpu: number; gpu: number; gpu_infos: null; memory: number; storage_ephemeral: number; }; bandwidth: `${string};${string}`; version: `${string};${string}`; os: string; arch: string; cuda_version: string; nvidia_driver_version: string; } interface IProvider { providerId: string; spec: string; hostUri: string; certificate: string; paymentsAccepted: string[]; status: string; trust: number; timestamp: number; } interface Attribute { id: bigint | string; units: bigint | string; } declare enum ProviderStatus { Unregistered, Registered, Active, Maintenance, Suspended, Deactivated, } declare enum ProviderTrustTier { One, Two, Three, Four, Five, Six, Seven, } interface Provider { providerId?: bigint; name: string; region: string; spec: string; walletAddress: string; paymentsAccepted: string[]; hostUri: string; certificate: string; status: ProviderStatus; tier: ProviderTrustTier; joinTimestamp: bigint; rewardWallet: string; } type Category = 'CPU' | 'GPU'; type RawProviderAttribute = [id: string, units: string]; declare class ProviderModule { private provider: ethers.Provider; private networkType: NetworkType | undefined; constructor(provider: ethers.Provider, networkType?: NetworkType) { this.provider = provider; this.networkType = networkType; } async getProviderDetails(providerAddress: string): Promise<IProvider> { if (!providerAddress) { throw new Error('Pass Provider Address'); } if (!isValidEthereumAddress(providerAddress)) { throw new Error('Pass Valid Address'); } try { const contractAbi = abiMap[this.networkType as NetworkType].providerRegistry; const contractAddress = contractAddresses[this.networkType as NetworkType].providerRegistry; const contract = new ethers.Contract(contractAddress, contractAbi, this.provider); const response = await contract.getProviderByAddress(providerAddress); const providerId = await contract.addressToProviderId(providerAddress); const providerDetailsData: IProvider = { providerId: providerId.toString(), spec: response[0], hostUri: response[1], certificate: response[2], paymentsAccepted: response[3], status: response[4].toString(), trust: Number(response[5].toString()) + 1, timestamp: Number(response[6].toString()), }; return providerDetailsData; } catch (error) { const errorMessage = handleContractError(error, ProviderRegistryAbi); throw errorMessage; } } async getProviderPendingAttributes(providerAddress: string, category: Category) { if (!providerAddress) { throw new Error('Pass Provider Address'); } if (!isValidEthereumAddress(providerAddress)) { throw new Error('Pass Valid Address'); } if (!category) { throw new Error('Please pass a category'); } try { const contractAbi = abiMap[this.networkType as NetworkType].providerAttributeRegistry; const contractAddress = contractAddresses[this.networkType as NetworkType].providerAttributeRegistry; const contract = new ethers.Contract(contractAddress, contractAbi, this.provider); const response = await contract.getProviderPendingAttributes(providerAddress, category); return response; } catch (error) { const errorMessage = handleContractError(error, ProviderAttributeRegistryAbi); throw errorMessage; } } async getProviderAttributes(providerAddress: string, category: Category) { if (!providerAddress) { throw new Error('Pass Provider Address'); } if (!isValidEthereumAddress(providerAddress)) { throw new Error('Pass Valid Address'); } if (!category) { throw new Error('Please pass a category'); } try { const contractAbi = abiMap[this.networkType as NetworkType].providerAttributeRegistry; const contractAddress = contractAddresses[this.networkType as NetworkType].providerAttributeRegistry; const contract = new ethers.Contract(contractAddress, contractAbi, this.provider); const response = await contract.getAttributes(providerAddress, category); return response; } catch (error) { const errorMessage = handleContractError(error, ProviderAttributeRegistryAbi); throw errorMessage; } } async getProvider(providerId: bigint): Promise<Provider> { try { const contractAddress = contractAddresses[this.networkType as NetworkType].providerRegistry; const contractAbi = abiMap[this.networkType as NetworkType].providerRegistry; const contract = new ethers.Contract(contractAddress, contractAbi, this.provider); const providerData = await contract.getProvider(providerId); let name, region; try { const { Name, Region } = JSON.parse(providerData.spec); name = Name; region = Region; } catch { try { const { Name, Region } = decompressProviderSpec(providerData.spec) as { Name: string; Region: string; }; name = Name; region = Region; } catch { name = ''; region = ''; } } return { name, region, spec: providerData.spec, hostUri: providerData.hostUri, certificate: providerData.certificate, paymentsAccepted: providerData.paymentsAccepted, status: providerData.status, tier: providerData.tier, joinTimestamp: providerData.joinTimestamp, walletAddress: providerData.walletAddress, rewardWallet: providerData.rewardWallet, }; } catch (error) { const errorMessage = handleContractError(error, ProviderRegistryAbi); throw errorMessage; } } async getProviderByAddress(walletAddress: string): Promise<Provider> { try { const contractAddress = contractAddresses[this.networkType as NetworkType].providerRegistry; const contractAbi = abiMap[this.networkType as NetworkType].providerRegistry; const contract = new ethers.Contract(contractAddress, contractAbi, this.provider); const providerData = await contract.getProviderByAddress(walletAddress); let name, region; try { const { Name, Region } = JSON.parse(providerData.spec); name = Name; region = Region; } catch { try { const { Name, Region } = decompressProviderSpec(providerData.spec) as { Name: string; Region: string; }; name = Name; region = Region; } catch { name = ''; region = ''; } } return { name, region, spec: providerData.spec, hostUri: providerData.hostUri, certificate: providerData.certificate, paymentsAccepted: providerData.paymentsAccepted, status: providerData.status, tier: providerData.tier, joinTimestamp: providerData.joinTimestamp, walletAddress, rewardWallet: providerData.rewardWallet, }; } catch (error) { const errorMessage = handleContractError(error, ProviderRegistryAbi); throw errorMessage; } } async getAllProviders(): Promise<Provider[]> { try { const contractAddress = contractAddresses[this.networkType as NetworkType].providerRegistry; const contractAbi = abiMap[this.networkType as NetworkType].providerRegistry; const contract = new ethers.Contract(contractAddress, contractAbi, this.provider); const providersData = await contract.getAllProviders(); const providers: Provider[] = providersData.map((provider: Provider) => { let name, region; try { const { Name, Region } = JSON.parse(provider.spec as string); name = Name; region = Region; } catch { try { const { Name, Region } = decompressProviderSpec(provider.spec as string) as { Name: string; Region: string; }; name = Name; region = Region; } catch { name = ''; region = ''; } } return { name, region, providerId: (provider.providerId as bigint).toString(), walletAddress: provider.walletAddress, paymentsAccepted: provider.paymentsAccepted, spec: provider.spec, hostUri: provider.hostUri, certificate: provider.certificate, status: ProviderStatus[provider.status], tier: Number(provider.tier.toString()), // tier: ProviderTrustTier[provider.tier], joinTimestamp: Number(provider.joinTimestamp.toString()), rewardWallet: provider.rewardWallet, }; }); return providers; } catch (error) { const errorMessage = handleContractError(error, ProviderRegistryAbi); throw errorMessage; } } async getAttributes(providerAddress: string, category: string): Promise<Attribute[]> { try { const contractAddress = contractAddresses[this.networkType as NetworkType].providerAttributeRegistry; const contractAbi = abiMap[this.networkType as NetworkType].providerAttributeRegistry; const contract = new ethers.Contract(contractAddress, contractAbi, this.provider); const attributes: RawProviderAttribute[] = await contract.getAttributes( providerAddress, category ); const decoratedAttributes = attributes.map((attr: RawProviderAttribute) => ({ id: attr[0], units: attr[1], })); return decoratedAttributes; } catch (error) { const errorMessage = handleContractError(error, ProviderRegistryAbi); throw errorMessage; } } async getPendingAttributes(providerAddress: string, category: string): Promise<Attribute[]> { try { const contractAddress = contractAddresses[this.networkType as Net