Some checks failed
deploy / deploy (push) Failing after 38s
Proof: Gnosis NEAR Intents assets now persist bridge chain eth:100, repo seed corrects legacy USDC chain rows, and tests prove USDC is not inventory-enabled until a pair activation enables it. Assumptions: NEAR Intents bridge deposit_address uses eth:100 for Gnosis funding handles while dashboard blockchain labels remain gnosis. Still fake: USDC deposit credit and live solver fills are not proven by this change; supported_tokens RPC remains upstream-unavailable.
285 lines
8.5 KiB
JavaScript
285 lines
8.5 KiB
JavaScript
import crypto from 'node:crypto';
|
|
|
|
import { pairKey } from './assets.mjs';
|
|
|
|
export const NEAR_INTENTS_VENUE = 'near-intents';
|
|
export const ONE_CLICK_TOKENS_URL = 'https://1click.chaindefuser.com/v0/tokens';
|
|
|
|
export const CURRENT_NBTC_ASSET_ID = 'nep141:nbtc.bridge.near';
|
|
export const LEGACY_OMFT_BTC_ASSET_ID = 'nep141:btc.omft.near';
|
|
export const CURRENT_EURE_ASSET_ID =
|
|
'nep141:gnosis-0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430.omft.near';
|
|
export const CURRENT_USDC_ASSET_ID =
|
|
'nep141:gnosis-0x2a22f9c3b484c3629090feed35f17ff8f88f76f0.omft.near';
|
|
|
|
export const CURRENT_PAIR_KEY = pairKey(CURRENT_NBTC_ASSET_ID, CURRENT_EURE_ASSET_ID);
|
|
export const CURRENT_REVERSE_PAIR_KEY = pairKey(CURRENT_EURE_ASSET_ID, CURRENT_NBTC_ASSET_ID);
|
|
export const CURRENT_EDGE_BPS = 49;
|
|
export const CURRENT_STRATEGY_MAX_NOTIONAL = '150';
|
|
export const CURRENT_REQUEST_DEFAULT_NOTIONAL_EURE = '5';
|
|
export const CURRENT_REQUEST_MAX_NOTIONAL_EURE = '5';
|
|
export const CURRENT_SLIPPAGE_BPS = 200;
|
|
export const CURRENT_MIN_DEADLINE_MS = 60_000;
|
|
export const CURRENT_PRICE_MAX_AGE_MS = 30_000;
|
|
export const CURRENT_INVENTORY_MAX_AGE_MS = 30_000;
|
|
|
|
export const PAIR_MODES = new Set(['observe_only', 'maker', 'taker', 'both']);
|
|
export const PAIR_STATUSES = new Set(['disabled', 'observe_only', 'maker', 'taker', 'both']);
|
|
|
|
export function normalizeOneClickToken(token) {
|
|
if (!isRecord(token)) throw new Error('token record must be an object');
|
|
|
|
const assetId = stringField(token.assetId ?? token.asset_id ?? token.defuseAssetId, 'assetId');
|
|
const symbol = stringField(token.symbol, 'symbol');
|
|
const decimals = integerField(token.decimals, 'decimals');
|
|
const blockchain = stringField(token.blockchain ?? token.chain, 'blockchain');
|
|
const chain = normalizeBridgeChain({ assetId, blockchain });
|
|
const contractAddress = optionalString(token.contractAddress ?? token.contract_address);
|
|
const latestPrice = token.price == null ? null : String(token.price);
|
|
const priceUpdatedAt = optionalTimestamp(token.priceUpdatedAt ?? token.price_updated_at);
|
|
|
|
return {
|
|
assetId,
|
|
venue: NEAR_INTENTS_VENUE,
|
|
symbol,
|
|
label: symbol,
|
|
decimals,
|
|
blockchain,
|
|
chain,
|
|
contractAddress,
|
|
latestPrice,
|
|
priceUpdatedAt,
|
|
supported: true,
|
|
rawPayload: token,
|
|
};
|
|
}
|
|
|
|
export function normalizeOneClickTokenResponse(response) {
|
|
const tokens = Array.isArray(response)
|
|
? response
|
|
: Array.isArray(response?.tokens)
|
|
? response.tokens
|
|
: Array.isArray(response?.result)
|
|
? response.result
|
|
: null;
|
|
|
|
if (!tokens) throw new Error('supported token response must be an array');
|
|
return tokens.map((token) => normalizeOneClickToken(token));
|
|
}
|
|
|
|
export function hashJson(value) {
|
|
return crypto.createHash('sha256')
|
|
.update(JSON.stringify(value))
|
|
.digest('hex');
|
|
}
|
|
|
|
export function buildSeedAssets() {
|
|
return [
|
|
{
|
|
assetId: CURRENT_NBTC_ASSET_ID,
|
|
venue: NEAR_INTENTS_VENUE,
|
|
symbol: 'BTC',
|
|
label: 'BTC / nBTC reserve',
|
|
decimals: 8,
|
|
blockchain: 'near',
|
|
chain: 'btc:mainnet',
|
|
contractAddress: 'nbtc.bridge.near',
|
|
latestPrice: null,
|
|
priceUpdatedAt: null,
|
|
supported: true,
|
|
enabledForInventory: true,
|
|
role: 'trading',
|
|
rawPayload: { source: 'repo_seed', assetId: CURRENT_NBTC_ASSET_ID },
|
|
},
|
|
{
|
|
assetId: LEGACY_OMFT_BTC_ASSET_ID,
|
|
venue: NEAR_INTENTS_VENUE,
|
|
symbol: 'BTC',
|
|
label: 'BTC / legacy OMFT',
|
|
decimals: 8,
|
|
blockchain: 'btc',
|
|
chain: 'btc:mainnet',
|
|
contractAddress: null,
|
|
latestPrice: null,
|
|
priceUpdatedAt: null,
|
|
supported: true,
|
|
enabledForInventory: true,
|
|
role: 'legacy',
|
|
rawPayload: { source: 'repo_seed', assetId: LEGACY_OMFT_BTC_ASSET_ID },
|
|
},
|
|
{
|
|
assetId: CURRENT_EURE_ASSET_ID,
|
|
venue: NEAR_INTENTS_VENUE,
|
|
symbol: 'EURe',
|
|
label: 'EURe',
|
|
decimals: 18,
|
|
blockchain: 'gnosis',
|
|
chain: 'eth:100',
|
|
contractAddress: '0x420ca0f9b9b604ce0fd9c18ef134c705e5fa3430',
|
|
latestPrice: null,
|
|
priceUpdatedAt: null,
|
|
supported: true,
|
|
enabledForInventory: true,
|
|
role: 'trading',
|
|
withdrawAddress: '0x6C40267e03A97B2132e7a7d3159C88534eBEfdFb',
|
|
rawPayload: { source: 'repo_seed', assetId: CURRENT_EURE_ASSET_ID },
|
|
},
|
|
{
|
|
assetId: CURRENT_USDC_ASSET_ID,
|
|
venue: NEAR_INTENTS_VENUE,
|
|
symbol: 'USDC',
|
|
label: 'USDC',
|
|
decimals: 6,
|
|
blockchain: 'gnosis',
|
|
chain: 'eth:100',
|
|
contractAddress: '0x2a22f9c3b484c3629090feed35f17ff8f88f76f0',
|
|
latestPrice: null,
|
|
priceUpdatedAt: null,
|
|
supported: true,
|
|
enabledForInventory: false,
|
|
role: null,
|
|
rawPayload: { source: 'repo_seed', assetId: CURRENT_USDC_ASSET_ID },
|
|
},
|
|
];
|
|
}
|
|
|
|
export function buildSeedPairs() {
|
|
return [
|
|
{
|
|
pairId: CURRENT_PAIR_KEY,
|
|
venue: NEAR_INTENTS_VENUE,
|
|
assetIn: CURRENT_NBTC_ASSET_ID,
|
|
assetOut: CURRENT_EURE_ASSET_ID,
|
|
mode: 'both',
|
|
enabled: true,
|
|
status: 'both',
|
|
},
|
|
{
|
|
pairId: CURRENT_REVERSE_PAIR_KEY,
|
|
venue: NEAR_INTENTS_VENUE,
|
|
assetIn: CURRENT_EURE_ASSET_ID,
|
|
assetOut: CURRENT_NBTC_ASSET_ID,
|
|
mode: 'both',
|
|
enabled: true,
|
|
status: 'both',
|
|
},
|
|
];
|
|
}
|
|
|
|
export function buildSeedStrategyConfig(pairId, {
|
|
version = 1,
|
|
active = true,
|
|
createdBy = 'repo_seed',
|
|
reason = 'seed current nBTC/EURe production config',
|
|
} = {}) {
|
|
return {
|
|
configId: `${pairId}:v${version}`,
|
|
pairId,
|
|
version,
|
|
active,
|
|
edgeBps: CURRENT_EDGE_BPS,
|
|
maxNotional: CURRENT_STRATEGY_MAX_NOTIONAL,
|
|
minNotional: '0',
|
|
slippageBps: CURRENT_SLIPPAGE_BPS,
|
|
minDeadlineMs: CURRENT_MIN_DEADLINE_MS,
|
|
priceMaxAgeMs: CURRENT_PRICE_MAX_AGE_MS,
|
|
inventoryMaxAgeMs: CURRENT_INVENTORY_MAX_AGE_MS,
|
|
requestDefaultNotional: CURRENT_REQUEST_DEFAULT_NOTIONAL_EURE,
|
|
requestMaxNotional: CURRENT_REQUEST_MAX_NOTIONAL_EURE,
|
|
requestMaxSlippageBps: CURRENT_SLIPPAGE_BPS,
|
|
createdBy,
|
|
reason,
|
|
};
|
|
}
|
|
|
|
export function buildSeedPriceRoute(pairId) {
|
|
return {
|
|
routeId: `${pairId}:btc-eur-reference`,
|
|
pairId,
|
|
source: 'btc_eur_reference',
|
|
baseAssetId: CURRENT_NBTC_ASSET_ID,
|
|
quoteAssetId: CURRENT_EURE_ASSET_ID,
|
|
routeConfig: {
|
|
reference_pair: 'BTC/EUR',
|
|
eure_per_eur_assumption: '1',
|
|
},
|
|
maxAgeMs: CURRENT_PRICE_MAX_AGE_MS,
|
|
enabled: true,
|
|
};
|
|
}
|
|
|
|
export function buildBtcUsdcPriceRoute(pairId) {
|
|
return {
|
|
routeId: `${pairId}:btc-usdc-reference`,
|
|
pairId,
|
|
source: 'btc_usdc_reference',
|
|
baseAssetId: CURRENT_NBTC_ASSET_ID,
|
|
quoteAssetId: CURRENT_USDC_ASSET_ID,
|
|
routeConfig: {
|
|
reference_pair: 'BTC/USDC',
|
|
kraken_pair: 'XBTUSDC',
|
|
coingecko_id: 'bitcoin',
|
|
coingecko_vs_currency: 'usd',
|
|
usdc_per_usd_assumption: '1',
|
|
},
|
|
maxAgeMs: CURRENT_PRICE_MAX_AGE_MS,
|
|
enabled: true,
|
|
};
|
|
}
|
|
|
|
export function pairCanObserve(pair) {
|
|
return Boolean(pair?.enabled) && PAIR_MODES.has(pair.mode) && pair.status !== 'disabled';
|
|
}
|
|
|
|
export function pairCanMake(pair) {
|
|
return pairCanObserve(pair) && ['maker', 'both'].includes(pair.mode);
|
|
}
|
|
|
|
export function pairCanTake(pair) {
|
|
return pairCanObserve(pair) && ['taker', 'both'].includes(pair.mode);
|
|
}
|
|
|
|
export function normalizePairMode(mode) {
|
|
const normalized = String(mode || '').trim().toLowerCase();
|
|
if (!PAIR_MODES.has(normalized)) throw new Error(`unsupported pair mode: ${mode}`);
|
|
return normalized;
|
|
}
|
|
|
|
function stringField(value, field) {
|
|
const normalized = optionalString(value);
|
|
if (!normalized) throw new Error(`${field} is required`);
|
|
return normalized;
|
|
}
|
|
|
|
function optionalString(value) {
|
|
const normalized = String(value ?? '').trim();
|
|
return normalized || null;
|
|
}
|
|
|
|
function normalizeBridgeChain({ assetId, blockchain }) {
|
|
const normalizedBlockchain = String(blockchain || '').trim();
|
|
const normalizedAssetId = String(assetId || '').trim();
|
|
if (normalizedBlockchain === 'gnosis' || normalizedAssetId.startsWith('nep141:gnosis-')) {
|
|
return 'eth:100';
|
|
}
|
|
return normalizedBlockchain;
|
|
}
|
|
|
|
function integerField(value, field) {
|
|
const number = Number(value);
|
|
if (!Number.isInteger(number) || number < 0) {
|
|
throw new Error(`${field} must be a non-negative integer`);
|
|
}
|
|
return number;
|
|
}
|
|
|
|
function optionalTimestamp(value) {
|
|
if (value == null || value === '') return null;
|
|
const date = new Date(value);
|
|
if (Number.isNaN(date.getTime())) throw new Error('priceUpdatedAt must be a timestamp');
|
|
return date.toISOString();
|
|
}
|
|
|
|
function isRecord(value) {
|
|
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
}
|