Add configured withdrawal defaults
All checks were successful
deploy / deploy (push) Successful in 22s
All checks were successful
deploy / deploy (push) Successful in 22s
Proof: The funded NEAR Intents operator path should have a stable configured withdrawal destination for the active assets so exits do not depend on retyping recipient addresses. Assumptions: Active asset withdrawal destinations are long-lived operator settings and can safely live in runtime config; actual withdrawals still require explicit unfreeze and operator action. Still fake: Strategy and executor remain disarmed, no live trade quote has been submitted, and the live withdrawal transaction itself has not been exercised yet.
This commit is contained in:
parent
3f0a119987
commit
b4186d9715
7 changed files with 48 additions and 7 deletions
|
|
@ -14,10 +14,12 @@ TRADING_BTC_ASSET_ID=nep141:btc.omft.near
|
|||
TRADING_BTC_SYMBOL=BTC
|
||||
TRADING_BTC_DECIMALS=8
|
||||
TRADING_BTC_CHAIN=btc:mainnet
|
||||
TRADING_BTC_WITHDRAW_ADDRESS=
|
||||
TRADING_EURE_ASSET_ID=nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near
|
||||
TRADING_EURE_SYMBOL=EURe
|
||||
TRADING_EURE_DECIMALS=18
|
||||
TRADING_EURE_CHAIN=eth:100
|
||||
TRADING_EURE_WITHDRAW_ADDRESS=
|
||||
|
||||
# Control APIs
|
||||
NEAR_INTENTS_CONTROL_API_ENABLED=true
|
||||
|
|
|
|||
|
|
@ -17,10 +17,12 @@ data:
|
|||
TRADING_BTC_SYMBOL: BTC
|
||||
TRADING_BTC_DECIMALS: "8"
|
||||
TRADING_BTC_CHAIN: btc:mainnet
|
||||
TRADING_BTC_WITHDRAW_ADDRESS: ""
|
||||
TRADING_EURE_ASSET_ID: nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near
|
||||
TRADING_EURE_SYMBOL: EURe
|
||||
TRADING_EURE_DECIMALS: "18"
|
||||
TRADING_EURE_CHAIN: "eth:100"
|
||||
TRADING_EURE_WITHDRAW_ADDRESS: "0x6C40267e03A97B2132e7a7d3159C88534eBEfdFb"
|
||||
NEAR_INTENTS_CONTROL_API_ENABLED: "true"
|
||||
NEAR_INTENTS_CONTROL_HOST: 0.0.0.0
|
||||
NEAR_INTENTS_CONTROL_PORT: "8081"
|
||||
|
|
|
|||
|
|
@ -116,6 +116,7 @@ Notes:
|
|||
|
||||
- Deposit addresses are built in. `liquidity-manager` refreshes them from the bridge `deposit_address` RPC and exposes them through `/state`.
|
||||
- The repo withdrawal action is for external-chain exits on the active assets. It submits `intents.near::ft_withdraw`, using the active OMFT token contract as `receiver_id` and `memo=WITHDRAW_TO:<destination>`, then tracks the returned NEAR transaction hash through bridge `withdrawal_status`.
|
||||
- `destination_address` can be omitted only when a default withdrawal address is configured for that asset via `TRADING_BTC_WITHDRAW_ADDRESS` or `TRADING_EURE_WITHDRAW_ADDRESS`.
|
||||
- Withdrawals stay frozen by default. Unfreeze explicitly before calling `/withdraw`, then freeze them again after the operation if you do not want further exits.
|
||||
|
||||
## Safe arming sequence
|
||||
|
|
|
|||
|
|
@ -304,6 +304,10 @@ const controlApi = startControlApi({
|
|||
getState() {
|
||||
return {
|
||||
account_id: config.nearIntentsAccountId,
|
||||
withdrawal_defaults: {
|
||||
[config.tradingBtc.assetId]: config.tradingBtc.withdrawAddress || null,
|
||||
[config.tradingEure.assetId]: config.tradingEure.withdrawAddress || null,
|
||||
},
|
||||
...store.getState(),
|
||||
};
|
||||
},
|
||||
|
|
@ -374,10 +378,10 @@ const controlApi = startControlApi({
|
|||
method: 'POST',
|
||||
path: '/withdrawal-estimate',
|
||||
handler: async ({ body }) => {
|
||||
if (!body.asset_id || !body.amount || !body.destination_address) {
|
||||
if (!body.asset_id || !body.amount) {
|
||||
return {
|
||||
statusCode: 400,
|
||||
payload: { error: 'asset_id, amount, and destination_address are required' },
|
||||
payload: { error: 'asset_id and amount are required' },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -450,10 +454,10 @@ const controlApi = startControlApi({
|
|||
method: 'POST',
|
||||
path: '/withdraw',
|
||||
handler: async ({ body }) => {
|
||||
if (!body.asset_id || !body.amount || !body.destination_address) {
|
||||
if (!body.asset_id || !body.amount) {
|
||||
return {
|
||||
statusCode: 400,
|
||||
payload: { error: 'asset_id, amount, and destination_address are required' },
|
||||
payload: { error: 'asset_id and amount are required' },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,20 +8,20 @@ export function buildBridgeWithdrawalPlan({
|
|||
}) {
|
||||
const normalizedAssetId = String(assetId || '').trim();
|
||||
const normalizedAmount = String(amount || '').trim();
|
||||
const normalizedDestination = String(destinationAddress || '').trim();
|
||||
const requestedDestination = String(destinationAddress || '').trim();
|
||||
const requestedChain = chain == null ? null : String(chain).trim();
|
||||
|
||||
if (!normalizedAssetId) throw new Error('asset_id is required');
|
||||
if (!/^\d+$/.test(normalizedAmount) || BigInt(normalizedAmount) <= 0n) {
|
||||
throw new Error('amount must be a positive integer string in smallest units');
|
||||
}
|
||||
if (!normalizedDestination) throw new Error('destination_address is required');
|
||||
|
||||
const asset = config.assetRegistry.get(normalizedAssetId);
|
||||
if (!asset) throw new Error(`unsupported asset_id: ${normalizedAssetId}`);
|
||||
if (requestedChain && requestedChain !== asset.chain) {
|
||||
throw new Error(`chain mismatch for ${normalizedAssetId}: expected ${asset.chain}`);
|
||||
}
|
||||
const normalizedDestination = requestedDestination || String(asset.withdrawAddress || '').trim();
|
||||
|
||||
const nearTokenId = stripAssetPrefix(normalizedAssetId);
|
||||
const supported = Object.values(supportedTokens).find((token) => (
|
||||
|
|
@ -33,6 +33,10 @@ export function buildBridgeWithdrawalPlan({
|
|||
throw new Error(`unsupported bridge withdrawal token: ${normalizedAssetId}`);
|
||||
}
|
||||
|
||||
if (!normalizedDestination) {
|
||||
throw new Error(`destination_address is required for ${normalizedAssetId}`);
|
||||
}
|
||||
|
||||
const minWithdrawalAmount = String(supported.min_withdrawal_amount || '0');
|
||||
if (BigInt(normalizedAmount) < BigInt(minWithdrawalAmount)) {
|
||||
throw new Error(`amount below minimum withdrawal: ${minWithdrawalAmount}`);
|
||||
|
|
|
|||
|
|
@ -41,10 +41,12 @@ const DEFAULTS = {
|
|||
tradingBtcSymbol: 'BTC',
|
||||
tradingBtcDecimals: 8,
|
||||
tradingBtcChain: 'btc:mainnet',
|
||||
tradingBtcWithdrawAddress: '',
|
||||
tradingEureAssetId: 'nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near',
|
||||
tradingEureSymbol: 'EURe',
|
||||
tradingEureDecimals: 18,
|
||||
tradingEureChain: 'eth:100',
|
||||
tradingEureWithdrawAddress: '',
|
||||
marketReferenceRefreshMs: 5_000,
|
||||
marketReferenceCoinGeckoRefreshMs: 15_000,
|
||||
marketReferenceMaxAgeMs: 30_000,
|
||||
|
|
@ -84,12 +86,13 @@ function parseBoolean(value, fallback) {
|
|||
return fallback;
|
||||
}
|
||||
|
||||
function buildAsset({ assetId, symbol, decimals, chain }) {
|
||||
function buildAsset({ assetId, symbol, decimals, chain, withdrawAddress = '' }) {
|
||||
return {
|
||||
assetId,
|
||||
symbol,
|
||||
decimals,
|
||||
chain,
|
||||
withdrawAddress,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -101,12 +104,16 @@ export function loadConfig({ envPath = '.env' } = {}) {
|
|||
symbol: process.env.TRADING_BTC_SYMBOL || DEFAULTS.tradingBtcSymbol,
|
||||
decimals: parseNumber(process.env.TRADING_BTC_DECIMALS, DEFAULTS.tradingBtcDecimals),
|
||||
chain: process.env.TRADING_BTC_CHAIN || DEFAULTS.tradingBtcChain,
|
||||
withdrawAddress:
|
||||
process.env.TRADING_BTC_WITHDRAW_ADDRESS || DEFAULTS.tradingBtcWithdrawAddress,
|
||||
});
|
||||
const tradingEure = buildAsset({
|
||||
assetId: process.env.TRADING_EURE_ASSET_ID || DEFAULTS.tradingEureAssetId,
|
||||
symbol: process.env.TRADING_EURE_SYMBOL || DEFAULTS.tradingEureSymbol,
|
||||
decimals: parseNumber(process.env.TRADING_EURE_DECIMALS, DEFAULTS.tradingEureDecimals),
|
||||
chain: process.env.TRADING_EURE_CHAIN || DEFAULTS.tradingEureChain,
|
||||
withdrawAddress:
|
||||
process.env.TRADING_EURE_WITHDRAW_ADDRESS || DEFAULTS.tradingEureWithdrawAddress,
|
||||
});
|
||||
|
||||
const projectName = process.env.PROJECT_NAME || DEFAULTS.projectName;
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ const config = {
|
|||
['nep141:btc.omft.near', {
|
||||
assetId: 'nep141:btc.omft.near',
|
||||
chain: 'btc:mainnet',
|
||||
withdrawAddress: '',
|
||||
}],
|
||||
['nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near', {
|
||||
assetId: 'nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near',
|
||||
chain: 'eth:100',
|
||||
withdrawAddress: '0x6C40267e03A97B2132e7a7d3159C88534eBEfdFb',
|
||||
}],
|
||||
]),
|
||||
};
|
||||
|
|
@ -62,3 +64,22 @@ test('buildBridgeWithdrawalPlan rejects amounts below the bridge minimum', () =>
|
|||
config,
|
||||
}), /amount below minimum withdrawal: 10000/);
|
||||
});
|
||||
|
||||
test('buildBridgeWithdrawalPlan falls back to configured asset withdrawal address', () => {
|
||||
const plan = buildBridgeWithdrawalPlan({
|
||||
assetId: 'nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near',
|
||||
amount: '1000000000000000000',
|
||||
destinationAddress: '',
|
||||
supportedTokens: {
|
||||
eure: {
|
||||
near_token_id: 'gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near',
|
||||
defuse_asset_identifier: 'eth:100:0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430',
|
||||
min_withdrawal_amount: '1',
|
||||
withdrawal_fee: '200000000000',
|
||||
},
|
||||
},
|
||||
config,
|
||||
});
|
||||
|
||||
assert.equal(plan.destination_address, '0x6C40267e03A97B2132e7a7d3159C88534eBEfdFb');
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue