Consolidate webapp structure by merging nested components into the main repository

This commit is contained in:
defiQUG
2025-11-05 16:12:53 -08:00
parent 09c5a1fd5e
commit 3b09c35c47
55 changed files with 10240 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./interfaces/IAdapterRegistry.sol";
/**
* @title AdapterRegistry
* @notice Manages whitelist/blacklist of protocol adapters
*/
contract AdapterRegistry is IAdapterRegistry, Ownable {
mapping(address => AdapterInfo) public adapters;
mapping(address => bool) public whitelist;
mapping(address => bool) public blacklist;
event AdapterRegistered(address indexed adapter, string name, AdapterType adapterType);
event AdapterWhitelisted(address indexed adapter, bool whitelisted);
event AdapterBlacklisted(address indexed adapter, bool blacklisted);
/**
* @notice Register a new adapter
*/
function registerAdapter(
address adapter,
string calldata name,
AdapterType adapterType
) external onlyOwner {
require(adapters[adapter].registeredAt == 0, "Adapter already registered");
adapters[adapter] = AdapterInfo({
name: name,
adapterType: adapterType,
registeredAt: block.timestamp,
whitelisted: false
});
emit AdapterRegistered(adapter, name, adapterType);
}
/**
* @notice Whitelist an adapter
*/
function setWhitelist(address adapter, bool _whitelisted) external onlyOwner {
require(adapters[adapter].registeredAt > 0, "Adapter not registered");
adapters[adapter].whitelisted = _whitelisted;
whitelist[adapter] = _whitelisted;
emit AdapterWhitelisted(adapter, _whitelisted);
}
/**
* @notice Blacklist an adapter
*/
function setBlacklist(address adapter, bool _blacklisted) external onlyOwner {
blacklist[adapter] = _blacklisted;
if (_blacklisted) {
adapters[adapter].whitelisted = false;
whitelist[adapter] = false;
}
emit AdapterBlacklisted(adapter, _blacklisted);
}
/**
* @notice Check if adapter is whitelisted
*/
function isWhitelisted(address adapter) external view override returns (bool) {
return !blacklist[adapter] && adapters[adapter].whitelisted;
}
/**
* @notice Get adapter info
*/
function getAdapter(address adapter) external view returns (AdapterInfo memory) {
return adapters[adapter];
}
}

202
contracts/ComboHandler.sol Normal file
View File

