LYS Labs x Chainlink Functions Guide
Creating a Basic Smart Contract for Polygon Using LYS Labs API
This guide walks you through creating a basic smart contract on Polygon that integrates with the LYS Labs Solana Aggregation API using Chainlink Functions.
Overview
This example demonstrates how to: 1. Create a smart contract that requests data from an external API 2. Use Chainlink Functions to fetch data from the LYS Labs API 3. Process and store the API response on-chain 4. Deploy the contract to Polygon
Prerequisites
Node.js (v18 or higher)
pnpm package manager
A Polygon wallet with testnet tokens (for Polygon Amoy) or mainnet
tokens
Chainlink Functions subscription (for making API calls)
Access to the LYS Labs API (API key if required)
Step 1: Create LYS Labs API Account and Get API Key
Before you can use the LYS Labs Solana Aggregation API with Chainlink Functions, you need to create an account and obtain an API key.
1.1 Access the API Documentation
Visit the LYS Labs API Documentation to understand the available endpoints and authentication requirements.
1.2 Create an Account
1. Navigate to the LYS Labs Developer Portal
2. Click on “Sign Up” or “Create Account”
3. Sign up for a new account using your email address 4. Verify your email address if required
5. Complete any necessary account setup steps1.3 Generate an API Key
Log in to your account at https://dev-stage.lyslabs.ai/
Navigate to the API Keys section in your dashboard (this may be under
Settings, API Management, or Developer Tools)
Click “Create New API Key” or “Generate API Key”
Give your API key a descriptive name (e.g., “Chainlink Functions Integration”)
Copy the generated API key immediately - you may not be able to view it again
Save the API key securely - you’ll need it for the next steps
Important: Keep your API key secure and never commit it to version control. You’ll store it as a secret in Chainlink Functions, not in your code or .env file.
1.4 Understand API Limits
Review the API documentation for rate limits and usage quotas
Check if there are any restrictions on the number of requests per
day/month
Understand the pricing model if applicable
1.5 Test Your API Key (Optional)
You can test your API key locally before integrating with Chainlink:
# Test with curl
curl -H “x-api-key: YOUR_API_KEY” \
https://agg-api-solana-mainnet.lyslabs-stage.xyz/v1/aggregated/SOLANA_MINT_ADDRESS
Or create a simple test script:
// test-api-key.js
const apiKey = “your-api-key-here”;
const solMint = “So11111111111111111111111111111111111111112”;
fetch(`https://agg-api-solana-mainnet.lyslabs-stage.xyz/v1/aggregated/${solMint}`, {
headers: {
“x-api-key”: apiKey
}
})
.then(res => res.json()) .then(data => {console.log(” API key is valid!”);
console.log(JSON.stringify(data, null, 2)); })
.catch(err => {
console.error(” API request failed:”, err);});
Once you have your API key, proceed to the next step. You’ll configure it inChainlink Functions secrets in Step 9.
If you’re starting from scratch, initialize a new Hardhat project:
# Install dependencies
pnpm install
# Or if starting fresh:
npm init -y
pnpm add hardhat @nomicfoundation/hardhat-toolbox
pnpm add @chainlink/contracts @openzeppelin/contracts ethers
pnpm add -D typescript ts-node @types/node dotenv
Step 2: Project Setup
Create a .env file in the project root:
Note: Replace the placeholder values below with your actual configuration. For Chainlink Functions addresses, see the “Network Configuration” section at the end of this guide.
# Polygon Network Configuration
POLYGON_RPC_URL=https://polygon-amoy.g.alchemy.com/v2/YOUR_API_KEY
POLYGON_WALLET_PRIVATE_KEY=your_private_key_here
# Contract Address (update after deployment)
MIRROR_CONTRACT=0xYourContractAddress
# Chainlink Functions Configuration
# Get these values from: https://docs.chain.link/chainlink-functions/supported-networks
# Example values for Polygon Amoy testnet are provided in the Network Configuration section
CHAINLINK_FUNCTIONS_ROUTER=0xYourChainlinkFunctionsRouterAddress
CHAINLINK_DON_ID=0xYourChainlinkDONId
CHAINLINK_SUBSCRIPTION_ID=your_subscription_id
# LYS Labs API Configuration
# Note: The API key will be stored in Chainlink Functions secrets (see Step 9)
# Do NOT store your API key in this .env file or commit it to version control
LYS_API_URL=https://agg-api-solana-mainnet.lyslabs-stage.xyz
Step 4: Create the Smart Contract
Create a new contract file contracts/LysApiConsumer.sol: // SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {FunctionsClient} from “@chainlink/contracts/src/v0.8/functions/v1_0_0/FunctionsClien
import {FunctionsRequest} from “@chainlink/contracts/src/v0.8/functions/v1_0_0/libraries/Fun
import “@openzeppelin/contracts/access/Ownable.sol”;
/**
* @title LysApiConsumer
* @notice A basic smart contract that fetches data from LYS Labs Solana Aggregation API
* @dev Uses Chainlink Functions to make HTTP requests to external APIs
*/
contract LysApiConsumer is FunctionsClient, Ownable {
using FunctionsRequest for FunctionsRequest.Request;
bytes32 public donId;
uint64 public subscriptionId;
// Storage for API responses
mapping(bytes32 => string) public requestData;
mapping(bytes32 => bool) public requestFulfilled;
// Events
event ApiRequestSent(bytes32 indexed requestId, string solMint);
event ApiResponseReceived(bytes32 indexed requestId, string data);
event RequestFailed(bytes32 indexed requestId, string error);
/**
* @notice Constructor
* @param _router Chainlink Functions Router address
* @param _donId DON ID for the Chainlink Functions network
* @param _subscriptionId Your Chainlink Functions subscription ID
*/
constructor(
address _router,
bytes32 _donId,
uint64 _subscriptionId
) FunctionsClient(_router) Ownable(msg.sender) {
donId = _donId;
subscriptionId = _subscriptionId;
}
/**
* @notice Request data from LYS Labs API for a Solana token
* @param solMint The Solana token mint address
* @param sourceCode The JavaScript source code for Chainlink Functions
* @param args Array of arguments to pass to the source code
* @return requestId The Chainlink Functions request ID
*/
function requestSolanaData(
string calldata solMint,
string calldata sourceCode,
string[] calldata args
) external onlyOwner returns (bytes32) {
FunctionsRequest.Request memory req;
// Initialize request with JavaScript source code
req.initializeRequestForInlineJavaScript(sourceCode);
// Set arguments (e.g., solMint address)
req.setArgs(args);
// Send the request to Chainlink Functions
bytes32 requestId = _sendRequest(
req.encodeCBOR(),
subscriptionId,
300000, // Gas limit for the callback
donId
);
emit ApiRequestSent(requestId, solMint);
return requestId;
}
/**
* @notice Callback function called by Chainlink Functions with API response
* @param requestId The request ID from the original request
* @param response The API response data
* @param err Error message if the request failed
*/
function fulfillRequest(
bytes32 requestId,
bytes memory response,
bytes memory err
) internal override {
if (err.length > 0) {
string memory errorMessage = string(err);
emit RequestFailed(requestId, errorMessage);
return;
}
// Store the response
string memory data = string(response);
requestData[requestId] = data;
requestFulfilled[requestId] = true;
emit ApiResponseReceived(requestId, data);
}
/**
* @notice Get stored data for a request
* @param requestId The request ID to query
* @return data The stored API response data
* @return fulfilled Whether the request has been fulfilled
*/
function getRequestData(bytes32 requestId)
external
view
returns (string memory data, bool fulfilled)
{
return (requestData[requestId], requestFulfilled[requestId]);
}
/**
* @notice Update subscription ID (only owner)
* @param _subscriptionId New subscription ID
*/
function setSubscriptionId(uint64 _subscriptionId) external onlyOwner {
subscriptionId = _subscriptionId;
} }
Step 5: Create Chainlink Functions Source Code
Create src/chainlink/lys-api-source.js:
/**
* Chainlink Functions JavaScript source code
* Fetches data from LYS Labs Solana Aggregation API
*
* API Documentation: https://agg-api-solana-mainnet.lyslabs-stage.xyz/api-docs
*/
// Get arguments passed from the contract
const solMint = args[0]; // Solana token mint address
// Get API key from Chainlink Secrets (configured in Chainlink Functions UI) const apiKey = secrets.apiKey; // Your LYS Labs API key
// Construct the API URL
// Adjust the endpoint based on LYS Labs API documentation
const baseUrl = “https://agg-api-solana-mainnet.lyslabs-stage.xyz”;
const endpoint = `/v1/aggregated/${solMint}`; // Adjust based on actual API endpoints const url = `${baseUrl}${endpoint}`;
// Make HTTP request to LYS Labs API
const requestConfig = { url: url,
method: “GET”,
headers: {
“Content-Type”: “application/json”,
// Add API key if required
...(apiKey ? { “x-api-key”: apiKey } : {})
} };
const response = await Functions.makeHttpRequest(requestConfig);
// Handle errors
if (response.error) {
throw new Error(`API request failed: ${response.error}`);
}
// Parse the response
const data = response.data;
// Extract relevant data based on LYS Labs API response structure // Adjust these fields based on the actual API response schema const tokenData = {
mint: solMint,
name: data.name || data.lifecycle?.name || “Unknown”, symbol: data.symbol || data.lifecycle?.symbol || “TOK”, supply: data.supply || data.lifecycle?.uiAmount || 0, // Add more fields as needed
};
// Encode the response for on-chain storage
// Adjust the return type based on what your contract expects return Functions.encodeString(JSON.stringify(tokenData));
// Alternative: Return structured data if your contract decodes it
// return Functions.encodeAbi(
// [”string”, “string”, “string”, “uint256”],
// [solMint, tokenData.name, tokenData.symbol, tokenData.supply]
// );
Step 6: Create Deployment Script
Create ignition/modules/LysApiConsumer.ts:
import { buildModule } from “@nomicfoundation/hardhat-ignition/modules”;
// Chainlink Functions configuration
// IMPORTANT: Replace these placeholders with actual addresses from:
// https://docs.chain.link/chainlink-functions/supported-networks
// Example values for Polygon Amoy testnet are provided in the Network Configuration section const ROUTER_ADDRESS = “0xYourChainlinkFunctionsRouterAddress”;
const DON_ID = “0xYourChainlinkDONId”;
const SUBSCRIPTION_ID = 0; // Update this with your actual subscription ID
export default buildModule(”LysApiConsumer”, (m) => {
const router = m.getParameter(”router”, ROUTER_ADDRESS);
const donId = m.getParameter(”donId”, DON_ID);
const subscriptionId = m.getParameter(”subscriptionId”, SUBSCRIPTION_ID);
const lysApiConsumer = m.contract(”LysApiConsumer”, [ router,
donId,
subscriptionId,
]);
return { lysApiConsumer }; });
Step 7: Create Interaction Script
Create scripts/request-lys-data.ts:
import { ethers } from “ethers”; import fs from “fs”;
import path from “path”;
import { fileURLToPath } from “url”; import dotenv from “dotenv”;
dotenv.config();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Contract ABI (minimal for this example)
const CONTRACT_ABI = [
“function requestSolanaData(string calldata solMint, string calldata sourceCode, string[] “event ApiRequestSent(bytes32 indexed requestId, string solMint)”,
“function getRequestData(bytes32 requestId) external view returns (string memory data, boo
];
async function requestLysData(solMint: string) { // Validate environment variables
if (!process.env.POLYGON_RPC_URL) {
throw new Error(”POLYGON_RPC_URL not set in .env”); }
if (!process.env.POLYGON_WALLET_PRIVATE_KEY) {
throw new Error(”POLYGON_WALLET_PRIVATE_KEY not set in .env”);
}
if (!process.env.LYS_CONTRACT_ADDRESS) {
throw new Error(”LYS_CONTRACT_ADDRESS not set in .env”);
}
// Setup provider and wallet
const provider = new ethers.JsonRpcProvider(process.env.POLYGON_RPC_URL); const wallet = new ethers.Wallet(
process.env.POLYGON_WALLET_PRIVATE_KEY,
provider );
console.log(”Using wallet:”, wallet.address);
// Connect to contract
const contract = new ethers.Contract( process.env.LYS_CONTRACT_ADDRESS, CONTRACT_ABI,
wallet
);
// Load JavaScript source code
const sourceCode = fs.readFileSync(
path.join(__dirname, “../src/chainlink/lys-api-source.js”), “utf8”
);
// Prepare arguments
const args = [solMint];
console.log(`Requesting data for Solana mint: ${solMint}`);
// Send transaction
const tx = await contract.requestSolanaData(solMint, sourceCode, args); console.log(”Transaction sent:”, tx.hash);
// Wait for confirmation
const receipt = await tx.wait();
console.log(”Transaction confirmed in block:”, receipt.blockNumber);
// Listen for the event
const event = receipt.logs.find( (log: any) =>
log.topics[0] ===
ethers.id(”ApiRequestSent(bytes32,string)”)
);
if (event) {
const decoded = contract.interface.decodeEventLog(
“ApiRequestSent”,
event.data,
event.topics
);
const requestId = decoded.requestId; console.log(”Request ID:”, requestId);
// Poll for fulfillment (in production, use event listeners)
console.log(”Waiting for Chainlink Functions to fulfill request...”); let fulfilled = false;
let attempts = 0;
const maxAttempts = 60; // Wait up to 5 minutes
while (!fulfilled && attempts < maxAttempts) {
await new Promise((resolve) => setTimeout(resolve, 5000)); // Wait 5 seconds const result = await contract.getRequestData(requestId);
fulfilled = result[1]; // fulfilled boolean
if (fulfilled) {
console.log(”Request fulfilled!”); console.log(”Data:”, result[0]); // data string
} else {
attempts++;
console.log(`Waiting... (${attempts}/${maxAttempts})`);
} }
if (!fulfilled) {
console.log(”Request not fulfilled within timeout period”);
} }
}
// Get Solana mint from command line argument
const solMint = process.argv[2];
if (!solMint) {
console.error(”Usage: pnpm tsx scripts/request-lys-data.ts <SOLANA_MINT_ADDRESS>”); process.exit(1);
}
requestLysData(solMint) .then(() => process.exit(0)) .catch((error) => {
console.error(error);
process.exit(1);
});
Step 8: Deploy the Contract
Compile the contract: pnpm hardhat compile
Deploy to Polygon Amoy testnet:
pnpm hardhat ignition deploy ignition/modules/LysApiConsumer.ts --network polygonAmoyUpdate your .env file with the deployed contract address: LYS_CONTRACT_ADDRESS=0xYourDeployedContractAddress
Step 9: Configure Chainlink Functions
1. Create a Chainlink Functions subscription: • Go to Chainlink Functions UI
• Create a new subscription on Polygon Amoy • Fund it with LINK tokens
• Note your subscription ID and update it in .env 2. Configure secrets (API keys):
• In the Chainlink Functions UI, navigate to the Secrets section
• Add your LYS Labs API key (obtained in Step 1) as a secret
• NameitapiKey(thismatchesthenameusedinlys-api-source.js) • Save the secret - it will be encrypted and used by Chainlink Functions
when making API calls
3. Update the subscription ID in your deployment module or contract
Step 10: Make API Requests
Run the interaction script to request data from the LYS Labs API:
pnpm tsx scripts/request-lys-data.ts <SOLANA_MINT_ADDRESS> Example:
pnpm tsx scripts/request-lys-data.ts So11111111111111111111111111111111111111112
Step 11: Understanding the API Response
The LYS Labs API response structure may vary. Check the API documentation to understand:
• Available endpoints
• Request/response formats
• Authentication requirements • Rate limits
Adjust the lys-api-source.js file to match the actual API structure.
Troubleshooting
Common Issues
“Insufficient LINK”: Ensure your Chainlink Functions subscription has enough LINK tokens
“Invalid subscription”: Verify your subscription ID is correct
“API request failed”: Check your API key and endpoint URL
“Gas estimation failed”: Ensure your wallet has enough MATIC for
gas
Testing Locally
You can test the JavaScript source code locally before deploying:
// test-api-source.js
const solMint = “So11111111111111111111111111111111111111112”; const apiKey = “your-api-key”;
const url = `https://agg-api-solana-mainnet.lyslabs-stage.xyz/v1/aggregated/${solMint}`;
fetch(url, {
headers: {
“x-api-key”: apiKey
}
})
.then(res => res.json())
.then(data => console.log(JSON.stringify(data, null, 2))) .catch(err => console.error(err));
Next Steps
• Parse and store specific data fields on-chain • Add access control for who can request data
• Implement caching to avoid redundant API calls
• Add error handling and retry logic
• Create events for better off-chain monitoring
• Deploy to Polygon mainnet (update router and DON ID)
Resources
• Chainlink Functions Documentation • LYS Labs API Documentation
• Hardhat Documentation
• OpenZeppelin Contracts
Network Configuration
Important: Always verify the latest Chainlink Functions addresses from the official documentation as they may change.
Polygon Amoy Testnet
• Chain ID: 80002
• Functions Router: 0xC22a79eBA640940ABB6dF0f7982cc119578E11De
(verify at Chainlink Functions docs)
• DONID:0x66756e2d706f6c79676f6e2d616d6f792d310000000000000000000000000000
(verify at Chainlink Functions docs)
Polygon Mainnet
• Chain ID: 137
• Functions Router: Check Chainlink Functions docs for current address • DON ID: Check Chainlink Functions docs for mainnet DON ID
Finding the Correct Addresses
Visit the Chainlink Functions Supported Networks page
Select your target network (Polygon Amoy for testnet, Polygon for mainnet)
Copy the Functions Router address
Copy the DON ID (also called DON Hosted Secrets ID)
Replace the placeholders in your .env file and deployment script with
these values