diff --git a/chainlink-functions/.gitignore b/chainlink-functions/.gitignore new file mode 100644 index 0000000..3302050 --- /dev/null +++ b/chainlink-functions/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +.env.enc diff --git a/chainlink-functions/README.md b/chainlink-functions/README.md new file mode 100644 index 0000000..4943c99 --- /dev/null +++ b/chainlink-functions/README.md @@ -0,0 +1,13 @@ +This example is heavily borrowed from https://docs.chain.link/chainlink-functions/tutorials/api-use-secrets. +1. Run `npm install` to install the dependencies. +2. Set up your on chain resources as shown here: https://docs.chain.link/chainlink-functions/tutorials/api-use-secrets#configure-your-onchain-resources. +3. Add the following environment variables, either in a `.env` file, or using `npx env-enc set`. + ``` + CONSUMER_ADDRESS=0x8dFf78B7EE3128D00E90611FBeD20A71397064D9 # REPLACE this with your Functions consumer address + SUBSCRIPTION_ID=3 # REPLACE this with your subscription ID + LINK_TOKEN_ADDRESS=0x779877A7B0D9E8603169DdbD7836e478b4624789 # REPLACE this with your wallet address + ETHEREUM_SEPOLIA_RPC_URL= + PRIVATE_KEY= + SXT_API_KEY= + ``` +4. Run `node example/request.js` to upload the secrets, run a simulation, and then submit a chainlink job. \ No newline at end of file diff --git a/chainlink-functions/example/request.js b/chainlink-functions/example/request.js new file mode 100644 index 0000000..79d32fa --- /dev/null +++ b/chainlink-functions/example/request.js @@ -0,0 +1,250 @@ +const fs = require("fs"); +const path = require("path"); +const { + SubscriptionManager, + SecretsManager, + simulateScript, + ResponseListener, + ReturnType, + decodeResult, + FulfillmentCode, +} = require("@chainlink/functions-toolkit"); +const functionsConsumerAbi = require("../smart-contract/FunctionsConsumerAbi.json"); +const ethers = require("ethers"); +require('dotenv').config(); // This will load from .env file in addition to env-enc file. +require("@chainlink/env-enc").config(); + +const consumerAddress = process.env.CONSUMER_ADDRESS; +const subscriptionId = process.env.SUBSCRIPTION_ID; + +const makeRequestSepolia = async () => { + // hardcoded for Ethereum Sepolia + const routerAddress = "0xb83E47C2bC239B3bf370bc41e1459A34b41238D0"; + const linkTokenAddress = process.env.LINK_TOKEN_ADDRESS; + const donId = "fun-ethereum-sepolia-1"; + const explorerUrl = "https://sepolia.etherscan.io"; + const gatewayUrls = [ + "https://01.functions-gateway.testnet.chain.link/", + "https://02.functions-gateway.testnet.chain.link/", + ]; + + // Initialize functions settings + const source = fs + .readFileSync(path.resolve(__dirname, "source.js")) + .toString(); + + const args = []; + const secrets = { apiKey: process.env.SXT_API_KEY }; + const slotIdNumber = 0; // slot ID where to upload the secrets + const expirationTimeMinutes = 15; // expiration time in minutes of the secrets + const gasLimit = 300000; + + // Initialize ethers signer and provider to interact with the contracts onchain + const privateKey = process.env.PRIVATE_KEY; // fetch PRIVATE_KEY + if (!privateKey) + throw new Error( + "private key not provided - check your environment variables" + ); + + const rpcUrl = process.env.ETHEREUM_SEPOLIA_RPC_URL; // fetch Sepolia RPC URL + + if (!rpcUrl) + throw new Error(`rpcUrl not provided - check your environment variables`); + + const provider = new ethers.providers.JsonRpcProvider(rpcUrl); + + const wallet = new ethers.Wallet(privateKey); + const signer = wallet.connect(provider); // create ethers signer for signing transactions + + ///////// START SIMULATION //////////// + + console.log("Start simulation..."); + + const response = await simulateScript({ + source: source, + args: args, + bytesArgs: [], // bytesArgs - arguments can be encoded off-chain to bytes. + secrets: secrets, + }); + + console.log("Simulation result", response); + const errorString = response.errorString; + if (errorString) { + console.log(`❌ Error during simulation: `, errorString); + } else { + const returnType = ReturnType.uint256; + const responseBytesHexstring = response.responseBytesHexstring; + if (ethers.utils.arrayify(responseBytesHexstring).length > 0) { + const decodedResponse = decodeResult( + response.responseBytesHexstring, + returnType + ); + console.log(`✅ Decoded response to ${returnType}: `, decodedResponse); + } + } + + //////// ESTIMATE REQUEST COSTS //////// + console.log("\nEstimate request costs..."); + // Initialize and return SubscriptionManager + const subscriptionManager = new SubscriptionManager({ + signer: signer, + linkTokenAddress: linkTokenAddress, + functionsRouterAddress: routerAddress, + }); + await subscriptionManager.initialize(); + + // estimate costs in Juels + + const gasPriceWei = await signer.getGasPrice(); // get gasPrice in wei + + const estimatedCostInJuels = + await subscriptionManager.estimateFunctionsRequestCost({ + donId: donId, // ID of the DON to which the Functions request will be sent + subscriptionId: subscriptionId, // Subscription ID + callbackGasLimit: gasLimit, // Total gas used by the consumer contract's callback + gasPriceWei: BigInt(gasPriceWei), // Gas price in gWei + }); + + console.log( + `Fulfillment cost estimated to ${ethers.utils.formatEther( + estimatedCostInJuels + )} LINK` + ); + + // process.exit(0); // Uncomment to stop the script after simulation + + //////// MAKE REQUEST //////// + + console.log("\nMake request..."); + + // First encrypt secrets and upload the encrypted secrets to the DON + const secretsManager = new SecretsManager({ + signer: signer, + functionsRouterAddress: routerAddress, + donId: donId, + }); + await secretsManager.initialize(); + + // Encrypt secrets and upload to DON + const encryptedSecretsObj = await secretsManager.encryptSecrets(secrets); + + console.log( + `Upload encrypted secret to gateways ${gatewayUrls}. slotId ${slotIdNumber}. Expiration in minutes: ${expirationTimeMinutes}` + ); + // Upload secrets + const uploadResult = await secretsManager.uploadEncryptedSecretsToDON({ + encryptedSecretsHexstring: encryptedSecretsObj.encryptedSecrets, + gatewayUrls: gatewayUrls, + slotId: slotIdNumber, + minutesUntilExpiration: expirationTimeMinutes, + }); + + if (!uploadResult.success) + throw new Error(`Encrypted secrets not uploaded to ${gatewayUrls}`); + + console.log( + `\n✅ Secrets uploaded properly to gateways ${gatewayUrls}! Gateways response: `, + uploadResult + ); + + const donHostedSecretsVersion = parseInt(uploadResult.version); // fetch the reference of the encrypted secrets + + const functionsConsumer = new ethers.Contract( + consumerAddress, + functionsConsumerAbi, + signer + ); + + // Actual transaction call + const transaction = await functionsConsumer.sendRequest( + source, // source + "0x", // user hosted secrets - encryptedSecretsUrls - empty in this example + slotIdNumber, // slot ID of the encrypted secrets + donHostedSecretsVersion, // version of the encrypted secrets + args, + [], // bytesArgs - arguments can be encoded off-chain to bytes. + subscriptionId, + gasLimit, + ethers.utils.formatBytes32String(donId) // jobId is bytes32 representation of donId + ); + + // Log transaction details + console.log( + `\n✅ Functions request sent! Transaction hash ${transaction.hash}. Waiting for a response...` + ); + + console.log( + `See your request in the explorer ${explorerUrl}/tx/${transaction.hash}` + ); + + const responseListener = new ResponseListener({ + provider: provider, + functionsRouterAddress: routerAddress, + }); // Instantiate a ResponseListener object to wait for fulfillment. + (async () => { + try { + const response = await new Promise((resolve, reject) => { + responseListener + .listenForResponseFromTransaction(transaction.hash) + .then((response) => { + resolve(response); // Resolves once the request has been fulfilled. + }) + .catch((error) => { + reject(error); // Indicate that an error occurred while waiting for fulfillment. + }); + }); + + const fulfillmentCode = response.fulfillmentCode; + + if (fulfillmentCode === FulfillmentCode.FULFILLED) { + console.log( + `\n✅ Request ${response.requestId + } successfully fulfilled. Cost is ${ethers.utils.formatEther( + response.totalCostInJuels + )} LINK.Complete reponse: `, + response + ); + } else if (fulfillmentCode === FulfillmentCode.USER_CALLBACK_ERROR) { + console.log( + `\n⚠️ Request ${response.requestId + } fulfilled. However, the consumer contract callback failed. Cost is ${ethers.utils.formatEther( + response.totalCostInJuels + )} LINK.Complete reponse: `, + response + ); + } else { + console.log( + `\n❌ Request ${response.requestId + } not fulfilled. Code: ${fulfillmentCode}. Cost is ${ethers.utils.formatEther( + response.totalCostInJuels + )} LINK.Complete reponse: `, + response + ); + } + + const errorString = response.errorString; + if (errorString) { + console.log(`\n❌ Error during the execution: `, errorString); + } else { + const responseBytesHexstring = response.responseBytesHexstring; + if (ethers.utils.arrayify(responseBytesHexstring).length > 0) { + const decodedResponse = decodeResult( + response.responseBytesHexstring, + ReturnType.uint256 + ); + console.log( + `\n✅ Decoded response to ${ReturnType.uint256}: `, + decodedResponse + ); + } + } + } catch (error) { + console.error("Error listening for response:", error); + } + })(); +}; + +makeRequestSepolia().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/chainlink-functions/example/source.js b/chainlink-functions/example/source.js new file mode 100644 index 0000000..8d2575f --- /dev/null +++ b/chainlink-functions/example/source.js @@ -0,0 +1,27 @@ +// Import the package +const SxTProofModule = await import("npm:sxt-chain-sdk"); + +// Extract the default export (SxTProof class) +const SxTProof = SxTProofModule.default; + +// Define test parameters +const queryString = 'SELECT SUM(BLOCK_NUMBER), COUNT(*) FROM ETHEREUM.BLOCKS'; +const commitmentKey = + '0xca407206ec1ab726b2636c4b145ac28749505e273536fae35330b966dac69e86a4832a125c0464e066dd20add960efb518424c4f434b5320455448455245554d4a9e6f9b8d43f6ad008f8c291929dee201'; + +if (!secrets.apiKey) { + throw Error("Missing secret: apiKey"); +} + +// Initialize the SxTProof instance +const proof = new SxTProof(queryString, commitmentKey, secrets.apiKey); + +try { + // Kick off the proof and await execution + const result = await proof.executeWorkflow(); + console.log("Workflow completed successfully:", result); + return Functions.encodeString("Verified"); +} catch (error) { + console.log("Workflow failed: ", error); + return Functions.encodeString("Failed: ", error); +} \ No newline at end of file diff --git a/chainlink-functions/package.json b/chainlink-functions/package.json new file mode 100644 index 0000000..5a1cab7 --- /dev/null +++ b/chainlink-functions/package.json @@ -0,0 +1,19 @@ +{ + "name": "functions-examples", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@chainlink/env-enc": "^1.0.5", + "@chainlink/functions-toolkit": "^0.2.7", + "ethers": "^5.7.2" + }, + "devDependencies": { + "dotenv": "^16.4.5" + } +} diff --git a/chainlink-functions/smart-contract/FunctionsConsumerAbi.json b/chainlink-functions/smart-contract/FunctionsConsumerAbi.json new file mode 100644 index 0000000..703db80 --- /dev/null +++ b/chainlink-functions/smart-contract/FunctionsConsumerAbi.json @@ -0,0 +1,326 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "EmptyArgs", + "type": "error" + }, + { + "inputs": [], + "name": "EmptySecrets", + "type": "error" + }, + { + "inputs": [], + "name": "EmptySource", + "type": "error" + }, + { + "inputs": [], + "name": "NoInlineSecrets", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyRouterCanFulfill", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + } + ], + "name": "UnexpectedRequestID", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "RequestFulfilled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "RequestSent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "response", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "err", + "type": "bytes" + } + ], + "name": "Response", + "type": "event" + }, + { + "inputs": [], + "name": "acceptOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "response", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "err", + "type": "bytes" + } + ], + "name": "handleOracleFulfillment", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "s_lastError", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "s_lastRequestId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "s_lastResponse", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "source", + "type": "string" + }, + { + "internalType": "bytes", + "name": "encryptedSecretsUrls", + "type": "bytes" + }, + { + "internalType": "uint8", + "name": "donHostedSecretsSlotID", + "type": "uint8" + }, + { + "internalType": "uint64", + "name": "donHostedSecretsVersion", + "type": "uint64" + }, + { + "internalType": "string[]", + "name": "args", + "type": "string[]" + }, + { + "internalType": "bytes[]", + "name": "bytesArgs", + "type": "bytes[]" + }, + { + "internalType": "uint64", + "name": "subscriptionId", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "gasLimit", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "donID", + "type": "bytes32" + } + ], + "name": "sendRequest", + "outputs": [ + { + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "request", + "type": "bytes" + }, + { + "internalType": "uint64", + "name": "subscriptionId", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "gasLimit", + "type": "uint32" + }, + { + "internalType": "bytes32", + "name": "donID", + "type": "bytes32" + } + ], + "name": "sendRequestCBOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "requestId", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/chainlink-functions/smart-contract/FunctionsConsumerExample.sol b/chainlink-functions/smart-contract/FunctionsConsumerExample.sol new file mode 100644 index 0000000..8b28421 --- /dev/null +++ b/chainlink-functions/smart-contract/FunctionsConsumerExample.sol @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.19; + +import {FunctionsClient} from "@chainlink/contracts/src/v0.8/functions/v1_0_0/FunctionsClient.sol"; +import {ConfirmedOwner} from "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol"; +import {FunctionsRequest} from "@chainlink/contracts/src/v0.8/functions/v1_0_0/libraries/FunctionsRequest.sol"; + +/** + * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. + * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. + * DO NOT USE THIS CODE IN PRODUCTION. + */ +contract FunctionsConsumerExample is FunctionsClient, ConfirmedOwner { + using FunctionsRequest for FunctionsRequest.Request; + + bytes32 public s_lastRequestId; + bytes public s_lastResponse; + bytes public s_lastError; + + error UnexpectedRequestID(bytes32 requestId); + + event Response(bytes32 indexed requestId, bytes response, bytes err); + + constructor( + address router + ) FunctionsClient(router) ConfirmedOwner(msg.sender) {} + + /** + * @notice Send a simple request + * @param source JavaScript source code + * @param encryptedSecretsUrls Encrypted URLs where to fetch user secrets + * @param donHostedSecretsSlotID Don hosted secrets slotId + * @param donHostedSecretsVersion Don hosted secrets version + * @param args List of arguments accessible from within the source code + * @param bytesArgs Array of bytes arguments, represented as hex strings + * @param subscriptionId Billing ID + */ + function sendRequest( + string memory source, + bytes memory encryptedSecretsUrls, + uint8 donHostedSecretsSlotID, + uint64 donHostedSecretsVersion, + string[] memory args, + bytes[] memory bytesArgs, + uint64 subscriptionId, + uint32 gasLimit, + bytes32 donID + ) external onlyOwner returns (bytes32 requestId) { + FunctionsRequest.Request memory req; + req.initializeRequestForInlineJavaScript(source); + if (encryptedSecretsUrls.length > 0) + req.addSecretsReference(encryptedSecretsUrls); + else if (donHostedSecretsVersion > 0) { + req.addDONHostedSecrets( + donHostedSecretsSlotID, + donHostedSecretsVersion + ); + } + if (args.length > 0) req.setArgs(args); + if (bytesArgs.length > 0) req.setBytesArgs(bytesArgs); + s_lastRequestId = _sendRequest( + req.encodeCBOR(), + subscriptionId, + gasLimit, + donID + ); + return s_lastRequestId; + } + + /** + * @notice Send a pre-encoded CBOR request + * @param request CBOR-encoded request data + * @param subscriptionId Billing ID + * @param gasLimit The maximum amount of gas the request can consume + * @param donID ID of the job to be invoked + * @return requestId The ID of the sent request + */ + function sendRequestCBOR( + bytes memory request, + uint64 subscriptionId, + uint32 gasLimit, + bytes32 donID + ) external onlyOwner returns (bytes32 requestId) { + s_lastRequestId = _sendRequest( + request, + subscriptionId, + gasLimit, + donID + ); + return s_lastRequestId; + } + + /** + * @notice Store latest result/error + * @param requestId The request ID, returned by sendRequest() + * @param response Aggregated response from the user code + * @param err Aggregated error from the user code or from the execution pipeline + * Either response or error parameter will be set, but never both + */ + function fulfillRequest( + bytes32 requestId, + bytes memory response, + bytes memory err + ) internal override { + if (s_lastRequestId != requestId) { + revert UnexpectedRequestID(requestId); + } + s_lastResponse = response; + s_lastError = err; + emit Response(requestId, s_lastResponse, s_lastError); + } +} diff --git a/chainlink-functions/source/index.js b/chainlink-functions/source/index.js new file mode 100644 index 0000000..5e2bd5a --- /dev/null +++ b/chainlink-functions/source/index.js @@ -0,0 +1,647 @@ +// sxt-chain-sdk/index.js + +class SxTProof { + constructor(queryString, commitmentKey, apiKey) { + this.queryString = queryString; + this.commitmentKey = commitmentKey; + this.apiKey = apiKey; + + } + + async executeWorkflow() { + try { + const heap = new Array(128).fill(undefined); + + heap.push(undefined, null, true, false); + + function getObject(idx) { + return heap[idx]; + } + + let heap_next = heap.length; + + function dropObject(idx) { + if (idx < 132) return; + heap[idx] = heap_next; + heap_next = idx; + } + + function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; + } + + const cachedTextDecoder = + typeof TextDecoder !== "undefined" + ? new TextDecoder("utf-8", { ignoreBOM: true, fatal: true }) + : { + decode: () => { + throw Error("TextDecoder not available"); + }, + }; + + if (typeof TextDecoder !== "undefined") { + cachedTextDecoder.decode(); + } + + let cachedUint8ArrayMemory0 = null; + + function getUint8ArrayMemory0() { + if ( + cachedUint8ArrayMemory0 === null || + cachedUint8ArrayMemory0.byteLength === 0 + ) { + cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer); + } + return cachedUint8ArrayMemory0; + } + + function getStringFromWasm0(ptr, len) { + ptr = ptr >>> 0; + return cachedTextDecoder.decode( + getUint8ArrayMemory0().subarray(ptr, ptr + len), + ); + } + + function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; + } + + let WASM_VECTOR_LEN = 0; + + const cachedTextEncoder = + typeof TextEncoder !== "undefined" + ? new TextEncoder("utf-8") + : { + encode: () => { + throw Error("TextEncoder not available"); + }, + }; + + const encodeString = function (arg, view) { + return cachedTextEncoder.encodeInto(arg, view); + }; + + function passStringToWasm0(arg, malloc, realloc) { + if (realloc === undefined) { + const buf = cachedTextEncoder.encode(arg); + const ptr = malloc(buf.length, 1) >>> 0; + getUint8ArrayMemory0() + .subarray(ptr, ptr + buf.length) + .set(buf); + WASM_VECTOR_LEN = buf.length; + return ptr; + } + + let len = arg.length; + let ptr = malloc(len, 1) >>> 0; + + const mem = getUint8ArrayMemory0(); + + let offset = 0; + + for (; offset < len; offset++) { + const code = arg.charCodeAt(offset); + if (code > 0x7f) break; + mem[ptr + offset] = code; + } + + if (offset !== len) { + if (offset !== 0) { + arg = arg.slice(offset); + } + ptr = realloc(ptr, len, (len = offset + arg.length * 3), 1) >>> 0; + const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len); + const ret = encodeString(arg, view); + + offset += ret.written; + ptr = realloc(ptr, len, offset, 1) >>> 0; + } + + WASM_VECTOR_LEN = offset; + return ptr; + } + + function isLikeNone(x) { + return x === undefined || x === null; + } + + let cachedDataViewMemory0 = null; + + function getDataViewMemory0() { + if ( + cachedDataViewMemory0 === null || + cachedDataViewMemory0.buffer.detached === true || + (cachedDataViewMemory0.buffer.detached === undefined && + cachedDataViewMemory0.buffer !== wasm.memory.buffer) + ) { + cachedDataViewMemory0 = new DataView(wasm.memory.buffer); + } + return cachedDataViewMemory0; + } + + function getArrayJsValueFromWasm0(ptr, len) { + ptr = ptr >>> 0; + const mem = getDataViewMemory0(); + const result = []; + for (let i = ptr; i < ptr + 4 * len; i += 4) { + result.push(takeObject(mem.getUint32(i, true))); + } + return result; + } + + function passArrayJsValueToWasm0(array, malloc) { + const ptr = malloc(array.length * 4, 4) >>> 0; + const mem = getDataViewMemory0(); + for (let i = 0; i < array.length; i++) { + mem.setUint32(ptr + 4 * i, addHeapObject(array[i]), true); + } + WASM_VECTOR_LEN = array.length; + return ptr; + } + /** + * @param {string} table_ref + * @returns {string} + */ + function commitment_storage_key_dory(table_ref) { + let deferred3_0; + let deferred3_1; + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0( + table_ref, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len0 = WASM_VECTOR_LEN; + wasm.commitment_storage_key_dory(retptr, ptr0, len0); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + var r3 = getDataViewMemory0().getInt32(retptr + 4 * 3, true); + var ptr2 = r0; + var len2 = r1; + if (r3) { + ptr2 = 0; + len2 = 0; + throw takeObject(r2); + } + deferred3_0 = ptr2; + deferred3_1 = len2; + return getStringFromWasm0(ptr2, len2); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + wasm.__wbindgen_free(deferred3_0, deferred3_1, 1); + } + } + + /** + * @param {string} query + * @param {(TableRefAndCommitment)[]} commitments + * @returns {ProverQueryAndQueryExprAndCommitments} + */ + function plan_prover_query_dory(query, commitments) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passStringToWasm0( + query, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passArrayJsValueToWasm0(commitments, wasm.__wbindgen_malloc); + const len1 = WASM_VECTOR_LEN; + wasm.plan_prover_query_dory(retptr, ptr0, len0, ptr1, len1); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + return ProverQueryAndQueryExprAndCommitments.__wrap(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + + /** + * @param {any} prover_response_json + * @param {any} query_expr_json + * @param {(TableRefAndCommitment)[]} commitments + * @returns {any} + */ + function verify_prover_response_dory( + prover_response_json, + query_expr_json, + commitments, + ) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + const ptr0 = passArrayJsValueToWasm0(commitments, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.verify_prover_response_dory( + retptr, + addHeapObject(prover_response_json), + addHeapObject(query_expr_json), + ptr0, + len0, + ); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true); + if (r2) { + throw takeObject(r1); + } + return takeObject(r0); + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + + function handleError(f, args) { + try { + return f.apply(this, args); + } catch (e) { + wasm.__wbindgen_exn_store(addHeapObject(e)); + } + } + + const ProverQueryAndQueryExprAndCommitmentsFinalization = + typeof FinalizationRegistry === "undefined" + ? { register: () => { }, unregister: () => { } } + : new FinalizationRegistry((ptr) => + wasm.__wbg_proverqueryandqueryexprandcommitments_free(ptr >>> 0, 1), + ); + + class ProverQueryAndQueryExprAndCommitments { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(ProverQueryAndQueryExprAndCommitments.prototype); + obj.__wbg_ptr = ptr; + ProverQueryAndQueryExprAndCommitmentsFinalization.register( + obj, + obj.__wbg_ptr, + obj, + ); + return obj; + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + ProverQueryAndQueryExprAndCommitmentsFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_proverqueryandqueryexprandcommitments_free(ptr, 0); + } + /** + * @returns {any} + */ + get prover_query_json() { + const ret = + wasm.__wbg_get_proverqueryandqueryexprandcommitments_prover_query_json( + this.__wbg_ptr, + ); + return takeObject(ret); + } + /** + * @param {any} arg0 + */ + set prover_query_json(arg0) { + wasm.__wbg_set_proverqueryandqueryexprandcommitments_prover_query_json( + this.__wbg_ptr, + addHeapObject(arg0), + ); + } + /** + * @returns {any} + */ + get query_expr_json() { + const ret = + wasm.__wbg_get_proverqueryandqueryexprandcommitments_query_expr_json( + this.__wbg_ptr, + ); + return takeObject(ret); + } + /** + * @param {any} arg0 + */ + set query_expr_json(arg0) { + wasm.__wbg_set_proverqueryandqueryexprandcommitments_query_expr_json( + this.__wbg_ptr, + addHeapObject(arg0), + ); + } + /** + * @returns {(TableRefAndCommitment)[]} + */ + get commitments() { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + wasm.__wbg_get_proverqueryandqueryexprandcommitments_commitments( + retptr, + this.__wbg_ptr, + ); + var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true); + var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true); + var v1 = getArrayJsValueFromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 4, 4); + return v1; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } + } + /** + * @param {(TableRefAndCommitment)[]} arg0 + */ + set commitments(arg0) { + const ptr0 = passArrayJsValueToWasm0(arg0, wasm.__wbindgen_malloc); + const len0 = WASM_VECTOR_LEN; + wasm.__wbg_set_proverqueryandqueryexprandcommitments_commitments( + this.__wbg_ptr, + ptr0, + len0, + ); + } + } + + const TableRefAndCommitmentFinalization = + typeof FinalizationRegistry === "undefined" + ? { register: () => { }, unregister: () => { } } + : new FinalizationRegistry((ptr) => + wasm.__wbg_tablerefandcommitment_free(ptr >>> 0, 1), + ); + + class TableRefAndCommitment { + static __wrap(ptr) { + ptr = ptr >>> 0; + const obj = Object.create(TableRefAndCommitment.prototype); + obj.__wbg_ptr = ptr; + TableRefAndCommitmentFinalization.register(obj, obj.__wbg_ptr, obj); + return obj; + } + + static __unwrap(jsValue) { + if (!(jsValue instanceof TableRefAndCommitment)) { + return 0; + } + return jsValue.__destroy_into_raw(); + } + + __destroy_into_raw() { + const ptr = this.__wbg_ptr; + this.__wbg_ptr = 0; + TableRefAndCommitmentFinalization.unregister(this); + return ptr; + } + + free() { + const ptr = this.__destroy_into_raw(); + wasm.__wbg_tablerefandcommitment_free(ptr, 0); + } + /** + * @param {string} table_ref + * @param {string} table_commitment_hex + */ + constructor(table_ref, table_commitment_hex) { + const ptr0 = passStringToWasm0( + table_ref, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len0 = WASM_VECTOR_LEN; + const ptr1 = passStringToWasm0( + table_commitment_hex, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + const len1 = WASM_VECTOR_LEN; + const ret = wasm.tablerefandcommitment_new(ptr0, len0, ptr1, len1); + this.__wbg_ptr = ret >>> 0; + TableRefAndCommitmentFinalization.register(this, this.__wbg_ptr, this); + return this; + } + } + + const imports = { + __wbindgen_placeholder__: { + __wbindgen_object_drop_ref: function (arg0) { + takeObject(arg0); + }, + __wbindgen_is_undefined: function (arg0) { + const ret = getObject(arg0) === undefined; + return ret; + }, + __wbindgen_string_new: function (arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }, + __wbindgen_object_clone_ref: function (arg0) { + const ret = getObject(arg0); + return addHeapObject(ret); + }, + __wbg_tablerefandcommitment_new: function (arg0) { + const ret = TableRefAndCommitment.__wrap(arg0); + return addHeapObject(ret); + }, + __wbg_tablerefandcommitment_unwrap: function (arg0) { + const ret = TableRefAndCommitment.__unwrap(takeObject(arg0)); + return ret; + }, + __wbindgen_string_get: function (arg0, arg1) { + const obj = getObject(arg1); + const ret = typeof obj === "string" ? obj : undefined; + var ptr1 = isLikeNone(ret) + ? 0 + : passStringToWasm0( + ret, + wasm.__wbindgen_malloc, + wasm.__wbindgen_realloc, + ); + var len1 = WASM_VECTOR_LEN; + getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true); + getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true); + }, + __wbg_parse_51ee5409072379d3: function () { + return handleError(function (arg0, arg1) { + const ret = JSON.parse(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }, arguments); + }, + __wbg_stringify_eead5648c09faaf8: function () { + return handleError(function (arg0) { + const ret = JSON.stringify(getObject(arg0)); + return addHeapObject(ret); + }, arguments); + }, + __wbindgen_throw: function (arg0, arg1) { + throw new Error(getStringFromWasm0(arg0, arg1)); + }, + }, + }; + + const wasmRequest = await makeHttpRequest({ + url: "https://raw.githubusercontent.com/spaceandtimelabs/sxt-proof-of-sql-sdk/feat/wasm-bindgen/crates/proof-of-sql-sdk-wasm/pkg/sxt_proof_of_sql_sdk_wasm_bg.wasm", + method: "GET", + responseType: "arraybuffer", + }); + + if (wasmRequest.error || wasmRequest.status !== 200) { + throw new Error("Error retrieving wasm file"); + } + + // Convert the response data to a Uint8Array for WebAssembly instantiation + const wasmCode = new Uint8Array(wasmRequest.data); + + const wasmInstance = (await WebAssembly.instantiate(wasmCode, imports)) + .instance; + const wasm = wasmInstance.exports; + const __wasm = wasm; + + // Ensure the API key is available + if (!this.apiKey) { + throw Error("API Key Not Found"); + } + + // Construct a payload to fetch an accesstoken to be used for + // api access to the prover. It is a required component of the payload to + // receive a proof. + // Set the secrets field to an apiKey that you own for your sxt account. + + // Execute the API request using makeHttpRequest + const authResponse = await makeHttpRequest({ + url: "https://proxy.api.spaceandtime.dev/auth/apikey", + method: "POST", + headers: { + apikey: this.apiKey, + "Content-Type": "application/json", + }, + }); + + if (authResponse.error) { + throw new Error("Error querying auth endpoint: " + authResponse.message); + } + + // Extract the access token, truncate it to 256 characters, and return it as an encoded string + const accessToken = authResponse.data.accessToken; + + // TODO: make this not be hardcoded: + const commitmentResponse = await makeHttpRequest({ + url: "https://rpc.testnet.sxt.network/", + method: "POST", + headers: { + "Content-Type": "application/json", + }, + data: { + id: 1, + jsonrpc: "2.0", + method: "state_getStorage", + params: [this.commitmentKey], + }, + }); + + if (commitmentResponse.error) { + throw new Error("Error querying RPC node: " + commitmentResponse.message); + } + + let commitment = commitmentResponse.data.result.slice(2); // remove the 0x prefix + let commitments = [new TableRefAndCommitment("ETHEREUM.BLOCKS", commitment)]; + const plannedProverQuery = plan_prover_query_dory( + this.queryString, + commitments, + ); + const proverQuery = plannedProverQuery.prover_query_json; + const queryExpr = plannedProverQuery.query_expr_json; + commitments = plannedProverQuery.commitments; + + // proverQuery.query_context["ETHEREUM.BLOCKS"].ends = [100]; + + const proverResponse = await makeHttpRequest({ + url: "https://api.spaceandtime.dev/v1/prove", + method: "POST", + headers: { + Authorization: "Bearer " + accessToken, + "content-type": "application/json", + }, + data: proverQuery, + }); + + if (proverResponse.error) { + throw new Error("Error querying prover: " + proverResponse.message); + } + + const proverResponseJson = proverResponse.data; + + try { + const result = verify_prover_response_dory( + proverResponseJson, + queryExpr, + commitments, + ); + return Functions.encodeString("Verified"); + } catch (e) { + if (e instanceof String) { + if (e.slice(0, 22) === "verification failure: ") { + return Functions.encodeString("Invalid"); + } + } + throw e; + } + + } catch (e) { + return Functions.encodeString("Failed: ", e); + } + } +} + +export default SxTProof; + + +async function makeHttpRequest({ url, method = "GET", headers = {}, data = null, responseType = "json" }) { + + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), 3000); + const response = await fetch(url, { + method, + headers, + body: data ? JSON.stringify(data) : undefined, + signal: controller.signal, + }); + clearTimeout(id); + + if (!response.ok) { + return { error: `HTTP error! Status: ${response.status} ${response.statusText}` }; + } + + let data_; + try { + switch (responseType) { + case "json": + data_ = await response.json(); + break; + case "arraybuffer": + data_ = await response.arrayBuffer(); + break; + case "text": + data_ = await response.text(); + break; + default: + return { error: `Unsupported responseType: ${responseType}` }; + } + } catch (e) { + return { error: `Error parsing response: ${e.message}` }; + } + + return { data: data_ }; +} + +