@@ -0,0 +1,202 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./interfaces/IComboHandler.sol";
import "./interfaces/IAdapterRegistry.sol";
import "./interfaces/INotaryRegistry.sol";
/**
* @title ComboHandler
* @notice Aggregates multiple DeFi protocol calls and DLT operations into atomic transactions
*/
contract ComboHandler is IComboHandler, Ownable, ReentrancyGuard {
IAdapterRegistry public adapterRegistry;
INotaryRegistry public notaryRegistry;
mapping(bytes32 => ExecutionState) public executions;
struct ExecutionState {
ExecutionStatus status;
uint256 currentStep;
Step[] steps;
bool prepared;
}
event PlanExecuted(bytes32 indexed planId, bool success);
event PlanPrepared(bytes32 indexed planId);
event PlanCommitted(bytes32 indexed planId);
event PlanAborted(bytes32 indexed planId);
constructor(address _adapterRegistry, address _notaryRegistry) {
adapterRegistry = IAdapterRegistry(_adapterRegistry);
notaryRegistry = INotaryRegistry(_notaryRegistry);
}
/**
* @notice Execute a multi-step combo plan atomically
*/
function executeCombo(
bytes32 planId,
Step[] calldata steps,
bytes calldata signature
) external override nonReentrant returns (bool success, StepReceipt[] memory receipts) {
require(executions[planId].status == ExecutionStatus.PENDING, "Plan already executed");
// Verify signature
require(_verifySignature(planId, signature, msg.sender), "Invalid signature");
// Register with notary
notaryRegistry.registerPlan(planId, steps, msg.sender);
executions[planId] = ExecutionState({
status: ExecutionStatus.IN_PROGRESS,
currentStep: 0,
steps: steps,
prepared: false
});
receipts = new StepReceipt[](steps.length);
// Execute steps sequentially
for (uint256 i = 0; i < steps.length; i++) {
(bool stepSuccess, bytes memory returnData, uint256 gasUsed) = _executeStep(steps[i], i);
receipts[i] = StepReceipt({
stepIndex: i,
success: stepSuccess,
returnData: returnData,
gasUsed: gasUsed
});
if (!stepSuccess) {
executions[planId].status = ExecutionStatus.FAILED;
revert("Step execution failed");
}
}
executions[planId].status = ExecutionStatus.COMPLETE;
success = true;
emit PlanExecuted(planId, true);
// Finalize with notary
notaryRegistry.finalizePlan(planId, true);
}
/**
* @notice Prepare phase for 2PC (two-phase commit)
*/
function prepare(
bytes32 planId,
Step[] calldata steps
) external override returns (bool prepared) {
require(executions[planId].status == ExecutionStatus.PENDING, "Plan not pending");
// Validate all steps can be prepared
for (uint256 i = 0; i < steps.length; i++) {
require(_canPrepareStep(steps[i]), "Step cannot be prepared");
}
executions[planId] = ExecutionState({
status: ExecutionStatus.IN_PROGRESS,
currentStep: 0,
steps: steps,
prepared: true
});
emit PlanPrepared(planId);
prepared = true;
}
/**
* @notice Commit phase for 2PC
*/
function commit(bytes32 planId) external override returns (bool committed) {
ExecutionState storage state = executions[planId];
require(state.prepared, "Plan not prepared");
require(state.status == ExecutionStatus.IN_PROGRESS, "Invalid state");
// Execute all prepared steps
for (uint256 i = 0; i < state.steps.length; i++) {
(bool success, , ) = _executeStep(state.steps[i], i);
require(success, "Commit failed");
}
state.status = ExecutionStatus.COMPLETE;
committed = true;
emit PlanCommitted(planId);
notaryRegistry.finalizePlan(planId, true);
}
/**
* @notice Abort phase for 2PC (rollback)
*/
function abort(bytes32 planId) external override {
ExecutionState storage state = executions[planId];
require(state.status == ExecutionStatus.IN_PROGRESS, "Cannot abort");
// Release any reserved funds/collateral
_rollbackSteps(planId);
state.status = ExecutionStatus.ABORTED;
emit PlanAborted(planId);
notaryRegistry.finalizePlan(planId, false);
}
/**
* @notice Get execution status for a plan
*/
function getExecutionStatus(bytes32 planId) external view override returns (ExecutionStatus) {
return executions[planId].status;
}
/**
* @notice Execute a single step
*/
function _executeStep(Step memory step, uint256 stepIndex) internal returns (bool success, bytes memory returnData, uint256 gasUsed) {
// Verify adapter is whitelisted
require(adapterRegistry.isWhitelisted(step.target), "Adapter not whitelisted");
uint256 gasBefore = gasleft();
(success, returnData) = step.target.call{value: step.value}(
abi.encodeWithSignature("executeStep(bytes)", step.data)
);
gasUsed = gasBefore - gasleft();
}
/**
* @notice Check if step can be prepared
*/
function _canPrepareStep(Step memory step) internal view returns (bool) {
// Check if adapter supports prepare phase
return adapterRegistry.isWhitelisted(step.target);
}
/**
* @notice Rollback steps on abort
*/
function _rollbackSteps(bytes32 planId) internal {
// Release reserved funds, unlock collateral, etc.
// Implementation depends on specific step types
}
/**
* @notice Verify user signature on plan
*/
function _verifySignature(bytes32 planId, bytes calldata signature, address signer) internal pure returns (bool) {
// Simplified signature verification
// In production, use ECDSA.recover or similar
bytes32 messageHash = keccak256(abi.encodePacked(planId, signer));
// Verify signature matches signer
return true; // Simplified for now
}
}

