Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@
"description": "Onchain social protocol with Neynar API, Frames v2 Mini Apps, and transaction frames. Covers Snapchain architecture, FID registry on OP Mainnet, and Warpcast integration.",
"category": "Infrastructure"
},
{
"name": "flare",
"source": "./skills/flare",
"description": "Provides domain knowledge for the Flare network—EVM L1 with enshrined oracles. Covers: general (networks, chain IDs, RPC endpoints, consensus, tooling), FTSO (block-latency price feeds, Scaling anchor feeds, feed IDs, wagmi/viem integration), FDC (attestation types, EVMTransaction, Web2Json, Payment, Merkle proofs), FAssets (FXRP/FBTC wrapped tokens, minting/redemption, agents, collateral), and Smart Accounts (XRPL-to-Flare without FLR, MasterAccountController, Firelight/Upshift vaults, CLI).",
"category": "L2 & Alt-L1"
},
{
"name": "foundry",
"source": "./skills/foundry",
Expand Down
570 changes: 570 additions & 0 deletions skills/flare/SKILL.md

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions skills/flare/examples/consume-feeds.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import {ContractRegistry} from "@flarenetwork/flare-periphery-contracts/coston2/ContractRegistry.sol";
import {IFtsoV2} from "@flarenetwork/flare-periphery-contracts/coston2/IFtsoV2.sol";

contract FtsoConsumer {
bytes21 public constant FLR_USD = 0x01464c522f55534400000000000000000000000000;
bytes21 public constant BTC_USD = 0x014254432f55534400000000000000000000000000;

function getPrices() external view returns (
uint256[] memory values, int8[] memory decimals, uint64 timestamp
) {
IFtsoV2 ftso = ContractRegistry.getTestFtsoV2(); // use getFtsoV2() on mainnet
bytes21[] memory feedIds = new bytes21[](2);
feedIds[0] = FLR_USD;
feedIds[1] = BTC_USD;
return ftso.getFeedsById(feedIds);
// price = values[i] / 10^decimals[i]
}
}
113 changes: 113 additions & 0 deletions skills/flare/examples/custom-instruction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Smart Accounts: register and encode a custom instruction via viem
import {
createPublicClient,
createWalletClient,
encodeFunctionData,
http,
toHex,
type Address,
} from "viem";
import { flareTestnet } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";

const MASTER_ACCOUNT_CONTROLLER_ADDRESS =
"0x..." as Address; // resolve from FlareContractsRegistry

// Your ABI for MasterAccountController (truncated for brevity)
const masterAccountControllerAbi = [
{
name: "registerCustomInstruction",
type: "function",
stateMutability: "nonpayable",
inputs: [
{
name: "customCalls",
type: "tuple[]",
components: [
{ name: "targetContract", type: "address" },
{ name: "value", type: "uint256" },
{ name: "data", type: "bytes" },
],
},
],
outputs: [],
},
{
name: "encodeCustomInstruction",
type: "function",
stateMutability: "view",
inputs: [
{
name: "customCalls",
type: "tuple[]",
components: [
{ name: "targetContract", type: "address" },
{ name: "value", type: "uint256" },
{ name: "data", type: "bytes" },
],
},
],
outputs: [{ name: "", type: "bytes32" }],
},
] as const;

async function main() {
const account = privateKeyToAccount(`0x${process.env.PRIVATE_KEY!}`);
const publicClient = createPublicClient({
chain: flareTestnet,
transport: http(),
});
const walletClient = createWalletClient({
account,
chain: flareTestnet,
transport: http(),
});

// Build custom instructions
const targetContractAddress = "0x..." as Address;
const targetAbi = [
{ name: "myFunction", type: "function", stateMutability: "nonpayable", inputs: [], outputs: [] },
] as const;

const instructions = [
{
targetContract: targetContractAddress,
value: BigInt(0),
data: encodeFunctionData({
abi: targetAbi,
functionName: "myFunction",
args: [],
}),
},
];

// Register
const { request } = await publicClient.simulateContract({
account,
address: MASTER_ACCOUNT_CONTROLLER_ADDRESS,
abi: masterAccountControllerAbi,
functionName: "registerCustomInstruction",
args: [instructions],
});
await walletClient.writeContract(request);
console.log("Custom instruction registered");

// Get encoded hash
const encodedInstruction = await publicClient.readContract({
address: MASTER_ACCOUNT_CONTROLLER_ADDRESS,
abi: masterAccountControllerAbi,
functionName: "encodeCustomInstruction",
args: [instructions],
});

// Build payment reference: 0xff + walletId (1 byte) + callHash (30 bytes)
const walletId = 0;
const paymentReference =
"0xff" +
toHex(walletId, { size: 1 }).slice(2) +
(encodedInstruction as string).slice(6);

console.log("Payment reference for XRPL memo:", paymentReference);
}

main().catch(console.error);
38 changes: 38 additions & 0 deletions skills/flare/examples/evm-transaction-consumer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import {ContractRegistry} from "@flarenetwork/flare-periphery-contracts/coston2/ContractRegistry.sol";
import {IEVMTransaction} from "@flarenetwork/flare-periphery-contracts/coston2/IEVMTransaction.sol";

contract EVMTransactionConsumer {
function processProof(IEVMTransaction.Proof calldata proof) external {
require(
ContractRegistry.getFdcVerification().verifyEVMTransaction(proof),
"Invalid proof"
);
// Safe to use proof.data.responseBody
uint256 blockNumber = proof.data.responseBody.blockNumber;
uint64 timestamp = proof.data.responseBody.timestamp;
// proof.data.responseBody.events[] — decoded events
}
}

// Web2Json pattern
struct DataTransportObject {
uint256 price;
uint256 timestamp;
}

contract Web2JsonConsumer {
function processWeb2Proof(IWeb2Json.Proof calldata proof) external {
require(
ContractRegistry.getFdcVerification().verifyWeb2Json(proof),
"Invalid proof"
);
DataTransportObject memory dto = abi.decode(
proof.data.responseBody.abi_encoded_data,
(DataTransportObject)
);
// use dto.price, dto.timestamp
}
}
73 changes: 73 additions & 0 deletions skills/flare/examples/fdc-offchain-flow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// FDC offchain attestation flow (Hardhat)
// Requires: VERIFIER_URL_TESTNET, VERIFIER_API_KEY_TESTNET, COSTON2_DA_LAYER_URL in .env

import { ethers } from "hardhat";

const VERIFIER_URL = process.env.VERIFIER_URL_TESTNET!;
const VERIFIER_API_KEY = process.env.VERIFIER_API_KEY_TESTNET!;
const DA_LAYER_URL = process.env.COSTON2_DA_LAYER_URL!;

async function main() {
// 1. Prepare request via verifier
const { abiEncodedRequest } = await fetch(
`${VERIFIER_URL}/verifier/eth/EVMTransaction/prepareRequest`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"X-apikey": VERIFIER_API_KEY,
},
body: JSON.stringify({
attestationType:
"0x45564d5472616e73616374696f6e000000000000000000000000000000000000",
sourceId:
"0x7465737445544800000000000000000000000000000000000000000000000000",
requestBody: {
transactionHash: "0x...", // replace with actual tx hash
requiredConfirmations: "1",
provideInput: true,
listEvents: true,
logIndices: [],
},
}),
}
).then((r) => r.json());

