diff --git a/app/buy-no-demo/page.tsx b/app/buy-no-demo/page.tsx
index 5eaa1e3..a028e77 100644
--- a/app/buy-no-demo/page.tsx
+++ b/app/buy-no-demo/page.tsx
@@ -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 (
@@ -115,6 +117,14 @@ export default function BuyNoDemoPage() {
Estimated Shares
{shares.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
+
+ Trading fee
+ {fee.toFixed(2)} USDC
+
+
+ Estimated price impact
+ {(priceImpact * 100).toFixed(2)}%
+
Avg. Purchase Price
0.68 USDC
diff --git a/lib/stellar/archival.ts b/lib/stellar/archival.ts
new file mode 100644
index 0000000..e9c7566
--- /dev/null
+++ b/lib/stellar/archival.ts
@@ -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
(build: () => Promise>, restore: () => Promise): Promise> {
+ const tx = await build();
+ try { tx.simulation; return tx; }
+ catch (error) {
+ if (!isArchivedStateError(error)) throw error;
+ await restore();
+ return build();
+ }
+}
diff --git a/lib/stellar/authorization.ts b/lib/stellar/authorization.ts
new file mode 100644
index 0000000..0f0f82f
--- /dev/null
+++ b/lib/stellar/authorization.ts
@@ -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 };
+}
diff --git a/lib/stellar/transaction-recovery.ts b/lib/stellar/transaction-recovery.ts
new file mode 100644
index 0000000..e624535
--- /dev/null
+++ b/lib/stellar/transaction-recovery.ts
@@ -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(build: (feeBump: boolean) => Promise>, send: (tx: AssembledTransaction) => Promise): Promise {
+ try { return await send(await build(false)); }
+ catch (error) {
+ if (!isFeeSurge(error) && !isExpiredTransaction(error)) throw error;
+ return send(await build(isFeeSurge(error)));
+ }
+}