View File

@@ -0,0 +1,101 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "./interfaces/INotaryRegistry.sol";
/**
* @title NotaryRegistry
* @notice Immutable registry for plan hashes, codehashes, and audit trail
*/
contract NotaryRegistry is INotaryRegistry, Ownable {
mapping(bytes32 => PlanRecord) public plans;
mapping(bytes32 => CodehashRecord) public codehashes;
event PlanRegistered(bytes32 indexed planId, address indexed creator, bytes32 planHash);
event PlanFinalized(bytes32 indexed planId, bool success, bytes32 receiptHash);
event CodehashRegistered(address indexed contractAddress, bytes32 codehash, string version);
/**
* @notice Register a plan with notary
*/
function registerPlan(
bytes32 planId,
IComboHandler.Step[] calldata steps,
address creator
) external override {
require(plans[planId].registeredAt == 0, "Plan already registered");
bytes32 planHash = keccak256(abi.encode(planId, steps, creator));
plans[planId] = PlanRecord({
planHash: planHash,
creator: creator,
registeredAt: block.timestamp,
finalizedAt: 0,
success: false,
receiptHash: bytes32(0)
});
emit PlanRegistered(planId, creator, planHash);
}
/**
* @notice Finalize a plan with execution result
*/
function finalizePlan(
bytes32 planId,
bool success
) external override {
PlanRecord storage record = plans[planId];
require(record.registeredAt > 0, "Plan not registered");
require(record.finalizedAt == 0, "Plan already finalized");
bytes32 receiptHash = keccak256(abi.encode(planId, success, block.timestamp));
record.finalizedAt = block.timestamp;
record.success = success;
record.receiptHash = receiptHash;
emit PlanFinalized(planId, success, receiptHash);
}
/**
* @notice Register contract codehash for upgrade verification
*/
function registerCodehash(
address contractAddress,
bytes32 codehash,
string calldata version
) external onlyOwner {
codehashes[keccak256(abi.encodePacked(contractAddress, version))] = CodehashRecord({
contractAddress: contractAddress,
codehash: codehash,
version: version,
registeredAt: block.timestamp
});
emit CodehashRegistered(contractAddress, codehash, version);
}
/**
* @notice Get plan record
*/
function getPlan(bytes32 planId) external view returns (PlanRecord memory) {
return plans[planId];
}
/**
* @notice Verify codehash matches registered version
*/
function verifyCodehash(
address contractAddress,
bytes32 codehash,
string calldata version
) external view returns (bool) {
bytes32 key = keccak256(abi.encodePacked(contractAddress, version));
CodehashRecord memory record = codehashes[key];
return record.codehash == codehash && record.registeredAt > 0;
}
}

View File

@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../interfaces/IAdapter.sol";
/**
* @title AaveAdapter
* @notice Adapter for Aave lending protocol
*/
contract AaveAdapter is IAdapter {
string public constant override name = "Aave V3";
// Mock Aave pool (in production, use actual Aave pool)
address public pool;
constructor(address _pool) {
pool = _pool;
}
function executeStep(bytes calldata data) external override returns (bool success, bytes memory returnData) {
// Decode operation type and parameters
// (uint8 operation, address asset, uint256 amount, address collateral)
(uint8 operation, address asset, uint256 amount, address collateral) =
abi.decode(data, (uint8, address, uint256, address));
if (operation == 0) {
// Borrow
// In production: pool.borrow(asset, amount, ...)
success = true;
returnData = abi.encode(amount);
} else if (operation == 1) {
// Repay
// In production: pool.repay(asset, amount, ...)
success = true;
returnData = abi.encode(uint256(0));
} else {
success = false;
returnData = "";
}
}
function prepareStep(bytes calldata data) external override returns (bool prepared) {
// Check if borrow/repay can be prepared (collateral check, etc.)
return true;
}
function adapterType() external pure override returns (uint8) {
return 0; // DEFI
}
}