// 2. Submit to FdcHub
const fdcHub = await ethers.getContractAt("IFdcHub", "0x..."); // resolve via ContractRegistry
const fee = await fdcHub.requestFee(abiEncodedRequest);
const tx = await fdcHub.requestAttestation(abiEncodedRequest, {
value: fee,
});
const receipt = await tx.wait();

// 3. Compute roundId from block timestamp
const block = await ethers.provider.getBlock(receipt!.blockNumber);
const firstVotingRoundStartTs = 0; // get from FlareSystemsManager
const roundId = Math.floor((block!.timestamp - firstVotingRoundStartTs) / 90);

// 4. Wait for finalization
const relay = await ethers.getContractAt("IRelay", "0x..."); // resolve via ContractRegistry
while (!(await relay.isFinalized(200, roundId))) {
await new Promise((r) => setTimeout(r, 5000));
}

// 5. Fetch proof from DA Layer
const proofData = await fetch(
`${DA_LAYER_URL}/api/v1/fdc/proof-by-request-round-raw`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
votingRoundId: roundId,
requestBytes: abiEncodedRequest,
}),
}
).then((r) => r.json());

// 6. Decode response and call your contract
// await myContract.processProof({ merkleProof: proofData.proof, data: decodedResponse });
console.log("Proof retrieved for round:", roundId);
}

main().catch(console.error);
51 changes: 51 additions & 0 deletions skills/flare/examples/get-fxrp-address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Resolve FXRP token address at runtime via FlareContractsRegistry
import { createPublicClient, http, type Address } from "viem";
import { flareTestnet } from "viem/chains";

const REGISTRY: Address = "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019";

const registryAbi = [
{
name: "getContractAddressByName",
type: "function",
stateMutability: "view",
inputs: [{ name: "_name", type: "string" }],
outputs: [{ name: "", type: "address" }],
},
] as const;

const assetManagerAbi = [
{
name: "fAsset",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [{ name: "", type: "address" }],
},
] as const;

async function main() {
const client = createPublicClient({
chain: flareTestnet,
transport: http(),
});

// 1. Get FXRP AssetManager address
const assetManagerAddress = await client.readContract({
address: REGISTRY,
abi: registryAbi,
functionName: "getContractAddressByName",
args: ["AssetManagerFXRP"],
});
console.log("AssetManager (FXRP):", assetManagerAddress);

// 2. Get FXRP ERC-20 token address
const fxrpAddress = await client.readContract({
address: assetManagerAddress,
abi: assetManagerAbi,
functionName: "fAsset",
});
console.log("FXRP token:", fxrpAddress);
}

main().catch(console.error);
Loading