LinkLabs (ElizaOS+Functions)
  • Chainlink Workshop: Use AI agent to mint an NFT gift
    • Use an AI agent to interact with Blockchains
  • 1. Create table in Supabase
  • 2. Deploy the GetGift.sol
  • 3 Register A CL Functions Subscription
  • 4. Prepare DON hosted secrets
  • 5. Setup and start Eliza agent
  • 6. Use twitter client
Powered by GitBook
On this page
  • Where are we?
  • How to use API key in Chainlink Functions?
  • Use Functions-Toolkit inside Remix
  • Steps to upload secret to Chainlink Functions DON
Export as PDF

4. Prepare DON hosted secrets

Previous3 Register A CL Functions SubscriptionNext5. Setup and start Eliza agent

Last updated 3 days ago

Where are we?

How to use API key in Chainlink Functions?

In this workshop, we choose save the Supabase anon API key in the DON. Before the secrets upload, we need to encrypt the API key with the DON's mater key. @chainlink/functions-toolkitis a NPM (NodeJS Package Manager) library that can be used to encrypt and upload secrets to DON.

Use Functions-Toolkit inside Remix

The Remix Foundation and Chainlink have partnered to make some Chainlink NPM packages available directly inside remix, so you don't need to write separate scripts and run them in VS Code etc.

Enable Chainlink Packages in Remix

First we need to configure scripting dependencies inside Remix IDE as below:

Steps to upload secret to Chainlink Functions DON

We can encrypt the secrets and then upload to the Chainlink Functions DON using the below script. Create a new file in file explorer called script.js. Copy the js code below and paste to the file.

const { SecretsManager, SubscriptionManager } = require("@chainlink/functions-toolkit");

/**
 * DEV TODOS
 * 1. make sure your metamask is connected to Avalanche Fuji
 * 2. Have sufficient AVAX on Fuji
 * 3. update Constants below if you're using another network.
 */

const functionsRouterAddress = "0xA9d587a00A31A52Ed70D6026794a8FC5E2F5dCb0"; // Fuji
const donId = "fun-avalanche-fuji-1"; // Fuji
const linkTokenAddress = "0x0b9d5D9136855f6FEc3c0993feE6E9CE8a297846" // Fuji
const LINK_AMOUNT = "0.000001"

const GATEWAY_URLS = ["https://01.functions-gateway.testnet.chain.link/", "https://02.functions-gateway.testnet.chain.link/"] // Fuji

let provider, signer

// initialize
async function init() {
    provider = new ethers.providers.Web3Provider(window.ethereum);
    signer = provider.getSigner();

    // Switch to Avalanche
    await provider.send("wallet_switchEthereumChain", [{ chainId: '0xA869' }]); // Fuji Chain ID in hex
    n = await signer.provider.getNetwork();
    console.log("NETWORK IS:  ", n)


    if (!provider || !signer) {
        throw Error("MISSING PROVIDER OR SIGNER")
    } else {
        console.info("Connection initialized")
    }
}


const encryptAndUploadSecrets = async () => {
    const secretsManager = new SecretsManager({
        signer: signer,
        functionsRouterAddress: functionsRouterAddress,
        donId: donId,
    });
    await secretsManager.initialize();

    const SLOT_ID = 0;
    const expirationTimeMinutes = 1440;

    // Encrypt secrets
    const secrets = { apikey: "TODO TODO  ANON KEY TODO TODO" };
    const encryptedSecretsObj = await secretsManager.encryptSecrets(secrets);

    console.log(
        `Upload encrypted secret to gateways ${GATEWAY_URLS}. slotId ${SLOT_ID}. Expiration in minutes: ${expirationTimeMinutes}`
    );

    // Upload secrets
    const uploadResult = await secretsManager.uploadEncryptedSecretsToDON({
        encryptedSecretsHexstring: encryptedSecretsObj.encryptedSecrets,
        gatewayUrls: GATEWAY_URLS,
        slotId: SLOT_ID,
        minutesUntilExpiration: expirationTimeMinutes,
    });

    if (!uploadResult.success) throw new Error(`Encrypted secrets not uploaded to ${GATEWAY_URLS}`);

    console.log(`\n✅ Secrets uploaded properly to gateways ${GATEWAY_URLS}! Gateways response: `, uploadResult);

    const donHostedSecretsVersion = parseInt(uploadResult.version); // fetch the reference of the encrypted secrets

    console.log(`donHostedSecretsVersion is ${donHostedSecretsVersion}`);
}