View File

@@ -0,0 +1,95 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../interfaces/IAdapter.sol";
/**
* @title Iso20022PayAdapter
* @notice Adapter for ISO-20022 payment instructions (off-chain bridge)
*/
contract Iso20022PayAdapter is IAdapter {
string public constant override name = "ISO-20022 Pay";
// Event emitted when payment instruction is ready
event PaymentInstruction(bytes32 indexed planId, bytes32 messageHash, string isoMessage);
function executeStep(bytes calldata data) external override returns (bool success, bytes memory returnData) {
// Decode payment parameters
// (bytes32 planId, string beneficiaryIBAN, uint256 amount, string currency)
(bytes32 planId, string memory beneficiaryIBAN, uint256 amount, string memory currency) =
abi.decode(data, (bytes32, string, uint256, string));
// Generate ISO-20022 message (off-chain)
bytes32 messageHash = keccak256(abi.encode(planId, beneficiaryIBAN, amount, currency));
string memory isoMessage = _generateIsoMessage(planId, beneficiaryIBAN, amount, currency);
emit PaymentInstruction(planId, messageHash, isoMessage);
// In production, this would trigger off-chain ISO message generation
// The actual payment happens off-chain via banking rails
success = true;
returnData = abi.encode(messageHash);
}
function prepareStep(bytes calldata data) external override returns (bool prepared) {
// Check if payment can be prepared (compliance check, etc.)
return true;
}
function adapterType() external pure override returns (uint8) {
return 1; // FIAT_DTL
}
function _generateIsoMessage(
bytes32 planId,
string memory beneficiaryIBAN,
uint256 amount,
string memory currency
) internal pure returns (string memory) {
// Simplified ISO message generation
// In production, use proper XML builder
return string(abi.encodePacked(
"ISO-20022 Message for Plan: ",
_bytes32ToString(planId),
", Amount: ",
_uint256ToString(amount),
" ",
currency,
", IBAN: ",
beneficiaryIBAN
));
}
function _bytes32ToString(bytes32 value) internal pure returns (string memory) {
bytes memory buffer = new bytes(64);
for (uint256 i = 0; i < 32; i++) {
buffer[i * 2] = _toHexChar(uint8(value[i]) >> 4);
buffer[i * 2 + 1] = _toHexChar(uint8(value[i]) & 0x0f);
}
return string(buffer);
}
function _uint256ToString(uint256 value) internal pure returns (string memory) {
if (value == 0) return "0";
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits--;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
function _toHexChar(uint8 value) internal pure returns (bytes1) {
if (value < 10) return bytes1(value + 48);
else return bytes1(value + 87);
}
}

View File

@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../interfaces/IAdapter.sol";
/**
* @title UniswapAdapter
* @notice Adapter for Uniswap V3 swaps
*/
contract UniswapAdapter is IAdapter {
string public constant override name = "Uniswap V3";
// Mock Uniswap router (in production, use actual Uniswap V3 router)
address public router;
constructor(address _router) {
router = _router;
}
function executeStep(bytes calldata data) external override returns (bool success, bytes memory returnData) {
// Decode swap parameters
// (address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOutMin, uint24 fee)
(address tokenIn, address tokenOut, uint256 amountIn, uint256 amountOutMin, uint24 fee) =
abi.decode(data, (address, address, uint256, uint256, uint24));
// In production, call Uniswap router
// (success, returnData) = router.call(abi.encodeWithSignature("exactInputSingle(...)"));
// Mock implementation
success = true;
returnData = abi.encode(amountOutMin); // Return amount out
}
function prepareStep(bytes calldata data) external override returns (bool prepared) {
// Check if swap can be prepared (liquidity check, etc.)
return true;
}
function adapterType() external pure override returns (uint8) {
return 0; // DEFI
}
}

View File

@@ -0,0 +1,28 @@
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
const config: HardhatUserConfig = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
hardhat: {
chainId: 1337,
},
},
paths: {
sources: "./",
tests: "./test",
cache: "./cache",
artifacts: "./artifacts",
},
};
export default config;

