Add Z Bank mainnet payment presets
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
import { useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { useCentralBank } from './useCentralBank';
|
||||
|
||||
import ChainMatrixPanel from './ChainMatrixPanel';
|
||||
import ZBankMainnetTransferPanel from './ZBankMainnetTransferPanel';
|
||||
|
||||
import FineractLedgerBanner from '../omnl-dashboard/FineractLedgerBanner';
|
||||
|
||||
@@ -23,6 +25,7 @@ import { formatUsd } from '../../utils/formatMoney';
|
||||
export default function CentralBankDashboard() {
|
||||
|
||||
const cb = useCentralBank();
|
||||
const [activeTab, setActiveTab] = useState<'overview' | 'payments'>('overview');
|
||||
|
||||
const office = cb.office as {
|
||||
|
||||
@@ -80,6 +83,26 @@ export default function CentralBankDashboard() {
|
||||
|
||||
<QuickPortalLinks />
|
||||
|
||||
<div className="flex gap-2 flex-wrap mb-6">
|
||||
{([
|
||||
['overview', 'Overview'],
|
||||
['payments', 'Payments'],
|
||||
] as const).map(([value, label]) => (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
onClick={() => setActiveTab(value)}
|
||||
className={`px-3 py-1 text-xs border font-mono ${
|
||||
activeTab === value
|
||||
? 'border-[#00d4ff] text-[#00d4ff] bg-[#00d4ff]/10'
|
||||
: 'border-[#1e4a8a] text-[#7a9bb8]'
|
||||
}`}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
{cb.error && (
|
||||
@@ -102,19 +125,17 @@ export default function CentralBankDashboard() {
|
||||
|
||||
|
||||
|
||||
<FineractLedgerBanner
|
||||
{activeTab === 'payments' ? (
|
||||
<ZBankMainnetTransferPanel />
|
||||
) : (
|
||||
<>
|
||||
<FineractLedgerBanner
|
||||
moneySupply={cb.moneySupply}
|
||||
loading={cb.loading}
|
||||
officeName={office?.name}
|
||||
/>
|
||||
|
||||
moneySupply={cb.moneySupply}
|
||||
|
||||
loading={cb.loading}
|
||||
|
||||
officeName={office?.name}
|
||||
|
||||
/>
|
||||
|
||||
|
||||
|
||||
<section id="z-full-settlement" className="scroll-mt-20">
|
||||
<section id="z-full-settlement" className="scroll-mt-20">
|
||||
|
||||
<h2 className="dash-section-title mb-1 flex items-center gap-2">
|
||||
|
||||
@@ -146,11 +167,11 @@ export default function CentralBankDashboard() {
|
||||
|
||||
|
||||
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
|
||||
<div id="z-settlement-protocols" className="scroll-mt-20">
|
||||
|
||||
@@ -312,11 +333,12 @@ export default function CentralBankDashboard() {
|
||||
|
||||
<ChainMatrixPanel />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import NasaIcon from '../../components/icons/NasaIcon';
|
||||
|
||||
import { useZBankSettlement } from './useZBankSettlement';
|
||||
|
||||
export default function ZBankMainnetTransferPanel() {
|
||||
const settlement = useZBankSettlement();
|
||||
|
||||
const actionByPreset = {
|
||||
'eth-mainnet': settlement.sendEthToMainnet,
|
||||
'usdt-mainnet': settlement.sendUsdtToMainnet,
|
||||
} as const;
|
||||
|
||||
return (
|
||||
<section className="dash-card mb-6">
|
||||
<div className="flex flex-wrap items-start justify-between gap-3 mb-4">
|
||||
<div>
|
||||
<h2 className="dash-section-title flex items-center gap-2">
|
||||
<NasaIcon name="bridge" size={14} className="nasa-icon--telemetry" />
|
||||
Payments → Send to Ethereum mainnet
|
||||
</h2>
|
||||
<p className="dash-section-subtitle mt-1">
|
||||
One-click settlement, mint, and bridge presets for Ethereum mainnet.
|
||||
</p>
|
||||
</div>
|
||||
<span className="nasa-badge nasa-badge--telemetry">{settlement.status}</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<label className="block">
|
||||
<span className="dash-stat-label mb-1 block">Ethereum mainnet wallet</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="0x..."
|
||||
value={settlement.mainnetWallet}
|
||||
onChange={(event) => settlement.setMainnetWallet(event.target.value)}
|
||||
className="nasa-input text-sm"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="block">
|
||||
<span className="dash-stat-label mb-1 block">Z Bank IBAN</span>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="IBAN"
|
||||
value={settlement.creditorIban}
|
||||
onChange={(event) => settlement.setCreditorIban(event.target.value)}
|
||||
className="nasa-input text-sm"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-4">
|
||||
{settlement.presets.map((preset) => (
|
||||
<div key={preset.id} className="rounded border border-[#1e4a8a] bg-[#08131f] p-4">
|
||||
<div className="flex items-start justify-between gap-3 mb-3">
|
||||
<div>
|
||||
<p className="text-[#e8f4ff] font-semibold">{preset.buttonLabel}</p>
|
||||
<p className="text-xs text-[#7a9bb8] font-mono">
|
||||
{preset.bridgeRail} · {preset.tokenLineId} · targetChainId: {preset.destinationChainId}
|
||||
</p>
|
||||
</div>
|
||||
<span className="nasa-badge nasa-badge--caution">{preset.amountLabel}</span>
|
||||
</div>
|
||||
|
||||
<label className="block mb-3">
|
||||
<span className="dash-stat-label mb-1 block">{preset.amountLabel}</span>
|
||||
<input
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
placeholder="0.0"
|
||||
value={settlement.amounts[preset.id]}
|
||||
onChange={(event) =>
|
||||
settlement.setAmounts((current) => ({
|
||||
...current,
|
||||
[preset.id]: event.target.value,
|
||||
}))
|
||||
}
|
||||
className="nasa-input text-sm"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="nasa-btn nasa-btn--primary w-full"
|
||||
onClick={actionByPreset[preset.id]}
|
||||
disabled={settlement.loadingPreset !== null}
|
||||
>
|
||||
<NasaIcon name="rocket" size={14} />
|
||||
{settlement.loadingPreset === preset.id ? 'Processing…' : preset.buttonLabel}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{settlement.error && (
|
||||
<p className="text-sm text-[#fc3d21] flex items-center gap-1 mb-3">
|
||||
<NasaIcon name="alert" size={14} />
|
||||
{settlement.error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="rounded border border-[#1e4a8a] bg-[#050b14] p-4 text-sm">
|
||||
<p className="dash-stat-label mb-2">Result</p>
|
||||
{settlement.result ? (
|
||||
<dl className="space-y-2 font-mono text-xs">
|
||||
<div className="flex justify-between gap-3">
|
||||
<dt className="text-[#7a9bb8]">Settlement phase</dt>
|
||||
<dd className="text-[#e8f4ff]">{settlement.result.settlement?.phase ?? '—'}</dd>
|
||||
</div>
|
||||
<div className="flex justify-between gap-3">
|
||||
<dt className="text-[#7a9bb8]">Settlement ID</dt>
|
||||
<dd className="text-[#e8f4ff] break-all text-right">{settlement.result.settlement?.settlementId ?? '—'}</dd>
|
||||
</div>
|
||||
<div className="flex justify-between gap-3">
|
||||
<dt className="text-[#7a9bb8]">Mint tx</dt>
|
||||
<dd className="text-[#e8f4ff] break-all text-right">{settlement.result.settlement?.chainTxHash ?? '—'}</dd>
|
||||
</div>
|
||||
<div className="flex justify-between gap-3">
|
||||
<dt className="text-[#7a9bb8]">Bridge tx</dt>
|
||||
<dd className="text-[#e8f4ff] break-all text-right">{settlement.result.bridgeTxHash ?? '—'}</dd>
|
||||
</div>
|
||||
<div className="flex justify-between gap-3">
|
||||
<dt className="text-[#7a9bb8]">Route</dt>
|
||||
<dd className="text-[#e8f4ff] text-right">
|
||||
{settlement.result.executionPlan?.plannerResponse?.selectedRouteReason ??
|
||||
settlement.result.executionPlan?.plannerResponse?.routePlan?.legs
|
||||
?.map((leg) => leg.provider)
|
||||
.filter(Boolean)
|
||||
.join(' → ') ??
|
||||
'—'}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
) : (
|
||||
<p className="text-[#7a9bb8]">No transfer submitted yet.</p>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
188
frontend-dapp/src/features/central-bank/useZBankSettlement.ts
Normal file
188
frontend-dapp/src/features/central-bank/useZBankSettlement.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useAccount, useSendTransaction } from 'wagmi';
|
||||
|
||||
import { omnlApiHeaders, parseAmountToWei, exchangeApi, settlementApi } from '../../config/dex';
|
||||
|
||||
import { ZBANK_MAINNET_PRESETS, type ZBankMainnetPreset } from './zbank-chain-assets';
|
||||
|
||||
type SettlementRecord = {
|
||||
settlementId?: string;
|
||||
phase?: string;
|
||||
chainTxHash?: string;
|
||||
errors?: string[];
|
||||
};
|
||||
|
||||
type ExecutionPlan = {
|
||||
error?: string;
|
||||
execution?: {
|
||||
contractAddress: string;
|
||||
encodedCalldata: string;
|
||||
};
|
||||
plannerResponse?: {
|
||||
selectedRouteReason?: string;
|
||||
routePlan?: {
|
||||
legs?: Array<{ provider?: string }>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
type TransferResult = {
|
||||
presetId: ZBankMainnetPreset['id'];
|
||||
amount: string;
|
||||
recipientWallet: string;
|
||||
creditorIban: string;
|
||||
settlement?: SettlementRecord;
|
||||
executionPlan?: ExecutionPlan;
|
||||
bridgeTxHash?: string;
|
||||
};
|
||||
|
||||
export function useZBankSettlement() {
|
||||
const { address, isConnected } = useAccount();
|
||||
const { sendTransactionAsync } = useSendTransaction();
|
||||
|
||||
const [mainnetWallet, setMainnetWallet] = useState('');
|
||||
const [creditorIban, setCreditorIban] = useState('');
|
||||
const [amounts, setAmounts] = useState<Record<ZBankMainnetPreset['id'], string>>({
|
||||
'eth-mainnet': '',
|
||||
'usdt-mainnet': '',
|
||||
});
|
||||
const [loadingPreset, setLoadingPreset] = useState<ZBankMainnetPreset['id'] | null>(null);
|
||||
const [status, setStatus] = useState('Idle');
|
||||
const [result, setResult] = useState<TransferResult | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (address && !mainnetWallet) {
|
||||
setMainnetWallet(address);
|
||||
}
|
||||
}, [address, mainnetWallet]);
|
||||
|
||||
async function postJson<T>(url: string, body: Record<string, unknown>): Promise<T> {
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: omnlApiHeaders(),
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
const data = (await res.json()) as T & { error?: string };
|
||||
if (!res.ok || data.error) {
|
||||
throw new Error(data.error || `Request failed: ${res.status}`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async function runPreset(preset: ZBankMainnetPreset) {
|
||||
const amount = amounts[preset.id]?.trim();
|
||||
if (!isConnected || !address) {
|
||||
setError('Connect wallet before sending to mainnet.');
|
||||
return;
|
||||
}
|
||||
if (!mainnetWallet.trim()) {
|
||||
setError('Ethereum mainnet wallet is required.');
|
||||
return;
|
||||
}
|
||||
if (!creditorIban.trim()) {
|
||||
setError('Z Bank IBAN is required.');
|
||||
return;
|
||||
}
|
||||
if (!amount) {
|
||||
setError(`${preset.amountLabel} amount is required.`);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingPreset(preset.id);
|
||||
setStatus('Posting settlement');
|
||||
setError(null);
|
||||
setResult(null);
|
||||
|
||||
try {
|
||||
const settlement = await postJson<SettlementRecord>(settlementApi('/convert/fiat-to-crypto'), {
|
||||
idempotencyKey: `ZB-${preset.id}-${Date.now()}`,
|
||||
valueDate: new Date().toISOString().slice(0, 10),
|
||||
currency: preset.currency,
|
||||
amount,
|
||||
creditorIban: creditorIban.trim(),
|
||||
beneficiaryName: 'Z Bank Mainnet Transfer',
|
||||
remittanceInfo: `${preset.buttonLabel} → ${mainnetWallet.trim()}`,
|
||||
convertToCrypto: {
|
||||
enabled: true,
|
||||
targetChainId: preset.destinationChainId,
|
||||
tokenLineId: preset.tokenLineId,
|
||||
recipientAddress: address,
|
||||
symbol: preset.sourceSymbol,
|
||||
tokenAddress: preset.sourceTokenAddress,
|
||||
},
|
||||
});
|
||||
|
||||
setResult({
|
||||
presetId: preset.id,
|
||||
amount,
|
||||
recipientWallet: mainnetWallet.trim(),
|
||||
creditorIban: creditorIban.trim(),
|
||||
settlement,
|
||||
});
|
||||
|
||||
setStatus('Building bridge execution');
|
||||
const executionPlan = await postJson<ExecutionPlan>(exchangeApi('/execute-plan'), {
|
||||
tokenIn: preset.sourceTokenAddress,
|
||||
tokenOut: preset.destinationTokenAddress,
|
||||
amountIn: parseAmountToWei(amount, preset.sourceDecimals),
|
||||
recipient: mainnetWallet.trim(),
|
||||
destinationChainId: preset.destinationChainId,
|
||||
allowBridge: true,
|
||||
preferredBridges: [preset.preferredBridgeLabel],
|
||||
});
|
||||
|
||||
if (!executionPlan.execution) {
|
||||
throw new Error(executionPlan.error || 'Bridge execution plan missing calldata.');
|
||||
}
|
||||
|
||||
setResult((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
executionPlan,
|
||||
}
|
||||
: current,
|
||||
);
|
||||
|
||||
setStatus('Submitting bridge transaction');
|
||||
const bridgeTxHash = await sendTransactionAsync({
|
||||
to: executionPlan.execution.contractAddress as `0x${string}`,
|
||||
data: executionPlan.execution.encodedCalldata as `0x${string}`,
|
||||
});
|
||||
|
||||
setResult((current) =>
|
||||
current
|
||||
? {
|
||||
...current,
|
||||
bridgeTxHash,
|
||||
}
|
||||
: current,
|
||||
);
|
||||
setStatus('Submitted');
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Mainnet transfer failed');
|
||||
setStatus('Failed');
|
||||
} finally {
|
||||
setLoadingPreset(null);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
address,
|
||||
isConnected,
|
||||
mainnetWallet,
|
||||
setMainnetWallet,
|
||||
creditorIban,
|
||||
setCreditorIban,
|
||||
amounts,
|
||||
setAmounts,
|
||||
loadingPreset,
|
||||
status,
|
||||
result,
|
||||
error,
|
||||
presets: ZBANK_MAINNET_PRESETS,
|
||||
sendEthToMainnet: () => runPreset(ZBANK_MAINNET_PRESETS[0]),
|
||||
sendUsdtToMainnet: () => runPreset(ZBANK_MAINNET_PRESETS[1]),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
export type ZBankMainnetPreset = {
|
||||
id: 'eth-mainnet' | 'usdt-mainnet';
|
||||
buttonLabel: string;
|
||||
amountLabel: string;
|
||||
sourceSymbol: string;
|
||||
sourceTokenAddress: `0x${string}`;
|
||||
sourceDecimals: number;
|
||||
destinationSymbol: string;
|
||||
destinationTokenAddress: `0x${string}`;
|
||||
destinationChainId: 1;
|
||||
bridgeRail: string;
|
||||
preferredBridgeLabel: string;
|
||||
tokenLineId: string;
|
||||
currency: string;
|
||||
};
|
||||
|
||||
export const ZBANK_MAINNET_PRESETS: ZBankMainnetPreset[] = [
|
||||
{
|
||||
id: 'eth-mainnet',
|
||||
buttonLabel: 'Send ETH → Mainnet',
|
||||
amountLabel: 'ETH',
|
||||
sourceSymbol: 'WETH',
|
||||
sourceTokenAddress: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||
sourceDecimals: 18,
|
||||
destinationSymbol: 'WETH',
|
||||
destinationTokenAddress: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
|
||||
destinationChainId: 1,
|
||||
bridgeRail: 'ccip-weth9',
|
||||
preferredBridgeLabel: 'CCIPWETH9Bridge',
|
||||
tokenLineId: 'WETH-M2',
|
||||
currency: 'USD',
|
||||
},
|
||||
{
|
||||
id: 'usdt-mainnet',
|
||||
buttonLabel: 'Send USDT → Mainnet',
|
||||
amountLabel: 'USDT',
|
||||
sourceSymbol: 'USDT',
|
||||
sourceTokenAddress: '0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1',
|
||||
sourceDecimals: 6,
|
||||
destinationSymbol: 'USDT',
|
||||
destinationTokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
|
||||
destinationChainId: 1,
|
||||
bridgeRail: 'trustless-lockbox',
|
||||
preferredBridgeLabel: 'GRUTransport',
|
||||
tokenLineId: 'USD-M2',
|
||||
currency: 'USD',
|
||||
},
|
||||
];
|
||||
@@ -57,6 +57,8 @@ export type ExternalTransferRequest = {
|
||||
targetChainId: number;
|
||||
tokenLineId: string;
|
||||
recipientAddress: string;
|
||||
symbol?: string;
|
||||
tokenAddress?: string;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -26,6 +26,8 @@ export async function fetchRoutePlan(body: {
|
||||
amountIn: string;
|
||||
recipient?: string;
|
||||
maxSlippageBps?: number;
|
||||
allowBridge?: boolean;
|
||||
preferredBridges?: string[];
|
||||
}) {
|
||||
const { data } = await axios.post(
|
||||
`${tokenAggUrl()}/api/v2/routes/plan`,
|
||||
@@ -36,7 +38,11 @@ export async function fetchRoutePlan(body: {
|
||||
tokenOut: body.tokenOut,
|
||||
amountIn: body.amountIn,
|
||||
recipient: body.recipient,
|
||||
constraints: { maxSlippageBps: body.maxSlippageBps ?? 50, allowBridge: false },
|
||||
constraints: {
|
||||
maxSlippageBps: body.maxSlippageBps ?? 50,
|
||||
allowBridge: body.allowBridge ?? false,
|
||||
preferredBridges: body.preferredBridges,
|
||||
},
|
||||
},
|
||||
{ timeout: 120000 },
|
||||
);
|
||||
@@ -51,6 +57,8 @@ export async function fetchExecutionPlan(body: {
|
||||
amountIn: string;
|
||||
recipient: string;
|
||||
maxSlippageBps?: number;
|
||||
allowBridge?: boolean;
|
||||
preferredBridges?: string[];
|
||||
}) {
|
||||
const { data } = await axios.post(
|
||||
`${tokenAggUrl()}/api/v2/routes/internal-execution-plan`,
|
||||
@@ -61,7 +69,11 @@ export async function fetchExecutionPlan(body: {
|
||||
tokenOut: body.tokenOut,
|
||||
amountIn: body.amountIn,
|
||||
recipient: body.recipient,
|
||||
constraints: { maxSlippageBps: body.maxSlippageBps ?? 50, allowBridge: false },
|
||||
constraints: {
|
||||
maxSlippageBps: body.maxSlippageBps ?? 50,
|
||||
allowBridge: body.allowBridge ?? false,
|
||||
preferredBridges: body.preferredBridges,
|
||||
},
|
||||
},
|
||||
{ timeout: 120000 },
|
||||
);
|
||||
|
||||
@@ -106,19 +106,32 @@ export function createExchangeRouter(): Router {
|
||||
router.post('/plan', async (req, res) => {
|
||||
if (!requireTrade(req, res)) return;
|
||||
try {
|
||||
const { tokenIn, tokenOut, amountIn, recipient, maxSlippageBps } = req.body as Record<string, unknown>;
|
||||
const {
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
amountIn,
|
||||
recipient,
|
||||
maxSlippageBps,
|
||||
destinationChainId,
|
||||
allowBridge,
|
||||
preferredBridges,
|
||||
} = req.body as Record<string, unknown>;
|
||||
if (!tokenIn || !tokenOut || !amountIn) {
|
||||
res.status(400).json({ error: 'tokenIn, tokenOut, amountIn required' });
|
||||
return;
|
||||
}
|
||||
const plan = await fetchRoutePlan({
|
||||
sourceChainId: cfg.chainId,
|
||||
destinationChainId: cfg.chainId,
|
||||
destinationChainId: destinationChainId ? Number(destinationChainId) : cfg.chainId,
|
||||
tokenIn: String(tokenIn),
|
||||
tokenOut: String(tokenOut),
|
||||
amountIn: String(amountIn),
|
||||
recipient: recipient ? String(recipient) : undefined,
|
||||
maxSlippageBps: maxSlippageBps ? Number(maxSlippageBps) : cfg.defaultSlippageBps,
|
||||
allowBridge: typeof allowBridge === 'boolean' ? allowBridge : undefined,
|
||||
preferredBridges: Array.isArray(preferredBridges)
|
||||
? preferredBridges.map((value) => String(value))
|
||||
: undefined,
|
||||
});
|
||||
res.json(plan);
|
||||
} catch (e) {
|
||||
@@ -129,19 +142,32 @@ export function createExchangeRouter(): Router {
|
||||
router.post('/execute-plan', async (req, res) => {
|
||||
if (!requireTrade(req, res)) return;
|
||||
try {
|
||||
const { tokenIn, tokenOut, amountIn, recipient, maxSlippageBps } = req.body as Record<string, unknown>;
|
||||
const {
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
amountIn,
|
||||
recipient,
|
||||
maxSlippageBps,
|
||||
destinationChainId,
|
||||
allowBridge,
|
||||
preferredBridges,
|
||||
} = req.body as Record<string, unknown>;
|
||||
if (!tokenIn || !tokenOut || !amountIn || !recipient) {
|
||||
res.status(400).json({ error: 'tokenIn, tokenOut, amountIn, recipient required' });
|
||||
return;
|
||||
}
|
||||
const plan = await fetchExecutionPlan({
|
||||
sourceChainId: cfg.chainId,
|
||||
destinationChainId: cfg.chainId,
|
||||
destinationChainId: destinationChainId ? Number(destinationChainId) : cfg.chainId,
|
||||
tokenIn: String(tokenIn),
|
||||
tokenOut: String(tokenOut),
|
||||
amountIn: String(amountIn),
|
||||
recipient: String(recipient),
|
||||
maxSlippageBps: maxSlippageBps ? Number(maxSlippageBps) : cfg.defaultSlippageBps,
|
||||
allowBridge: typeof allowBridge === 'boolean' ? allowBridge : undefined,
|
||||
preferredBridges: Array.isArray(preferredBridges)
|
||||
? preferredBridges.map((value) => String(value))
|
||||
: undefined,
|
||||
});
|
||||
res.json(plan);
|
||||
} catch (e) {
|
||||
|
||||
@@ -224,6 +224,8 @@ export function createSettlementRouter(): Router {
|
||||
targetChainId: body.convertToCrypto?.targetChainId ?? 138,
|
||||
tokenLineId: body.convertToCrypto?.tokenLineId ?? `${body.currency ?? 'USD'}-M2`,
|
||||
recipientAddress: body.convertToCrypto?.recipientAddress ?? '',
|
||||
symbol: body.convertToCrypto?.symbol,
|
||||
tokenAddress: body.convertToCrypto?.tokenAddress,
|
||||
},
|
||||
moneyLayers: ['M2', 'M3'],
|
||||
});
|
||||
|
||||
@@ -160,6 +160,8 @@ export async function processExternalTransfer(
|
||||
recipient: req.convertToCrypto.recipientAddress,
|
||||
settlementRef: record.settlementId,
|
||||
dryRun: !cfg.production.allowChainMintExecute,
|
||||
symbol: req.convertToCrypto.symbol,
|
||||
tokenAddress: req.convertToCrypto.tokenAddress,
|
||||
});
|
||||
advance('CHAIN_MINT_REQUESTED', { chainTxHash: mint.txHash });
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user