@rhea-finance/confidential-swap
v0.2.1
Published
Framework-independent confidential swap orchestration with separate signing and funding wallets.
Readme
@rhea-finance/confidential-swap
Independent TypeScript SDK extracted from the confidential trade flow in multi-chain-lending. Browser and Node.js 20+; ESM, CJS and type declarations. No React, browser globals, wallet connections, private keys or RPC credentials are embedded.
Wallet roles
The linking wallet signs confidential authorization and withdrawal intents. Supported standards are NEAR NEP-413, EVM ERC-191, Solana raw Ed25519, and Tron TIP-191. All signatures are verified locally using Noble cryptography.
The funding wallet performs source-chain transfers, approvals, chain switching and funding swaps. It can be a different wallet on a different chain. BTC, Zcash, Aptos and Sui can fund through a supplied funding adapter; they are not linking wallets.
Default resolveLinkingWallet selection prefers the connected source kind, then NEAR, EVM, Solana, Tron. An explicit preferred wallet overrides that priority. Quote plans freeze the selected account and public key; execution will not silently switch identity.
Setup
pnpm install
pnpm check
pnpm test:repeat
pnpm check:live-readonly # optional network-only registry check; no signatures or fundsFor package consumers: pnpm add @rhea-finance/confidential-swap after publication, or install a locally packed tarball. This repository has not been published automatically.
Client
import {
HttpConfidentialApi,
createConfidentialSwapClient,
type FundingAdapter,
} from '@rhea-finance/confidential-swap';
import { createEvmSigningAdapter } from '@rhea-finance/confidential-swap/adapters';
const api = new HttpConfidentialApi({
directUrl: 'https://1click.chaindefuser.com/v0',
proxyUrl: 'https://api.rhea.finance/proxy/1click/v0',
indexerUrl: 'https://api.rhea.finance',
nearRpcUrl: 'https://rpc.mainnet.near.org',
// Reference app uses signed-payload authorization for POST /private/withdraws.
// Select 'bearer' only when that matches your backend contract.
withdrawAuthorization: 'signed-payload',
});
const linking = createEvmSigningAdapter(() => connectedEvmSigner);
const funding: FundingAdapter = sourceChainAdapter;
const client = createConfidentialSwapClient({
api,
signingAdapters: [linking],
fundingAdapters: [funding],
// Add fundingSwapAdapter for non-DIRECT routes.
});
const plan = await client.quote({
sourceToken,
destinationToken,
amount: '1000000', // smallest-unit integer string, never a JS floating-point amount
sourceWallet: await funding.getIdentity(),
linkingWallet: await linking.getIdentity(),
recipients: destinationAddresses,
slippageBps: 50, // 0.5 percent
});
// Display plan.previews, plan.minAmountOut and the selected wallet identities for review.
const result = await client.execute(plan, {
signal: controller.signal,
onProgress: snapshot => updateProgress(snapshot),
});The example assumes the application's existing wallet, asset, funding adapter and progress objects. The SDK does not connect wallets or manage UI. Connect and choose those objects in the application; do not pass private keys into the SDK. Quote plans have a default maximum age of 10 minutes, bounded by any earlier service-provided deadline; configure maxQuoteAgeMs when a different review window is required.
Token.assetId identifies the registry asset. blockchain names its chain. contractAddress identifies its actual on-chain token. For numeric EVM funding identities supply the matching token swapChain (e.g. "1"); swapAddress explicitly maps an asset to the aggregation SDK's token address/native sentinel. No arbitrary EVM chain is guessed from evm kind alone.
preview accepts an optional linking identity and returns executable: false. A placeholder identity is display-only. Preview/quote may allocate deposit addresses on the backend, but neither signs nor broadcasts funds. quote requires a real linking identity. Default withdrawal fees match the reference app: 2 bps to dcl0001.near, referral rhea. App fees are attached only to withdrawal quotes; funding deposit quotes do not include them. Configured appFees must contain at least one valid NEAR recipient and an integer fee from 1 to 10000.
Funding adapters and paths
FundingAdapter.getIdentity returns { kind, accountId, chain }. Its validate checks capability and the source chain without sending funds. Its transfer receives the real source token, smallest-unit amount, deposit address, optional memo and stable execution ID. Return a source transaction hash. Implement chain-specific transfer logic with the application's existing wallet SDK; an adapter must check identity again immediately before signing/broadcasting.
For non-DIRECT funding, the funding adapter still validates the source wallet and chain, while FundingSwapAdapter performs quote/build/execute/report/status. Its quote data must be public and JSON-serializable. Its executor must use exactly the supplied source identity, chain and deposit recipient.
| Path | Waiting sequence | | --- | --- | | DIRECT | Transfer from the source wallet; wait for confidential deposit and credited balance | | SAME_CHAIN_SWAP | Swap to FLEX_INPUT, report, wait only for confidential deposit and balance | | CROSS_CHAIN_SWAP | Swap to FLEX_INPUT, report, wait for swap record success, then deposit and balance |
All paths authorize the linking identity before sending source funds. Routing prefers registered direct assets, then same-chain USDC/USDT/USDT0, then a NEAR asset or another registry asset. Native NEAR maps to wNEAR. FLEX minimum input is checked against the final aggregation minimum output, with bounded requotes.
fundingSource: 'balance' spends only the requested amount from an existing registered confidential balance, without a source transaction. These balance withdrawals are checkpointed in process memory only and are never written to the configured executionStore, so they are not recoverable after reload. mode: 'TRANSFER' requires the same funding and destination asset; otherwise mode defaults to SWAP. A batch has 1-10 unique recipients on one destination chain and asset. Randomized positive shares preserve the exact raw total with roughly 80-120 percent equal-share bounds, allowing integer rounding. The frozen random fractions are stored in the plan and reused after re-quoting or recovery against the actual credited amount.
Supply addressValidator for BTC/Zcash or unsupported destination chains, using an established chain library. Known EVM, NEAR, Solana, Tron, Aptos and Sui address formats are checked by default. Case-sensitive addresses are never lowercased or silently truncated.
Aggregation bridge
The optional @rhea-finance/confidential-swap/aggregation entry exposes createAggregationFundingAdapter and HttpFundingReporter. Install the optional peer @rhea-finance/cross-chain-aggregation-dex@^2.0.6 when using its bridge/types.
import { SwapClient } from '@rhea-finance/cross-chain-aggregation-dex';
import {
createAggregationFundingAdapter,
HttpFundingReporter,
} from '@rhea-finance/confidential-swap/aggregation';
const swapClient = new SwapClient({
baseUrl: indexerUrl,
apiKey: configuredApiToken,
reportMode: 'manual', // no duplicate automatic reports
executors: sourceChainExecutors,
});
const fundingSwapAdapter = createAggregationFundingAdapter({
client: swapClient,
reporter: new HttpFundingReporter({ baseUrl: indexerUrl, bearerToken: configuredApiToken }),
mapAsset: token => ({ chain: mapSwapChain(token), address: mapSwapAddress(token) }),
});Unlike the aggregation SDK's ordinary order query, advanced funding status uses /api/swap/order-status?recordId=.... The reporter sends confidentiality: 'advanced'. API credentials must be provided by the application, not copied from the reference repository.
Intent protocol boundary
Withdrawal payloads are signed as returned by /generate-intent, matching the reference implementation. The SDK does not enforce a local transfer format or compare intent tokens and amounts against the quote. Signers, standards, nonces, deadlines and signatures are independently validated. NEP-413 withdrawal recipients are signed exactly as returned; authorization still requires the configured verifier. NEP-413 nonces accept Base64 or Base64URL and must decode to 32 bytes.
Applications may opt into additional protocol-specific validation using verifyWithdrawIntent(data, context). Without this hook, generated intent effects are not locally validated. The hook does not bypass signer, standard, nonce, deadline or signature checks.
Recovery and safety
Use getExecution(id) to inspect one checkpoint and listRecoverableExecutions() to read credited unfinished executions in recovery order. Call resume(id) to continue; it refreshes an expired withdrawal review before polling or signing. Reusing execute(plan) for a saved ID resumes it instead of sending another transfer. Default memory stores last only for the client lifetime; supply ExecutionStore and SessionStore for your own storage.
Checkpoints are saved before funding broadcast, report submission and withdrawal submission. A lost response leaves an *_unknown stage. Such writes are never automatically retried, including on 401. Supply read-only reconcile/reconcileFunding and reconcileReport lookups keyed by execution ID/transaction/deposit. An unknown batch is recovered only by finding the existing signer-scoped order with all saved withdrawal deposit addresses; a missing match remains unknown.
Timeout and abort stop local waiting, not the chain transaction. Keep the execution ID and resume. Submitted checkpoints can poll without connected wallets. Partial success is retained per recipient; terminal partial failures return stage: 'failed' with all available item statuses, never a blind retry.
discardPrepared(id) releases only a created checkpoint that has attempted no funds. After credited funds suffer price changes, refreshWithdrawalReview(id) generates and stores a new withdrawal preview/minimum; show the new review and then explicitly call resume(id). It never repeats source funding.
Progress snapshots exclude session tokens and signed intent payloads, but include public quote/transaction metadata and addresses. Treat them as sensitive and trusted application data. The plan digest catches accidental edits, not malicious forgery. Do not accept arbitrary caller-created snapshots or mutate storage during execution.
Budget locks prevent concurrently running executions of the same identity/asset within one client. Stored unfinished executions do not block new executions and their checkpoints are retained. Multiple clients/processes sharing storage require an application-owned atomic/distributed lock; a plain async store is not a distributed lock. External balance changes can also affect confirmation; a balance check is not a cryptographic deposit attribution proof. Successful deposit status must provide actual swapDetails.amountOut; quoted output is not accepted as proof of funds.
NEAR named accounts may require an explicit public-key registration transaction on the linking chain. createNearSigningAdapter exposes a registerPublicKey callback and confirms registration. Implicit NEAR identities must match their Ed25519 key. Registration is not a source-chain funding transaction.
This is confidential-account orchestration, not a guarantee of end-to-end anonymity. Funding transactions, withdrawals, recipient reuse and backend reports can expose associations. Do not log tokens, full signed intents or wallet-provider errors.
Verification status
Local tests exercise actual cryptographic signatures, the three-path/four-linking-kind matrix, source wallet separation, randomized integer bounds, HTTP fixtures, session recovery, ambiguous writes, partial statuses, price re-review and the published aggregation SDK client with mocked transport/execution. Packed ESM/CJS and consumer declarations are checked without React, wallet SDKs or the aggregation peer installed.
Signature domain implementations follow the primary NEAR NEP-413 specification and TronWeb message implementation.
No real wallet signature, source funding transaction or npm publication is performed by tests. Live service checks are separate from fixture coverage. Before production use, verify the deployed intent format, POST withdrawal authorization, swap report schema, final status semantics, registry and fee policy in your environment; then perform explicitly approved small-value end-to-end tests.
