@spheron/protocol-sdk
Version:
Spheron Protocol SDK
1,668 lines (1,447 loc) • 112 kB
TypeScript
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