Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions app/buy-no-demo/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export default function BuyNoDemoPage() {

const price = 0.68;
const shares = (parseFloat(amount) || 0) / price;
const fee = (parseFloat(amount) || 0) * 0.01;
const priceImpact = Math.min(0.99, shares ? ((parseFloat(amount) || 0) / Math.max(shares, 1)) * 0.002 : 0);
const potentialPayout = shares; // Since it's a binary outcome usually 1 USDC per share

return (
Expand Down Expand Up @@ -115,6 +117,14 @@ export default function BuyNoDemoPage() {
<span className="font-body-md text-on-surface-variant">Estimated Shares</span>
<span className="font-data-mono font-bold text-on-surface">{shares.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}</span>
</div>
<div className="flex justify-between items-center text-sm sm:text-base">
<span className="font-body-md text-on-surface-variant">Trading fee</span>
<span className="font-data-mono font-bold text-on-surface">{fee.toFixed(2)} USDC</span>
</div>
<div className="flex justify-between items-center text-sm sm:text-base">
<span className="font-body-md text-on-surface-variant">Estimated price impact</span>
<span className="font-data-mono font-bold text-on-surface">{(priceImpact * 100).toFixed(2)}%</span>
</div>
<div className="flex justify-between items-center text-sm sm:text-base">
<span className="font-body-md text-on-surface-variant">Avg. Purchase Price</span>
<span className="font-data-mono font-bold text-on-surface">0.68 USDC</span>
Expand Down
14 changes: 14 additions & 0 deletions lib/stellar/archival.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { AssembledTransaction } from '@stellar/stellar-sdk/contract';

export const isArchivedStateError = (error: unknown): boolean => /archiv|missing ledger entry|ttl|entry.?not.?found/i.test(String(error));

/** Re-simulate after restoring archived entries through the supplied backend/client. */
export async function simulateWithRestore<T>(build: () => Promise<AssembledTransaction<T>>, restore: () => Promise<void>): Promise<AssembledTransaction<T>> {
const tx = await build();
try { tx.simulation; return tx; }
catch (error) {
if (!isArchivedStateError(error)) throw error;
await restore();
return build();
}
}
19 changes: 19 additions & 0 deletions lib/stellar/authorization.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Address, xdr } from '@stellar/stellar-sdk';

export interface AuthorizationInspection { count: number; signers: string[]; functions: string[]; warnings: string[]; valid: boolean; }

/** Inspect authorization entries before a wallet prompt; never sign unexpected addresses/functions. */
export function inspectAuthorizationEntries(entries: readonly xdr.SorobanAuthorizationEntry[], expectedAddress?: string): AuthorizationInspection {
const signers: string[] = [];
const functions: string[] = [];
const warnings: string[] = [];
for (const entry of entries) {
try {
const address = entry.credentials().switch().name === 'sorobanCredentialsAddress' ? Address.fromScAddress(entry.credentials().address().address()).toString() : undefined;
if (address) signers.push(address);
functions.push(String(entry.rootInvocation().function()));
if (expectedAddress && address && address !== expectedAddress) warnings.push(`Unexpected signer ${address}`);
} catch { warnings.push('Unable to decode an authorization entry'); }
}
return { count: entries.length, signers, functions, warnings, valid: warnings.length === 0 };
}
20 changes: 20 additions & 0 deletions lib/stellar/transaction-recovery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { AssembledTransaction } from '@stellar/stellar-sdk/contract';

export const isFeeSurge = (error: unknown): boolean => /tx_bad_seq|insufficient fee|fee.?bump|resource limit|timeout/i.test(String(error));
export const isExpiredTransaction = (error: unknown): boolean => /expired|tx_too_late|too late|time.?bound/i.test(String(error));

export interface RecoveryAdvice { kind: 'fee-bump' | 'rebuild' | 'retry'; message: string; }
export function transactionRecoveryAdvice(error: unknown): RecoveryAdvice {
if (isFeeSurge(error)) return { kind: 'fee-bump', message: 'Network fees increased. We can retry with a higher fee.' };
if (isExpiredTransaction(error)) return { kind: 'rebuild', message: 'This transaction expired. Rebuild it to refresh its sequence and time bounds.' };
return { kind: 'retry', message: 'The transaction did not complete. Please retry.' };
}

/** Retry a send once after a caller rebuilds the transaction with a fresh fee/sequence. */
export async function sendWithRecovery<T>(build: (feeBump: boolean) => Promise<AssembledTransaction<T>>, send: (tx: AssembledTransaction<T>) => Promise<T>): Promise<T> {
try { return await send(await build(false)); }
catch (error) {
if (!isFeeSurge(error) && !isExpiredTransaction(error)) throw error;
return send(await build(isFeeSurge(error)));
}
}