unrip/src/venues/near-intents/verifier-client.mjs
philipp 3f0a119987
All checks were successful
deploy / deploy (push) Successful in 24s
Add operator withdrawal path
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.
2026-04-02 12:24:59 +02:00

113 lines
3.1 KiB
JavaScript

import { Account, JsonRpcProvider, KeyPair, teraToGas } from 'near-api-js';
import { postJson } from '../../lib/http.mjs';
export function createVerifierClient({
nearRpcUrl,
verifierContract,
accountId = '',
signerPrivateKey = '',
}) {
const provider = new JsonRpcProvider({ url: nearRpcUrl });
const signer = signerPrivateKey ? KeyPair.fromString(signerPrivateKey) : null;
const operatorAccount = accountId && signerPrivateKey
? new Account(accountId, provider, signerPrivateKey)
: null;
return {
async currentSalt() {
return callView(provider, verifierContract, 'current_salt', {});
},
async mtBatchBalanceOf({ accountId, tokenIds }) {
const result = await callView(provider, verifierContract, 'mt_batch_balance_of', {
account_id: accountId,
token_ids: tokenIds,
});
if (!Array.isArray(result)) {
throw new Error('Unexpected mt_batch_balance_of response');
}
return Object.fromEntries(tokenIds.map((tokenId, index) => [tokenId, String(result[index] || '0')]));
},
async isPublicKeyRegistered({ accountId }) {
if (!signer) return false;
const publicKey = signer.getPublicKey().toString();
const result = await callView(provider, verifierContract, 'has_public_key', {
account_id: accountId,
public_key: publicKey,
}).catch(() => null);
if (typeof result === 'boolean') return result;
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() {
return signer;
},
};
}
export function createSolverRelayRpcClient({ rpcUrl }) {
let id = 1;
return {
async getStatus(intentHash) {
const response = await postJson(rpcUrl, {
jsonrpc: '2.0',
id: id++,
method: 'get_status',
params: [{ intent_hash: intentHash }],
});
if (response.error) {
throw new Error(response.error.message || 'Solver Relay get_status failed');
}
return response.result;
},
};
}
async function callView(provider, accountId, methodName, args) {
const response = await provider.query({
request_type: 'call_function',
finality: 'final',
account_id: accountId,
method_name: methodName,
args_base64: Buffer.from(JSON.stringify(args)).toString('base64'),
});
const bytes = response.result || [];
const text = Buffer.from(bytes).toString('utf8');
return text ? JSON.parse(text) : null;
}
function compact(record) {
return Object.fromEntries(
Object.entries(record).filter(([, value]) => value != null),
);
}