Proof: The active NEAR Intents funded market-maker loop needs a first-class operator withdrawal action so funded inventory can be exited through repo-controlled code rather than manual follow-up. Assumptions: The configured signer key is also a full-access key on the named NEAR account, and external-chain exits for active OMFT assets are triggered by intents.near::ft_withdraw with the token contract as receiver_id plus memo=WITHDRAW_TO:<destination>. Still fake: Strategy and executor remain disarmed, no live inventory is credited yet, and no live mainnet trade quote has been submitted.
This commit is contained in:
parent
57eb540b6e
commit
3f0a119987
6 changed files with 378 additions and 1 deletions
|
|
@ -82,6 +82,18 @@ curl -s -X POST http://127.0.0.1:8082/refresh
|
||||||
curl -s -X POST http://127.0.0.1:8083/refresh
|
curl -s -X POST http://127.0.0.1:8083/refresh
|
||||||
curl -s -X POST http://127.0.0.1:8084/refresh
|
curl -s -X POST http://127.0.0.1:8084/refresh
|
||||||
|
|
||||||
|
curl -s -X POST http://127.0.0.1:8084/withdrawal-estimate \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d '{"asset_id":"nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near","amount":"5000000000000000000","destination_address":"0xYourGnosisAddress"}'
|
||||||
|
|
||||||
|
curl -s -X POST http://127.0.0.1:8084/freeze-withdrawals \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d '{"frozen":false}'
|
||||||
|
|
||||||
|
curl -s -X POST http://127.0.0.1:8084/withdraw \
|
||||||
|
-H 'content-type: application/json' \
|
||||||
|
-d '{"asset_id":"nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near","amount":"5000000000000000000","destination_address":"0xYourGnosisAddress"}'
|
||||||
|
|
||||||
curl -s -X POST http://127.0.0.1:8086/arm
|
curl -s -X POST http://127.0.0.1:8086/arm
|
||||||
curl -s -X POST http://127.0.0.1:8086/disarm
|
curl -s -X POST http://127.0.0.1:8086/disarm
|
||||||
curl -s -X PUT http://127.0.0.1:8086/limits \
|
curl -s -X PUT http://127.0.0.1:8086/limits \
|
||||||
|
|
@ -100,6 +112,12 @@ curl -s -X POST http://127.0.0.1:8084/track-withdrawal \
|
||||||
-d '{"withdrawal_hash":"<near-burn-tx-hash>","asset_id":"nep141:btc.omft.near","chain":"btc:mainnet","amount":"1000"}'
|
-d '{"withdrawal_hash":"<near-burn-tx-hash>","asset_id":"nep141:btc.omft.near","chain":"btc:mainnet","amount":"1000"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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`.
|
||||||
|
- 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
|
## Safe arming sequence
|
||||||
|
|
||||||
1. Confirm `market-reference-ingest` is publishing fresh BTC/EUR data.
|
1. Confirm `market-reference-ingest` is publishing fresh BTC/EUR data.
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@ import { startControlApi } from '../core/control-api.mjs';
|
||||||
import { buildEventEnvelope } from '../core/event-envelope.mjs';
|
import { buildEventEnvelope } from '../core/event-envelope.mjs';
|
||||||
import { createJsonStateStore } from '../core/json-state-store.mjs';
|
import { createJsonStateStore } from '../core/json-state-store.mjs';
|
||||||
import { normalizeLiquidityState } from '../core/liquidity-state.mjs';
|
import { normalizeLiquidityState } from '../core/liquidity-state.mjs';
|
||||||
|
import { buildBridgeWithdrawalPlan } from '../core/liquidity-withdrawals.mjs';
|
||||||
import { createLogger, serializeError } from '../core/log.mjs';
|
import { createLogger, serializeError } from '../core/log.mjs';
|
||||||
import { assertLiquidityActionEvent } from '../core/schemas.mjs';
|
import { assertLiquidityActionEvent } from '../core/schemas.mjs';
|
||||||
import { loadConfig } from '../lib/config.mjs';
|
import { loadConfig } from '../lib/config.mjs';
|
||||||
import { createNearBridgeClient } from '../venues/near-intents/bridge-client.mjs';
|
import { createNearBridgeClient } from '../venues/near-intents/bridge-client.mjs';
|
||||||
|
import { createVerifierClient } from '../venues/near-intents/verifier-client.mjs';
|
||||||
|
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const logger = createLogger({
|
const logger = createLogger({
|
||||||
|
|
@ -33,6 +35,12 @@ const producer = await createProducer({
|
||||||
logger,
|
logger,
|
||||||
});
|
});
|
||||||
const bridgeClient = createNearBridgeClient({ rpcUrl: config.nearBridgeRpcUrl });
|
const bridgeClient = createNearBridgeClient({ rpcUrl: config.nearBridgeRpcUrl });
|
||||||
|
const verifierClient = createVerifierClient({
|
||||||
|
nearRpcUrl: config.nearRpcUrl,
|
||||||
|
verifierContract: config.nearVerifierContract,
|
||||||
|
accountId: config.nearIntentsAccountId,
|
||||||
|
signerPrivateKey: config.nearIntentsSignerPrivateKey,
|
||||||
|
});
|
||||||
const store = createJsonStateStore({
|
const store = createJsonStateStore({
|
||||||
stateDir: config.liquidityStateDir,
|
stateDir: config.liquidityStateDir,
|
||||||
fileName: 'liquidity.json',
|
fileName: 'liquidity.json',
|
||||||
|
|
@ -45,6 +53,8 @@ const store = createJsonStateStore({
|
||||||
supported_tokens: {},
|
supported_tokens: {},
|
||||||
last_refresh_at: null,
|
last_refresh_at: null,
|
||||||
last_error: null,
|
last_error: null,
|
||||||
|
last_withdrawal_request: null,
|
||||||
|
last_withdrawal_result: null,
|
||||||
publish_count: 0,
|
publish_count: 0,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -164,6 +174,106 @@ async function refreshWithdrawal(tracked, state) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function estimateWithdrawal({ assetId, amount, destinationAddress, chain = null, state }) {
|
||||||
|
const plan = buildBridgeWithdrawalPlan({
|
||||||
|
assetId,
|
||||||
|
amount,
|
||||||
|
destinationAddress,
|
||||||
|
chain,
|
||||||
|
supportedTokens: state.supported_tokens,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
const estimate = await bridgeClient.withdrawalEstimate({
|
||||||
|
chain: plan.chain,
|
||||||
|
token: plan.near_token_id,
|
||||||
|
address: plan.destination_address,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...plan,
|
||||||
|
estimate,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitWithdrawal({ assetId, amount, destinationAddress, chain = null }) {
|
||||||
|
const state = store.getState();
|
||||||
|
normalizeLiquidityState(state, {
|
||||||
|
withdrawalsFrozen: config.withdrawalsFrozen,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (state.paused) throw new Error('liquidity manager is paused');
|
||||||
|
if (state.withdrawals_frozen) throw new Error('withdrawals are frozen');
|
||||||
|
|
||||||
|
await refresh();
|
||||||
|
const planned = await estimateWithdrawal({
|
||||||
|
assetId,
|
||||||
|
amount,
|
||||||
|
destinationAddress,
|
||||||
|
chain,
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
|
||||||
|
state.last_withdrawal_request = {
|
||||||
|
asset_id: planned.asset_id,
|
||||||
|
amount: planned.amount,
|
||||||
|
chain: planned.chain,
|
||||||
|
destination_address: planned.destination_address,
|
||||||
|
requested_at: new Date().toISOString(),
|
||||||
|
estimate: planned.estimate,
|
||||||
|
};
|
||||||
|
store.setState(state);
|
||||||
|
|
||||||
|
const outcome = await verifierClient.ftWithdrawRaw({
|
||||||
|
token: planned.near_token_id,
|
||||||
|
receiverId: planned.receiver_id,
|
||||||
|
amount: planned.amount,
|
||||||
|
memo: planned.memo,
|
||||||
|
});
|
||||||
|
const withdrawalHash = outcome.transaction?.hash || outcome.transaction_outcome?.id;
|
||||||
|
if (!withdrawalHash) throw new Error('missing withdrawal hash from ft_withdraw outcome');
|
||||||
|
|
||||||
|
const tracked = {
|
||||||
|
withdrawal_hash: withdrawalHash,
|
||||||
|
asset_id: planned.asset_id,
|
||||||
|
chain: planned.chain,
|
||||||
|
amount: planned.amount,
|
||||||
|
address: planned.destination_address,
|
||||||
|
near_token_id: planned.near_token_id,
|
||||||
|
defuse_asset_identifier: planned.defuse_asset_identifier,
|
||||||
|
estimate: planned.estimate,
|
||||||
|
status: 'SUBMITTED',
|
||||||
|
submitted_at: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
state.tracked_withdrawals[withdrawalHash] = tracked;
|
||||||
|
state.last_withdrawal_result = {
|
||||||
|
withdrawal_hash: withdrawalHash,
|
||||||
|
status: 'SUBMITTED',
|
||||||
|
transaction: outcome.transaction || null,
|
||||||
|
transaction_outcome: outcome.transaction_outcome || null,
|
||||||
|
receipts_outcome: outcome.receipts_outcome || [],
|
||||||
|
};
|
||||||
|
store.setState(state);
|
||||||
|
|
||||||
|
await publishAction({
|
||||||
|
action_type: 'withdrawal_submitted',
|
||||||
|
status: 'SUBMITTED',
|
||||||
|
chain: planned.chain,
|
||||||
|
asset_id: planned.asset_id,
|
||||||
|
details: tracked,
|
||||||
|
}, state);
|
||||||
|
store.setState(state);
|
||||||
|
|
||||||
|
refreshWithdrawal(tracked, state)
|
||||||
|
.then(() => store.setState(state))
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
|
return {
|
||||||
|
plan: planned,
|
||||||
|
tracked,
|
||||||
|
outcome,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async function publishAction(payload, state) {
|
async function publishAction(payload, state) {
|
||||||
const event = buildEventEnvelope({
|
const event = buildEventEnvelope({
|
||||||
source: 'liquidity-manager',
|
source: 'liquidity-manager',
|
||||||
|
|
@ -260,6 +370,46 @@ const controlApi = startControlApi({
|
||||||
return { ok: true, withdrawals_frozen: state.withdrawals_frozen };
|
return { ok: true, withdrawals_frozen: state.withdrawals_frozen };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
path: '/withdrawal-estimate',
|
||||||
|
handler: async ({ body }) => {
|
||||||
|
if (!body.asset_id || !body.amount || !body.destination_address) {
|
||||||
|
return {
|
||||||
|
statusCode: 400,
|
||||||
|
payload: { error: 'asset_id, amount, and destination_address are required' },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = store.getState();
|
||||||
|
normalizeLiquidityState(state, {
|
||||||
|
withdrawalsFrozen: config.withdrawalsFrozen,
|
||||||
|
});
|
||||||
|
if (!Object.keys(state.supported_tokens).length) {
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const withdrawal = await estimateWithdrawal({
|
||||||
|
assetId: body.asset_id,
|
||||||
|
amount: body.amount,
|
||||||
|
destinationAddress: body.destination_address,
|
||||||
|
chain: body.chain,
|
||||||
|
state,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
withdrawal,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
statusCode: 400,
|
||||||
|
payload: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/track-withdrawal',
|
path: '/track-withdrawal',
|
||||||
|
|
@ -296,6 +446,39 @@ const controlApi = startControlApi({
|
||||||
return { ok: true, tracked: state.tracked_withdrawals[body.withdrawal_hash] };
|
return { ok: true, tracked: state.tracked_withdrawals[body.withdrawal_hash] };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
path: '/withdraw',
|
||||||
|
handler: async ({ body }) => {
|
||||||
|
if (!body.asset_id || !body.amount || !body.destination_address) {
|
||||||
|
return {
|
||||||
|
statusCode: 400,
|
||||||
|
payload: { error: 'asset_id, amount, and destination_address are required' },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const submitted = await submitWithdrawal({
|
||||||
|
assetId: body.asset_id,
|
||||||
|
amount: body.amount,
|
||||||
|
destinationAddress: body.destination_address,
|
||||||
|
chain: body.chain,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
withdrawal: submitted.plan,
|
||||||
|
tracked: submitted.tracked,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
statusCode: inferWithdrawStatusCode(error),
|
||||||
|
payload: {
|
||||||
|
error: error.message,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/notify-deposit',
|
path: '/notify-deposit',
|
||||||
|
|
@ -340,3 +523,16 @@ function mapDepositAssetId(defuseAssetIdentifier, chain) {
|
||||||
if (chain === config.tradingEure.chain) return config.tradingEure.assetId;
|
if (chain === config.tradingEure.chain) return config.tradingEure.assetId;
|
||||||
return defuseAssetIdentifier;
|
return defuseAssetIdentifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function inferWithdrawStatusCode(error) {
|
||||||
|
const message = String(error?.message || '');
|
||||||
|
if (
|
||||||
|
message.includes('required')
|
||||||
|
|| message.includes('unsupported')
|
||||||
|
|| message.includes('amount')
|
||||||
|
|| message.includes('chain mismatch')
|
||||||
|
) {
|
||||||
|
return 400;
|
||||||
|
}
|
||||||
|
return 409;
|
||||||
|
}
|
||||||
|
|
|
||||||
57
src/core/liquidity-withdrawals.mjs
Normal file
57
src/core/liquidity-withdrawals.mjs
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
export function buildBridgeWithdrawalPlan({
|
||||||
|
assetId,
|
||||||
|
amount,
|
||||||
|
destinationAddress,
|
||||||
|
chain = null,
|
||||||
|
supportedTokens = {},
|
||||||
|
config,
|
||||||
|
}) {
|
||||||
|
const normalizedAssetId = String(assetId || '').trim();
|
||||||
|
const normalizedAmount = String(amount || '').trim();
|
||||||
|
const normalizedDestination = 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 nearTokenId = stripAssetPrefix(normalizedAssetId);
|
||||||
|
const supported = Object.values(supportedTokens).find((token) => (
|
||||||
|
token.near_token_id === nearTokenId
|
||||||
|
&& String(token.defuse_asset_identifier || '').startsWith(`${asset.chain}:`)
|
||||||
|
));
|
||||||
|
|
||||||
|
if (!supported) {
|
||||||
|
throw new Error(`unsupported bridge withdrawal token: ${normalizedAssetId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const minWithdrawalAmount = String(supported.min_withdrawal_amount || '0');
|
||||||
|
if (BigInt(normalizedAmount) < BigInt(minWithdrawalAmount)) {
|
||||||
|
throw new Error(`amount below minimum withdrawal: ${minWithdrawalAmount}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
asset_id: normalizedAssetId,
|
||||||
|
amount: normalizedAmount,
|
||||||
|
chain: asset.chain,
|
||||||
|
destination_address: normalizedDestination,
|
||||||
|
near_token_id: supported.near_token_id,
|
||||||
|
defuse_asset_identifier: supported.defuse_asset_identifier,
|
||||||
|
receiver_id: supported.near_token_id,
|
||||||
|
memo: `WITHDRAW_TO:${normalizedDestination}`,
|
||||||
|
min_withdrawal_amount: minWithdrawalAmount,
|
||||||
|
withdrawal_fee: String(supported.withdrawal_fee || '0'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripAssetPrefix(assetId) {
|
||||||
|
return String(assetId || '').replace(/^nep141:/, '');
|
||||||
|
}
|
||||||
|
|
@ -39,6 +39,13 @@ export function createNearBridgeClient({ rpcUrl }) {
|
||||||
withdrawal_hash: withdrawalHash,
|
withdrawal_hash: withdrawalHash,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
withdrawalEstimate({ chain, token, address }) {
|
||||||
|
return rpc('withdrawal_estimate', {
|
||||||
|
chain,
|
||||||
|
token,
|
||||||
|
address,
|
||||||
|
});
|
||||||
|
},
|
||||||
notifyDeposit({ depositAddress, txHash }) {
|
notifyDeposit({ depositAddress, txHash }) {
|
||||||
return rpc('notify_deposit', {
|
return rpc('notify_deposit', {
|
||||||
deposit_address: depositAddress,
|
deposit_address: depositAddress,
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
import { JsonRpcProvider, KeyPair } from 'near-api-js';
|
import { Account, JsonRpcProvider, KeyPair, teraToGas } from 'near-api-js';
|
||||||
|
|
||||||
import { postJson } from '../../lib/http.mjs';
|
import { postJson } from '../../lib/http.mjs';
|
||||||
|
|
||||||
export function createVerifierClient({
|
export function createVerifierClient({
|
||||||
nearRpcUrl,
|
nearRpcUrl,
|
||||||
verifierContract,
|
verifierContract,
|
||||||
|
accountId = '',
|
||||||
signerPrivateKey = '',
|
signerPrivateKey = '',
|
||||||
}) {
|
}) {
|
||||||
const provider = new JsonRpcProvider({ url: nearRpcUrl });
|
const provider = new JsonRpcProvider({ url: nearRpcUrl });
|
||||||
const signer = signerPrivateKey ? KeyPair.fromString(signerPrivateKey) : null;
|
const signer = signerPrivateKey ? KeyPair.fromString(signerPrivateKey) : null;
|
||||||
|
const operatorAccount = accountId && signerPrivateKey
|
||||||
|
? new Account(accountId, provider, signerPrivateKey)
|
||||||
|
: null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async currentSalt() {
|
async currentSalt() {
|
||||||
|
|
@ -37,6 +41,31 @@ export function createVerifierClient({
|
||||||
if (typeof result === 'boolean') return result;
|
if (typeof result === 'boolean') return result;
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
async ftWithdrawRaw({
|
||||||
|
token,
|
||||||
|
receiverId,
|
||||||
|
amount,
|
||||||
|
memo = null,
|
||||||
|
msg = null,
|
||||||
|
storageDeposit = null,
|
||||||
|
}) {
|
||||||
|
if (!operatorAccount) throw new Error('operator account is not configured');
|
||||||
|
|
||||||
|
return operatorAccount.callFunctionRaw({
|
||||||
|
contractId: verifierContract,
|
||||||
|
methodName: 'ft_withdraw',
|
||||||
|
args: compact({
|
||||||
|
token,
|
||||||
|
receiver_id: receiverId,
|
||||||
|
amount: String(amount),
|
||||||
|
memo,
|
||||||
|
msg,
|
||||||
|
storage_deposit: storageDeposit,
|
||||||
|
}),
|
||||||
|
gas: teraToGas('100'),
|
||||||
|
deposit: 1n,
|
||||||
|
});
|
||||||
|
},
|
||||||
getSigner() {
|
getSigner() {
|
||||||
return signer;
|
return signer;
|
||||||
},
|
},
|
||||||
|
|
@ -76,3 +105,9 @@ async function callView(provider, accountId, methodName, args) {
|
||||||
const text = Buffer.from(bytes).toString('utf8');
|
const text = Buffer.from(bytes).toString('utf8');
|
||||||
return text ? JSON.parse(text) : null;
|
return text ? JSON.parse(text) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compact(record) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(record).filter(([, value]) => value != null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
64
test/liquidity-withdrawals.test.mjs
Normal file
64
test/liquidity-withdrawals.test.mjs
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
|
||||||
|
import { buildBridgeWithdrawalPlan } from '../src/core/liquidity-withdrawals.mjs';
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
assetRegistry: new Map([
|
||||||
|
['nep141:btc.omft.near', {
|
||||||
|
assetId: 'nep141:btc.omft.near',
|
||||||
|
chain: 'btc:mainnet',
|
||||||
|
}],
|
||||||
|
['nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near', {
|
||||||
|
assetId: 'nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near',
|
||||||
|
chain: 'eth:100',
|
||||||
|
}],
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
test('buildBridgeWithdrawalPlan creates an external-chain EURe withdrawal plan', () => {
|
||||||
|
const plan = buildBridgeWithdrawalPlan({
|
||||||
|
assetId: 'nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near',
|
||||||
|
amount: '5000000000000000000',
|
||||||
|
destinationAddress: '0x62bda91ac00CCa4e87cE3915Db56DF06773A1747',
|
||||||
|
supportedTokens: {
|
||||||
|
eure: {
|
||||||
|
near_token_id: 'gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near',
|
||||||
|
defuse_asset_identifier: 'eth:100:0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430',
|
||||||
|
min_withdrawal_amount: '1',
|
||||||
|
withdrawal_fee: '200000000000',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.deepEqual(plan, {
|
||||||
|
asset_id: 'nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near',
|
||||||
|
amount: '5000000000000000000',
|
||||||
|
chain: 'eth:100',
|
||||||
|
destination_address: '0x62bda91ac00CCa4e87cE3915Db56DF06773A1747',
|
||||||
|
near_token_id: 'gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near',
|
||||||
|
defuse_asset_identifier: 'eth:100:0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430',
|
||||||
|
receiver_id: 'gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near',
|
||||||
|
memo: 'WITHDRAW_TO:0x62bda91ac00CCa4e87cE3915Db56DF06773A1747',
|
||||||
|
min_withdrawal_amount: '1',
|
||||||
|
withdrawal_fee: '200000000000',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('buildBridgeWithdrawalPlan rejects amounts below the bridge minimum', () => {
|
||||||
|
assert.throws(() => buildBridgeWithdrawalPlan({
|
||||||
|
assetId: 'nep141:btc.omft.near',
|
||||||
|
amount: '9999',
|
||||||
|
destinationAddress: 'bc1qexample',
|
||||||
|
supportedTokens: {
|
||||||
|
btc: {
|
||||||
|
near_token_id: 'btc.omft.near',
|
||||||
|
defuse_asset_identifier: 'btc:mainnet:native',
|
||||||
|
min_withdrawal_amount: '10000',
|
||||||
|
withdrawal_fee: '1500',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
config,
|
||||||
|
}), /amount below minimum withdrawal: 10000/);
|
||||||
|
});
|
||||||
Loading…
Add table
Reference in a new issue