Skip to main content

Early Withdrawal

Users can redeem an AI trading order before the investment cycle ends. When they do, an early withdrawal penalty is applied based on how much of the cycle has elapsed. This page explains the penalty formula, the redemption API, and the result structure.

How Penalties Work

The penalty is designed to discourage premature withdrawals while still allowing users to exit if needed. The earlier you withdraw, the higher the penalty.

Penalty decreases as cycle progresses:

100% ┐
│ ████
75% │ ████████
│ ████████████
50% │ ████████████████
│ ████████████████████
25% │ ████████████████████████
│ ████████████████████████████
0% └────────────────────────────────
0% 25% 50% 75% 100%
Cycle Completion

Penalty Formula

The penalty is calculated as a percentage of the gross profit (not the total capital). If the order is at a loss, no penalty is applied to the loss portion.

completionRate = daysElapsed / cycleDays

penaltyPercent = maxPenaltyPercent * (1 - completionRate)

penaltyAmount = max(0, grossProfit) * penaltyPercent

netPayout = finalNav - penaltyAmount

Where:

VariableDescription
completionRateFraction of the cycle that has elapsed (0 to 1)
daysElapsedNumber of days since the order was activated
cycleDaysTotal lock period in days
maxPenaltyPercentMaximum penalty percentage, typically 30% (0.30)
grossProfitfinalNav - investedAmount (can be negative)
penaltyAmountAbsolute dollar amount deducted as penalty
netPayoutAmount returned to the user
No Penalty on Losses

If the order is at a loss (grossProfit <= 0), no penalty is applied. The user receives the full finalNav regardless of cycle completion. The penalty only applies to the profit portion.

Penalty Examples

The table below shows the penalty for a $1,000 investment with a $200 gross profit ($1,200 final NAV) across different completion rates, using a 30% max penalty.

Completion RateDays Elapsed (30-day cycle)Penalty %Penalty AmountNet Payout
0% (day 0)030%$60.00$1,140.00
25% (day 7.5)7.522.5%$45.00$1,155.00
50% (day 15)1515%$30.00$1,170.00
75% (day 22.5)22.57.5%$15.00$1,185.00
90% (day 27)273%$6.00$1,194.00
100% (day 30)300%$0.00$1,200.00

If the same order were at a loss (say $950 final NAV, -$50 gross profit):

Completion RatePenalty %Penalty AmountNet Payout
Any0%$0.00$950.00

Redeeming an Order

Use the redeemAIOrder method to perform an early withdrawal. This method works on orders with active or paused status.

import { OneEngineClient, setAITradingAccessToken } from '@one_deploy/sdk';

setAITradingAccessToken(userToken);

const engine = new OneEngineClient({
apiKey: process.env.ONE_API_KEY!,
projectId: process.env.ONE_PROJECT_ID!,
});

const result = await engine.redeemAIOrder('order_xyz789');

console.log('Final NAV:', result.finalNav);
console.log('Gross profit:', result.grossProfit);
console.log('Completion rate:', (result.completionRate * 100).toFixed(1) + '%');
console.log('Penalty:', result.penaltyPercent + '%', '=', '$' + result.penaltyAmount.toFixed(2));
console.log('Net payout:', '$' + result.netPayout.toFixed(2));
console.log('Payout tx:', result.transactionHash);

Method Signature

redeemAIOrder(orderId: string): Promise<AIRedemptionResult>

AIRedemptionResult Type

interface AIRedemptionResult {
/** The updated order object with status 'redeemed'. */
order: AIOrder;

/** NAV at the time of redemption. */
finalNav: number;

/** Gross profit before penalty (finalNav - invested amount). */
grossProfit: number;

/** Penalty amount deducted for early withdrawal. 0 if the cycle completed or at a loss. */
penaltyAmount: number;

/** Penalty percentage applied (0-30). */
penaltyPercent: number;

/** Net amount paid out to the user (finalNav - penaltyAmount). */
netPayout: number;

/** Wallet address the payout was sent to. */
payoutAddress: string;

/** On-chain transaction hash for the payout. */
transactionHash: string;

/** Completion rate at the time of redemption (0 to 1). */
completionRate: number;

/** ISO 8601 timestamp of the redemption. */
redeemedAt: string;
}

