Skip to content

Commit

Permalink
Merge pull request #1 from LIT-Protocol/wyatt/pkp-balance-methods
Browse files Browse the repository at this point in the history
Add `checkPkpBalance`, `fundPkp`, and `readFromChain` methods
  • Loading branch information
spacesailor24 authored Nov 27, 2024
2 parents b0d361e + 94f20d6 commit 31513aa
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 10 deletions.
106 changes: 100 additions & 6 deletions src/lit-oracle-kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ interface FetchToChainParams {
chain: string;
}

/**
* Interface for parameters passed to readLatestDataFromChain
*/
interface ReadFromChainParams {
/** Solidity function signature that will be called to read data */
functionAbi: string;
/** Address of the contract to read from */
contractAddress: string;
/** Chain to read from (e.g., "yellowstone") */
chain: string;
}

/**
* Information about a minted PKP (Programmable Key Pair)
*/
Expand Down Expand Up @@ -272,19 +284,58 @@ export class LitOracleKit {
};

// let's fund the pkp with some gas
const fundingTxn = await this.ethersWallet.sendTransaction({
to: pkpInfo.ethAddress,
value: ethers.utils.parseEther("0.001"),
});
await fundingTxn.wait();
// console.log("Funded PKP!", fundingTxn.hash);
await this.fundPkp(pkpEthAddress);

localStorage.setItem(`pkp-for-ipfsCid-${ipfsCid}`, JSON.stringify(pkpInfo));
console.log(
`Minted PKP with address ${pkpInfo.ethAddress} and funded with 0.001 ETH`
);
return pkpInfo;
}

/**
* Checks the balance of a PKP and funds it if it's low
* @param pkpEthAddress - Ethereum address of the PKP
* @param fundIfLow - Whether to fund the PKP if it's low
* @returns Balance of the PKP in ETH
*/
async checkPkpBalance(
pkpEthAddress: string,
fundIfLow: boolean = false
): Promise<string> {
let balance = await this.ethersWallet.provider!.getBalance(
pkpEthAddress,
"latest"
);
let balanceInEth = ethers.utils.formatEther(balance);
console.log(`PKP balance: ${balanceInEth} ETH`);

if (fundIfLow && parseFloat(balanceInEth) <= 0.00001) {
await this.fundPkp(pkpEthAddress);

return this.checkPkpBalance(pkpEthAddress, false);
}

return balanceInEth;
}

/**
* Funds a PKP with 0.001 ETH if the balance is less than 0.00001 ETH
* @param pkpEthAddress - Ethereum address of the PKP
* @returns Transaction hash of the funding transaction
*/
async fundPkp(pkpEthAddress: string): Promise<string> {
console.log(`Funding PKP with 0.001 ETH`);
const fundingTxn = await this.ethersWallet.sendTransaction({
to: pkpEthAddress,
value: ethers.utils.parseEther("0.001"),
});
await fundingTxn.wait();
console.log(`Funded PKP: ${fundingTxn.hash}`);

return fundingTxn.hash;
}

/**
* Executes a Lit Action to fetch data and write it to a smart contract
* @param params - Parameters specifying the data source, contract, and function to call
Expand Down Expand Up @@ -324,6 +375,7 @@ export class LitOracleKit {
pkpInfo = await this.mintAndBindPkp(ipfsCid);
} else {
pkpInfo = JSON.parse(pkpFromLocalStorage);
await this.checkPkpBalance(pkpInfo.ethAddress, true);
}
console.log(`Writing data to chain from PKP address ${pkpInfo.ethAddress}`);
const pkpPublicKey = pkpInfo.publicKey;
Expand All @@ -348,6 +400,48 @@ export class LitOracleKit {
return result;
}

/**
* Reads data from a smart contract on the blockchain
* @param params - Parameters specifying the function to call and the contract to read from
* @returns Response from the read operation
*
* @example
* ```typescript
* const weatherData = await sdk.readFromChain<WeatherData>({
* functionAbi: "function currentWeather() view returns (int256 temperature, uint8 precipitationProbability, uint256 lastUpdated)",
* contractAddress: "0xE2c2A8A1f52f8B19A46C97A6468628db80d31673",
* chain: "yellowstone"
* });
* ```
*/
async readFromChain<T>(params: ReadFromChainParams): Promise<T> {
if (!this.litNodeClient.ready) {
await this.connect();
}

const provider = new ethers.providers.JsonRpcProvider(
LIT_RPC.CHRONICLE_YELLOWSTONE
);

// Create contract interface using the provided function ABI
const contractInterface = new ethers.utils.Interface([params.functionAbi]);

const contract = new ethers.Contract(
params.contractAddress,
contractInterface,
provider
);

// Get the function name from the ABI
const functionName =
contractInterface.functions[Object.keys(contractInterface.functions)[0]]
.name;

// Call the function and return the result
const result = await contract[functionName]();
return result as T;
}

/**
* Tests a data source function without writing to chain
* @param dataSource - JavaScript code that fetches and returns data
Expand Down
36 changes: 32 additions & 4 deletions test/lit-oracle-kit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ import { LitOracleKit } from "../src/lit-oracle-kit";
import { LitNodeClientNodeJs } from "@lit-protocol/lit-node-client-nodejs";

describe("LitOracleKit Integration Tests", () => {
const deployedWeatherOracleContractAddress =
"0xE2c2A8A1f52f8B19A46C97A6468628db80d31673";

let sdk: LitOracleKit;

beforeAll(async () => {
Expand All @@ -20,6 +23,10 @@ describe("LitOracleKit Integration Tests", () => {
await sdk.connect();
});

afterAll(async () => {
await sdk.disconnect();
});

test("should successfully connect to Lit Network", async () => {
expect(sdk.ready()).toBe(true);
});
Expand All @@ -42,8 +49,6 @@ describe("LitOracleKit Integration Tests", () => {
}, 30000); // Increased timeout for network requests

test("should execute a Lit Action that fetches weather data", async () => {
const deployedWeatherOracleContractAddress =
"0xE2c2A8A1f52f8B19A46C97A6468628db80d31673";
const result = await sdk.writeToChain({
dataSource: `
const url = "https://api.weather.gov/gridpoints/LWX/97,71/forecast";
Expand All @@ -66,7 +71,30 @@ describe("LitOracleKit Integration Tests", () => {
// Verify the response contains the expected data structure
expect(functionArgs).toHaveLength(2);
expect(txnHash).toBeDefined();

await sdk.disconnect();
}, 30000); // Increased timeout for network requests

test("should read latest weather data from chain", async () => {
interface WeatherData {
temperature: bigint;
precipitationProbability: bigint;
lastUpdated: bigint;
}

const weatherData = await sdk.readFromChain<WeatherData>({
functionAbi:
"function currentWeather() view returns (int256 temperature, uint8 precipitationProbability, uint256 lastUpdated)",
contractAddress: deployedWeatherOracleContractAddress,
chain: "yellowstone",
});

// Verify the response contains the expected data structure
expect(weatherData.temperature).toBeDefined();
expect(weatherData.precipitationProbability).toBeDefined();
expect(weatherData.lastUpdated).toBeDefined();

// Verify the lastUpdated timestamp is recent (within last 2 minutes)
const lastUpdatedDate = new Date(Number(weatherData.lastUpdated) * 1000);
const twoMinutesAgo = new Date(Date.now() - 2 * 60 * 1000);
expect(lastUpdatedDate.getTime()).toBeGreaterThan(twoMinutesAgo.getTime());
}, 60000); // Increased timeout for network requests
});

0 comments on commit 31513aa

Please sign in to comment.