Files
defiQUG 651ff4f7eb Initial project setup: Add contracts, API definitions, tests, and documentation
- Add Foundry project configuration (foundry.toml, foundry.lock)
- Add Solidity contracts (TokenFactory138, BridgeVault138, ComplianceRegistry, etc.)
- Add API definitions (OpenAPI, GraphQL, gRPC, AsyncAPI)
- Add comprehensive test suite (unit, integration, fuzz, invariants)
- Add API services (REST, GraphQL, orchestrator, packet service)
- Add documentation (ISO20022 mapping, runbooks, adapter guides)
- Add development tools (RBC tool, Swagger UI, mock server)
- Update OpenZeppelin submodules to v5.0.0
2025-12-12 10:59:41 -08:00

455 lines
15 KiB
TypeScript

/**
* Blockchain contract interaction layer
* Wrappers for TokenFactory138, DebtRegistry, ComplianceRegistry, etc.
*/
import { ethers } from 'ethers';
import { keccak256, toUtf8Bytes } from 'ethers';
// Contract interfaces (minimal ABIs for the methods we need)
const TOKEN_FACTORY_ABI = [
'function deployToken(string name, string symbol, tuple(address issuer, uint8 decimals, uint8 defaultLienMode, bool bridgeOnly, address bridge) config) returns (address)',
'function tokenByCodeHash(bytes32) view returns (address)',
'event TokenDeployed(address indexed token, bytes32 indexed codeHash, string name, string symbol, uint8 decimals, address indexed issuer, uint8 defaultLienMode, bool bridgeOnly, address bridge)'
];
const DEBT_REGISTRY_ABI = [
'function activeLienAmount(address) view returns (uint256)',
'function hasActiveLien(address) view returns (bool)',
'function activeLienCount(address) view returns (uint256)',
'function getLien(uint256) view returns (tuple(address debtor, uint256 amount, uint64 expiry, uint8 priority, address authority, bytes32 reasonCode, bool active))',
'function placeLien(address debtor, uint256 amount, uint64 expiry, uint8 priority, bytes32 reasonCode) returns (uint256)',
'function reduceLien(uint256 lienId, uint256 reduceBy)',
'function releaseLien(uint256 lienId)',
'event LienPlaced(uint256 indexed lienId, address indexed debtor, uint256 amount, uint64 expiry, uint8 priority, address indexed authority, bytes32 reasonCode)',
'event LienReduced(uint256 indexed lienId, uint256 reduceBy, uint256 newAmount)',
'event LienReleased(uint256 indexed lienId)'
];
const COMPLIANCE_REGISTRY_ABI = [
'function isAllowed(address) view returns (bool)',
'function isFrozen(address) view returns (bool)',
'function riskTier(address) view returns (uint8)',
'function jurisdictionHash(address) view returns (bytes32)',
'function setCompliance(address account, bool allowed, uint8 tier, bytes32 jurHash)',
'function setFrozen(address account, bool frozen)',
'event ComplianceUpdated(address indexed account, bool allowed, uint8 tier, bytes32 jurisdictionHash)',
'event FrozenUpdated(address indexed account, bool frozen)'
];
const POLICY_MANAGER_ABI = [
'function isPaused(address) view returns (bool)',
'function bridgeOnly(address) view returns (bool)',
'function bridge(address) view returns (address)',
'function lienMode(address) view returns (uint8)',
'function isTokenFrozen(address, address) view returns (bool)',
'function canTransfer(address, address, address, uint256) view returns (bool, bytes32)',
'function setPaused(address, bool)',
'function setBridgeOnly(address, bool)',
'function setBridge(address, address)',
'function setLienMode(address, uint8)',
'function freeze(address, address, bool)'
];
const ERC20_ABI = [
'function name() view returns (string)',
'function symbol() view returns (string)',
'function decimals() view returns (uint8)',
'function totalSupply() view returns (uint256)',
'function balanceOf(address) view returns (uint256)',
'function transfer(address, uint256) returns (bool)',
'function mint(address, uint256)',
'function burn(uint256)',
'function clawback(address, uint256)',
'function forceTransfer(address, address, uint256)'
];
const BRIDGE_VAULT_ABI = [
'function lock(address token, uint256 amount, bytes32 targetChain, address targetRecipient)',
'function unlock(address token, address recipient, uint256 amount, bytes32 sourceChain, bytes32 sourceTx)',
'function getLockStatus(bytes32 lockId) view returns (bool, address, uint256, bytes32, address)',
'event Locked(bytes32 indexed lockId, address indexed token, address indexed from, uint256 amount, bytes32 targetChain, address targetRecipient)',
'event Unlocked(bytes32 indexed unlockId, address indexed token, address indexed recipient, uint256 amount, bytes32 sourceChain, bytes32 sourceTx)'
];
export interface TokenConfig {
issuer: string;
decimals: number;
defaultLienMode: number; // 1 = hard freeze, 2 = encumbered
bridgeOnly: boolean;
bridge?: string;
}
export interface LienParams {
debtor: string;
amount: string; // BigNumber as string
expiry?: number; // Unix timestamp, 0 = no expiry
priority: number;
reasonCode: string; // bytes32 as hex string
}
export interface ComplianceParams {
account: string;
allowed: boolean;
tier: number;
jurisdictionHash: string; // bytes32 as hex string
}
export class BlockchainClient {
private provider: ethers.JsonRpcProvider;
private signer?: ethers.Wallet;
private tokenFactory?: ethers.Contract;
private debtRegistry?: ethers.Contract;
private complianceRegistry?: ethers.Contract;
private policyManager?: ethers.Contract;
private bridgeVault?: ethers.Contract;
constructor(
rpcUrl: string,
privateKey?: string,
contractAddresses?: {
tokenFactory?: string;
debtRegistry?: string;
complianceRegistry?: string;
policyManager?: string;
bridgeVault?: string;
}
) {
this.provider = new ethers.JsonRpcProvider(rpcUrl);
if (privateKey) {
this.signer = new ethers.Wallet(privateKey, this.provider);
}
// Initialize contracts if addresses provided
if (contractAddresses) {
if (contractAddresses.tokenFactory && this.signer) {
this.tokenFactory = new ethers.Contract(
contractAddresses.tokenFactory,
TOKEN_FACTORY_ABI,
this.signer
);
}
if (contractAddresses.debtRegistry) {
this.debtRegistry = new ethers.Contract(
contractAddresses.debtRegistry,
DEBT_REGISTRY_ABI,
this.signer || this.provider
);
}
if (contractAddresses.complianceRegistry) {
this.complianceRegistry = new ethers.Contract(
contractAddresses.complianceRegistry,
COMPLIANCE_REGISTRY_ABI,
this.signer || this.provider
);
}
if (contractAddresses.policyManager) {
this.policyManager = new ethers.Contract(
contractAddresses.policyManager,
POLICY_MANAGER_ABI,
this.signer || this.provider
);
}
if (contractAddresses.bridgeVault) {
this.bridgeVault = new ethers.Contract(
contractAddresses.bridgeVault,
BRIDGE_VAULT_ABI,
this.signer || this.provider
);
}
}
}
// Token Factory operations
async deployToken(name: string, symbol: string, config: TokenConfig): Promise<string> {
if (!this.tokenFactory || !this.signer) {
throw new Error('TokenFactory contract not initialized or signer not available');
}
const tx = await this.tokenFactory.deployToken(name, symbol, {
issuer: config.issuer,
decimals: config.decimals,
defaultLienMode: config.defaultLienMode,
bridgeOnly: config.bridgeOnly,
bridge: config.bridge || ethers.ZeroAddress,
});
const receipt = await tx.wait();
const event = receipt.logs.find((log: any) => {
try {
const parsed = this.tokenFactory!.interface.parseLog(log);
return parsed?.name === 'TokenDeployed';
} catch {
return false;
}
});
if (event) {
const parsed = this.tokenFactory.interface.parseLog(event);
return parsed!.args.token;
}
throw new Error('TokenDeployed event not found');
}
async getTokenByCodeHash(codeHash: string): Promise<string | null> {
if (!this.tokenFactory) {
throw new Error('TokenFactory contract not initialized');
}
try {
const address = await this.tokenFactory.tokenByCodeHash(codeHash);
return address === ethers.ZeroAddress ? null : address;
} catch {
return null;
}
}
// Debt Registry operations
async placeLien(params: LienParams): Promise<bigint> {
if (!this.debtRegistry || !this.signer) {
throw new Error('DebtRegistry contract not initialized or signer not available');
}
const tx = await this.debtRegistry.placeLien(
params.debtor,
params.amount,
params.expiry || 0,
params.priority,
params.reasonCode
);
const receipt = await tx.wait();
const event = receipt.logs.find((log: any) => {
try {
const parsed = this.debtRegistry!.interface.parseLog(log);
return parsed?.name === 'LienPlaced';
} catch {
return false;
}
});
if (event) {
const parsed = this.debtRegistry.interface.parseLog(event);
return parsed!.args.lienId;
}
throw new Error('LienPlaced event not found');
}
async getLien(lienId: bigint) {
if (!this.debtRegistry) {
throw new Error('DebtRegistry contract not initialized');
}
return await this.debtRegistry.getLien(lienId);
}
async reduceLien(lienId: bigint, reduceBy: string) {
if (!this.debtRegistry || !this.signer) {
throw new Error('DebtRegistry contract not initialized or signer not available');
}
const tx = await this.debtRegistry.reduceLien(lienId, reduceBy);
return await tx.wait();
}
async releaseLien(lienId: bigint) {
if (!this.debtRegistry || !this.signer) {
throw new Error('DebtRegistry contract not initialized or signer not available');
}
const tx = await this.debtRegistry.releaseLien(lienId);
return await tx.wait();
}
async getActiveLienAmount(debtor: string): Promise<bigint> {
if (!this.debtRegistry) {
throw new Error('DebtRegistry contract not initialized');
}
return await this.debtRegistry.activeLienAmount(debtor);
}
async hasActiveLien(debtor: string): Promise<boolean> {
if (!this.debtRegistry) {
throw new Error('DebtRegistry contract not initialized');
}
return await this.debtRegistry.hasActiveLien(debtor);
}
// Compliance Registry operations
async setCompliance(params: ComplianceParams) {
if (!this.complianceRegistry || !this.signer) {
throw new Error('ComplianceRegistry contract not initialized or signer not available');
}
const tx = await this.complianceRegistry.setCompliance(
params.account,
params.allowed,
params.tier,
params.jurisdictionHash
);
return await tx.wait();
}
async setFrozen(account: string, frozen: boolean) {
if (!this.complianceRegistry || !this.signer) {
throw new Error('ComplianceRegistry contract not initialized or signer not available');
}
const tx = await this.complianceRegistry.setFrozen(account, frozen);
return await tx.wait();
}
async getComplianceProfile(account: string) {
if (!this.complianceRegistry) {
throw new Error('ComplianceRegistry contract not initialized');
}
const [allowed, frozen, tier, jurisdictionHash] = await Promise.all([
this.complianceRegistry.isAllowed(account),
this.complianceRegistry.isFrozen(account),
this.complianceRegistry.riskTier(account),
this.complianceRegistry.jurisdictionHash(account),
]);
return { allowed, frozen, tier: Number(tier), jurisdictionHash };
}
// Policy Manager operations
async getTokenPolicy(tokenAddress: string) {
if (!this.policyManager) {
throw new Error('PolicyManager contract not initialized');
}
const [isPaused, bridgeOnly, bridge, lienMode] = await Promise.all([
this.policyManager.isPaused(tokenAddress),
this.policyManager.bridgeOnly(tokenAddress),
this.policyManager.bridge(tokenAddress),
this.policyManager.lienMode(tokenAddress),
]);
return {
isPaused,
bridgeOnly,
bridge: bridge === ethers.ZeroAddress ? null : bridge,
lienMode: Number(lienMode),
};
}
async updateTokenPolicy(tokenAddress: string, updates: {
paused?: boolean;
bridgeOnly?: boolean;
bridge?: string;
lienMode?: number;
}) {
if (!this.policyManager || !this.signer) {
throw new Error('PolicyManager contract not initialized or signer not available');
}
const txs = [];
if (updates.paused !== undefined) {
txs.push(this.policyManager.setPaused(tokenAddress, updates.paused));
}
if (updates.bridgeOnly !== undefined) {
txs.push(this.policyManager.setBridgeOnly(tokenAddress, updates.bridgeOnly));
}
if (updates.bridge !== undefined) {
txs.push(this.policyManager.setBridge(tokenAddress, updates.bridge || ethers.ZeroAddress));
}
if (updates.lienMode !== undefined) {
txs.push(this.policyManager.setLienMode(tokenAddress, updates.lienMode));
}
return await Promise.all(txs.map(tx => tx.wait()));
}
// Token operations (ERC20 + custom)
async getTokenInfo(tokenAddress: string) {
const token = new ethers.Contract(tokenAddress, ERC20_ABI, this.provider);
const [name, symbol, decimals, totalSupply] = await Promise.all([
token.name(),
token.symbol(),
token.decimals(),
token.totalSupply(),
]);
return { name, symbol, decimals: Number(decimals), totalSupply: totalSupply.toString() };
}
async getTokenBalance(tokenAddress: string, account: string): Promise<bigint> {
const token = new ethers.Contract(tokenAddress, ERC20_ABI, this.provider);
return await token.balanceOf(account);
}
async mintToken(tokenAddress: string, to: string, amount: string) {
if (!this.signer) {
throw new Error('Signer not available');
}
const token = new ethers.Contract(tokenAddress, ERC20_ABI, this.signer);
const tx = await token.mint(to, amount);
return await tx.wait();
}
async burnToken(tokenAddress: string, amount: string) {
if (!this.signer) {
throw new Error('Signer not available');
}
const token = new ethers.Contract(tokenAddress, ERC20_ABI, this.signer);
const tx = await token.burn(amount);
return await tx.wait();
}
async clawbackToken(tokenAddress: string, from: string, amount: string) {
if (!this.signer) {
throw new Error('Signer not available');
}
const token = new ethers.Contract(tokenAddress, ERC20_ABI, this.signer);
const tx = await token.clawback(from, amount);
return await tx.wait();
}
async forceTransferToken(tokenAddress: string, from: string, to: string, amount: string) {
if (!this.signer) {
throw new Error('Signer not available');
}
const token = new ethers.Contract(tokenAddress, ERC20_ABI, this.signer);
const tx = await token.forceTransfer(from, to, amount);
return await tx.wait();
}
// Bridge operations
async lockTokens(tokenAddress: string, amount: string, targetChain: string, targetRecipient: string) {
if (!this.bridgeVault || !this.signer) {
throw new Error('BridgeVault contract not initialized or signer not available');
}
const tx = await this.bridgeVault.lock(
tokenAddress,
amount,
targetChain,
targetRecipient
);
return await tx.wait();
}
async unlockTokens(
tokenAddress: string,
recipient: string,
amount: string,
sourceChain: string,
sourceTx: string
) {
if (!this.bridgeVault || !this.signer) {
throw new Error('BridgeVault contract not initialized or signer not available');
}
const tx = await this.bridgeVault.unlock(
tokenAddress,
recipient,
amount,
sourceChain,
sourceTx
);
return await tx.wait();
}
}
// Singleton instance
export const blockchainClient = new BlockchainClient(
process.env.RPC_URL || 'http://localhost:8545',
process.env.PRIVATE_KEY,
{
tokenFactory: process.env.TOKEN_FACTORY_ADDRESS,
debtRegistry: process.env.DEBT_REGISTRY_ADDRESS,
complianceRegistry: process.env.COMPLIANCE_REGISTRY_ADDRESS,
policyManager: process.env.POLICY_MANAGER_ADDRESS,
bridgeVault: process.env.BRIDGE_VAULT_ADDRESS,
}
);