Early Withdrawal Confirmation UI

Before redeeming, you should show users a preview of the penalty they will incur. Here is an example confirmation screen.

import { useState, useEffect } from 'react';
import { OneEngineClient, DEFAULT_SHARE_RATES } from '@one_deploy/sdk';
import type { AIOrder, AIRedemptionResult } from '@one_deploy/sdk';

const MAX_PENALTY_PERCENT = 0.30;

interface WithdrawalConfirmProps {
engine: OneEngineClient;
order: AIOrder;
onComplete: (result: AIRedemptionResult) => void;
}

function WithdrawalConfirm({ engine, order, onComplete }: WithdrawalConfirmProps) {
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);

// Calculate estimated penalty
const now = new Date();
const created = new Date(order.createdAt);
const expires = new Date(order.expiresAt);

const totalDays = (expires.getTime() - created.getTime()) / (1000 * 60 * 60 * 24);
const elapsedDays = (now.getTime() - created.getTime()) / (1000 * 60 * 60 * 24);
const completionRate = Math.min(elapsedDays / totalDays, 1);

const grossProfit = order.currentNav - order.amount;
const penaltyPercent = grossProfit > 0
? MAX_PENALTY_PERCENT * (1 - completionRate)
: 0;
const penaltyAmount = Math.max(0, grossProfit) * penaltyPercent;
const estimatedPayout = order.currentNav - penaltyAmount;

const handleRedeem = async () => {
setSubmitting(true);
setError(null);
try {
const result = await engine.redeemAIOrder(order.id);
onComplete(result);
} catch (err) {
setError(err instanceof Error ? err.message : 'Redemption failed');
} finally {
setSubmitting(false);
}
};

return (
<View style={styles.container}>
<Text style={styles.heading}>Early Withdrawal</Text>
<Text style={styles.warning}>
You are redeeming before the cycle ends. A penalty may apply.
</Text>

<View style={styles.details}>
<DetailRow label="Invested" value={`$${order.amount.toFixed(2)}`} />
<DetailRow label="Current NAV" value={`$${order.currentNav.toFixed(2)}`} />
<DetailRow label="Gross Profit" value={`$${grossProfit.toFixed(2)}`} />
<DetailRow
label="Cycle Progress"
value={`${(completionRate * 100).toFixed(1)}% (${elapsedDays.toFixed(0)} / ${totalDays.toFixed(0)} days)`}
/>
<DetailRow
label="Penalty"
value={`${(penaltyPercent * 100).toFixed(1)}% = $${penaltyAmount.toFixed(2)}`}
/>
<View style={styles.divider} />
<DetailRow
label="Estimated Payout"
value={`$${estimatedPayout.toFixed(2)}`}
bold
/>
</View>

{error && <Text style={styles.error}>{error}</Text>}

<Pressable
onPress={handleRedeem}
disabled={submitting}
style={styles.redeemButton}
>
<Text style={styles.redeemButtonText}>
{submitting ? 'Processing...' : 'Confirm Withdrawal'}
</Text>
</Pressable>

<Text style={styles.disclaimer}>
Final amounts are calculated at the time of execution and may differ
slightly from the estimates shown above due to NAV changes.
</Text>
</View>
);
}

function DetailRow({
label,
value,
bold,
}: {
label: string;
value: string;
bold?: boolean;
}) {
return (
<View style={styles.detailRow}>
<Text style={bold ? styles.boldLabel : styles.label}>{label}</Text>
<Text style={bold ? styles.boldValue : styles.value}>{value}</Text>
</View>
);
}

Key Points

  • Penalties only apply to profits, never to the original capital.
  • If an order is at a loss, the full current NAV is returned with no penalty.
  • The maximum penalty is 30% of gross profit, applied when the completion rate is 0%.
  • The penalty decreases linearly as the cycle progresses toward completion.
  • At 100% completion (cycle end), the penalty is 0% -- the order completes normally.
  • Penalties are calculated at the moment of redemption -- the values shown in a preview are estimates.

Next Steps