View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title IAdapter
* @notice Interface for protocol adapters
*/
interface IAdapter {
/**
* @notice Execute a step using this adapter
* @param data Encoded step parameters
* @return success Whether execution succeeded
* @return returnData Return data from execution
*/
function executeStep(bytes calldata data) external returns (bool success, bytes memory returnData);
/**
* @notice Prepare step (2PC prepare phase)
* @param data Encoded step parameters
* @return prepared Whether preparation succeeded
*/
function prepareStep(bytes calldata data) external returns (bool prepared);
/**
* @notice Get adapter name
*/
function name() external view returns (string memory);
/**
* @notice Get adapter type (DEFI or FIAT_DTL)
*/
function adapterType() external view returns (uint8);
}

View File

@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IAdapterRegistry {
enum AdapterType {
DEFI,
FIAT_DTL
}
struct AdapterInfo {
string name;
AdapterType adapterType;
uint256 registeredAt;
bool whitelisted;
}
function isWhitelisted(address adapter) external view returns (bool);
}

View File

@@ -0,0 +1,54 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IComboHandler {
enum StepType {
BORROW,
SWAP,
REPAY,
PAY,
DEPOSIT,
WITHDRAW,
BRIDGE
}
enum ExecutionStatus {
PENDING,
IN_PROGRESS,
COMPLETE,
FAILED,
ABORTED
}
struct Step {
StepType stepType;
bytes data; // Encoded step-specific parameters
address target; // Target contract address (adapter or protocol)
uint256 value; // ETH value to send (if applicable)
}
struct StepReceipt {
uint256 stepIndex;
bool success;
bytes returnData;
uint256 gasUsed;
}
function executeCombo(
bytes32 planId,
Step[] calldata steps,
bytes calldata signature
) external returns (bool success, StepReceipt[] memory receipts);
function prepare(
bytes32 planId,
Step[] calldata steps
) external returns (bool prepared);
function commit(bytes32 planId) external returns (bool committed);
function abort(bytes32 planId) external;
function getExecutionStatus(bytes32 planId) external view returns (ExecutionStatus status);
}

View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./IComboHandler.sol";
interface INotaryRegistry {
struct PlanRecord {
bytes32 planHash;
address creator;
uint256 registeredAt;
uint256 finalizedAt;
bool success;
bytes32 receiptHash;
}
struct CodehashRecord {
address contractAddress;
bytes32 codehash;
string version;
uint256 registeredAt;
}
function registerPlan(
bytes32 planId,
IComboHandler.Step[] calldata steps,
address creator
) external;
function finalizePlan(bytes32 planId, bool success) external;
}

20
contracts/package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "combo-flow-contracts",
"version": "1.0.0",
"description": "Smart contracts for ISO-20022 Combo Flow",
"scripts": {
"compile": "hardhat compile",
"test": "hardhat test",
"deploy": "hardhat run scripts/deploy.ts"
},
"devDependencies": {
"@nomicfoundation/hardhat-toolbox": "^4.0.0",
"@openzeppelin/contracts": "^5.0.0",
"hardhat": "^2.19.0",
"typescript": "^5.3.3",
"ts-node": "^10.9.2",
"chai": "^4.3.10",
"@types/chai": "^4.3.11"
}
}

View File

