662 lines
19 KiB
Markdown
662 lines
19 KiB
Markdown
# Adapter Architecture Specification
|
|
|
|
## Overview
|
|
This document specifies the architecture for the hybrid adapter system that supports both DeFi protocols and Fiat/DTL (banking rails) connectors. It defines adapter interfaces, whitelist/blacklist mechanisms, protocol versioning, upgrade paths, and integration guides.
|
|
|
|
---
|
|
|
|
## 1. Adapter System Architecture
|
|
|
|
### High-Level Design
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Combo Builder UI │
|
|
│ (Drag & Drop Adapter Selection) │
|
|
└────────────────────────┬────────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Adapter Registry Contract │
|
|
│ (Whitelist/Blacklist, Version Management) │
|
|
└──────────────┬──────────────────────────────┬───────────────┘
|
|
│ │
|
|
▼ ▼
|
|
┌──────────────────┐ ┌──────────────────┐
|
|
│ DeFi Adapters │ │ Fiat/DTL Adapters│
|
|
│ │ │ │
|
|
│ • Uniswap V3 │ │ • ISO-20022 Pay │
|
|
│ • Aave │ │ • SWIFT MT │
|
|
│ • Compound │ │ • SEPA │
|
|
│ • Bridge │ │ • FedNow │
|
|
└──────────────────┘ └──────────────────┘
|
|
│ │
|
|
▼ ▼
|
|
┌──────────────────┐ ┌──────────────────┐
|
|
│ DeFi Protocols │ │ Banking Rails │
|
|
│ (On-Chain) │ │ (Off-Chain) │
|
|
└──────────────────┘ └──────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 2. Adapter Interface Contract
|
|
|
|
### Base Interface: `IAdapter`
|
|
|
|
```solidity
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
interface IAdapter {
|
|
/**
|
|
* @notice Execute a step using this adapter
|
|
* @param stepData Encoded step-specific parameters
|
|
* @return success Whether execution succeeded
|
|
* @return returnData Return data from execution
|
|
*/
|
|
function executeStep(bytes calldata stepData) external returns (bool success, bytes memory returnData);
|
|
|
|
/**
|
|
* @notice Prepare phase for 2PC (optional, if supported)
|
|
* @param stepData Encoded step parameters
|
|
* @return prepared Whether preparation succeeded
|
|
*/
|
|
function prepareStep(bytes calldata stepData) external returns (bool prepared);
|
|
|
|
/**
|
|
* @notice Get adapter metadata
|
|
* @return name Adapter name
|
|
* @return version Adapter version
|
|
* @return adapterType Type (DEFI or FIAT_DTL)
|
|
*/
|
|
function getMetadata() external view returns (string memory name, string memory version, AdapterType adapterType);
|
|
|
|
/**
|
|
* @notice Check if adapter supports a specific step type
|
|
* @param stepType Step type to check
|
|
* @return supported Whether step type is supported
|
|
*/
|
|
function supportsStepType(StepType stepType) external view returns (bool supported);
|
|
}
|
|
|
|
enum AdapterType {
|
|
DEFI,
|
|
FIAT_DTL
|
|
}
|
|
|
|
enum StepType {
|
|
BORROW,
|
|
SWAP,
|
|
REPAY,
|
|
PAY,
|
|
DEPOSIT,
|
|
WITHDRAW,
|
|
BRIDGE
|
|
}
|
|
```
|
|
|
|
### DeFi Adapter Example: `UniswapV3Adapter.sol`
|
|
|
|
```solidity
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "./IAdapter.sol";
|
|
import "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
|
|
|
|
contract UniswapV3Adapter is IAdapter {
|
|
ISwapRouter public constant swapRouter = ISwapRouter(0xE592427A0AEce92De3Edee1F18E0157C05861564);
|
|
|
|
function executeStep(bytes calldata stepData) external override returns (bool success, bytes memory returnData) {
|
|
SwapParams memory params = abi.decode(stepData, (SwapParams));
|
|
|
|
ISwapRouter.ExactInputSingleParams memory swapParams = ISwapRouter.ExactInputSingleParams({
|
|
tokenIn: params.tokenIn,
|
|
tokenOut: params.tokenOut,
|
|
fee: params.fee,
|
|
recipient: params.recipient,
|
|
deadline: block.timestamp + 300,
|
|
amountIn: params.amountIn,
|
|
amountOutMinimum: params.amountOutMinimum,
|
|
sqrtPriceLimitX96: 0
|
|
});
|
|
|
|
uint256 amountOut = swapRouter.exactInputSingle(swapParams);
|
|
|
|
return (true, abi.encode(amountOut));
|
|
}
|
|
|
|
function prepareStep(bytes calldata) external pure override returns (bool) {
|
|
// Uniswap doesn't support prepare phase
|
|
return false;
|
|
}
|
|
|
|
function getMetadata() external pure override returns (string memory, string memory, AdapterType) {
|
|
return ("Uniswap V3", "3.0.1", AdapterType.DEFI);
|
|
}
|
|
|
|
function supportsStepType(StepType stepType) external pure override returns (bool) {
|
|
return stepType == StepType.SWAP;
|
|
}
|
|
|
|
struct SwapParams {
|
|
address tokenIn;
|
|
address tokenOut;
|
|
uint24 fee;
|
|
address recipient;
|
|
uint256 amountIn;
|
|
uint256 amountOutMinimum;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Fiat/DTL Adapter Example: `ISO20022PayAdapter.sol`
|
|
|
|
```solidity
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "./IAdapter.sol";
|
|
|
|
contract ISO20022PayAdapter is IAdapter {
|
|
address public orchestrator;
|
|
mapping(bytes32 => PaymentRequest) public pendingPayments;
|
|
|
|
struct PaymentRequest {
|
|
bytes32 planId;
|
|
string beneficiaryIBAN;
|
|
uint256 amount;
|
|
string currency;
|
|
bool executed;
|
|
}
|
|
|
|
function executeStep(bytes calldata stepData) external override returns (bool success, bytes memory returnData) {
|
|
require(msg.sender == orchestrator, "Only orchestrator");
|
|
|
|
PayParams memory params = abi.decode(stepData, (PayParams));
|
|
|
|
// Store payment request for off-chain processing
|
|
bytes32 requestId = keccak256(abi.encodePacked(params.planId, params.beneficiaryIBAN, params.amount));
|
|
pendingPayments[requestId] = PaymentRequest({
|
|
planId: params.planId,
|
|
beneficiaryIBAN: params.beneficiaryIBAN,
|
|
amount: params.amount,
|
|
currency: params.currency,
|
|
executed: false
|
|
});
|
|
|
|
// Emit event for off-chain orchestrator to process
|
|
emit PaymentRequested(requestId, params.planId, params.beneficiaryIBAN, params.amount, params.currency);
|
|
|
|
return (true, abi.encode(requestId));
|
|
}
|
|
|
|
function prepareStep(bytes calldata stepData) external override returns (bool) {
|
|
// Fiat payments can support prepare phase (provisional ISO message)
|
|
PayParams memory params = abi.decode(stepData, (PayParams));
|
|
bytes32 requestId = keccak256(abi.encodePacked(params.planId, params.beneficiaryIBAN, params.amount));
|
|
|
|
// Mark as prepared (provisional)
|
|
pendingPayments[requestId].executed = false; // Not yet executed
|
|
|
|
emit PaymentPrepared(requestId);
|
|
return true;
|
|
}
|
|
|
|
function getMetadata() external pure override returns (string memory, string memory, AdapterType) {
|
|
return ("ISO-20022 Pay", "1.2.0", AdapterType.FIAT_DTL);
|
|
}
|
|
|
|
function supportsStepType(StepType stepType) external pure override returns (bool) {
|
|
return stepType == StepType.PAY;
|
|
}
|
|
|
|
function confirmPayment(bytes32 requestId, string memory isoMessageId) external {
|
|
require(msg.sender == orchestrator, "Only orchestrator");
|
|
PaymentRequest storage payment = pendingPayments[requestId];
|
|
require(!payment.executed, "Already executed");
|
|
|
|
payment.executed = true;
|
|
emit PaymentConfirmed(requestId, isoMessageId);
|
|
}
|
|
|
|
event PaymentRequested(bytes32 indexed requestId, bytes32 indexed planId, string beneficiaryIBAN, uint256 amount, string currency);
|
|
event PaymentPrepared(bytes32 indexed requestId);
|
|
event PaymentConfirmed(bytes32 indexed requestId, string isoMessageId);
|
|
|
|
struct PayParams {
|
|
bytes32 planId;
|
|
string beneficiaryIBAN;
|
|
uint256 amount;
|
|
string currency;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Whitelist/Blacklist Mechanisms
|
|
|
|
### On-Chain Registry (Smart Contract)
|
|
|
|
```solidity
|
|
// Managed by AdapterRegistry contract (see Smart_Contract_Interfaces.md)
|
|
// - registerAdapter() - Register new adapter
|
|
// - whitelistAdapter() - Add to whitelist
|
|
// - blacklistAdapter() - Remove from whitelist
|
|
// - isWhitelisted() - Check whitelist status
|
|
```
|
|
|
|
### Off-Chain API Filtering
|
|
|
|
```typescript
|
|
// Backend API filters adapters based on:
|
|
// 1. On-chain whitelist status
|
|
// 2. User role/permissions
|
|
// 3. Compliance requirements
|
|
// 4. Geographic restrictions
|
|
|
|
GET /api/adapters?type=DEFI&whitelistedOnly=true&userId=user123
|
|
```
|
|
|
|
### UI Filtering
|
|
|
|
```typescript
|
|
// Frontend filters adapters based on:
|
|
// 1. User selection (All, DeFi, Fiat/DTL, Whitelisted Only)
|
|
// 2. Chain compatibility
|
|
// 3. Compliance requirements
|
|
|
|
const filteredAdapters = adapters.filter(adapter => {
|
|
if (filter === 'DEFI') return adapter.type === 'DEFI';
|
|
if (filter === 'FIAT_DTL') return adapter.type === 'FIAT_DTL';
|
|
if (filter === 'WHITELISTED') return adapter.whitelisted;
|
|
return true; // ALL
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Protocol Versioning
|
|
|
|
### Version String Format
|
|
```
|
|
Major.Minor.Patch
|
|
Example: "3.0.1", "1.2.0"
|
|
```
|
|
|
|
### Version Management
|
|
|
|
#### On-Chain (Adapter Contract)
|
|
```solidity
|
|
function getMetadata() external view returns (string memory, string memory, AdapterType) {
|
|
return ("Uniswap V3", "3.0.1", AdapterType.DEFI);
|
|
}
|
|
```
|
|
|
|
#### Off-Chain (API/Registry)
|
|
```json
|
|
{
|
|
"id": "uniswap-v3",
|
|
"name": "Uniswap V3",
|
|
"version": "3.0.1",
|
|
"type": "DEFI",
|
|
"whitelisted": true,
|
|
"deprecated": false,
|
|
"replacedBy": null,
|
|
"chainIds": [1, 137, 42161],
|
|
"lastUpdated": "2025-01-15T00:00:00Z"
|
|
}
|
|
```
|
|
|
|
### Version Upgrade Path
|
|
|
|
1. **Register New Version**: Deploy new adapter contract with incremented version
|
|
2. **Register in AdapterRegistry**: Call `registerAdapter()` with new address
|
|
3. **Whitelist New Version**: Call `whitelistAdapter()` for new address
|
|
4. **Deprecate Old Version**: Optionally blacklist old version
|
|
5. **Update UI**: Frontend fetches latest version from registry
|
|
|
|
### Breaking Changes
|
|
|
|
- **Major Version**: Incompatible API changes (new interface required)
|
|
- **Minor Version**: New features, backward compatible
|
|
- **Patch Version**: Bug fixes, backward compatible
|
|
|
|
---
|
|
|
|
## 5. Upgrade Paths
|
|
|
|
### Option 1: New Contract Deployment (Recommended)
|
|
- Deploy new adapter contract
|
|
- Register in AdapterRegistry
|
|
- Whitelist new contract
|
|
- Update frontend to use new address
|
|
- Old adapter remains for existing plans
|
|
|
|
### Option 2: Proxy Pattern (For Complex Adapters)
|
|
```solidity
|
|
// Use Transparent Proxy or UUPS
|
|
// Allows upgrade without changing address
|
|
// Requires careful upgrade governance
|
|
```
|
|
|
|
### Option 3: Adapter Factory Pattern
|
|
```solidity
|
|
contract AdapterFactory {
|
|
function createAdapter(string memory version) external returns (address) {
|
|
// Deploy new adapter instance
|
|
// Register automatically
|
|
// Return address
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Integration Guide for Adding New Adapters
|
|
|
|
### Step 1: Implement IAdapter Interface
|
|
|
|
```solidity
|
|
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "./IAdapter.sol";
|
|
|
|
contract MyNewAdapter is IAdapter {
|
|
function executeStep(bytes calldata stepData) external override returns (bool, bytes memory) {
|
|
// Implementation
|
|
}
|
|
|
|
function prepareStep(bytes calldata stepData) external override returns (bool) {
|
|
// Implementation (optional)
|
|
}
|
|
|
|
function getMetadata() external pure override returns (string memory, string memory, AdapterType) {
|
|
return ("My New Adapter", "1.0.0", AdapterType.DEFI);
|
|
}
|
|
|
|
function supportsStepType(StepType stepType) external pure override returns (bool) {
|
|
return stepType == StepType.SWAP; // Example
|
|
}
|
|
}
|
|
```
|
|
|
|
### Step 2: Deploy Contract
|
|
|
|
```bash
|
|
# Deploy to target network
|
|
npx hardhat run scripts/deploy.js --network mainnet
|
|
```
|
|
|
|
### Step 3: Register in AdapterRegistry
|
|
|
|
```solidity
|
|
// Call from admin account
|
|
adapterRegistry.registerAdapter(
|
|
myNewAdapterAddress,
|
|
AdapterType.DEFI,
|
|
"1.0.0",
|
|
abi.encode(ipfsHash) // Metadata
|
|
);
|
|
```
|
|
|
|
### Step 4: Register Codehash in NotaryRegistry
|
|
|
|
```solidity
|
|
// Get codehash
|
|
bytes32 codeHash;
|
|
assembly {
|
|
codeHash := extcodehash(myNewAdapterAddress)
|
|
}
|
|
|
|
// Register
|
|
notaryRegistry.registerCodeHash(myNewAdapterAddress, codeHash);
|
|
```
|
|
|
|
### Step 5: Whitelist Adapter
|
|
|
|
```solidity
|
|
// After security review
|
|
adapterRegistry.whitelistAdapter(myNewAdapterAddress);
|
|
```
|
|
|
|
### Step 6: Update Backend API
|
|
|
|
```typescript
|
|
// Add adapter to database/configuration
|
|
const adapter = {
|
|
id: 'my-new-adapter',
|
|
address: myNewAdapterAddress,
|
|
type: 'DEFI',
|
|
version: '1.0.0',
|
|
whitelisted: true
|
|
};
|
|
|
|
await db.adapters.insert(adapter);
|
|
```
|
|
|
|
### Step 7: Update Frontend
|
|
|
|
```typescript
|
|
// Adapter should appear automatically via API
|
|
// If custom UI needed, add to adapter palette configuration
|
|
```
|
|
|
|
### Step 8: Testing
|
|
|
|
- Unit tests for adapter contract
|
|
- Integration tests with ComboHandler
|
|
- E2E tests in UI
|
|
- Security audit (if handling significant funds)
|
|
|
|
---
|
|
|
|
## 7. DeFi Adapter Integration Examples
|
|
|
|
### Aave Lending Adapter
|
|
|
|
```solidity
|
|
contract AaveAdapter is IAdapter {
|
|
IPool public constant aavePool = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2);
|
|
|
|
function executeStep(bytes calldata stepData) external override returns (bool, bytes memory) {
|
|
LendingParams memory params = abi.decode(stepData, (LendingParams));
|
|
|
|
if (params.action == LendingAction.BORROW) {
|
|
aavePool.borrow(params.asset, params.amount, 2, 0, msg.sender); // Variable rate
|
|
} else if (params.action == LendingAction.REPAY) {
|
|
aavePool.repay(params.asset, params.amount, 2, msg.sender);
|
|
}
|
|
|
|
return (true, "");
|
|
}
|
|
|
|
enum LendingAction { BORROW, REPAY, DEPOSIT, WITHDRAW }
|
|
|
|
struct LendingParams {
|
|
LendingAction action;
|
|
address asset;
|
|
uint256 amount;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Bridge Adapter (Cross-Chain)
|
|
|
|
```solidity
|
|
contract BridgeAdapter is IAdapter {
|
|
function executeStep(bytes calldata stepData) external override returns (bool, bytes memory) {
|
|
BridgeParams memory params = abi.decode(stepData, (BridgeParams));
|
|
|
|
// Lock tokens on source chain
|
|
// Emit event for bridge service
|
|
emit BridgeRequest(params.token, params.amount, params.targetChain, params.recipient);
|
|
|
|
return (true, "");
|
|
}
|
|
|
|
event BridgeRequest(address indexed token, uint256 amount, uint256 targetChain, address recipient);
|
|
|
|
struct BridgeParams {
|
|
address token;
|
|
uint256 amount;
|
|
uint256 targetChain;
|
|
address recipient;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Fiat/DTL Adapter Integration Examples
|
|
|
|
### SWIFT MT Adapter
|
|
|
|
```solidity
|
|
contract SWIFTAdapter is IAdapter {
|
|
function executeStep(bytes calldata stepData) external override returns (bool, bytes memory) {
|
|
SWIFTParams memory params = abi.decode(stepData, (SWIFTParams));
|
|
|
|
// Store SWIFT message request
|
|
bytes32 messageId = keccak256(abi.encodePacked(params.planId, params.beneficiary, params.amount));
|
|
emit SWIFTMessageRequested(messageId, params.planId, params.beneficiary, params.amount);
|
|
|
|
return (true, abi.encode(messageId));
|
|
}
|
|
|
|
event SWIFTMessageRequested(bytes32 indexed messageId, bytes32 indexed planId, string beneficiary, uint256 amount);
|
|
|
|
struct SWIFTParams {
|
|
bytes32 planId;
|
|
string beneficiary;
|
|
uint256 amount;
|
|
string currency;
|
|
string messageType; // MT103, MT202, etc.
|
|
}
|
|
}
|
|
```
|
|
|
|
### SEPA Adapter
|
|
|
|
```solidity
|
|
contract SEPAAdapter is IAdapter {
|
|
function executeStep(bytes calldata stepData) external override returns (bool, bytes memory) {
|
|
SEPAParams memory params = abi.decode(stepData, (SEPAParams));
|
|
|
|
bytes32 paymentId = keccak256(abi.encodePacked(params.planId, params.creditorIBAN, params.amount));
|
|
emit SEPACreditTransferRequested(paymentId, params.planId, params.creditorIBAN, params.amount);
|
|
|
|
return (true, abi.encode(paymentId));
|
|
}
|
|
|
|
event SEPACreditTransferRequested(bytes32 indexed paymentId, bytes32 indexed planId, string creditorIBAN, uint256 amount);
|
|
|
|
struct SEPAParams {
|
|
bytes32 planId;
|
|
string creditorIBAN;
|
|
string creditorName;
|
|
uint256 amount;
|
|
string currency;
|
|
string remittanceInfo;
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Security Considerations
|
|
|
|
### Adapter Validation
|
|
|
|
1. **Codehash Verification**: Verify adapter codehash matches registered hash before execution
|
|
2. **Whitelist Check**: Only execute whitelisted adapters
|
|
3. **Reentrancy Protection**: Use ReentrancyGuard in handler contract
|
|
4. **Input Validation**: Validate all step parameters before execution
|
|
|
|
### Access Control
|
|
|
|
1. **Orchestrator-Only Execution**: Only orchestrator can call adapter execute functions
|
|
2. **Admin Functions**: Multi-sig required for whitelist/blacklist operations
|
|
3. **Timelock**: Implement timelock for critical operations
|
|
|
|
### Audit Requirements
|
|
|
|
1. **Security Audit**: All adapters must pass security audit before whitelisting
|
|
2. **Code Review**: Peer review required for adapter code
|
|
3. **Testing**: Comprehensive test coverage required
|
|
|
|
---
|
|
|
|
## 10. Testing Requirements
|
|
|
|
### Unit Tests
|
|
|
|
```solidity
|
|
// Test adapter interface implementation
|
|
function testExecuteStep() public {
|
|
// Test successful execution
|
|
// Test failure cases
|
|
// Test return data
|
|
}
|
|
|
|
function testPrepareStep() public {
|
|
// Test prepare phase (if supported)
|
|
}
|
|
```
|
|
|
|
### Integration Tests
|
|
|
|
```solidity
|
|
// Test adapter with ComboHandler
|
|
function testAdapterInCombo() public {
|
|
// Test adapter works in multi-step combo
|
|
// Test step dependencies
|
|
// Test error handling
|
|
}
|
|
```
|
|
|
|
### E2E Tests
|
|
|
|
```typescript
|
|
// Test adapter in full UI flow
|
|
describe('Uniswap V3 Adapter', () => {
|
|
it('should execute swap in combo', async () => {
|
|
// Build combo with Uniswap step
|
|
// Execute combo
|
|
// Verify results
|
|
});
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## 11. Best Practices
|
|
|
|
### Adapter Design
|
|
|
|
1. **Keep It Simple**: Adapters should do one thing well
|
|
2. **Error Handling**: Return clear error messages
|
|
3. **Gas Optimization**: Minimize gas usage
|
|
4. **Event Emission**: Emit events for off-chain tracking
|
|
|
|
### Version Management
|
|
|
|
1. **Semantic Versioning**: Follow semver (Major.Minor.Patch)
|
|
2. **Backward Compatibility**: Maintain backward compatibility when possible
|
|
3. **Deprecation Policy**: Clearly communicate deprecation timeline
|
|
|
|
### Documentation
|
|
|
|
1. **README**: Document adapter purpose, parameters, usage
|
|
2. **API Docs**: Document all functions and parameters
|
|
3. **Examples**: Provide usage examples
|
|
|
|
---
|
|
|
|
**Document Version**: 1.0
|
|
**Last Updated**: 2025-01-15
|
|
**Author**: Architecture Team
|
|
|