const createAndFundSub = async (consumerAddress = undefined) => {
    const subscriptionManager = new SubscriptionManager({
        signer,
        linkTokenAddress,
        functionsRouterAddress,
    });

    await subscriptionManager.initialize();

    // Create Subscription
    const subscriptionId = await subscriptionManager.createSubscription();
    console.log(`\nSubscription ${subscriptionId} created.`);

    // add consumer to subscription
    let addConsumerReceipt
    if (consumerAddress) {
        const addConsumerReceipt = await subscriptionManager.addConsumer({
            subscriptionId,
            consumerAddress,
        })
        console.log(
            `\nSubscription ${subscriptionId} now has ${consumerAddress} as a consumer. Tx Receipt: ${addConsumerReceipt})`
        );
    }


    // Fund Subscription
    const juelsAmount = ethers.utils.parseUnits(LINK_AMOUNT, 18).toString();
    console.log(`Funding Subscription ${subscriptionId} with ${juelsAmount} juels`)

    await subscriptionManager.fundSubscription({
        subscriptionId,
        juelsAmount,
    });

    console.log(
        `\nSubscription ${subscriptionId} funded with ${LINK_AMOUNT} LINK. Done.`
    );
};

async function main() {
    await init()
    await encryptAndUploadSecrets()
    //   await createAndFundSub()

}

main().catch((e) => { console.log("ERROR:   ", e) })

IMPORTANT: Ensure that you have to add your Supabase anon key to the script (Get the key from your Supabase Organization > Project > Project Settings > API Keys):

const secrets = { apikey: "TODO TODO  ANON KEY TODO TODO" };

Also ensure that these two function calls inside main() at the bottom of the script are uncommented.

await init()
await encryptAndUploadSecrets()

Then click on the green "Play" button(see the image below) on the upper left of editor and watch the Remix IDE terminal for updates.

Metamask will then open twice to get you to confirm the transaction and sign the upload request. Once you've confirmed the Metamask transactions the script will communicate with the Chainlink Functions DON gateways and send your encrypted secrets to be hosted on the DON securely.

When successfully uploaded you should see something like:

Connection initialized
Upload encrypted secret to gateways https://01.functions-gateway.testnet.chain.link/,https://02.functions-gateway.testnet.chain.link/. slotId 0. Expiration in minutes: 1440

✅ Secrets uploaded properly to gateways https://01.functions-gateway.testnet.chain.link/,https://02.functions-gateway.testnet.chain.link/! Gateways response: 
{"version":1748493210,"success":true} 
donHostedSecretsVersion is 1748493210

For now, the secret is encrypted and saved in DON, and you will have a secret version code in your Remix Terminal. Make a note of the version number printed (and do not clear your Remix console) as you will need this as input shortly.

The secret version will expire in 24 hours. If you want to update the secrets within 24 hours, run the command again and the old secret will be overwrited.

Before the Chainlink Functions help us to send an API call, it has to know the Supabase API "anon" key with which to communicate with our database. Chainlink Functions provides 2 ways to host secrets that the DON can consume when executing the custom JS: and .

In Chainlink Functions, secrets are encrypted as a security feature, regardless of which of the 2 ways you use. Instead of a centralized private key, the DON uses a Master Key and partitions the master key into distinct shares and each node in the DON has only one "shard" of the key. This encryption method is called and it ensures the high system availability and fault tolerance.

The is a collection of JS helper functions that help you interact with Chainlink Functions smart contracts to create and manage subscriptions, encrypt and upload secrets, make requests etc. The README contains extensive documentation.

Note that to illustrate how powerful Functions Toolkit is we have left in code that creates and funds a Functions Subscription programmatically. We had previously done this via the WebApp UI so we don't need to do this now - that's why createAndFundSub() is commented out at the bottom.

DON-hosted secrets
offchain secrets
Threshold encryption
Chainlink Functions Toolkit NPM package
functions.chain.link
Click the play button on the upper left of editor