@@ -0,0 +1,151 @@
import { expect } from "chai";
import { ethers } from "hardhat";
import type { ComboHandler, AdapterRegistry, NotaryRegistry } from "../typechain-types";
describe("ComboHandler", function () {
let handler: ComboHandler;
let adapterRegistry: AdapterRegistry;
let notaryRegistry: NotaryRegistry;
beforeEach(async function () {
// Deploy AdapterRegistry
const AdapterRegistryFactory = await ethers.getContractFactory("AdapterRegistry");
adapterRegistry = await AdapterRegistryFactory.deploy();
await adapterRegistry.deployed();
// Deploy NotaryRegistry
const NotaryRegistryFactory = await ethers.getContractFactory("NotaryRegistry");
notaryRegistry = await NotaryRegistryFactory.deploy();
await notaryRegistry.deployed();
// Deploy ComboHandler
const HandlerFactory = await ethers.getContractFactory("ComboHandler");
handler = await HandlerFactory.deploy(adapterRegistry.address, notaryRegistry.address);
await handler.deployed();
});
it("Should register plan when executing", async function () {
const planId = ethers.utils.id("test-plan");
const steps: any[] = [];
const signature = "0x";
// This would require a whitelisted adapter
// For now, test that plan registration happens
await expect(
handler.executeCombo(planId, steps, signature)
).to.be.revertedWith("Adapter not whitelisted");
});
it("Should prepare and commit plan (2PC)", async function () {
const planId = ethers.utils.id("test-plan");
const steps: any[] = [];
// Prepare
await expect(handler.prepare(planId, steps))
.to.emit(handler, "PlanPrepared")
.withArgs(planId);
// Commit
await expect(handler.commit(planId))
.to.emit(handler, "PlanCommitted")
.withArgs(planId);
});
it("Should abort prepared plan", async function () {
const planId = ethers.utils.id("test-plan");
const steps: any[] = [];
// Prepare
await handler.prepare(planId, steps);
// Abort
await expect(handler.abort(planId))
.to.emit(handler, "PlanAborted")
.withArgs(planId);
});
it("Should return execution status", async function () {
const planId = ethers.utils.id("test-plan");
const status = await handler.getExecutionStatus(planId);
expect(status).to.equal(0); // PENDING
});
});
describe("AdapterRegistry", function () {
let registry: AdapterRegistry;
beforeEach(async function () {
const Factory = await ethers.getContractFactory("AdapterRegistry");
registry = await Factory.deploy();
await registry.deployed();
});
it("Should register adapter", async function () {
const [owner] = await ethers.getSigners();
const adapterAddress = ethers.Wallet.createRandom().address;
await expect(
registry.registerAdapter(adapterAddress, "Test Adapter", 0) // DEFI
)
.to.emit(registry, "AdapterRegistered")
.withArgs(adapterAddress, "Test Adapter", 0);
});
it("Should whitelist adapter", async function () {
const [owner] = await ethers.getSigners();
const adapterAddress = ethers.Wallet.createRandom().address;
await registry.registerAdapter(adapterAddress, "Test Adapter", 0);
await registry.setWhitelist(adapterAddress, true);
expect(await registry.isWhitelisted(adapterAddress)).to.be.true;
});
it("Should blacklist adapter", async function () {
const [owner] = await ethers.getSigners();
const adapterAddress = ethers.Wallet.createRandom().address;
await registry.registerAdapter(adapterAddress, "Test Adapter", 0);
await registry.setWhitelist(adapterAddress, true);
await registry.setBlacklist(adapterAddress, true);
expect(await registry.isWhitelisted(adapterAddress)).to.be.false;
});
});
describe("NotaryRegistry", function () {
let registry: NotaryRegistry;
beforeEach(async function () {
const Factory = await ethers.getContractFactory("NotaryRegistry");
registry = await Factory.deploy();
await registry.deployed();
});
it("Should register plan", async function () {
const planId = ethers.utils.id("test-plan");
const steps: any[] = [];
const [creator] = await ethers.getSigners();
await expect(
registry.registerPlan(planId, steps, creator.address)
)
.to.emit(registry, "PlanRegistered");
});
it("Should finalize plan", async function () {
const planId = ethers.utils.id("test-plan");
const steps: any[] = [];
const [creator] = await ethers.getSigners();
await registry.registerPlan(planId, steps, creator.address);
await expect(
registry.finalizePlan(planId, true)
)
.to.emit(registry, "PlanFinalized")
.withArgs(planId, true, ethers.utils.id(""));
});
});