@@ -476,14 +463,14 @@ export default async function BorrowerProfilePage() {
- Email Verified
+ Wallet Verified
@@ -492,18 +479,18 @@ export default async function BorrowerProfilePage() {
width: "7px",
height: "7px",
borderRadius: "50%",
- background: user.email_confirmed_at ? "#22cf9d" : "#f59e0b",
+ background: user.walletAddress ? "#22cf9d" : "#f59e0b",
display: "inline-block",
}}
/>
- {user.email_confirmed_at ? "Verified" : "Not verified"}
+ {user.walletAddress ? "Verified" : "Not verified"}
Member Since
- {user.created_at
- ? new Date(user.created_at).toLocaleDateString("en-US", {
+ {user.createdAt
+ ? new Date(user.createdAt).toLocaleDateString("en-US", {
month: "long",
year: "numeric",
})
diff --git a/app/dashboard/borrower/referrals/page.tsx b/app/dashboard/borrower/referrals/page.tsx
index 9261c98..a296b39 100644
--- a/app/dashboard/borrower/referrals/page.tsx
+++ b/app/dashboard/borrower/referrals/page.tsx
@@ -16,7 +16,7 @@ export default async function BorrowerReferralsPage() {
heading="Refer a friend"
description="Share your invite link and earn a TLND bonus each time someone you invited takes out their first loan."
email={user.email ?? null}
- userName={String(user.user_metadata?.full_name ?? "")}
+ userName={String(user.fullName ?? "")}
metrics={[]}
links={borrowerNavLinks}
currentPath="/dashboard/borrower/referrals"
diff --git a/app/dashboard/borrower/repay/page.tsx b/app/dashboard/borrower/repay/page.tsx
index 3a1201f..842ceb0 100644
--- a/app/dashboard/borrower/repay/page.tsx
+++ b/app/dashboard/borrower/repay/page.tsx
@@ -3,7 +3,8 @@ import { BorrowerRepayWidget } from "@/components/dashboard/BorrowerRepayWidget"
import { requireAuthenticatedUser } from "@/lib/auth/session";
import { getBorrowerDashboardMetrics, presentBorrowerMetrics } from "@/lib/dashboard/metrics";
import { borrowerNavLinks } from "@/lib/dashboard/borrower-links";
-import { getServerSupabaseClient } from "@/lib/supabase/server";
+import { getDb } from "@/lib/db/client";
+import { getBorrowerLoans, getProfile } from "@/lib/db/queries";
import { Badge } from "@/components/ui/Badge";
import { formatCurrency } from "@/lib/utils/formatting";
import { getFundingProgress } from "@/lib/loans/funding";
@@ -19,25 +20,8 @@ export default async function BorrowerRepayPage({
const { user } = await requireAuthenticatedUser("borrower");
const metrics = await getBorrowerDashboardMetrics(user.id);
- const supabase = await getServerSupabaseClient();
- const [loansRes, profileRes] = supabase
- ? await Promise.all([
- supabase
- .from("loans")
- .select("id, status, principal_amount, funded_amount, repaid_amount, apr_bps, duration_days, due_at, created_at")
- .eq("borrower_id", user.id)
- .order("created_at", { ascending: false })
- .limit(20),
- supabase
- .from("profiles")
- .select("full_name")
- .eq("id", user.id)
- .maybeSingle(),
- ])
- : [{ data: [] }, { data: null }];
-
- const loans = loansRes.data ?? [];
- const profile = profileRes.data;
+ const db = getDb();
+ const [loans, profile] = await Promise.all([getBorrowerLoans(db, user.id, 20), getProfile(db, user.id)]);
// A loan is only repayable once lenders have covered the full principal —
// a partially filled request is not yet active (Issue #269).
@@ -68,7 +52,7 @@ export default async function BorrowerRepayPage({
heading="Repay Loan"
description="Make an early repayment on your active loan to save on interest and boost your Trust Score."
email={user.email ?? null}
- userName={String(user.user_metadata?.full_name ?? profile?.full_name ?? "")}
+ userName={String(user.fullName ?? profile?.full_name ?? "")}
metrics={presentBorrowerMetrics(metrics)}
currentPath="/dashboard/borrower/repay"
links={borrowerNavLinks}
diff --git a/app/dashboard/borrower/tasks/page.tsx b/app/dashboard/borrower/tasks/page.tsx
index 1557038..41b535f 100644
--- a/app/dashboard/borrower/tasks/page.tsx
+++ b/app/dashboard/borrower/tasks/page.tsx
@@ -3,33 +3,30 @@ import { TasksBoard } from "@/components/dashboard/TasksBoard";
import { requireAuthenticatedUser } from "@/lib/auth/session";
import { getBorrowerDashboardMetrics, presentBorrowerMetrics } from "@/lib/dashboard/metrics";
import { borrowerNavLinks } from "@/lib/dashboard/borrower-links";
-import { getServerSupabaseClient } from "@/lib/supabase/server";
+import { and, eq } from "drizzle-orm";
+import { getDb } from "@/lib/db/client";
+import { getProfile } from "@/lib/db/queries";
+import { reputationEvents } from "@/lib/db/schema";
import { getPlatformTasks } from "@/app/api/tasks/complete/route";
export default async function BorrowerTasksPage() {
const { user } = await requireAuthenticatedUser("borrower");
const metrics = await getBorrowerDashboardMetrics(user.id);
- const supabase = await getServerSupabaseClient();
+ const db = getDb();
- const [profileRes, completedEventsRes] = supabase
- ? await Promise.all([
- supabase
- .from("profiles")
- .select("full_name")
- .eq("id", user.id)
- .maybeSingle(),
- // Which tasks has this user already completed?
- supabase
- .from("reputation_events")
- .select("source_key, source_id")
- .eq("user_id", user.id)
- .eq("source_type", "task_completion"),
- ])
- : [{ data: null }, { data: [] }];
+ const [profile, completedEvents] = await Promise.all([
+ getProfile(db, user.id),
+ // Which tasks has this user already completed?
+ db
+ ? db
+ .select({ source_key: reputationEvents.sourceKey, source_id: reputationEvents.sourceId })
+ .from(reputationEvents)
+ .where(and(eq(reputationEvents.userId, user.id), eq(reputationEvents.sourceType, "task_completion")))
+ : Promise.resolve([]),
+ ]);
- const profile = profileRes.data;
const completedTaskIds = new Set(
- (completedEventsRes.data ?? []).map((e) => String(e.source_key ?? e.source_id ?? ""))
+ completedEvents.map((e) => String(e.source_key ?? e.source_id ?? ""))
);
const currentScore = metrics.reputationScore;
@@ -46,7 +43,7 @@ export default async function BorrowerTasksPage() {
heading="Trust Tasks"
description="Complete these tasks to build your trust score. Higher score = better loan terms and higher limits."
email={user.email ?? null}
- userName={String(user.user_metadata?.full_name ?? profile?.full_name ?? "")}
+ userName={String(user.fullName ?? profile?.full_name ?? "")}
metrics={presentBorrowerMetrics(metrics)}
currentPath="/dashboard/borrower/tasks"
links={borrowerNavLinks}
diff --git a/app/dashboard/lender/history/page.tsx b/app/dashboard/lender/history/page.tsx
index 703ad35..250147e 100644
--- a/app/dashboard/lender/history/page.tsx
+++ b/app/dashboard/lender/history/page.tsx
@@ -1,7 +1,12 @@
import { WorkspaceFrame } from "@/components/dashboard/WorkspaceFrame";
import { requireAuthenticatedUser } from "@/lib/auth/session";
import { getLenderDashboardMetrics, presentLenderMetrics } from "@/lib/dashboard/metrics";
-import { getServerSupabaseClient, getServiceRoleClient } from "@/lib/supabase/server";
+import { desc, eq } from "drizzle-orm";
+import { getDb } from "@/lib/db/client";
+import { readMetadata } from "@/lib/db/metadata";
+import { getProfile } from "@/lib/db/queries";
+import { ledgerToRow } from "@/lib/db/rows";
+import { ledgerTransactions } from "@/lib/db/schema";
import { lenderNavLinks } from "@/lib/dashboard/lender-links";
import { ExportCsvButton } from "@/components/dashboard/ExportCsvButton";
import { LenderHistoryClient } from "./client";
@@ -9,67 +14,56 @@ import { LenderHistoryClient } from "./client";
export default async function LenderHistoryPage() {
const { user } = await requireAuthenticatedUser("lender");
const metrics = await getLenderDashboardMetrics(user.id);
- const supabase = await getServerSupabaseClient();
- const srClient = getServiceRoleClient();
+ const db = getDb();
// Profile data
- const { data: profile } = supabase
- ? await supabase.from("profiles").select("full_name").eq("id", user.id).maybeSingle()
- : { data: null };
+ const profile = await getProfile(db, user.id);
// Fetch initial transactions with limit for server-side rendering
const PAGE_SIZE = 20;
- const userTxsQuery = supabase
- ? await supabase
- .from("ledger_transactions")
- .select("id, category, ref_type, ref_id, amount, currency, status, metadata, created_at")
- .eq("user_id", user.id)
- .order("created_at", { ascending: false })
- .limit(PAGE_SIZE + 1)
- : { data: [] };
-
- const { data: userTxs } = userTxsQuery;
-
- const hasMore = (userTxs?.length ?? 0) > PAGE_SIZE;
- const items = userTxs?.slice(0, PAGE_SIZE) ?? [];
-
- // Fetch incoming repayments
- const { data: allRepays } = srClient
- ? await srClient
- .from("ledger_transactions")
- .select("id, category, ref_type, ref_id, amount, currency, status, metadata, created_at")
- .eq("ref_type", "loan_repay")
- .order("created_at", { ascending: false })
- .limit(200)
- : { data: [] };
-
- const incomingRepays = (allRepays ?? []).filter((tx) => {
- try {
- const meta = JSON.parse(String(tx.metadata || "{}"));
- return String(meta.lenderUserId) === String(user.id) || String(meta.lenderAddress) === String(user.id);
- } catch { return false; }
+ const [userTxs, allRepays] = db
+ ? await Promise.all([
+ db
+ .select()
+ .from(ledgerTransactions)
+ .where(eq(ledgerTransactions.userId, user.id))
+ .orderBy(desc(ledgerTransactions.createdAt))
+ .limit(PAGE_SIZE + 1),
+ // Incoming repayments are written by the borrower; match on metadata.
+ db
+ .select()
+ .from(ledgerTransactions)
+ .where(eq(ledgerTransactions.refType, "loan_repay"))
+ .orderBy(desc(ledgerTransactions.createdAt))
+ .limit(200),
+ ])
+ : [[], []];
+
+ const hasMore = userTxs.length > PAGE_SIZE;
+ const items = userTxs.slice(0, PAGE_SIZE).map(ledgerToRow);
+
+ const incomingRepays = allRepays.map(ledgerToRow).filter((tx) => {
+ const meta = readMetadata(tx.metadata);
+ return String(meta.lenderUserId) === user.id || String(meta.lenderAddress) === user.walletAddress;
});
// Merge and dedup
- const txMap = new Map();
+ const txMap = new Map();
for (const t of items) txMap.set(t.id, t);
for (const t of incomingRepays) txMap.set(t.id, t);
const transactions = Array.from(txMap.values()).sort(
- (a, b) => new Date(String(b.created_at)).getTime() - new Date(String(a.created_at)).getTime()
+ (a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
);
// Format transactions
const initialTransactions = transactions.map((tx) => {
- let txHash = "";
+ const meta = readMetadata(tx.metadata);
+ const txHash = String(meta.txHash ?? "");
let subLabel = "";
- try {
- const meta = JSON.parse(String(tx.metadata ?? "{}"));
- txHash = String(meta.txHash ?? "");
- if (meta.loanId) subLabel = `Loan #${String(meta.loanId).slice(0, 8)}`;
- else if (tx.ref_id) subLabel = `Ref #${String(tx.ref_id).slice(0, 8)}`;
- } catch { /* ok */ }
+ if (meta.loanId) subLabel = `Loan #${String(meta.loanId).slice(0, 8)}`;
+ else if (tx.ref_id) subLabel = `Ref #${String(tx.ref_id).slice(0, 8)}`;
let label = "Transaction";
if (tx.ref_type === "loan_fund") label = "P2P Loan Deployed";
@@ -114,7 +108,7 @@ export default async function LenderHistoryPage() {
description="A full chronological record of every investment, pool deposit, and repayment — fully verifiable on-chain."
email={user.email ?? null}
userName={String(
- user.user_metadata?.full_name ?? profile?.full_name ?? ""
+ user.fullName ?? profile?.full_name ?? ""
)}
metrics={presentLenderMetrics(metrics)}
currentPath="/dashboard/lender/history"
diff --git a/app/dashboard/lender/loading.tsx b/app/dashboard/lender/loading.tsx
index e2381f9..71ec014 100644
--- a/app/dashboard/lender/loading.tsx
+++ b/app/dashboard/lender/loading.tsx
@@ -3,7 +3,7 @@ import { WorkspaceFrame } from "@/components/dashboard/WorkspaceFrame";
/**
* Next.js renders this file immediately (via React Suspense) while
- * the main `LenderHomePage` resolves its Supabase + Stellar fetches.
+ * the main `LenderHomePage` resolves its database + Stellar fetches.
* Prevents the blank/jump screen on initial lender dashboard load.
*/
export default function LenderDashboardLoading() {
diff --git a/app/dashboard/lender/marketplace/page.tsx b/app/dashboard/lender/marketplace/page.tsx
index e44aa44..fa4213a 100644
--- a/app/dashboard/lender/marketplace/page.tsx
+++ b/app/dashboard/lender/marketplace/page.tsx
@@ -11,10 +11,12 @@ import {
buildStellarTxVerificationUrl,
isLikelyTxHash,
} from "@/lib/stellar/explorer";
-import {
- getServerSupabaseClient,
- getServiceRoleClient,
-} from "@/lib/supabase/server";
+import { and, desc, eq } from "drizzle-orm";
+import { getDb } from "@/lib/db/client";
+import { getMarketplaceLoans, getProfile } from "@/lib/db/queries";
+import { metaString } from "@/lib/db/metadata";
+import { ledgerToRow } from "@/lib/db/rows";
+import { ledgerTransactions } from "@/lib/db/schema";
import {
DEFAULT_SORT,
DURATION_FILTER_OPTIONS,
@@ -26,21 +28,6 @@ import {
type SearchParams = Promise>;
-type MarketplaceLoanRow = {
- id: string;
- principal_amount: number;
- /** Total contributed by all lenders so far (Issue #269). */
- funded_amount?: number;
- /** Lenders who already hold a slice of this loan. */
- lender_count?: number;
- apr_bps: number;
- duration_days: number;
- borrower_id: string;
- borrower_name: string;
- borrower_wallet: string;
- trust_score: number;
-};
-
function readSearchParam(
params: Record,
key: string,
@@ -64,95 +51,24 @@ export default async function LenderMarketplacePage({
const { user } = await requireAuthenticatedUser("lender");
const walletAddress =
- String(user.user_metadata?.wallet_address ?? "") || null;
+ String(user.walletAddress ?? "") || null;
const metrics = await getLenderDashboardMetrics(user.id);
- const supabase = await getServerSupabaseClient();
- const srClient = getServiceRoleClient();
-
- const fundedTxsRes = srClient
- ? await srClient
- .from("ledger_transactions")
- .select("id, ref_id, amount, metadata, created_at")
- .eq("user_id", user.id)
- .eq("ref_type", "loan_fund")
- .order("created_at", { ascending: false })
- .limit(20)
- : { data: [] };
-
- const openLoansRes = srClient
- ? await srClient.rpc("get_marketplace_loans")
- : { data: null, error: null };
-
- let openLoans: MarketplaceLoanRow[] = [];
-
- if (!openLoansRes.error) {
- openLoans = (openLoansRes.data ?? []) as MarketplaceLoanRow[];
- } else if (srClient) {
- const fallbackLoansRes = await srClient
- .from("loans")
- .select(
- "id, principal_amount, funded_amount, apr_bps, duration_days, borrower_id",
- )
- .in("status", ["requested", "approved"])
- .order(
- sort === "term_asc" || sort === "term_desc"
- ? "duration_days"
- : "apr_bps",
- { ascending: sort === "apr_asc" || sort === "term_asc" },
- );
+ const db = getDb();
- const fallbackLoans = fallbackLoansRes.data ?? [];
- const borrowerIds = Array.from(
- new Set(fallbackLoans.map((loan) => String(loan.borrower_id))),
- );
+ const [fundedTxRows, openLoans] = await Promise.all([
+ db
+ ? db
+ .select()
+ .from(ledgerTransactions)
+ .where(and(eq(ledgerTransactions.userId, user.id), eq(ledgerTransactions.refType, "loan_fund")))
+ .orderBy(desc(ledgerTransactions.createdAt))
+ .limit(20)
+ : Promise.resolve([]),
+ getMarketplaceLoans(db),
+ ]);
- const [profilesRes, snapshotsRes] =
- borrowerIds.length > 0
- ? await Promise.all([
- srClient
- .from("profiles")
- .select("id, full_name, wallet_address")
- .in("id", borrowerIds),
- srClient
- .from("reputation_snapshots")
- .select("user_id, score_total")
- .in("user_id", borrowerIds),
- ])
- : [{ data: [] }, { data: [] }];
-
- const profileMap = new Map(
- (profilesRes.data ?? []).map((profile) => [String(profile.id), profile]),
- );
- const scoreMap = new Map(
- (snapshotsRes.data ?? []).map((snapshot) => [
- String(snapshot.user_id),
- Number(snapshot.score_total ?? 250),
- ]),
- );
-
- openLoans = fallbackLoans.map((loan) => {
- const borrowerId = String(loan.borrower_id);
- const profile = profileMap.get(borrowerId);
-
- return {
- id: String(loan.id),
- principal_amount: Number(loan.principal_amount ?? 0),
- funded_amount: Number(loan.funded_amount ?? 0),
- apr_bps: Number(loan.apr_bps ?? 0),
- duration_days: Number(loan.duration_days ?? 30),
- borrower_id: borrowerId,
- borrower_name:
- profile?.full_name && String(profile.full_name).trim() !== ""
- ? String(profile.full_name)
- : `Borrower ${borrowerId.slice(0, 6)}`,
- borrower_wallet: String(profile?.wallet_address ?? ""),
- trust_score: Number(scoreMap.get(borrowerId) ?? 250),
- };
- });
- }
-
- const fundedTxs = fundedTxsRes.data ?? [];
+ const fundedTxs = fundedTxRows.map(ledgerToRow);
const marketplaceLoans = openLoans
.map((loan) => ({
id: String(loan.id),
@@ -167,8 +83,7 @@ export default async function LenderMarketplacePage({
),
borrower_wallet: String(loan.borrower_wallet ?? ""),
}))
- // Drop anything already at 100%. The RPC filters these out server-side, but
- // the fallback query cannot compare two columns, so enforce it here too.
+ // Drop anything already at 100% (the query filters these too; belt and braces).
.filter((loan) => !getFundingProgress(loan.principal_amount, loan.funded_amount).isFullyFunded);
const visibleMarketplaceLoans = filterMarketplaceLoans(marketplaceLoans, {
@@ -178,14 +93,7 @@ export default async function LenderMarketplacePage({
highReputationThreshold: HIGH_REPUTATION_THRESHOLD,
});
- const profileRes = supabase
- ? await supabase
- .from("profiles")
- .select("full_name")
- .eq("id", user.id)
- .maybeSingle()
- : { data: null };
- const profile = profileRes.data;
+ const profile = await getProfile(db, user.id);
return (
{fundedTxs.map((tx) => {
- let meta: Record = {};
-
- try {
- meta = JSON.parse(String(tx.metadata ?? "{}"));
- } catch {
- meta = {};
- }
-
- const txHash = meta.txHash ?? "";
+ const txHash = metaString(tx.metadata, "txHash");
return (
diff --git a/app/dashboard/lender/page.tsx b/app/dashboard/lender/page.tsx
index 322fc16..26e5946 100644
--- a/app/dashboard/lender/page.tsx
+++ b/app/dashboard/lender/page.tsx
@@ -5,10 +5,12 @@ import {
getLenderDashboardMetrics,
presentLenderMetrics,
} from "@/lib/dashboard/metrics";
-import {
- getServerSupabaseClient,
- getServiceRoleClient,
-} from "@/lib/supabase/server";
+import { and, desc, eq, inArray, sql } from "drizzle-orm";
+import { getDb } from "@/lib/db/client";
+import { metaString } from "@/lib/db/metadata";
+import { getProfile } from "@/lib/db/queries";
+import { ledgerToRow, loanToRow, positionToRow } from "@/lib/db/rows";
+import { ledgerTransactions, loans as loansTable, poolPositions } from "@/lib/db/schema";
import { formatTokenBalance } from "@/lib/utils/formatting";
import { lenderNavLinks } from "@/lib/dashboard/lender-links";
import Link from "next/link";
@@ -16,79 +18,48 @@ import Link from "next/link";
export default async function LenderHomePage() {
const { user } = await requireAuthenticatedUser("lender");
const walletAddress =
- String(user.user_metadata?.wallet_address ?? "") || null;
+ String(user.walletAddress ?? "") || null;
const metrics = await getLenderDashboardMetrics(user.id);
- const supabase = await getServerSupabaseClient();
- const srClient = getServiceRoleClient();
+ const db = getDb();
- const [
- positionsRes,
- profileRes,
- p2pRes,
- openLoanCountRes,
- allLoansRes,
- repaysRes,
- ] =
- supabase && srClient
- ? await Promise.all([
- supabase
- .from("pool_positions")
- .select("id, pool_id, status, principal_amount, earned_interest")
- .eq("lender_id", user.id)
- .order("created_at", { ascending: false })
- .limit(5),
- supabase
- .from("profiles")
- .select("full_name, kyc_status")
- .eq("id", user.id)
- .maybeSingle(),
- supabase
- .from("ledger_transactions")
- .select("id, ref_id, amount, status, metadata, created_at")
- .eq("user_id", user.id)
- .eq("ref_type", "loan_fund")
- .order("created_at", { ascending: false })
- .limit(20),
- supabase
- .from("loans")
- .select("id", { count: "exact", head: true })
- .in("status", ["requested", "approved"]),
- srClient
- .from("loans")
- .select("id, status, repaid_amount, principal_amount"),
- srClient
- .from("ledger_transactions")
- .select("ref_id, metadata")
- .eq("ref_type", "loan_repay"),
- ])
- : [
- { data: [] },
- { data: null },
- { data: [] },
- { count: 0 },
- { data: [] },
- { data: [] },
- ];
+ const [positionRows, profile, p2pRows, [openLoanCountRow], allLoanRows, repayRows] = db
+ ? await Promise.all([
+ db
+ .select()
+ .from(poolPositions)
+ .where(eq(poolPositions.lenderId, user.id))
+ .orderBy(desc(poolPositions.createdAt))
+ .limit(5),
+ getProfile(db, user.id),
+ db
+ .select()
+ .from(ledgerTransactions)
+ .where(and(eq(ledgerTransactions.userId, user.id), eq(ledgerTransactions.refType, "loan_fund")))
+ .orderBy(desc(ledgerTransactions.createdAt))
+ .limit(20),
+ db
+ .select({ count: sql`count(*)::int` })
+ .from(loansTable)
+ .where(inArray(loansTable.status, ["requested", "approved"])),
+ db.select().from(loansTable),
+ db
+ .select({ ref_id: ledgerTransactions.refId, metadata: ledgerTransactions.metadata })
+ .from(ledgerTransactions)
+ .where(eq(ledgerTransactions.refType, "loan_repay")),
+ ])
+ : [[], null, [], [{ count: 0 }], [], []];
- const positions = positionsRes.data ?? [];
- const dbP2pInvestments = p2pRes.data ?? [];
- const profile = profileRes.data;
- const openLoanCount = openLoanCountRes.count ?? 0;
+ const positions = positionRows.map(positionToRow);
+ const p2pInvestments = p2pRows.map(ledgerToRow);
+ const openLoanCount = openLoanCountRow?.count ?? 0;
const isKycVerified = profile?.kyc_status === "verified";
-
- const p2pInvestments = dbP2pInvestments;
- const allLoansArray = allLoansRes.data ?? [];
- const loanMap = Object.fromEntries(
- allLoansArray.map((l) => [String(l.id), l]),
- );
+ const loanMap = Object.fromEntries(allLoanRows.map(loanToRow).map((l) => [l.id, l]));
const repayMap: Record = {};
- for (const r of repaysRes.data ?? []) {
- try {
- const m = JSON.parse(String(r.metadata || "{}"));
- if (m.txHash) repayMap[String(r.ref_id)] = m.txHash;
- } catch {}
+ for (const r of repayRows) {
+ const hash = metaString(r.metadata, "txHash");
+ if (hash && r.ref_id) repayMap[r.ref_id] = hash;
}
const netEarnings = metrics.totalEarnings;
@@ -100,7 +71,7 @@ export default async function LenderHomePage() {
description="Your lending overview at a glance. Use the navigation to fund loans or manage your pool investments."
email={user.email ?? null}
userName={String(
- user.user_metadata?.full_name ?? profile?.full_name ?? "",
+ user.fullName ?? profile?.full_name ?? "",
)}
metrics={presentLenderMetrics(metrics)}
headerWidget={
@@ -428,15 +399,11 @@ export default async function LenderHomePage() {
{p2pInvestments.map((tx) => {
- let fundTxHash = "";
- try {
- const meta = JSON.parse(String(tx.metadata || "{}"));
- fundTxHash = meta.txHash ?? "";
- } catch {}
+ const fundTxHash = metaString(tx.metadata, "txHash");
// Find actual loan data
const actualLoan = loanMap[String(tx.ref_id)];
- const rawStatus = actualLoan?.status ?? "processing";
+ const rawStatus: string = actualLoan?.status ?? "processing";
const repaid = Number(actualLoan?.repaid_amount ?? 0);
const profit = Math.max(0, repaid - Number(tx.amount));
diff --git a/app/dashboard/lender/pools/loading.tsx b/app/dashboard/lender/pools/loading.tsx
index a9ecced..e7af471 100644
--- a/app/dashboard/lender/pools/loading.tsx
+++ b/app/dashboard/lender/pools/loading.tsx
@@ -5,7 +5,7 @@ import { WorkspaceFrame } from "@/components/dashboard/WorkspaceFrame";
/**
* Next.js automatically renders this file (via React Suspense) while
* `LenderPoolsPage` is waiting for its async server-side data fetches
- * (Supabase queries + Stellar Horizon balance).
+ * (database queries + Stellar Horizon balance).
*
* The result: users see a polished skeleton layout instantly on navigation
* instead of a blank/delayed screen.
diff --git a/app/dashboard/lender/pools/page.tsx b/app/dashboard/lender/pools/page.tsx
index 9d9ee8c..fd528d9 100644
--- a/app/dashboard/lender/pools/page.tsx
+++ b/app/dashboard/lender/pools/page.tsx
@@ -6,7 +6,12 @@ import {
getLenderDashboardMetrics,
presentLenderMetrics,
} from "@/lib/dashboard/metrics";
-import { getServerSupabaseClient } from "@/lib/supabase/server";
+import { and, asc, desc, eq } from "drizzle-orm";
+import { getDb } from "@/lib/db/client";
+import { metaString } from "@/lib/db/metadata";
+import { getProfile } from "@/lib/db/queries";
+import { ledgerToRow, poolToRow, positionToRow } from "@/lib/db/rows";
+import { ledgerTransactions, lendingPools, poolPositions } from "@/lib/db/schema";
import { lenderNavLinks } from "@/lib/dashboard/lender-links";
import { formatTokenBalance, formatCurrency, formatXlmPrecise } from "@/lib/utils/formatting";
import {
@@ -18,46 +23,32 @@ import { STELLAR_TESTNET } from "@/lib/stellar/testnet";
export default async function LenderPoolsPage() {
const { user } = await requireAuthenticatedUser("lender");
const metrics = await getLenderDashboardMetrics(user.id);
- const supabase = await getServerSupabaseClient();
+ const db = getDb();
const walletAddress =
- String(user.user_metadata?.wallet_address ?? "") || null;
+ String(user.walletAddress ?? "") || null;
- const [poolsRes, positionsRes, profileRes, txHistoryRes] = supabase
+ const [poolRows, positionRows, profile, txHistoryRows] = db
? await Promise.all([
- supabase
- .from("lending_pools")
- .select(
- "id, name, status, apr_bps, total_liquidity, available_liquidity",
- )
- .order("created_at", { ascending: false })
- .limit(8),
- supabase
- .from("pool_positions")
- .select(
- "id, pool_id, status, principal_amount, earned_interest, opened_at",
- )
- .eq("lender_id", user.id)
- .order("opened_at", { ascending: true }),
- supabase
- .from("profiles")
- .select("full_name, kyc_status")
- .eq("id", user.id)
- .maybeSingle(),
- supabase
- .from("ledger_transactions")
- .select("id, amount, category, metadata, status, created_at")
- .eq("user_id", user.id)
- .eq("ref_type", "pool_position")
- .order("created_at", { ascending: false })
+ db.select().from(lendingPools).orderBy(desc(lendingPools.createdAt)).limit(8),
+ db
+ .select()
+ .from(poolPositions)
+ .where(eq(poolPositions.lenderId, user.id))
+ .orderBy(asc(poolPositions.openedAt)),
+ getProfile(db, user.id),
+ db
+ .select()
+ .from(ledgerTransactions)
+ .where(and(eq(ledgerTransactions.userId, user.id), eq(ledgerTransactions.refType, "pool_position")))
+ .orderBy(desc(ledgerTransactions.createdAt))
.limit(10),
])
- : [{ data: [] }, { data: [] }, { data: null }, { data: [] }];
+ : [[], [], null, []];
- const pools = poolsRes.data ?? [];
- const positions = positionsRes.data ?? [];
- const profile = profileRes.data;
- const txHistory = txHistoryRes.data ?? [];
+ const pools = poolRows.map(poolToRow);
+ const positions = positionRows.map(positionToRow);
+ const txHistory = txHistoryRows.map(ledgerToRow);
const isKycVerified = profile?.kyc_status === "verified";
const totalDeployed = positions.reduce(
@@ -131,7 +122,7 @@ export default async function LenderPoolsPage() {
description="Deposit XLM into a lending pool and earn passive APR. The pool auto-matches your capital to open borrower requests."
email={user.email ?? null}
userName={String(
- user.user_metadata?.full_name ?? profile?.full_name ?? "",
+ user.fullName ?? profile?.full_name ?? "",
)}
metrics={presentLenderMetrics(metrics)}
currentPath="/dashboard/lender/pools"
@@ -369,15 +360,6 @@ export default async function LenderPoolsPage() {
- {/* ── Available pools – rendered client-side with skeleton loading ── */}
- {/*
- AvailablePools is a Client Component that:
- 1. Starts with isLoading = true and renders
- 2. Fetches lending_pools from Supabase browser client
- 3. Sets isLoading = false and renders animated pool cards
- This eliminates the blank-screen delay caused by the old
- server-side blocking table render.
- */}
Available Lending Pools
{pools.length === 0 ? (
@@ -456,8 +438,8 @@ export default async function LenderPoolsPage() {
{/* ── Deposit / Withdraw forms ──────────────────────────── */}
({ ...p, available_liquidity: Number(p.available_liquidity) }))}
+ positions={positions.map((p) => ({ ...p, principal_amount: Number(p.principal_amount) }))}
walletBalance={availableWalletBalance}
isKycVerified={isKycVerified}
/>
@@ -572,11 +554,7 @@ export default async function LenderPoolsPage() {
{txHistory.map((tx) => {
- let txHash = "";
- try {
- const meta = JSON.parse(String(tx.metadata || "{}"));
- txHash = meta.txHash ?? "";
- } catch {}
+ const txHash = metaString(tx.metadata, "txHash");
const isDeposit = tx.category === "deposit";
diff --git a/app/dashboard/lender/portfolio/page.tsx b/app/dashboard/lender/portfolio/page.tsx
index e494491..2d55dee 100644
--- a/app/dashboard/lender/portfolio/page.tsx
+++ b/app/dashboard/lender/portfolio/page.tsx
@@ -1,7 +1,12 @@
import { WorkspaceFrame } from "@/components/dashboard/WorkspaceFrame";
import { requireAuthenticatedUser } from "@/lib/auth/session";
import { getLenderDashboardMetrics, presentLenderMetrics } from "@/lib/dashboard/metrics";
-import { getServerSupabaseClient, getServiceRoleClient } from "@/lib/supabase/server";
+import { and, desc, eq } from "drizzle-orm";
+import { getDb } from "@/lib/db/client";
+import { readMetadata } from "@/lib/db/metadata";
+import { getProfile } from "@/lib/db/queries";
+import { ledgerToRow, poolToRow, positionToRow } from "@/lib/db/rows";
+import { ledgerTransactions, lendingPools, poolPositions } from "@/lib/db/schema";
import { lenderNavLinks } from "@/lib/dashboard/lender-links";
import { formatCurrency, formatXlmPrecise } from "@/lib/utils/formatting";
import { TaxReportExportButton } from "@/components/dashboard/TaxReportExportButton";
@@ -13,62 +18,42 @@ export default async function LenderPortfolioPage() {
const { user } = await requireAuthenticatedUser("lender");
const metrics = await getLenderDashboardMetrics(user.id);
- const supabase = await getServerSupabaseClient();
- const srClient = getServiceRoleClient();
+ const db = getDb();
- // 1. Fetch Pool Positions, Profiles, and Lending Pools
- const [positionsRes, profileRes, poolsRes] = supabase
+ // 1. Pool positions, profile, lending pools, and both sides of the P2P ledger
+ const [positionRows, profile, poolRows, p2pFundRows, p2pRepayRows] = db
? await Promise.all([
- supabase
- .from("pool_positions")
- .select("id, pool_id, status, principal_amount, earned_interest, opened_at, closed_at")
- .eq("lender_id", user.id)
- .order("opened_at", { ascending: false })
+ db
+ .select()
+ .from(poolPositions)
+ .where(eq(poolPositions.lenderId, user.id))
+ .orderBy(desc(poolPositions.openedAt))
.limit(20),
- supabase
- .from("profiles")
- .select("full_name")
- .eq("id", user.id)
- .maybeSingle(),
- supabase
- .from("lending_pools")
- .select("id, name, status, apr_bps, total_liquidity, available_liquidity"),
+ getProfile(db, user.id),
+ db.select().from(lendingPools),
+ db
+ .select()
+ .from(ledgerTransactions)
+ .where(and(eq(ledgerTransactions.userId, user.id), eq(ledgerTransactions.refType, "loan_fund"))),
+ db.select().from(ledgerTransactions).where(eq(ledgerTransactions.refType, "loan_repay")),
])
- : [{ data: [] }, { data: null }, { data: [] }];
+ : [[], null, [], [], []];
- const positions = positionsRes.data ?? [];
- const profile = profileRes.data;
- const pools = poolsRes.data ?? [];
+ const positions = positionRows.map(positionToRow);
+ const pools = poolRows.map(poolToRow);
+ const p2pFunds = p2pFundRows.map(ledgerToRow);
- // 2. Fetch Direct Marketplace Loans for Profit
- // P2P Funds
- const { data: p2pFunds } = supabase
- ? await supabase
- .from("ledger_transactions")
- .select("amount, ref_id, created_at")
- .eq("user_id", user.id)
- .eq("ref_type", "loan_fund")
- : { data: [] };
-
- const { data: p2pRepays } = srClient
- ? await srClient
- .from("ledger_transactions")
- .select("amount, metadata, ref_id, created_at")
- .eq("ref_type", "loan_repay")
- : { data: [] };
-
- const lenderRepays = (p2pRepays ?? []).filter(tx => {
- try {
- const meta = JSON.parse(String(tx.metadata || "{}"));
- return String(meta.lenderUserId) === String(user.id) || String(meta.lenderAddress) === String(user.id);
- } catch { return false; }
+ // Repayment rows are written by the borrower; the lender is identified from metadata.
+ const lenderRepays = p2pRepayRows.map(ledgerToRow).filter((tx) => {
+ const meta = readMetadata(tx.metadata);
+ return String(meta.lenderUserId) === user.id || String(meta.lenderAddress) === user.walletAddress;
});
// Calculate comprehensive yield analytics & pool breakdown (Issue #256)
const yieldAnalytics = calculateLenderYieldAnalytics({
positions,
pools,
- p2pFunds: p2pFunds ?? [],
+ p2pFunds,
p2pRepays: lenderRepays,
});
@@ -94,7 +79,7 @@ export default async function LenderPortfolioPage() {
heading="Portfolio & Yield Analytics"
description="Track historical APY yield, projected future returns, and earnings breakdown across lending pools."
email={user.email ?? null}
- userName={String(user.user_metadata?.full_name ?? profile?.full_name ?? "")}
+ userName={String(user.fullName ?? profile?.full_name ?? "")}
metrics={presentLenderMetrics(metrics)}
currentPath="/dashboard/lender/portfolio"
links={lenderNavLinks}
diff --git a/app/dashboard/lender/profile/page.tsx b/app/dashboard/lender/profile/page.tsx
index 348649b..8c2bf97 100644
--- a/app/dashboard/lender/profile/page.tsx
+++ b/app/dashboard/lender/profile/page.tsx
@@ -7,7 +7,8 @@ import {
presentLenderMetrics,
} from "@/lib/dashboard/metrics";
import { lenderNavLinks } from "@/lib/dashboard/lender-links";
-import { getServerSupabaseClient } from "@/lib/supabase/server";
+import { getDb } from "@/lib/db/client";
+import { getProfile } from "@/lib/db/queries";
const KYC_CONFIG: Record<
string,
@@ -23,14 +24,7 @@ export default async function LenderProfilePage() {
const { user } = await requireAuthenticatedUser("lender");
const metrics = await getLenderDashboardMetrics(user.id);
- const supabase = await getServerSupabaseClient();
- const { data: profile } = supabase
- ? await supabase
- .from("profiles")
- .select("full_name, phone, date_of_birth, role, kyc_status, risk_status, government_id_url, kyc_submitted_at, kyc_provider_id")
- .eq("id", user.id)
- .maybeSingle()
- : { data: null as Record | null };
+ const profile = await getProfile(getDb(), user.id);
const kycStatusKey = String(profile?.kyc_status ?? "pending") as keyof typeof KYC_CONFIG;
const kycInfo = KYC_CONFIG[kycStatusKey] ?? KYC_CONFIG.pending;
@@ -42,7 +36,7 @@ export default async function LenderProfilePage() {
heading="Profile Settings & Security"
description="Update your personal details and complete required compliance checks to manage lending pools."
email={user.email ?? null}
- userName={String(user.user_metadata?.full_name ?? profile?.full_name ?? "")}
+ userName={String(user.fullName ?? profile?.full_name ?? "")}
metrics={presentLenderMetrics(metrics)}
currentPath="/dashboard/lender/profile"
links={lenderNavLinks}
@@ -145,16 +139,16 @@ export default async function LenderProfilePage() {
- Email Verified
-
-
- {user.email_confirmed_at ? "Verified" : "Not verified"}
+ Wallet Verified
+
+
+ {user.walletAddress ? "Verified" : "Not verified"}
Member Since
- {user.created_at ? new Date(user.created_at).toLocaleDateString("en-US", { month: "long", year: "numeric" }) : "—"}
+ {user.createdAt ? new Date(user.createdAt).toLocaleDateString("en-US", { month: "long", year: "numeric" }) : "—"}
diff --git a/app/dashboard/lender/risk/page.tsx b/app/dashboard/lender/risk/page.tsx
index c53ad1f..1fc50e6 100644
--- a/app/dashboard/lender/risk/page.tsx
+++ b/app/dashboard/lender/risk/page.tsx
@@ -5,30 +5,22 @@ import {
presentLenderMetrics,
} from "@/lib/dashboard/metrics";
import { lenderNavLinks } from "@/lib/dashboard/lender-links";
-import { getServerSupabaseClient } from "@/lib/supabase/server";
+import { asc } from "drizzle-orm";
+import { getDb } from "@/lib/db/client";
+import { getProfile } from "@/lib/db/queries";
+import { loanToRow } from "@/lib/db/rows";
+import { loans as loansTable } from "@/lib/db/schema";
export default async function LenderRiskPage() {
const { user } = await requireAuthenticatedUser("lender");
const metrics = await getLenderDashboardMetrics(user.id);
- const supabase = await getServerSupabaseClient();
- const [loansRes, profileRes] = supabase
- ? await Promise.all([
- supabase
- .from("loans")
- .select("id, status, principal_amount, due_at")
- .order("due_at", { ascending: true })
- .limit(12),
- supabase
- .from("profiles")
- .select("full_name")
- .eq("id", user.id)
- .maybeSingle(),
- ])
- : [{ data: [] }, { data: null }];
-
- const loans = loansRes.data ?? [];
- const profile = profileRes.data;
+ const db = getDb();
+ const [loanRows, profile] = await Promise.all([
+ db ? db.select().from(loansTable).orderBy(asc(loansTable.dueAt)).limit(12) : Promise.resolve([]),
+ getProfile(db, user.id),
+ ]);
+ const loans = loanRows.map(loanToRow);
return (
diff --git a/commitlint.config.ts b/commitlint.config.ts
index 8f04d9b..cf5660d 100644
--- a/commitlint.config.ts
+++ b/commitlint.config.ts
@@ -4,7 +4,7 @@ import type { UserConfig } from "@commitlint/types";
* commitlint configuration for TrustLend.
*
* Extends `@commitlint/config-conventional` with additional scopes and types
- * relevant to the project's stack (Soroban, Stellar, Next.js, Supabase).
+ * relevant to the project's stack (Soroban, Stellar, Next.js, Neon).
*
* Conventional commit format:
* ():
@@ -64,7 +64,8 @@ const Configuration: UserConfig = {
"api",
"ci",
"db",
- "supabase",
+ "neon",
+ "drizzle",
"stellar",
"soroban",
"docs",
diff --git a/components/dashboard/ProfileSettingsForm.tsx b/components/dashboard/ProfileSettingsForm.tsx
index 9bdfdb5..d5639c9 100644
--- a/components/dashboard/ProfileSettingsForm.tsx
+++ b/components/dashboard/ProfileSettingsForm.tsx
@@ -97,12 +97,10 @@ export function ProfileSettingsForm({
const handleLogout = async () => {
setSigningOut(true);
try {
- const { getBrowserSupabaseClient } = await import("@/lib/supabase/client");
- const supabase = getBrowserSupabaseClient();
- if (supabase) {
- await supabase.auth.signOut();
- }
+ const { signOut } = await import("@/lib/auth/siws-client");
+ await signOut();
router.push("/auth");
+ router.refresh();
} catch (err) {
console.error("Logout failed:", err);
setSigningOut(false);
diff --git a/components/dashboard/WalletCard.tsx b/components/dashboard/WalletCard.tsx
index a7f6626..77cac36 100644
--- a/components/dashboard/WalletCard.tsx
+++ b/components/dashboard/WalletCard.tsx
@@ -1,7 +1,7 @@
"use client";
import { useEffect, useMemo, useState } from "react";
-import { getBrowserSupabaseClient } from "@/lib/supabase/client";
+import { updateWalletAddress } from "@/app/actions/update-profile";
import { formatCurrency } from "@/lib/utils/formatting";
import { STELLAR_TESTNET } from "@/lib/stellar/testnet";
import {
@@ -80,31 +80,9 @@ export function WalletCard({
nextAddress: string | null,
provider: StellarWalletProvider | null,
) => {
- const supabase = getBrowserSupabaseClient();
- if (!supabase) return;
-
- const { data } = await supabase.auth.getSession();
- const session = data.session;
- if (!session) return;
-
- const nextMetadata = {
- ...session.user.user_metadata,
- wallet_address: nextAddress,
- wallet_network: nextAddress ? "stellar-testnet" : null,
- wallet_provider: provider,
- };
- const { error: authErr } = await supabase.auth.updateUser({
- data: nextMetadata,
- });
- if (authErr) throw new Error(authErr.message);
-
- const { error: profileErr } = await supabase
- .from("profiles")
- .update({ wallet_address: nextAddress })
- .eq("id", session.user.id);
-
- if (profileErr) {
- console.warn("profiles wallet_address sync failed:", profileErr.message);
+ const result = await updateWalletAddress(nextAddress);
+ if (!result.success) {
+ console.warn("profiles wallet_address sync failed:", result.error);
}
if (nextAddress) {
diff --git a/contracts/governance/test_snapshots/test/test_double_vote_panics.1.json b/contracts/governance/test_snapshots/test/test_double_vote_panics.1.json
index 1e7181a..17dc4ba 100644
--- a/contracts/governance/test_snapshots/test/test_double_vote_panics.1.json
+++ b/contracts/governance/test_snapshots/test/test_double_vote_panics.1.json
@@ -881,18 +881,18 @@
},
{
"key": {
- "symbol": "freeze_reason"
+ "symbol": "flags"
},
"val": {
- "string": ""
+ "u32": 2
}
},
{
"key": {
- "symbol": "is_frozen"
+ "symbol": "freeze_reason"
},
"val": {
- "bool": false
+ "string": ""
}
},
{
diff --git a/contracts/governance/test_snapshots/test/test_execute_requires_passed.1.json b/contracts/governance/test_snapshots/test/test_execute_requires_passed.1.json
index 4a9b75d..7af17d1 100644
--- a/contracts/governance/test_snapshots/test/test_execute_requires_passed.1.json
+++ b/contracts/governance/test_snapshots/test/test_execute_requires_passed.1.json
@@ -856,18 +856,18 @@
},
{
"key": {
- "symbol": "freeze_reason"
+ "symbol": "flags"
},
"val": {
- "string": ""
+ "u32": 2
}
},
{
"key": {
- "symbol": "is_frozen"
+ "symbol": "freeze_reason"
},
"val": {
- "bool": false
+ "string": ""
}
},
{
diff --git a/contracts/governance/test_snapshots/test/test_full_fee_change_flow.1.json b/contracts/governance/test_snapshots/test/test_full_fee_change_flow.1.json
index 42d531b..78db879 100644
--- a/contracts/governance/test_snapshots/test/test_full_fee_change_flow.1.json
+++ b/contracts/governance/test_snapshots/test/test_full_fee_change_flow.1.json
@@ -1055,18 +1055,18 @@
},
{
"key": {
- "symbol": "freeze_reason"
+ "symbol": "flags"
},
"val": {
- "string": ""
+ "u32": 2
}
},
{
"key": {
- "symbol": "is_frozen"
+ "symbol": "freeze_reason"
},
"val": {
- "bool": false
+ "string": ""
}
},
{
@@ -1194,18 +1194,18 @@
},
{
"key": {
- "symbol": "freeze_reason"
+ "symbol": "flags"
},
"val": {
- "string": ""
+ "u32": 2
}
},
{
"key": {
- "symbol": "is_frozen"
+ "symbol": "freeze_reason"
},
"val": {
- "bool": false
+ "string": ""
}
},
{
diff --git a/contracts/governance/test_snapshots/test/test_propose_above_cap_rejected.1.json b/contracts/governance/test_snapshots/test/test_propose_above_cap_rejected.1.json
index 6fe328e..ded68f8 100644
--- a/contracts/governance/test_snapshots/test/test_propose_above_cap_rejected.1.json
+++ b/contracts/governance/test_snapshots/test/test_propose_above_cap_rejected.1.json
@@ -834,18 +834,18 @@
},
{
"key": {
- "symbol": "freeze_reason"
+ "symbol": "flags"
},
"val": {
- "string": ""
+ "u32": 2
}
},
{
"key": {
- "symbol": "is_frozen"
+ "symbol": "freeze_reason"
},
"val": {
- "bool": false
+ "string": ""
}
},
{
diff --git a/contracts/governance/test_snapshots/test/test_rejected_when_against_wins.1.json b/contracts/governance/test_snapshots/test/test_rejected_when_against_wins.1.json
index 6ac2380..7236bf1 100644
--- a/contracts/governance/test_snapshots/test/test_rejected_when_against_wins.1.json
+++ b/contracts/governance/test_snapshots/test/test_rejected_when_against_wins.1.json
@@ -1050,18 +1050,18 @@
},
{
"key": {
- "symbol": "freeze_reason"
+ "symbol": "flags"
},
"val": {
- "string": ""
+ "u32": 2
}
},
{
"key": {
- "symbol": "is_frozen"
+ "symbol": "freeze_reason"
},
"val": {
- "bool": false
+ "string": ""
}
},
{
@@ -1189,18 +1189,18 @@
},
{
"key": {
- "symbol": "freeze_reason"
+ "symbol": "flags"
},
"val": {
- "string": ""
+ "u32": 2
}
},
{
"key": {
- "symbol": "is_frozen"
+ "symbol": "freeze_reason"
},
"val": {
- "bool": false
+ "string": ""
}
},
{
diff --git a/contracts/governance/test_snapshots/test/test_rejected_when_quorum_not_met.1.json b/contracts/governance/test_snapshots/test/test_rejected_when_quorum_not_met.1.json
index edaf9bd..52bf5d9 100644
--- a/contracts/governance/test_snapshots/test/test_rejected_when_quorum_not_met.1.json
+++ b/contracts/governance/test_snapshots/test/test_rejected_when_quorum_not_met.1.json
@@ -323,18 +323,18 @@
},
{
"key": {
- "symbol": "freeze_reason"
+ "symbol": "flags"
},
"val": {
- "string": ""
+ "u32": 2
}
},
{
"key": {
- "symbol": "is_frozen"
+ "symbol": "freeze_reason"
},
"val": {
- "bool": false
+ "string": ""
}
},
{
diff --git a/contracts/governance/test_snapshots/test/test_vote_after_period_panics.1.json b/contracts/governance/test_snapshots/test/test_vote_after_period_panics.1.json
index 0e022df..9f5cca9 100644
--- a/contracts/governance/test_snapshots/test/test_vote_after_period_panics.1.json
+++ b/contracts/governance/test_snapshots/test/test_vote_after_period_panics.1.json
@@ -856,18 +856,18 @@
},
{
"key": {
- "symbol": "freeze_reason"
+ "symbol": "flags"
},
"val": {
- "string": ""
+ "u32": 2
}
},
{
"key": {
- "symbol": "is_frozen"
+ "symbol": "freeze_reason"
},
"val": {
- "bool": false
+ "string": ""
}
},
{
diff --git a/contracts/governance/test_snapshots/test/test_vote_without_power_panics.1.json b/contracts/governance/test_snapshots/test/test_vote_without_power_panics.1.json
index 37d6f7a..7188a75 100644
--- a/contracts/governance/test_snapshots/test/test_vote_without_power_panics.1.json
+++ b/contracts/governance/test_snapshots/test/test_vote_without_power_panics.1.json
@@ -856,18 +856,18 @@
},
{
"key": {
- "symbol": "freeze_reason"
+ "symbol": "flags"
},
"val": {
- "string": ""
+ "u32": 2
}
},
{
"key": {
- "symbol": "is_frozen"
+ "symbol": "freeze_reason"
},
"val": {
- "bool": false
+ "string": ""
}
},
{
diff --git a/contracts/usdc_lending_pool/src/lib.rs b/contracts/usdc_lending_pool/src/lib.rs
index 821f919..8b4c7d8 100644
--- a/contracts/usdc_lending_pool/src/lib.rs
+++ b/contracts/usdc_lending_pool/src/lib.rs
@@ -140,10 +140,11 @@ impl UsdcLendingPool {
panic!("amount must be > 0");
}
- // Transfer USDC from depositor → pool contract.
+ // Transfer USDC from depositor → pool contract. `depositor.require_auth()`
+ // above covers the token transfer, so no prior allowance is needed.
let usdc = Self::usdc_client(&env);
let contract_addr = env.current_contract_address();
- usdc.transfer_from(&contract_addr, &depositor, &contract_addr, &amount);
+ usdc.transfer(&depositor, &contract_addr, &amount);
let current_ledger = env.ledger().sequence();
@@ -351,7 +352,7 @@ impl UsdcLendingPool {
// Use u128 intermediary to avoid i128 overflow on large principals.
let numerator = (principal as u128)
.saturating_mul(annual_yield_bps as u128)
- .saturating_mul(ledgers_elapsed);
+ .saturating_mul(ledgers_elapsed as u128);
let denominator = MAX_BPS.saturating_mul(LEDGERS_PER_YEAR);
(numerator / denominator as u128) as i128
}
@@ -367,7 +368,7 @@ impl UsdcLendingPool {
}
/// Build a typed SEP-41 token client for the USDC contract.
- fn usdc_client(env: &Env) -> token::TokenClient {
+ fn usdc_client(env: &Env) -> token::TokenClient<'_> {
let addr: Address = env
.storage()
.instance()
diff --git a/contracts/usdc_lending_pool/src/test.rs b/contracts/usdc_lending_pool/src/test.rs
index 0902850..c89f169 100644
--- a/contracts/usdc_lending_pool/src/test.rs
+++ b/contracts/usdc_lending_pool/src/test.rs
@@ -12,7 +12,7 @@ use soroban_sdk::{
// ─── Test helpers ─────────────────────────────────────────────────────────────
/// Deploy a native Stellar asset (acts as USDC in tests) and return its address.
-fn create_token(env: &Env, admin: &Address) -> (Address, StellarAssetClient, TokenClient) {
+fn create_token<'a>(env: &'a Env, admin: &Address) -> (Address, StellarAssetClient<'a>, TokenClient<'a>) {
let token_id = env.register_stellar_asset_contract_v2(admin.clone());
let addr = token_id.address();
let asset_client = StellarAssetClient::new(env, &addr);
@@ -25,6 +25,13 @@ fn create_token(env: &Env, admin: &Address) -> (Address, StellarAssetClient, Tok
fn setup() -> (Env, Address, Address, Address, Address) {
let env = Env::default();
env.mock_all_auths();
+ // Yield tests fast-forward the ledger by up to a full year (6.3M ledgers).
+ // Raise the entry TTLs so contract storage is not archived along the way.
+ env.ledger().with_mut(|li| {
+ li.min_temp_entry_ttl = 100_000_000;
+ li.min_persistent_entry_ttl = 100_000_000;
+ li.max_entry_ttl = 100_000_000;
+ });
let admin = Address::generate(&env);
let user = Address::generate(&env);
@@ -131,7 +138,7 @@ fn test_deposit_rejects_negative_amount() {
#[test]
fn test_deposit_accumulates_on_second_deposit() {
- let (env, pool_id, _admin, user, usdc_addr) = setup();
+ let (env, pool_id, _admin, user, _usdc_addr) = setup();
let client = UsdcLendingPoolClient::new(&env, &pool_id);
// First deposit
@@ -319,10 +326,9 @@ fn test_pause_prevents_deposit() {
client.pause(&admin);
assert!(client.is_paused());
- // deposit should panic
- let result = std::panic::catch_unwind(|| {
- client.deposit(&user, &100_000_000_i128);
- });
+ // deposit must fail while paused (try_ variant surfaces the panic as Err
+ // without needing catch_unwind, which the Env handle does not support)
+ let result = client.try_deposit(&user, &100_000_000_i128);
assert!(result.is_err(), "deposit should be blocked when paused");
}
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_deposit_accumulates_on_second_deposit.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_deposit_accumulates_on_second_deposit.1.json
new file mode 100644
index 0000000..82277f6
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_deposit_accumulates_on_second_deposit.1.json
@@ -0,0 +1,849 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "deposit",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": [
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "deposit",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 50000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": [
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 50000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 6307200,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 2032731177588607455
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 2032731177588607455
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 106307199
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "deposit_ledger"
+ },
+ "val": {
+ "u32": 6307200
+ }
+ },
+ {
+ "key": {
+ "symbol": "principal"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 155000000
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 1
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 150000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 850000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 650000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_deposit_records_principal_and_updates_pool.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_deposit_records_principal_and_updates_pool.1.json
new file mode 100644
index 0000000..3250cdf
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_deposit_records_principal_and_updates_pool.1.json
@@ -0,0 +1,767 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "deposit",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": [
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "deposit_ledger"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "principal"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 1
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 900000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 600000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_deposit_rejects_negative_amount.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_deposit_rejects_negative_amount.1.json
new file mode 100644
index 0000000..a155904
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_deposit_rejects_negative_amount.1.json
@@ -0,0 +1,619 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_deposit_rejects_zero_amount.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_deposit_rejects_zero_amount.1.json
new file mode 100644
index 0000000..a155904
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_deposit_rejects_zero_amount.1.json
@@ -0,0 +1,619 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_full_year_yield_approximately_correct.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_full_year_yield_approximately_correct.1.json
new file mode 100644
index 0000000..11b7179
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_full_year_yield_approximately_correct.1.json
@@ -0,0 +1,766 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "deposit",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": [
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 6307200,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "deposit_ledger"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "principal"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 1
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 900000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 600000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_initialize_rejects_double_init.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_initialize_rejects_double_init.1.json
new file mode 100644
index 0000000..a155904
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_initialize_rejects_double_init.1.json
@@ -0,0 +1,619 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_initialize_rejects_yield_over_max.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_initialize_rejects_yield_over_max.1.json
new file mode 100644
index 0000000..a677e02
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_initialize_rejects_yield_over_max.1.json
@@ -0,0 +1,268 @@
+{
+ "generators": {
+ "address": 3,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 6311999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 4095
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000002"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 120960
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 4095
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_initialize_rejects_zero_yield.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_initialize_rejects_zero_yield.1.json
new file mode 100644
index 0000000..a677e02
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_initialize_rejects_zero_yield.1.json
@@ -0,0 +1,268 @@
+{
+ "generators": {
+ "address": 3,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 6311999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": null
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 4095
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBEPDNVYXQGWB5YUBXKJWYJA7OXTZW5LFLNO5JRRGE6Z6C5OSUZPCCEL",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGWF"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000002"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 120960
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 4095
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_initialize_stores_pool_state.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_initialize_stores_pool_state.1.json
new file mode 100644
index 0000000..a155904
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_initialize_stores_pool_state.1.json
@@ -0,0 +1,619 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_multiple_depositors_tracked_independently.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_multiple_depositors_tracked_independently.1.json
new file mode 100644
index 0000000..2cd3633
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_multiple_depositors_tracked_independently.1.json
@@ -0,0 +1,1046 @@
+{
+ "generators": {
+ "address": 5,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "deposit",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": [
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "deposit",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 200000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": [
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 200000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ }
+ ]
+ ],
+ [],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 100,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 2032731177588607455
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 2032731177588607455
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "deposit_ledger"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "principal"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "deposit_ledger"
+ },
+ "val": {
+ "u32": 100
+ }
+ },
+ {
+ "key": {
+ "symbol": "principal"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 200000000
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 100000099
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 2
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 300000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4270020994084947596
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4270020994084947596
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 100000099
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 900000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 800000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 300000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_pause_by_non_admin_panics.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_pause_by_non_admin_panics.1.json
new file mode 100644
index 0000000..a155904
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_pause_by_non_admin_panics.1.json
@@ -0,0 +1,619 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_pause_prevents_deposit.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_pause_prevents_deposit.1.json
new file mode 100644
index 0000000..d6d77ae
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_pause_prevents_deposit.1.json
@@ -0,0 +1,672 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "pause",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_set_yield_rate_by_non_admin_panics.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_set_yield_rate_by_non_admin_panics.1.json
new file mode 100644
index 0000000..a155904
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_set_yield_rate_by_non_admin_panics.1.json
@@ -0,0 +1,619 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_set_yield_rate_rejects_zero.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_set_yield_rate_rejects_zero.1.json
new file mode 100644
index 0000000..a155904
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_set_yield_rate_rejects_zero.1.json
@@ -0,0 +1,619 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_set_yield_rate_updates_pool_state.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_set_yield_rate_updates_pool_state.1.json
new file mode 100644
index 0000000..305d070
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_set_yield_rate_updates_pool_state.1.json
@@ -0,0 +1,674 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "set_yield_rate",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "u32": 1000
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 1000
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_unpause_restores_deposits.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_unpause_restores_deposits.1.json
new file mode 100644
index 0000000..2fde57e
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_unpause_restores_deposits.1.json
@@ -0,0 +1,871 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "pause",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "unpause",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "deposit",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": [
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 2032731177588607455
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 2032731177588607455
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4270020994084947596
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4270020994084947596
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "deposit_ledger"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "principal"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 1
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 900000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 600000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_withdraw_clears_deposit_record.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_withdraw_clears_deposit_record.1.json
new file mode 100644
index 0000000..139fc90
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_withdraw_clears_deposit_record.1.json
@@ -0,0 +1,753 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "deposit",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": [
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "withdraw",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 2032731177588607455
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 2032731177588607455
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 1
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_withdraw_returns_principal_plus_yield.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_withdraw_returns_principal_plus_yield.1.json
new file mode 100644
index 0000000..cd1a5cd
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_withdraw_returns_principal_plus_yield.1.json
@@ -0,0 +1,755 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "deposit",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": [
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ }
+ ]
+ ],
+ [],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "withdraw",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 3153600,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 2032731177588607455
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 2032731177588607455
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 103153599
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 1
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 102500000
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 1002500000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 497500000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_withdraw_updates_pool_total_withdrawn.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_withdraw_updates_pool_total_withdrawn.1.json
new file mode 100644
index 0000000..401a56a
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_withdraw_updates_pool_total_withdrawn.1.json
@@ -0,0 +1,754 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "deposit",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 50000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": [
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 50000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "withdraw",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 100,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 2032731177588607455
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 2032731177588607455
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 100000099
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 1
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 50000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 50000039
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000039
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 499999961
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_withdraw_without_deposit_panics.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_withdraw_without_deposit_panics.1.json
new file mode 100644
index 0000000..a155904
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_withdraw_without_deposit_panics.1.json
@@ -0,0 +1,619 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_yield_increases_with_ledger_advancement.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_yield_increases_with_ledger_advancement.1.json
new file mode 100644
index 0000000..45d524e
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_yield_increases_with_ledger_advancement.1.json
@@ -0,0 +1,767 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "deposit",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": [
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ }
+ ]
+ ],
+ [],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 2000000,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "deposit_ledger"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "principal"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 1
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 900000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 600000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/contracts/usdc_lending_pool/test_snapshots/test/test_zero_yield_when_same_ledger.1.json b/contracts/usdc_lending_pool/test_snapshots/test/test_zero_yield_when_same_ledger.1.json
new file mode 100644
index 0000000..b4a3304
--- /dev/null
+++ b/contracts/usdc_lending_pool/test_snapshots/test/test_zero_yield_when_same_ledger.1.json
@@ -0,0 +1,766 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "set_admin",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 1000000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "mint",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 500000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "deposit",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": [
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "function_name": "transfer",
+ "args": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ },
+ {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 22,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 100000000,
+ "min_temp_entry_ttl": 100000000,
+ "max_entry_ttl": 100000000,
+ "ledger_entries": [
+ [
+ {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "account": {
+ "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "balance": 0,
+ "seq_num": 0,
+ "num_sub_entries": 0,
+ "inflation_dest": null,
+ "flags": 0,
+ "home_domain": "",
+ "thresholds": "01010101",
+ "signers": [],
+ "ext": "v0"
+ }
+ },
+ "ext": "v0"
+ },
+ null
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Deposit"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "deposit_ledger"
+ },
+ "val": {
+ "u32": 0
+ }
+ },
+ {
+ "key": {
+ "symbol": "principal"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "IsPaused"
+ }
+ ]
+ },
+ "val": {
+ "bool": false
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Pool"
+ }
+ ]
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "annual_yield_bps"
+ },
+ "val": {
+ "u32": 500
+ }
+ },
+ {
+ "key": {
+ "symbol": "depositor_count"
+ },
+ "val": {
+ "u32": 1
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_deposited"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 100000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "total_withdrawn"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 0
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "UsdcToken"
+ }
+ ]
+ },
+ "val": {
+ "address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF"
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 900000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": {
+ "vec": [
+ {
+ "symbol": "Balance"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4"
+ }
+ ]
+ },
+ "durability": "persistent",
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "amount"
+ },
+ "val": {
+ "i128": {
+ "hi": 0,
+ "lo": 600000000
+ }
+ }
+ },
+ {
+ "key": {
+ "symbol": "authorized"
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "symbol": "clawback"
+ },
+ "val": {
+ "bool": false
+ }
+ }
+ ]
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": "stellar_asset",
+ "storage": [
+ {
+ "key": {
+ "symbol": "METADATA"
+ },
+ "val": {
+ "map": [
+ {
+ "key": {
+ "symbol": "decimal"
+ },
+ "val": {
+ "u32": 7
+ }
+ },
+ {
+ "key": {
+ "symbol": "name"
+ },
+ "val": {
+ "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V"
+ }
+ },
+ {
+ "key": {
+ "symbol": "symbol"
+ },
+ "val": {
+ "string": "aaa"
+ }
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "AssetInfo"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "symbol": "AlphaNum4"
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "asset_code"
+ },
+ "val": {
+ "string": "aaa\\0"
+ }
+ },
+ {
+ "key": {
+ "symbol": "issuer"
+ },
+ "val": {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000003"
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 99999999
+ ]
+ ]
+ ]
+ },
+ "events": []
+}
\ No newline at end of file
diff --git a/docs/auth-siws.md b/docs/auth-siws.md
index 0950564..edd8b47 100644
--- a/docs/auth-siws.md
+++ b/docs/auth-siws.md
@@ -2,10 +2,10 @@
> Implements issue **#69 — [Backend/Integration] Implement Web3 authentication via SIWS**
-Adds "Sign in with Stellar" as a Web3-native login option alongside
-password/email and Google — users authenticate by **signing a challenge with
-their wallet** (Freighter) instead of typing a password, and the backend turns
-that proof into a normal Supabase session.
+"Sign in with Stellar" is the only login method: users authenticate by
+**signing a challenge with their wallet** (Freighter, xBull, Albedo, or a
+WalletConnect mobile wallet) instead of typing a password, and the backend turns
+that proof into a signed session cookie.
---
@@ -17,31 +17,29 @@ challenge-response transaction — it's the same mechanism the SEP-24 fiat-ramp
issuing the challenge and the **relying party** consuming the proof:
```
-Client Backend (/api/auth/siws/*) Supabase
- │ connect wallet (Freighter) │ │
+Client Backend (/api/auth/siws/*) Postgres (Neon)
+ │ connect wallet │ │
│────────────────────────────────────►│ │
│ POST /challenge { address } │ │
│─────────────────────────────────────► buildChallenge(address) │
│ │ WebAuth.buildChallengeTx() │
│ ◄──────────── { transaction, networkPassphrase } │
- │ sign challenge tx (Freighter) │ │
- │ POST /verify { address, signedTxXdr} │
+ │ sign challenge tx (wallet) │ │
+ │ POST /verify { address, signedTxXdr, role? } │
│─────────────────────────────────────► verifyChallenge() │
│ │ WebAuth.readChallengeTx() │
│ │ WebAuth.verifyChallengeTxSigners()
- │ │ issueSessionForWallet() ────► admin.createUser / signInWithPassword
- │ ◄──────── { access_token, refresh_token, isNewUser } ◄─────────────│
- │ supabase.auth.setSession(...) │ │
+ │ │ issueSessionForWallet() ────► upsert users + profiles
+ │ ◄──── 200 { userId, role, isNewUser } + Set-Cookie: tl_session ◄──│
```
-**Why this bridges to Supabase without a custom-JWT signer:** rather than hand-
-rolling a Supabase-compatible JWT (which requires the project's JWT signing
-secret and is brittle across Supabase versions), the backend uses the
-**service-role key** to deterministically provision a Supabase Auth user per
-wallet (`@siws.trustlend.app`, HMAC-derived password) and mints a real
-session via `signInWithPassword`. The client then adopts it with
-`supabase.auth.setSession(...)`. This is a standard, supported pattern for
-"custom auth providers" on Supabase and requires no extra Supabase config.
+**Sessions** are stateless: `/verify` signs an HS256 JWT (`jose`) holding the
+user id, wallet and role, and stores it in the HttpOnly `tl_session` cookie
+(7 days). The edge proxy verifies the cookie locally for routing; pages and API
+routes call `getSessionUser()` / `requireAuthenticatedUser()` in
+[lib/auth/session.ts](../lib/auth/session.ts), which re-reads the `users` row so
+a deleted or re-roled account is reflected immediately.
+`POST /api/auth/signout` clears the cookie.
## 2. Backend: challenge endpoint (Task 2)
@@ -108,8 +106,8 @@ details matter for this to work end to end:
```jsonc
// request
{ "address": "GABC...", "signedTxXdr": "" }
-// response 200
-{ "access_token": "...", "refresh_token": "...", "isNewUser": true }
+// response 200 (+ Set-Cookie: tl_session=; HttpOnly; SameSite=Lax)
+{ "userId": "uuid", "role": "borrower", "isNewUser": true }
```
`verifyChallenge()` performs three checks via the Stellar SDK's `WebAuth` module:
1. **Structure & expiry** — `WebAuth.readChallengeTx` (throws on a malformed or
@@ -119,10 +117,10 @@ details matter for this to work end to end:
3. **Signature** — `WebAuth.verifyChallengeTxSigners` confirms the wallet actually
signed it.
-On success, `issueSessionForWallet()` creates (if new) a Supabase user keyed to
-the wallet and returns a session; the client adopts it with `setSession`, and
-existing role-based routing (`getDashboardPath`) takes over — new SIWS users
-default to the `borrower` role like any fresh signup.
+On success, `issueSessionForWallet()` upserts the `users` row keyed by wallet
+address (and a matching `profiles` row), the route sets the session cookie, and
+role-based routing (`getDashboardPath`) takes over. The role chosen on the auth
+page only applies to brand-new accounts; an existing account keeps its role.
## 5. Error states (Task 5)
@@ -147,10 +145,9 @@ network, user rejected) via the existing `signTransactionWithWallet` error paths
```bash
NEXT_PUBLIC_SIWS_DOMAIN=localhost:3000 # SEP-10 home/web-auth domain (both sides must match)
SIWS_SERVER_SECRET= # dedicated SEP-10 signing key (S...)
-SIWS_PASSWORD_SECRET= # HMAC secret for wallet-user passwords
+SESSION_SECRET= # >= 32 chars; signs the session cookie
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID= # Reown/WalletConnect Cloud project id (enables mobile wallets)
-# reuses: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY,
-# SUPABASE_SERVICE_ROLE_KEY, NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE
+# reuses: DATABASE_URL, NEXT_PUBLIC_STELLAR_NETWORK_PASSPHRASE
```
## 7. Tests
@@ -158,14 +155,14 @@ NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID= # Reown/WalletConnect Cloud project id
[__tests__/auth/siws.test.ts](__tests__/auth/siws.test.ts) exercises the real
`WebAuth` roundtrip (build → sign → verify) with an in-memory test keypair —
happy path, wrong-signer, address mismatch, expired challenge, and malformed XDR —
-without hitting the network or Supabase.
+without hitting the network or the database.
## 8. Notes
- The SEP-10 signing key is intentionally **separate** from the platform admin
key used elsewhere (oracle, governance, default-management) — compromising one
cannot forge the other.
-- Rotating `SIWS_PASSWORD_SECRET` invalidates existing wallet-derived passwords;
- treat it like rotating a real secret (plan a re-auth, don't do it casually).
-- Future: link a SIWS identity to an *existing* password-based account instead of
- always creating a fresh one keyed by address.
+- Rotating `SESSION_SECRET` signs every user out (their cookies stop
+ verifying); the accounts themselves are unaffected.
+- Admin access requires both an allowlist entry (`TRADE_VAULT_ADMIN_EMAILS`
+ accepts wallet addresses or e-mails) and `profiles.role = 'admin'`.
diff --git a/docs/default-automation.md b/docs/default-automation.md
index 6f3599b..9eb8803 100644
--- a/docs/default-automation.md
+++ b/docs/default-automation.md
@@ -81,7 +81,7 @@ period; `trigger_insurance_payout` fires once a loan reaches the Reported phase
```bash
CRON_SECRET= # required: shared scheduler secret
ADMIN_SECRET_KEY= # required for on-chain calls (S...)
-SUPABASE_SERVICE_ROLE_KEY= # required: trusted DB access
+DATABASE_URL= # required: Neon connection string
DEFAULT_GRACE_PERIOD_DAYS=7 # optional (default 7)
DEFAULT_INSURANCE_PAYOUT_DAYS=60 # optional (default 60)
# reuses: NEXT_PUBLIC_LENDING_CONTRACT_ID, NEXT_PUBLIC_DEFAULT_CONTRACT_ID,
diff --git a/docs/disaster-recovery.md b/docs/disaster-recovery.md
index fe21e41..5e0fd3f 100644
--- a/docs/disaster-recovery.md
+++ b/docs/disaster-recovery.md
@@ -156,20 +156,18 @@ Restoring a single table:
pg_restore --dbname="$DATABASE_URL" --data-only --table=loans restore.dump
```
-### 3.3 Supabase notes
+### 3.3 Neon notes
- Dumps are taken with `--no-owner --no-privileges` so they restore into a
project whose roles differ from production — which is the normal case when
restoring into a fresh project.
-- A full dump includes Supabase-managed schemas (`auth`, `storage`, …).
- Restoring those into a *new* project can conflict with what the platform
- provisions. If you hit that, restore only what you need:
- `pg_restore --schema=public …`.
-- If `pg_dump` fails on a platform-managed schema (`vault`, `pgsodium`), set the
- `BACKUP_EXCLUDE_SCHEMAS` repository variable, e.g. `vault,pgsodium`.
-- RLS policies live in [supabase/rls-policies.sql](supabase/rls-policies.sql) and
- are captured by the dump, but re-applying that file is a good sanity check
- after a restore into a new project.
+- Use the **direct** (non-pooler) Neon host for `pg_dump`/`pg_restore`; set it as
+ `BACKUP_DATABASE_URL` when the app's `DATABASE_URL` is the pooled one.
+- Restore only the application schema into a new project
+ (`pg_restore --schema=public …`); Neon provisions its own system schemas.
+- The schema, triggers and SQL functions are also reproducible from source with
+ `npm run db:migrate` against an empty database — a restore only needs to
+ bring back the *data* if the migrations have already been applied.
---
@@ -212,8 +210,9 @@ hypothesis, not a backup.**
in repositories with no activity for 60 days — if the repo goes quiet, confirm
the schedule is still enabled.
- **Recovery point objective is 24 hours.** Up to a day of writes can be lost.
- Supabase's own point-in-time recovery is the tool for a tighter RPO; this job
- is the independent, off-site copy that survives losing the Supabase project.
+ Neon's own point-in-time restore (branch from a timestamp) is the tool for a
+ tighter RPO; this job is the independent, off-site copy that survives losing
+ the Neon project.
- **Restores are manual.** There is deliberately no automated restore path — an
automated process that can overwrite production is a bigger risk than the
minutes it saves.
diff --git a/docs/getting-started.md b/docs/getting-started.md
index db35d2f..a8c8bdd 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -100,7 +100,7 @@ git remote add upstream https://github.com/thisisouvik/trustlend-stellar.git
npm install
```
-This installs all frontend dependencies listed in `package.json` (React 19, Next.js 16, Tailwind CSS 4, Supabase SDK, Stellar SDK, etc.). The install also triggers Husky's `prepare` script which activates the commit-msg hook for conventional commit enforcement.
+This installs all frontend dependencies listed in `package.json` (React 19, Next.js 16, Tailwind CSS 4, Drizzle ORM, Stellar SDK, etc.). The install also triggers Husky's `prepare` script which activates the commit-msg hook for conventional commit enforcement.
### 3.2 Configure Environment Variables
@@ -110,11 +110,12 @@ Copy the example environment file:
cp .env.example .env.local
```
-Open `.env.local` and set the required values. For **local development with an offline Supabase project**, at minimum configure:
+Open `.env.local` and set the required values. At minimum configure:
```bash
-NEXT_PUBLIC_SUPABASE_URL=https://.supabase.co
-NEXT_PUBLIC_SUPABASE_ANON_KEY=
+DATABASE_URL=postgres://... # Neon pooled connection string
+SESSION_SECRET=
+SIWS_SERVER_SECRET=
NEXT_PUBLIC_STELLAR_NETWORK=testnet
NEXT_PUBLIC_STELLAR_HORIZON_URL=https://horizon-testnet.stellar.org
NEXT_PUBLIC_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
@@ -122,21 +123,36 @@ NEXT_PUBLIC_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org
> Refer to `.env.example` for the full list of supported variables and their descriptions.
-### 3.3 Set Up Supabase (Database)
+### 3.3 Set Up the Database (Neon Postgres)
-The project uses Supabase for auth, Postgres, and storage. Run the SQL migration files in your Supabase SQL editor **in order**:
+TrustLend stores off-chain state in Postgres on [Neon](https://neon.tech) and
+talks to it through [Drizzle ORM](https://orm.drizzle.team). The schema lives in
+`lib/db/schema.ts`; SQL migrations are generated from it into `drizzle/`.
-| Order | File | Purpose |
-|---|---|---|
-| 1 | `sql/01_core_schema.sql` | Core tables: users, loans, pools, etc. |
-| 2 | `sql/02_security_rls.sql` | Row-level security policies |
-| 3 | `sql/03_functions_rpcs.sql` | PostgreSQL functions & RPCs |
-| 4 | `sql/04_pool_performance_rpc.sql` | Optimized pool-query RPCs |
-| 5 | `sql/05_interest_rate_model.sql` | Interest rate model logic |
-| 6 | `sql/05_horizon_sync_schema.sql` | Horizon sync tables |
-| 7 | `sql/06_kyc_provider.sql` | KYC provider schema |
+1. Create a Neon project and copy the **pooled** connection string into
+ `DATABASE_URL` in `.env.local`.
+2. Apply the migrations:
+
+ ```bash
+ npm run db:migrate
+ ```
+
+ This creates every table, trigger and SQL function the app needs (the
+ `drizzle/0001_functions_and_triggers.sql` migration holds the row-locked
+ loan-funding and referral functions).
+3. Optional: `npm run db:studio` opens a browser UI over the database.
+
+When you change `lib/db/schema.ts`, run `npm run db:generate` to produce the
+next migration and commit it alongside the schema change.
+
+**Authentication** is Sign-In with Stellar (SEP-10): there are no passwords and
+no third-party auth service. A successful wallet signature creates a row in
+`users` + `profiles` and sets a signed HttpOnly session cookie
+(`SESSION_SECRET`). See [auth-siws.md](auth-siws.md).
-Also create a private storage bucket named **`kyc-documents`** in the Supabase dashboard for user document uploads.
+**KYC documents** are stored as private files in
+[Vercel Blob](https://vercel.com/docs/storage/vercel-blob); set
+`BLOB_READ_WRITE_TOKEN` to enable uploads locally.
### 3.4 Start the Development Server
@@ -408,11 +424,14 @@ rustup target add wasm32-unknown-unknown
```
- Ensure your Node.js version is >= 20.
-### Supabase queries return empty / auth doesn't work
+### Dashboards are empty / sign-in fails
-- Verify your `.env.local` has the correct `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY`.
-- Ensure all SQL migration files have been run in your Supabase project.
-- Check that Row-Level Security (RLS) policies are applied.
+- Verify `DATABASE_URL` in `.env.local` points at your Neon project and that
+ `npm run db:migrate` has been run against it.
+- `SESSION_SECRET` must be at least 32 characters; `SIWS_SERVER_SECRET` must
+ be a valid Stellar secret key.
+- The dev server logs `Database is not configured` when `DATABASE_URL` is
+ missing — pages then render their empty states rather than crashing.
### Tests are slow
diff --git a/docs/liquidation-keeper.md b/docs/liquidation-keeper.md
index db127a3..038b1ad 100644
--- a/docs/liquidation-keeper.md
+++ b/docs/liquidation-keeper.md
@@ -10,7 +10,7 @@ loans and liquidates them on-chain before they become bad debt.
## Flow
-1. **Fetch open loans** — either `--source=db` (Supabase `loans` table, resolving the
+1. **Fetch open loans** — either `--source=db` (the `loans` table, resolving the
on-chain loan id from the funding ledger entry) or `--source=chain` (iterates the
LendingContract directly via `get_loan_count`/`get_loan`).
2. **Read authoritative on-chain state** — collateral + remaining debt from
@@ -39,17 +39,22 @@ individually error-handled — one bad loan never aborts the run — matching th
## Deployment (issue #259)
-Two ways to run the keeper as a background worker that monitors every minute:
+Three ways to run the keeper as a background worker:
-1. **Vercel Cron (default deployment).** [`vercel.json`](vercel.json) schedules
- `POST /api/cron/liquidation` every minute (`* * * * *`). The route
- ([`app/api/cron/liquidation/route.ts`](app/api/cron/liquidation/route.ts))
+1. **GitHub Actions (default deployment).**
+ [`.github/workflows/keepers.yml`](../.github/workflows/keepers.yml) calls
+ `POST /api/cron/liquidation` every 5 minutes. Set the `KEEPER_BASE_URL` and
+ `CRON_SECRET` repository secrets to enable it; without them the workflow is a
+ no-op. The route
+ ([`app/api/cron/liquidation/route.ts`](../app/api/cron/liquidation/route.ts))
authenticates the caller with `Bearer ${CRON_SECRET}` (same scheme as the
`payment-due` / `default-management` crons), loads the keeper config from env,
- runs a full scan, and returns the per-run summary. Every-minute schedules
- require a Vercel Pro plan; on Hobby, run the self-hosted variant below (or an
- external cron) instead.
-2. **Self-hosted service.** `npm run liquidation:keeper:service` runs
+ runs a full scan, and returns the per-run summary.
+2. **Vercel Cron (safety net).** [`vercel.json`](../vercel.json) also schedules
+ the route once a day. Vercel Hobby rejects deployments that declare any cron
+ more frequent than daily, which is why the 5-minute cadence lives in GitHub
+ Actions rather than here.
+3. **Self-hosted service.** `npm run liquidation:keeper:service` runs
`scripts/liquidation-keeper.ts --interval=60` — a long-lived loop that rescans
prices and liquidates every 60 seconds. Wrap it in Docker/systemd/PM2 on any
always-on host. One-shot cron invocations (item 1 of [Usage](#usage)) remain
diff --git a/docs/oracle-price-feeds.md b/docs/oracle-price-feeds.md
index fec1c08..05e4616 100644
--- a/docs/oracle-price-feeds.md
+++ b/docs/oracle-price-feeds.md
@@ -51,8 +51,8 @@ supplies the missing off-chain feeder.
## The 5-second requirement
-Vercel Cron's finest granularity is **one minute**, so a cron route alone cannot
-meet the acceptance criterion. The 5-second cadence comes from a long-lived
+A scheduled cron route alone cannot meet the acceptance criterion (Vercel Hobby
+allows one run per day; GitHub Actions bottoms out at five minutes). The 5-second cadence comes from a long-lived
process, mirroring how `liquidation-keeper` handles its own sub-minute mode:
```bash
@@ -64,9 +64,10 @@ npm run price:oracle -- --interval=15
Run it under Docker, systemd or PM2 on any always-on host.
-`/api/cron/price-oracle` is also scheduled every minute in `vercel.json` as a
-**safety net** — if the long-lived keeper dies, prices still refresh once a
-minute rather than going completely stale. It is not a substitute for the
+`/api/cron/price-oracle` is also triggered every 5 minutes by
+`.github/workflows/keepers.yml` (and once a day by `vercel.json`) as a
+**safety net** — if the long-lived keeper dies, prices still refresh every few
+minutes rather than going completely stale. It is not a substitute for the
keeper. Note that each cron invocation starts with empty state, so its
in-memory cache fallback is unavailable and it falls straight through to TWAP.
diff --git a/docs/roadmap.md b/docs/roadmap.md
index 3dbd8c2..2c1f5c0 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -4,7 +4,7 @@ Welcome to the TrustLend Roadmap! This document outlines our high-level goals an
## 🚀 Phase 1: Foundation (Current)
- [x] Initial smart contract deployment on Stellar/Soroban Testnet
-- [x] Supabase integration for auth & user profiles
+- [x] Wallet-native auth (SEP-10) with Postgres-backed user profiles
- [x] Web frontend MVP with Next.js & React
- [x] Open-source repository setup & documentation
diff --git a/docs/sep24-fiat-ramp.md b/docs/sep24-fiat-ramp.md
index 386f0d2..b3fc73d 100644
--- a/docs/sep24-fiat-ramp.md
+++ b/docs/sep24-fiat-ramp.md
@@ -98,6 +98,6 @@ serving the borrower's region (bank / mobile-money rails) and set the appropriat
- Surface a matching **"Add Funds from Fiat"** (deposit / on-ramp) button using the
already-implemented `startInteractiveDeposit`.
-- Persist SEP-24 transaction ids to Supabase so withdrawals show in **History**.
+- Persist SEP-24 transaction ids to the database so withdrawals show in **History**.
- Multi-anchor selection by borrower region/currency.
- SEP-6 (programmatic, non-interactive) rails for power users.
diff --git a/drizzle.config.ts b/drizzle.config.ts
new file mode 100644
index 0000000..e44bd3e
--- /dev/null
+++ b/drizzle.config.ts
@@ -0,0 +1,21 @@
+import { defineConfig } from "drizzle-kit";
+
+/**
+ * drizzle-kit configuration.
+ *
+ * npm run db:generate # diff lib/db/schema.ts → new SQL migration in drizzle/
+ * npm run db:migrate # apply pending migrations to DATABASE_URL
+ * npm run db:studio # browse the database
+ *
+ * DATABASE_URL is only needed for migrate/studio, not for generate.
+ */
+export default defineConfig({
+ dialect: "postgresql",
+ schema: "./lib/db/schema.ts",
+ out: "./drizzle",
+ dbCredentials: {
+ url: process.env.DATABASE_URL ?? "postgres://localhost:5432/trustlend",
+ },
+ strict: true,
+ verbose: true,
+});
diff --git a/drizzle/0000_init.sql b/drizzle/0000_init.sql
new file mode 100644
index 0000000..d230b5a
--- /dev/null
+++ b/drizzle/0000_init.sql
@@ -0,0 +1,334 @@
+CREATE TYPE "public"."app_role" AS ENUM('borrower', 'lender', 'admin');--> statement-breakpoint
+CREATE TYPE "public"."kyc_status" AS ENUM('pending', 'submitted', 'verified', 'rejected');--> statement-breakpoint
+CREATE TYPE "public"."loan_status" AS ENUM('requested', 'approved', 'funded', 'active', 'repaid', 'defaulted', 'cancelled');--> statement-breakpoint
+CREATE TYPE "public"."pool_status" AS ENUM('active', 'paused', 'closed');--> statement-breakpoint
+CREATE TYPE "public"."position_status" AS ENUM('active', 'closed');--> statement-breakpoint
+CREATE TYPE "public"."referral_status" AS ENUM('pending', 'qualified', 'paid', 'rejected');--> statement-breakpoint
+CREATE TYPE "public"."risk_decision" AS ENUM('allow', 'manual_review', 'reject');--> statement-breakpoint
+CREATE TYPE "public"."risk_status" AS ENUM('low', 'medium', 'high', 'blocked');--> statement-breakpoint
+CREATE TYPE "public"."task_difficulty" AS ENUM('easy', 'medium', 'hard');--> statement-breakpoint
+CREATE TYPE "public"."task_status" AS ENUM('open', 'assigned', 'completed', 'verified', 'cancelled');--> statement-breakpoint
+CREATE TYPE "public"."tx_status" AS ENUM('pending', 'confirmed', 'failed', 'cancelled');--> statement-breakpoint
+CREATE TYPE "public"."verification_status" AS ENUM('pending', 'verified', 'rejected', 'expired');--> statement-breakpoint
+CREATE TABLE "chain_events" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "tx_hash" text NOT NULL,
+ "contract_id" text,
+ "event_type" text NOT NULL,
+ "payload" jsonb DEFAULT '{}'::jsonb NOT NULL,
+ "happened_at" timestamp with time zone,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "external_verifications" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "user_id" uuid NOT NULL,
+ "provider" text NOT NULL,
+ "verification_type" text NOT NULL,
+ "status" "verification_status" DEFAULT 'pending' NOT NULL,
+ "verified_at" timestamp with time zone,
+ "payload_meta" jsonb DEFAULT '{}'::jsonb NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "fraud_signals" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "user_id" uuid NOT NULL,
+ "signal_type" text NOT NULL,
+ "severity" smallint NOT NULL,
+ "payload" jsonb DEFAULT '{}'::jsonb NOT NULL,
+ "resolved" boolean DEFAULT false NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "resolved_at" timestamp with time zone
+);
+--> statement-breakpoint
+CREATE TABLE "ledger_transactions" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "user_id" uuid NOT NULL,
+ "category" text NOT NULL,
+ "amount" numeric(20, 6) NOT NULL,
+ "currency" text DEFAULT 'XLM' NOT NULL,
+ "status" "tx_status" DEFAULT 'pending' NOT NULL,
+ "ref_type" text,
+ "ref_id" uuid,
+ "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "lending_pools" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "name" text NOT NULL,
+ "description" text,
+ "status" "pool_status" DEFAULT 'active' NOT NULL,
+ "currency" text DEFAULT 'XLM' NOT NULL,
+ "apr_bps" integer NOT NULL,
+ "total_liquidity" numeric(20, 6) DEFAULT '0' NOT NULL,
+ "available_liquidity" numeric(20, 6) DEFAULT '0' NOT NULL,
+ "total_borrowed" numeric(20, 6) DEFAULT '0' NOT NULL,
+ "borrow_cap" numeric(20, 7),
+ "created_by" uuid,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "loan_fundings" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "loan_id" uuid NOT NULL,
+ "lender_id" uuid NOT NULL,
+ "amount" numeric(20, 6) NOT NULL,
+ "tx_hash" text NOT NULL,
+ "lender_address" text,
+ "funded_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "loan_repayments" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "loan_id" uuid NOT NULL,
+ "payer_id" uuid NOT NULL,
+ "amount" numeric(20, 6) NOT NULL,
+ "paid_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "tx_ref" text,
+ "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "loans" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "borrower_id" uuid NOT NULL,
+ "pool_id" uuid,
+ "status" "loan_status" DEFAULT 'requested' NOT NULL,
+ "principal_amount" numeric(20, 6) NOT NULL,
+ "apr_bps" integer NOT NULL,
+ "duration_days" integer NOT NULL,
+ "rate_model" text DEFAULT 'fixed' NOT NULL,
+ "rate_switch_count" integer DEFAULT 0 NOT NULL,
+ "last_rate_switch_at" timestamp with time zone,
+ "funded_amount" numeric(20, 6) DEFAULT '0' NOT NULL,
+ "repaid_amount" numeric(20, 6) DEFAULT '0' NOT NULL,
+ "requested_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "approved_at" timestamp with time zone,
+ "funded_at" timestamp with time zone,
+ "due_at" timestamp with time zone,
+ "closed_at" timestamp with time zone,
+ "defaulted_at" timestamp with time zone,
+ "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "notifications" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "user_id" uuid NOT NULL,
+ "type" text NOT NULL,
+ "title" text NOT NULL,
+ "message" text NOT NULL,
+ "read" boolean DEFAULT false NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "pool_positions" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "pool_id" uuid NOT NULL,
+ "lender_id" uuid NOT NULL,
+ "status" "position_status" DEFAULT 'active' NOT NULL,
+ "principal_amount" numeric(20, 6) NOT NULL,
+ "earned_interest" numeric(20, 6) DEFAULT '0' NOT NULL,
+ "withdrawn_amount" numeric(20, 6) DEFAULT '0' NOT NULL,
+ "opened_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "closed_at" timestamp with time zone,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "profiles" (
+ "id" uuid PRIMARY KEY NOT NULL,
+ "full_name" text DEFAULT '' NOT NULL,
+ "role" "app_role" DEFAULT 'borrower' NOT NULL,
+ "wallet_address" text,
+ "country_code" text,
+ "phone" text,
+ "date_of_birth" date,
+ "kyc_status" "kyc_status" DEFAULT 'pending' NOT NULL,
+ "risk_status" "risk_status" DEFAULT 'medium' NOT NULL,
+ "government_id_ipfs_hash" text,
+ "government_id_url" text,
+ "kyc_submitted_at" timestamp with time zone,
+ "kyc_verified_at" timestamp with time zone,
+ "kyc_rejection_reason" text,
+ "kyc_provider_id" text,
+ "kyc_provider_status" text,
+ "regulated_pool_access" boolean DEFAULT false NOT NULL,
+ "referral_code" text,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "referrals" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "referrer_id" uuid NOT NULL,
+ "referee_id" uuid NOT NULL,
+ "referral_code" text NOT NULL,
+ "status" "referral_status" DEFAULT 'pending' NOT NULL,
+ "qualifying_loan_id" uuid,
+ "bonus_amount" numeric(20, 7) DEFAULT '0' NOT NULL,
+ "payout_tx_hash" text,
+ "qualified_at" timestamp with time zone,
+ "paid_at" timestamp with time zone,
+ "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "reputation_events" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "user_id" uuid NOT NULL,
+ "source_type" text NOT NULL,
+ "source_id" uuid,
+ "source_key" text,
+ "points_delta" integer NOT NULL,
+ "reason" text NOT NULL,
+ "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "reputation_snapshots" (
+ "user_id" uuid PRIMARY KEY NOT NULL,
+ "score_total" integer DEFAULT 0 NOT NULL,
+ "repayment_score" integer DEFAULT 0 NOT NULL,
+ "lending_score" integer DEFAULT 0 NOT NULL,
+ "consistency_score" integer DEFAULT 0 NOT NULL,
+ "external_score" integer DEFAULT 0 NOT NULL,
+ "reputation_level" text DEFAULT 'bronze' NOT NULL,
+ "score_breakdown" jsonb DEFAULT '{}'::jsonb NOT NULL,
+ "calculated_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "risk_assessments" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "user_id" uuid NOT NULL,
+ "score" numeric(5, 2) NOT NULL,
+ "decision" "risk_decision" NOT NULL,
+ "reasons" jsonb DEFAULT '[]'::jsonb NOT NULL,
+ "assessed_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "tasks" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "creator_id" uuid NOT NULL,
+ "assigned_to" uuid,
+ "title" text NOT NULL,
+ "description" text,
+ "category" text,
+ "reward_xlm" numeric(20, 6) DEFAULT '0' NOT NULL,
+ "difficulty" "task_difficulty" DEFAULT 'easy' NOT NULL,
+ "status" "task_status" DEFAULT 'open' NOT NULL,
+ "completion_deadline" timestamp with time zone,
+ "completion_date" timestamp with time zone,
+ "proof_submission" text,
+ "creator_rating" smallint,
+ "metadata" jsonb DEFAULT '{}'::jsonb NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "users" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "wallet_address" text NOT NULL,
+ "role" "app_role" DEFAULT 'borrower' NOT NULL,
+ "email" text,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "last_sign_in_at" timestamp with time zone
+);
+--> statement-breakpoint
+CREATE TABLE "webhook_endpoints" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "name" text NOT NULL,
+ "url" text NOT NULL,
+ "platform" text NOT NULL,
+ "topic" text NOT NULL,
+ "is_active" boolean DEFAULT true NOT NULL,
+ "created_by" uuid,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+ALTER TABLE "external_verifications" ADD CONSTRAINT "external_verifications_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "fraud_signals" ADD CONSTRAINT "fraud_signals_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "ledger_transactions" ADD CONSTRAINT "ledger_transactions_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "lending_pools" ADD CONSTRAINT "lending_pools_created_by_profiles_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."profiles"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "loan_fundings" ADD CONSTRAINT "loan_fundings_loan_id_loans_id_fk" FOREIGN KEY ("loan_id") REFERENCES "public"."loans"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "loan_fundings" ADD CONSTRAINT "loan_fundings_lender_id_profiles_id_fk" FOREIGN KEY ("lender_id") REFERENCES "public"."profiles"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "loan_repayments" ADD CONSTRAINT "loan_repayments_loan_id_loans_id_fk" FOREIGN KEY ("loan_id") REFERENCES "public"."loans"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "loan_repayments" ADD CONSTRAINT "loan_repayments_payer_id_profiles_id_fk" FOREIGN KEY ("payer_id") REFERENCES "public"."profiles"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "loans" ADD CONSTRAINT "loans_borrower_id_profiles_id_fk" FOREIGN KEY ("borrower_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "loans" ADD CONSTRAINT "loans_pool_id_lending_pools_id_fk" FOREIGN KEY ("pool_id") REFERENCES "public"."lending_pools"("id") ON DELETE restrict ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "notifications" ADD CONSTRAINT "notifications_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "pool_positions" ADD CONSTRAINT "pool_positions_pool_id_lending_pools_id_fk" FOREIGN KEY ("pool_id") REFERENCES "public"."lending_pools"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "pool_positions" ADD CONSTRAINT "pool_positions_lender_id_profiles_id_fk" FOREIGN KEY ("lender_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "profiles" ADD CONSTRAINT "profiles_id_users_id_fk" FOREIGN KEY ("id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "referrals" ADD CONSTRAINT "referrals_referrer_id_profiles_id_fk" FOREIGN KEY ("referrer_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "referrals" ADD CONSTRAINT "referrals_referee_id_profiles_id_fk" FOREIGN KEY ("referee_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "referrals" ADD CONSTRAINT "referrals_qualifying_loan_id_loans_id_fk" FOREIGN KEY ("qualifying_loan_id") REFERENCES "public"."loans"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "reputation_events" ADD CONSTRAINT "reputation_events_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "reputation_snapshots" ADD CONSTRAINT "reputation_snapshots_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "risk_assessments" ADD CONSTRAINT "risk_assessments_user_id_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "tasks" ADD CONSTRAINT "tasks_creator_id_profiles_id_fk" FOREIGN KEY ("creator_id") REFERENCES "public"."profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "tasks" ADD CONSTRAINT "tasks_assigned_to_profiles_id_fk" FOREIGN KEY ("assigned_to") REFERENCES "public"."profiles"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "webhook_endpoints" ADD CONSTRAINT "webhook_endpoints_created_by_users_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
+CREATE UNIQUE INDEX "chain_events_tx_hash_event_type_key" ON "chain_events" USING btree ("tx_hash","event_type");--> statement-breakpoint
+CREATE INDEX "idx_chain_events_contract_id" ON "chain_events" USING btree ("contract_id");--> statement-breakpoint
+CREATE INDEX "idx_chain_events_happened_at" ON "chain_events" USING btree ("happened_at");--> statement-breakpoint
+CREATE INDEX "idx_external_verifications_user_id" ON "external_verifications" USING btree ("user_id");--> statement-breakpoint
+CREATE INDEX "idx_external_verifications_status" ON "external_verifications" USING btree ("status");--> statement-breakpoint
+CREATE INDEX "idx_fraud_signals_user_id_created_at" ON "fraud_signals" USING btree ("user_id","created_at");--> statement-breakpoint
+CREATE INDEX "idx_fraud_signals_resolved" ON "fraud_signals" USING btree ("resolved");--> statement-breakpoint
+CREATE INDEX "idx_ledger_transactions_user_id_created_at" ON "ledger_transactions" USING btree ("user_id","created_at");--> statement-breakpoint
+CREATE INDEX "idx_ledger_transactions_status" ON "ledger_transactions" USING btree ("status");--> statement-breakpoint
+CREATE INDEX "idx_lending_pools_status" ON "lending_pools" USING btree ("status");--> statement-breakpoint
+CREATE INDEX "idx_lending_pools_status_available" ON "lending_pools" USING btree ("status","available_liquidity");--> statement-breakpoint
+CREATE INDEX "idx_lending_pools_created_at_desc" ON "lending_pools" USING btree ("created_at");--> statement-breakpoint
+CREATE INDEX "idx_loan_fundings_loan_id" ON "loan_fundings" USING btree ("loan_id");--> statement-breakpoint
+CREATE INDEX "idx_loan_fundings_lender_id" ON "loan_fundings" USING btree ("lender_id");--> statement-breakpoint
+CREATE INDEX "idx_loan_fundings_lender_loan" ON "loan_fundings" USING btree ("lender_id","loan_id");--> statement-breakpoint
+CREATE UNIQUE INDEX "idx_loan_fundings_tx_hash" ON "loan_fundings" USING btree ("tx_hash");--> statement-breakpoint
+CREATE INDEX "idx_loan_repayments_loan_id" ON "loan_repayments" USING btree ("loan_id");--> statement-breakpoint
+CREATE INDEX "idx_loan_repayments_payer_id" ON "loan_repayments" USING btree ("payer_id");--> statement-breakpoint
+CREATE INDEX "idx_loans_borrower_id" ON "loans" USING btree ("borrower_id");--> statement-breakpoint
+CREATE INDEX "idx_loans_pool_id" ON "loans" USING btree ("pool_id");--> statement-breakpoint
+CREATE INDEX "idx_loans_status" ON "loans" USING btree ("status");--> statement-breakpoint
+CREATE INDEX "idx_loans_due_at" ON "loans" USING btree ("due_at");--> statement-breakpoint
+CREATE INDEX "idx_loans_borrower_status" ON "loans" USING btree ("borrower_id","status");--> statement-breakpoint
+CREATE INDEX "idx_loans_rate_model" ON "loans" USING btree ("rate_model");--> statement-breakpoint
+CREATE INDEX "idx_loans_status_funded" ON "loans" USING btree ("status","funded_amount");--> statement-breakpoint
+CREATE INDEX "idx_notifications_user_id_created_at" ON "notifications" USING btree ("user_id","created_at");--> statement-breakpoint
+CREATE INDEX "idx_pool_positions_lender_id" ON "pool_positions" USING btree ("lender_id");--> statement-breakpoint
+CREATE INDEX "idx_pool_positions_pool_id" ON "pool_positions" USING btree ("pool_id");--> statement-breakpoint
+CREATE INDEX "idx_profiles_role" ON "profiles" USING btree ("role");--> statement-breakpoint
+CREATE INDEX "idx_profiles_wallet_address" ON "profiles" USING btree ("wallet_address");--> statement-breakpoint
+CREATE INDEX "idx_profiles_kyc_status" ON "profiles" USING btree ("kyc_status");--> statement-breakpoint
+CREATE INDEX "idx_profiles_risk_status" ON "profiles" USING btree ("risk_status");--> statement-breakpoint
+CREATE INDEX "idx_profiles_kyc_submitted_at" ON "profiles" USING btree ("kyc_submitted_at");--> statement-breakpoint
+CREATE UNIQUE INDEX "profiles_referral_code_key" ON "profiles" USING btree ("referral_code");--> statement-breakpoint
+CREATE UNIQUE INDEX "idx_profiles_kyc_provider_id" ON "profiles" USING btree ("kyc_provider_id") WHERE "profiles"."kyc_provider_id" is not null;--> statement-breakpoint
+CREATE INDEX "idx_profiles_regulated_pool_access" ON "profiles" USING btree ("regulated_pool_access");--> statement-breakpoint
+CREATE UNIQUE INDEX "referrals_referee_id_key" ON "referrals" USING btree ("referee_id");--> statement-breakpoint
+CREATE INDEX "idx_referrals_referrer_id" ON "referrals" USING btree ("referrer_id");--> statement-breakpoint
+CREATE INDEX "idx_referrals_status" ON "referrals" USING btree ("status");--> statement-breakpoint
+CREATE INDEX "idx_referrals_referrer_status" ON "referrals" USING btree ("referrer_id","status");--> statement-breakpoint
+CREATE INDEX "idx_rep_events_user_id_created_at" ON "reputation_events" USING btree ("user_id","created_at");--> statement-breakpoint
+CREATE INDEX "idx_rep_events_source" ON "reputation_events" USING btree ("source_type","source_id");--> statement-breakpoint
+CREATE INDEX "idx_rep_events_source_key" ON "reputation_events" USING btree ("source_type","source_key");--> statement-breakpoint
+CREATE INDEX "idx_risk_assessments_user_id_assessed_at" ON "risk_assessments" USING btree ("user_id","assessed_at");--> statement-breakpoint
+CREATE INDEX "idx_tasks_creator_id" ON "tasks" USING btree ("creator_id");--> statement-breakpoint
+CREATE INDEX "idx_tasks_assigned_to" ON "tasks" USING btree ("assigned_to");--> statement-breakpoint
+CREATE INDEX "idx_tasks_status" ON "tasks" USING btree ("status");--> statement-breakpoint
+CREATE INDEX "idx_tasks_created_at" ON "tasks" USING btree ("created_at");--> statement-breakpoint
+CREATE UNIQUE INDEX "users_wallet_address_key" ON "users" USING btree ("wallet_address");--> statement-breakpoint
+CREATE INDEX "idx_webhook_endpoints_platform" ON "webhook_endpoints" USING btree ("platform");--> statement-breakpoint
+CREATE INDEX "idx_webhook_endpoints_topic" ON "webhook_endpoints" USING btree ("topic");
\ No newline at end of file
diff --git a/drizzle/0001_functions_and_triggers.sql b/drizzle/0001_functions_and_triggers.sql
new file mode 100644
index 0000000..1449e9d
--- /dev/null
+++ b/drizzle/0001_functions_and_triggers.sql
@@ -0,0 +1,439 @@
+-- Functions and triggers that live in the database.
+--
+-- Authorization is NOT enforced here (there is no auth.uid() on Neon): every
+-- caller is the application server, which verifies the session before it
+-- invokes any of these. Keep them limited to logic that genuinely benefits
+-- from running inside Postgres — row locks, atomic multi-row transitions and
+-- triggers.
+
+-- ─── updated_at maintenance ───────────────────────────────────────────────────
+
+create or replace function public.set_updated_at()
+returns trigger
+language plpgsql
+as $$
+begin
+ new.updated_at = now();
+ return new;
+end;
+$$;
+--> statement-breakpoint
+
+create trigger trg_profiles_updated_at
+before update on public.profiles
+for each row execute function public.set_updated_at();
+--> statement-breakpoint
+create trigger trg_reputation_snapshots_updated_at
+before update on public.reputation_snapshots
+for each row execute function public.set_updated_at();
+--> statement-breakpoint
+create trigger trg_tasks_updated_at
+before update on public.tasks
+for each row execute function public.set_updated_at();
+--> statement-breakpoint
+create trigger trg_lending_pools_updated_at
+before update on public.lending_pools
+for each row execute function public.set_updated_at();
+--> statement-breakpoint
+create trigger trg_pool_positions_updated_at
+before update on public.pool_positions
+for each row execute function public.set_updated_at();
+--> statement-breakpoint
+create trigger trg_loans_updated_at
+before update on public.loans
+for each row execute function public.set_updated_at();
+--> statement-breakpoint
+create trigger trg_ledger_transactions_updated_at
+before update on public.ledger_transactions
+for each row execute function public.set_updated_at();
+--> statement-breakpoint
+create trigger trg_external_verifications_updated_at
+before update on public.external_verifications
+for each row execute function public.set_updated_at();
+--> statement-breakpoint
+create trigger trg_webhook_endpoints_updated_at
+before update on public.webhook_endpoints
+for each row execute function public.set_updated_at();
+--> statement-breakpoint
+create trigger trg_referrals_updated_at
+before update on public.referrals
+for each row execute function public.set_updated_at();
+--> statement-breakpoint
+
+-- ─── Reputation snapshot kept in sync with events ─────────────────────────────
+
+create or replace function public.sync_reputation_snapshot_from_event()
+returns trigger
+language plpgsql
+set search_path = public
+as $$
+declare
+ existing_repayment_score integer := 0;
+ existing_lending_score integer := 0;
+ existing_consistency_score integer := 0;
+ existing_external_score integer := 0;
+ existing_level text := 'bronze';
+ total_points integer := 0;
+ computed_total integer := 250;
+begin
+ if new.user_id is null then
+ return new;
+ end if;
+
+ select
+ coalesce(score_total, 250),
+ coalesce(repayment_score, 0),
+ coalesce(lending_score, 0),
+ coalesce(consistency_score, 0),
+ coalesce(external_score, 0),
+ coalesce(reputation_level, 'bronze')
+ into computed_total, existing_repayment_score, existing_lending_score,
+ existing_consistency_score, existing_external_score, existing_level
+ from public.reputation_snapshots
+ where user_id = new.user_id;
+
+ select coalesce(sum(points_delta), 0)
+ into total_points
+ from public.reputation_events
+ where user_id = new.user_id;
+
+ computed_total := greatest(0, least(750, 250 + total_points));
+
+ insert into public.reputation_snapshots (
+ user_id, score_total, repayment_score, lending_score, consistency_score,
+ external_score, reputation_level, calculated_at, updated_at
+ )
+ values (
+ new.user_id, computed_total, existing_repayment_score, existing_lending_score,
+ existing_consistency_score, existing_external_score, existing_level, now(), now()
+ )
+ on conflict (user_id) do update
+ set score_total = excluded.score_total,
+ repayment_score = excluded.repayment_score,
+ lending_score = excluded.lending_score,
+ consistency_score = excluded.consistency_score,
+ external_score = excluded.external_score,
+ reputation_level = excluded.reputation_level,
+ calculated_at = excluded.calculated_at,
+ updated_at = excluded.updated_at;
+
+ return new;
+end;
+$$;
+--> statement-breakpoint
+
+create trigger trg_reputation_events_snapshot
+after insert on public.reputation_events
+for each row execute function public.sync_reputation_snapshot_from_event();
+--> statement-breakpoint
+
+-- ─── Referral codes ───────────────────────────────────────────────────────────
+
+create or replace function public.generate_referral_code()
+returns text
+language plpgsql
+volatile
+as $$
+declare
+ v_alphabet constant text := '23456789ABCDEFGHJKMNPQRSTVWXYZ';
+ v_code text;
+ v_attempt int := 0;
+begin
+ loop
+ v_code := 'TL';
+ for _ in 1..6 loop
+ v_code := v_code || substr(v_alphabet, floor(random() * 30)::int + 1, 1);
+ end loop;
+
+ exit when not exists (
+ select 1 from public.profiles where referral_code = v_code
+ );
+
+ v_attempt := v_attempt + 1;
+ if v_attempt > 20 then
+ raise exception 'Could not generate a unique referral code after % attempts', v_attempt;
+ end if;
+ end loop;
+
+ return v_code;
+end;
+$$;
+--> statement-breakpoint
+
+create or replace function public.ensure_referral_code(p_user_id uuid)
+returns text
+language plpgsql
+set search_path = public
+as $$
+declare
+ v_code text;
+begin
+ select referral_code into v_code from public.profiles where id = p_user_id;
+ if v_code is not null then
+ return v_code;
+ end if;
+
+ v_code := public.generate_referral_code();
+
+ update public.profiles
+ set referral_code = v_code
+ where id = p_user_id and referral_code is null;
+
+ select referral_code into v_code from public.profiles where id = p_user_id;
+ return v_code;
+end;
+$$;
+--> statement-breakpoint
+
+create or replace function public.assign_referral_code_on_profile()
+returns trigger
+language plpgsql
+set search_path = public
+as $$
+begin
+ if new.referral_code is null then
+ new.referral_code := public.generate_referral_code();
+ end if;
+ return new;
+exception
+ when others then
+ return new;
+end;
+$$;
+--> statement-breakpoint
+
+create trigger trg_profiles_referral_code
+before insert on public.profiles
+for each row execute function public.assign_referral_code_on_profile();
+--> statement-breakpoint
+
+-- Attribute a new user to a referrer. Idempotent: a referee is attributed once.
+create or replace function public.record_referral(
+ p_referee_id uuid,
+ p_referral_code text
+)
+returns table (
+ referral_id uuid,
+ referrer_id uuid,
+ status public.referral_status
+)
+language plpgsql
+set search_path = public
+as $$
+declare
+ v_referrer_id uuid;
+ v_code text;
+ v_referral_id uuid;
+ v_status public.referral_status;
+begin
+ v_code := upper(trim(p_referral_code));
+
+ if v_code is null or v_code = '' then
+ raise exception 'Referral code is required';
+ end if;
+
+ select id into v_referrer_id from public.profiles where referral_code = v_code;
+
+ if v_referrer_id is null then
+ raise exception 'Unknown referral code';
+ end if;
+
+ if v_referrer_id = p_referee_id then
+ raise exception 'Cannot refer yourself';
+ end if;
+
+ insert into public.referrals (referrer_id, referee_id, referral_code, status)
+ values (v_referrer_id, p_referee_id, v_code, 'pending')
+ on conflict (referee_id) do nothing
+ returning id into v_referral_id;
+
+ if v_referral_id is null then
+ select r.id, r.referrer_id, r.status
+ into v_referral_id, v_referrer_id, v_status
+ from public.referrals r
+ where r.referee_id = p_referee_id;
+
+ referral_id := v_referral_id;
+ referrer_id := v_referrer_id;
+ status := v_status;
+ return next;
+ return;
+ end if;
+
+ referral_id := v_referral_id;
+ referrer_id := v_referrer_id;
+ status := 'pending'::public.referral_status;
+ return next;
+end;
+$$;
+--> statement-breakpoint
+
+-- Mark a pending referral qualified when the referee's loan activates.
+create or replace function public.qualify_referral(
+ p_referee_id uuid,
+ p_loan_id uuid
+)
+returns table (
+ referral_id uuid,
+ referrer_id uuid,
+ status public.referral_status
+)
+language plpgsql
+set search_path = public
+as $$
+declare
+ v_row public.referrals;
+begin
+ select * into v_row from public.referrals where referee_id = p_referee_id for update;
+
+ if not found then
+ return;
+ end if;
+
+ if v_row.status <> 'pending'::public.referral_status then
+ referral_id := v_row.id;
+ referrer_id := v_row.referrer_id;
+ status := v_row.status;
+ return next;
+ return;
+ end if;
+
+ update public.referrals
+ set status = 'qualified'::public.referral_status,
+ qualifying_loan_id = p_loan_id,
+ qualified_at = now()
+ where id = v_row.id;
+
+ referral_id := v_row.id;
+ referrer_id := v_row.referrer_id;
+ status := 'qualified'::public.referral_status;
+ return next;
+end;
+$$;
+--> statement-breakpoint
+
+create or replace function public.settle_referral_payout(
+ p_referral_id uuid,
+ p_bonus_amount numeric,
+ p_tx_hash text
+)
+returns void
+language plpgsql
+set search_path = public
+as $$
+begin
+ if p_bonus_amount < 0 then
+ raise exception 'Bonus amount cannot be negative';
+ end if;
+
+ update public.referrals
+ set status = 'paid'::public.referral_status,
+ bonus_amount = p_bonus_amount,
+ payout_tx_hash = p_tx_hash,
+ paid_at = now()
+ where id = p_referral_id
+ and status <> 'paid'::public.referral_status;
+end;
+$$;
+--> statement-breakpoint
+
+-- ─── Loan funding (partial fills, row-locked) ─────────────────────────────────
+
+create or replace function public.record_loan_funding(
+ p_loan_id uuid,
+ p_lender_id uuid,
+ p_amount numeric,
+ p_tx_hash text,
+ p_lender_address text default null,
+ p_funded_at timestamptz default now()
+)
+returns table (
+ loan_id uuid,
+ status public.loan_status,
+ principal_amount numeric,
+ funded_amount numeric,
+ remaining_amount numeric,
+ is_fully_funded boolean,
+ funding_id uuid
+)
+language plpgsql
+set search_path = public
+as $$
+declare
+ v_loan public.loans;
+ v_new_total numeric(20, 6);
+ v_remaining numeric(20, 6);
+ v_funding_id uuid;
+ v_due_at timestamptz;
+begin
+ if p_amount is null or p_amount <= 0 then
+ raise exception 'funding amount must be greater than zero';
+ end if;
+
+ if p_tx_hash is null or length(trim(p_tx_hash)) = 0 then
+ raise exception 'a stellar transaction hash is required';
+ end if;
+
+ -- Lock the loan so two lenders cannot both read the same remaining amount.
+ select * into v_loan from public.loans where id = p_loan_id for update;
+
+ if not found then
+ raise exception 'loan not found';
+ end if;
+
+ if v_loan.status not in ('requested', 'approved') then
+ raise exception 'loan is not available for funding (status: %)', v_loan.status;
+ end if;
+
+ if v_loan.borrower_id = p_lender_id then
+ raise exception 'you cannot fund your own loan';
+ end if;
+
+ v_remaining := v_loan.principal_amount - v_loan.funded_amount;
+
+ if v_remaining <= 0 then
+ raise exception 'loan is already fully funded';
+ end if;
+
+ -- The lender already sent exactly p_amount on-chain; never silently cap it.
+ if p_amount > v_remaining then
+ raise exception 'funding amount % exceeds the remaining % on this loan', p_amount, v_remaining;
+ end if;
+
+ insert into public.loan_fundings (loan_id, lender_id, amount, tx_hash, lender_address, funded_at)
+ values (p_loan_id, p_lender_id, p_amount, trim(p_tx_hash), p_lender_address, p_funded_at)
+ returning id into v_funding_id;
+
+ v_new_total := v_loan.funded_amount + p_amount;
+
+ if v_new_total >= v_loan.principal_amount then
+ v_due_at := p_funded_at + make_interval(days => v_loan.duration_days);
+
+ update public.loans
+ set funded_amount = v_new_total,
+ status = 'active',
+ approved_at = coalesce(approved_at, p_funded_at),
+ funded_at = coalesce(funded_at, p_funded_at),
+ due_at = v_due_at,
+ updated_at = now()
+ where id = p_loan_id
+ returning * into v_loan;
+ else
+ update public.loans
+ set funded_amount = v_new_total,
+ updated_at = now()
+ where id = p_loan_id
+ returning * into v_loan;
+ end if;
+
+ return query
+ select
+ v_loan.id,
+ v_loan.status,
+ v_loan.principal_amount,
+ v_loan.funded_amount,
+ greatest(v_loan.principal_amount - v_loan.funded_amount, 0)::numeric,
+ (v_loan.funded_amount >= v_loan.principal_amount),
+ v_funding_id;
+end;
+$$;
diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json
new file mode 100644
index 0000000..f294483
--- /dev/null
+++ b/drizzle/meta/0000_snapshot.json
@@ -0,0 +1,2822 @@
+{
+ "id": "7651aee8-f11d-4065-a209-a2f45ddb9496",
+ "prevId": "00000000-0000-0000-0000-000000000000",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.chain_events": {
+ "name": "chain_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tx_hash": {
+ "name": "tx_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "contract_id": {
+ "name": "contract_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "happened_at": {
+ "name": "happened_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "chain_events_tx_hash_event_type_key": {
+ "name": "chain_events_tx_hash_event_type_key",
+ "columns": [
+ {
+ "expression": "tx_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "event_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_chain_events_contract_id": {
+ "name": "idx_chain_events_contract_id",
+ "columns": [
+ {
+ "expression": "contract_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_chain_events_happened_at": {
+ "name": "idx_chain_events_happened_at",
+ "columns": [
+ {
+ "expression": "happened_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.external_verifications": {
+ "name": "external_verifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "verification_type": {
+ "name": "verification_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "verification_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload_meta": {
+ "name": "payload_meta",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_external_verifications_user_id": {
+ "name": "idx_external_verifications_user_id",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_external_verifications_status": {
+ "name": "idx_external_verifications_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "external_verifications_user_id_profiles_id_fk": {
+ "name": "external_verifications_user_id_profiles_id_fk",
+ "tableFrom": "external_verifications",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.fraud_signals": {
+ "name": "fraud_signals",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "signal_type": {
+ "name": "signal_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "severity": {
+ "name": "severity",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "resolved": {
+ "name": "resolved",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_fraud_signals_user_id_created_at": {
+ "name": "idx_fraud_signals_user_id_created_at",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_fraud_signals_resolved": {
+ "name": "idx_fraud_signals_resolved",
+ "columns": [
+ {
+ "expression": "resolved",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "fraud_signals_user_id_profiles_id_fk": {
+ "name": "fraud_signals_user_id_profiles_id_fk",
+ "tableFrom": "fraud_signals",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ledger_transactions": {
+ "name": "ledger_transactions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "category": {
+ "name": "category",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount": {
+ "name": "amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "currency": {
+ "name": "currency",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'XLM'"
+ },
+ "status": {
+ "name": "status",
+ "type": "tx_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "ref_type": {
+ "name": "ref_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ref_id": {
+ "name": "ref_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_ledger_transactions_user_id_created_at": {
+ "name": "idx_ledger_transactions_user_id_created_at",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_ledger_transactions_status": {
+ "name": "idx_ledger_transactions_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "ledger_transactions_user_id_profiles_id_fk": {
+ "name": "ledger_transactions_user_id_profiles_id_fk",
+ "tableFrom": "ledger_transactions",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.lending_pools": {
+ "name": "lending_pools",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "pool_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "currency": {
+ "name": "currency",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'XLM'"
+ },
+ "apr_bps": {
+ "name": "apr_bps",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_liquidity": {
+ "name": "total_liquidity",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "available_liquidity": {
+ "name": "available_liquidity",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "total_borrowed": {
+ "name": "total_borrowed",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "borrow_cap": {
+ "name": "borrow_cap",
+ "type": "numeric(20, 7)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_lending_pools_status": {
+ "name": "idx_lending_pools_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_lending_pools_status_available": {
+ "name": "idx_lending_pools_status_available",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "available_liquidity",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_lending_pools_created_at_desc": {
+ "name": "idx_lending_pools_created_at_desc",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "lending_pools_created_by_profiles_id_fk": {
+ "name": "lending_pools_created_by_profiles_id_fk",
+ "tableFrom": "lending_pools",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.loan_fundings": {
+ "name": "loan_fundings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "loan_id": {
+ "name": "loan_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lender_id": {
+ "name": "lender_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount": {
+ "name": "amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tx_hash": {
+ "name": "tx_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lender_address": {
+ "name": "lender_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "funded_at": {
+ "name": "funded_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_loan_fundings_loan_id": {
+ "name": "idx_loan_fundings_loan_id",
+ "columns": [
+ {
+ "expression": "loan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_loan_fundings_lender_id": {
+ "name": "idx_loan_fundings_lender_id",
+ "columns": [
+ {
+ "expression": "lender_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_loan_fundings_lender_loan": {
+ "name": "idx_loan_fundings_lender_loan",
+ "columns": [
+ {
+ "expression": "lender_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "loan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_loan_fundings_tx_hash": {
+ "name": "idx_loan_fundings_tx_hash",
+ "columns": [
+ {
+ "expression": "tx_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "loan_fundings_loan_id_loans_id_fk": {
+ "name": "loan_fundings_loan_id_loans_id_fk",
+ "tableFrom": "loan_fundings",
+ "tableTo": "loans",
+ "columnsFrom": [
+ "loan_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "loan_fundings_lender_id_profiles_id_fk": {
+ "name": "loan_fundings_lender_id_profiles_id_fk",
+ "tableFrom": "loan_fundings",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "lender_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.loan_repayments": {
+ "name": "loan_repayments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "loan_id": {
+ "name": "loan_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payer_id": {
+ "name": "payer_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount": {
+ "name": "amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "paid_at": {
+ "name": "paid_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "tx_ref": {
+ "name": "tx_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_loan_repayments_loan_id": {
+ "name": "idx_loan_repayments_loan_id",
+ "columns": [
+ {
+ "expression": "loan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_loan_repayments_payer_id": {
+ "name": "idx_loan_repayments_payer_id",
+ "columns": [
+ {
+ "expression": "payer_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "loan_repayments_loan_id_loans_id_fk": {
+ "name": "loan_repayments_loan_id_loans_id_fk",
+ "tableFrom": "loan_repayments",
+ "tableTo": "loans",
+ "columnsFrom": [
+ "loan_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "loan_repayments_payer_id_profiles_id_fk": {
+ "name": "loan_repayments_payer_id_profiles_id_fk",
+ "tableFrom": "loan_repayments",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "payer_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.loans": {
+ "name": "loans",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "borrower_id": {
+ "name": "borrower_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pool_id": {
+ "name": "pool_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "loan_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'requested'"
+ },
+ "principal_amount": {
+ "name": "principal_amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apr_bps": {
+ "name": "apr_bps",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration_days": {
+ "name": "duration_days",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rate_model": {
+ "name": "rate_model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'fixed'"
+ },
+ "rate_switch_count": {
+ "name": "rate_switch_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "last_rate_switch_at": {
+ "name": "last_rate_switch_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "funded_amount": {
+ "name": "funded_amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "repaid_amount": {
+ "name": "repaid_amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "requested_at": {
+ "name": "requested_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "approved_at": {
+ "name": "approved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "funded_at": {
+ "name": "funded_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "due_at": {
+ "name": "due_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "closed_at": {
+ "name": "closed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "defaulted_at": {
+ "name": "defaulted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_loans_borrower_id": {
+ "name": "idx_loans_borrower_id",
+ "columns": [
+ {
+ "expression": "borrower_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_loans_pool_id": {
+ "name": "idx_loans_pool_id",
+ "columns": [
+ {
+ "expression": "pool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_loans_status": {
+ "name": "idx_loans_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_loans_due_at": {
+ "name": "idx_loans_due_at",
+ "columns": [
+ {
+ "expression": "due_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_loans_borrower_status": {
+ "name": "idx_loans_borrower_status",
+ "columns": [
+ {
+ "expression": "borrower_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_loans_rate_model": {
+ "name": "idx_loans_rate_model",
+ "columns": [
+ {
+ "expression": "rate_model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_loans_status_funded": {
+ "name": "idx_loans_status_funded",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "funded_amount",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "loans_borrower_id_profiles_id_fk": {
+ "name": "loans_borrower_id_profiles_id_fk",
+ "tableFrom": "loans",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "borrower_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "loans_pool_id_lending_pools_id_fk": {
+ "name": "loans_pool_id_lending_pools_id_fk",
+ "tableFrom": "loans",
+ "tableTo": "lending_pools",
+ "columnsFrom": [
+ "pool_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "restrict",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.notifications": {
+ "name": "notifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "read": {
+ "name": "read",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_notifications_user_id_created_at": {
+ "name": "idx_notifications_user_id_created_at",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "notifications_user_id_users_id_fk": {
+ "name": "notifications_user_id_users_id_fk",
+ "tableFrom": "notifications",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pool_positions": {
+ "name": "pool_positions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "pool_id": {
+ "name": "pool_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lender_id": {
+ "name": "lender_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "position_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "principal_amount": {
+ "name": "principal_amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "earned_interest": {
+ "name": "earned_interest",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "withdrawn_amount": {
+ "name": "withdrawn_amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "opened_at": {
+ "name": "opened_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "closed_at": {
+ "name": "closed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_pool_positions_lender_id": {
+ "name": "idx_pool_positions_lender_id",
+ "columns": [
+ {
+ "expression": "lender_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_pool_positions_pool_id": {
+ "name": "idx_pool_positions_pool_id",
+ "columns": [
+ {
+ "expression": "pool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "pool_positions_pool_id_lending_pools_id_fk": {
+ "name": "pool_positions_pool_id_lending_pools_id_fk",
+ "tableFrom": "pool_positions",
+ "tableTo": "lending_pools",
+ "columnsFrom": [
+ "pool_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "pool_positions_lender_id_profiles_id_fk": {
+ "name": "pool_positions_lender_id_profiles_id_fk",
+ "tableFrom": "pool_positions",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "lender_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.profiles": {
+ "name": "profiles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "full_name": {
+ "name": "full_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "role": {
+ "name": "role",
+ "type": "app_role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'borrower'"
+ },
+ "wallet_address": {
+ "name": "wallet_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "country_code": {
+ "name": "country_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "date_of_birth": {
+ "name": "date_of_birth",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kyc_status": {
+ "name": "kyc_status",
+ "type": "kyc_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "risk_status": {
+ "name": "risk_status",
+ "type": "risk_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'medium'"
+ },
+ "government_id_ipfs_hash": {
+ "name": "government_id_ipfs_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "government_id_url": {
+ "name": "government_id_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kyc_submitted_at": {
+ "name": "kyc_submitted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kyc_verified_at": {
+ "name": "kyc_verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kyc_rejection_reason": {
+ "name": "kyc_rejection_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kyc_provider_id": {
+ "name": "kyc_provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kyc_provider_status": {
+ "name": "kyc_provider_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "regulated_pool_access": {
+ "name": "regulated_pool_access",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "referral_code": {
+ "name": "referral_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_profiles_role": {
+ "name": "idx_profiles_role",
+ "columns": [
+ {
+ "expression": "role",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_profiles_wallet_address": {
+ "name": "idx_profiles_wallet_address",
+ "columns": [
+ {
+ "expression": "wallet_address",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_profiles_kyc_status": {
+ "name": "idx_profiles_kyc_status",
+ "columns": [
+ {
+ "expression": "kyc_status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_profiles_risk_status": {
+ "name": "idx_profiles_risk_status",
+ "columns": [
+ {
+ "expression": "risk_status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_profiles_kyc_submitted_at": {
+ "name": "idx_profiles_kyc_submitted_at",
+ "columns": [
+ {
+ "expression": "kyc_submitted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "profiles_referral_code_key": {
+ "name": "profiles_referral_code_key",
+ "columns": [
+ {
+ "expression": "referral_code",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_profiles_kyc_provider_id": {
+ "name": "idx_profiles_kyc_provider_id",
+ "columns": [
+ {
+ "expression": "kyc_provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"profiles\".\"kyc_provider_id\" is not null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_profiles_regulated_pool_access": {
+ "name": "idx_profiles_regulated_pool_access",
+ "columns": [
+ {
+ "expression": "regulated_pool_access",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "profiles_id_users_id_fk": {
+ "name": "profiles_id_users_id_fk",
+ "tableFrom": "profiles",
+ "tableTo": "users",
+ "columnsFrom": [
+ "id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.referrals": {
+ "name": "referrals",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "referrer_id": {
+ "name": "referrer_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "referee_id": {
+ "name": "referee_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "referral_code": {
+ "name": "referral_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "referral_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "qualifying_loan_id": {
+ "name": "qualifying_loan_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bonus_amount": {
+ "name": "bonus_amount",
+ "type": "numeric(20, 7)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "payout_tx_hash": {
+ "name": "payout_tx_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "qualified_at": {
+ "name": "qualified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "paid_at": {
+ "name": "paid_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "referrals_referee_id_key": {
+ "name": "referrals_referee_id_key",
+ "columns": [
+ {
+ "expression": "referee_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_referrals_referrer_id": {
+ "name": "idx_referrals_referrer_id",
+ "columns": [
+ {
+ "expression": "referrer_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_referrals_status": {
+ "name": "idx_referrals_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_referrals_referrer_status": {
+ "name": "idx_referrals_referrer_status",
+ "columns": [
+ {
+ "expression": "referrer_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "referrals_referrer_id_profiles_id_fk": {
+ "name": "referrals_referrer_id_profiles_id_fk",
+ "tableFrom": "referrals",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "referrer_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "referrals_referee_id_profiles_id_fk": {
+ "name": "referrals_referee_id_profiles_id_fk",
+ "tableFrom": "referrals",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "referee_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "referrals_qualifying_loan_id_loans_id_fk": {
+ "name": "referrals_qualifying_loan_id_loans_id_fk",
+ "tableFrom": "referrals",
+ "tableTo": "loans",
+ "columnsFrom": [
+ "qualifying_loan_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.reputation_events": {
+ "name": "reputation_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_key": {
+ "name": "source_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "points_delta": {
+ "name": "points_delta",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_rep_events_user_id_created_at": {
+ "name": "idx_rep_events_user_id_created_at",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_rep_events_source": {
+ "name": "idx_rep_events_source",
+ "columns": [
+ {
+ "expression": "source_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_rep_events_source_key": {
+ "name": "idx_rep_events_source_key",
+ "columns": [
+ {
+ "expression": "source_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "reputation_events_user_id_profiles_id_fk": {
+ "name": "reputation_events_user_id_profiles_id_fk",
+ "tableFrom": "reputation_events",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.reputation_snapshots": {
+ "name": "reputation_snapshots",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "score_total": {
+ "name": "score_total",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "repayment_score": {
+ "name": "repayment_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "lending_score": {
+ "name": "lending_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "consistency_score": {
+ "name": "consistency_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "external_score": {
+ "name": "external_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "reputation_level": {
+ "name": "reputation_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'bronze'"
+ },
+ "score_breakdown": {
+ "name": "score_breakdown",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "calculated_at": {
+ "name": "calculated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "reputation_snapshots_user_id_profiles_id_fk": {
+ "name": "reputation_snapshots_user_id_profiles_id_fk",
+ "tableFrom": "reputation_snapshots",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.risk_assessments": {
+ "name": "risk_assessments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "score": {
+ "name": "score",
+ "type": "numeric(5, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decision": {
+ "name": "decision",
+ "type": "risk_decision",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reasons": {
+ "name": "reasons",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "assessed_at": {
+ "name": "assessed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_risk_assessments_user_id_assessed_at": {
+ "name": "idx_risk_assessments_user_id_assessed_at",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "assessed_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "risk_assessments_user_id_profiles_id_fk": {
+ "name": "risk_assessments_user_id_profiles_id_fk",
+ "tableFrom": "risk_assessments",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tasks": {
+ "name": "tasks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "creator_id": {
+ "name": "creator_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_to": {
+ "name": "assigned_to",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "category": {
+ "name": "category",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reward_xlm": {
+ "name": "reward_xlm",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "difficulty": {
+ "name": "difficulty",
+ "type": "task_difficulty",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'easy'"
+ },
+ "status": {
+ "name": "status",
+ "type": "task_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "completion_deadline": {
+ "name": "completion_deadline",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completion_date": {
+ "name": "completion_date",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "proof_submission": {
+ "name": "proof_submission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "creator_rating": {
+ "name": "creator_rating",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_tasks_creator_id": {
+ "name": "idx_tasks_creator_id",
+ "columns": [
+ {
+ "expression": "creator_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_tasks_assigned_to": {
+ "name": "idx_tasks_assigned_to",
+ "columns": [
+ {
+ "expression": "assigned_to",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_tasks_status": {
+ "name": "idx_tasks_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_tasks_created_at": {
+ "name": "idx_tasks_created_at",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "tasks_creator_id_profiles_id_fk": {
+ "name": "tasks_creator_id_profiles_id_fk",
+ "tableFrom": "tasks",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "creator_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "tasks_assigned_to_profiles_id_fk": {
+ "name": "tasks_assigned_to_profiles_id_fk",
+ "tableFrom": "tasks",
+ "tableTo": "profiles",
+ "columnsFrom": [
+ "assigned_to"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "wallet_address": {
+ "name": "wallet_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "app_role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'borrower'"
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_sign_in_at": {
+ "name": "last_sign_in_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "users_wallet_address_key": {
+ "name": "users_wallet_address_key",
+ "columns": [
+ {
+ "expression": "wallet_address",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.webhook_endpoints": {
+ "name": "webhook_endpoints",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "topic": {
+ "name": "topic",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_webhook_endpoints_platform": {
+ "name": "idx_webhook_endpoints_platform",
+ "columns": [
+ {
+ "expression": "platform",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_webhook_endpoints_topic": {
+ "name": "idx_webhook_endpoints_topic",
+ "columns": [
+ {
+ "expression": "topic",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "webhook_endpoints_created_by_users_id_fk": {
+ "name": "webhook_endpoints_created_by_users_id_fk",
+ "tableFrom": "webhook_endpoints",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.app_role": {
+ "name": "app_role",
+ "schema": "public",
+ "values": [
+ "borrower",
+ "lender",
+ "admin"
+ ]
+ },
+ "public.kyc_status": {
+ "name": "kyc_status",
+ "schema": "public",
+ "values": [
+ "pending",
+ "submitted",
+ "verified",
+ "rejected"
+ ]
+ },
+ "public.loan_status": {
+ "name": "loan_status",
+ "schema": "public",
+ "values": [
+ "requested",
+ "approved",
+ "funded",
+ "active",
+ "repaid",
+ "defaulted",
+ "cancelled"
+ ]
+ },
+ "public.pool_status": {
+ "name": "pool_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "paused",
+ "closed"
+ ]
+ },
+ "public.position_status": {
+ "name": "position_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "closed"
+ ]
+ },
+ "public.referral_status": {
+ "name": "referral_status",
+ "schema": "public",
+ "values": [
+ "pending",
+ "qualified",
+ "paid",
+ "rejected"
+ ]
+ },
+ "public.risk_decision": {
+ "name": "risk_decision",
+ "schema": "public",
+ "values": [
+ "allow",
+ "manual_review",
+ "reject"
+ ]
+ },
+ "public.risk_status": {
+ "name": "risk_status",
+ "schema": "public",
+ "values": [
+ "low",
+ "medium",
+ "high",
+ "blocked"
+ ]
+ },
+ "public.task_difficulty": {
+ "name": "task_difficulty",
+ "schema": "public",
+ "values": [
+ "easy",
+ "medium",
+ "hard"
+ ]
+ },
+ "public.task_status": {
+ "name": "task_status",
+ "schema": "public",
+ "values": [
+ "open",
+ "assigned",
+ "completed",
+ "verified",
+ "cancelled"
+ ]
+ },
+ "public.tx_status": {
+ "name": "tx_status",
+ "schema": "public",
+ "values": [
+ "pending",
+ "confirmed",
+ "failed",
+ "cancelled"
+ ]
+ },
+ "public.verification_status": {
+ "name": "verification_status",
+ "schema": "public",
+ "values": [
+ "pending",
+ "verified",
+ "rejected",
+ "expired"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json
new file mode 100644
index 0000000..0a238e4
--- /dev/null
+++ b/drizzle/meta/0001_snapshot.json
@@ -0,0 +1,2822 @@
+{
+ "id": "0df67a3b-d5f4-42d0-9568-0f32a4f5e11a",
+ "prevId": "7651aee8-f11d-4065-a209-a2f45ddb9496",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.chain_events": {
+ "name": "chain_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "tx_hash": {
+ "name": "tx_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "contract_id": {
+ "name": "contract_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_type": {
+ "name": "event_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "happened_at": {
+ "name": "happened_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "chain_events_tx_hash_event_type_key": {
+ "name": "chain_events_tx_hash_event_type_key",
+ "columns": [
+ {
+ "expression": "tx_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "event_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_chain_events_contract_id": {
+ "name": "idx_chain_events_contract_id",
+ "columns": [
+ {
+ "expression": "contract_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_chain_events_happened_at": {
+ "name": "idx_chain_events_happened_at",
+ "columns": [
+ {
+ "expression": "happened_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.external_verifications": {
+ "name": "external_verifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider": {
+ "name": "provider",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "verification_type": {
+ "name": "verification_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "verification_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "verified_at": {
+ "name": "verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payload_meta": {
+ "name": "payload_meta",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_external_verifications_user_id": {
+ "name": "idx_external_verifications_user_id",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_external_verifications_status": {
+ "name": "idx_external_verifications_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "external_verifications_user_id_profiles_id_fk": {
+ "name": "external_verifications_user_id_profiles_id_fk",
+ "tableFrom": "external_verifications",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.fraud_signals": {
+ "name": "fraud_signals",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "signal_type": {
+ "name": "signal_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "severity": {
+ "name": "severity",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payload": {
+ "name": "payload",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "resolved": {
+ "name": "resolved",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "resolved_at": {
+ "name": "resolved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "idx_fraud_signals_user_id_created_at": {
+ "name": "idx_fraud_signals_user_id_created_at",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_fraud_signals_resolved": {
+ "name": "idx_fraud_signals_resolved",
+ "columns": [
+ {
+ "expression": "resolved",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "fraud_signals_user_id_profiles_id_fk": {
+ "name": "fraud_signals_user_id_profiles_id_fk",
+ "tableFrom": "fraud_signals",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.ledger_transactions": {
+ "name": "ledger_transactions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "category": {
+ "name": "category",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount": {
+ "name": "amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "currency": {
+ "name": "currency",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'XLM'"
+ },
+ "status": {
+ "name": "status",
+ "type": "tx_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "ref_type": {
+ "name": "ref_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ref_id": {
+ "name": "ref_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_ledger_transactions_user_id_created_at": {
+ "name": "idx_ledger_transactions_user_id_created_at",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_ledger_transactions_status": {
+ "name": "idx_ledger_transactions_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "ledger_transactions_user_id_profiles_id_fk": {
+ "name": "ledger_transactions_user_id_profiles_id_fk",
+ "tableFrom": "ledger_transactions",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.lending_pools": {
+ "name": "lending_pools",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "pool_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "currency": {
+ "name": "currency",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'XLM'"
+ },
+ "apr_bps": {
+ "name": "apr_bps",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "total_liquidity": {
+ "name": "total_liquidity",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "available_liquidity": {
+ "name": "available_liquidity",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "total_borrowed": {
+ "name": "total_borrowed",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "borrow_cap": {
+ "name": "borrow_cap",
+ "type": "numeric(20, 7)",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_lending_pools_status": {
+ "name": "idx_lending_pools_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_lending_pools_status_available": {
+ "name": "idx_lending_pools_status_available",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "available_liquidity",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_lending_pools_created_at_desc": {
+ "name": "idx_lending_pools_created_at_desc",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "lending_pools_created_by_profiles_id_fk": {
+ "name": "lending_pools_created_by_profiles_id_fk",
+ "tableFrom": "lending_pools",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "set null"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.loan_fundings": {
+ "name": "loan_fundings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "loan_id": {
+ "name": "loan_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lender_id": {
+ "name": "lender_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount": {
+ "name": "amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tx_hash": {
+ "name": "tx_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lender_address": {
+ "name": "lender_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "funded_at": {
+ "name": "funded_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_loan_fundings_loan_id": {
+ "name": "idx_loan_fundings_loan_id",
+ "columns": [
+ {
+ "expression": "loan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_loan_fundings_lender_id": {
+ "name": "idx_loan_fundings_lender_id",
+ "columns": [
+ {
+ "expression": "lender_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_loan_fundings_lender_loan": {
+ "name": "idx_loan_fundings_lender_loan",
+ "columns": [
+ {
+ "expression": "lender_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "loan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_loan_fundings_tx_hash": {
+ "name": "idx_loan_fundings_tx_hash",
+ "columns": [
+ {
+ "expression": "tx_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "loan_fundings_loan_id_loans_id_fk": {
+ "name": "loan_fundings_loan_id_loans_id_fk",
+ "tableFrom": "loan_fundings",
+ "columnsFrom": [
+ "loan_id"
+ ],
+ "tableTo": "loans",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ },
+ "loan_fundings_lender_id_profiles_id_fk": {
+ "name": "loan_fundings_lender_id_profiles_id_fk",
+ "tableFrom": "loan_fundings",
+ "columnsFrom": [
+ "lender_id"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "restrict"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.loan_repayments": {
+ "name": "loan_repayments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "loan_id": {
+ "name": "loan_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "payer_id": {
+ "name": "payer_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount": {
+ "name": "amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "paid_at": {
+ "name": "paid_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "tx_ref": {
+ "name": "tx_ref",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_loan_repayments_loan_id": {
+ "name": "idx_loan_repayments_loan_id",
+ "columns": [
+ {
+ "expression": "loan_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_loan_repayments_payer_id": {
+ "name": "idx_loan_repayments_payer_id",
+ "columns": [
+ {
+ "expression": "payer_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "loan_repayments_loan_id_loans_id_fk": {
+ "name": "loan_repayments_loan_id_loans_id_fk",
+ "tableFrom": "loan_repayments",
+ "columnsFrom": [
+ "loan_id"
+ ],
+ "tableTo": "loans",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ },
+ "loan_repayments_payer_id_profiles_id_fk": {
+ "name": "loan_repayments_payer_id_profiles_id_fk",
+ "tableFrom": "loan_repayments",
+ "columnsFrom": [
+ "payer_id"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "restrict"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.loans": {
+ "name": "loans",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "borrower_id": {
+ "name": "borrower_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "pool_id": {
+ "name": "pool_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "loan_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'requested'"
+ },
+ "principal_amount": {
+ "name": "principal_amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "apr_bps": {
+ "name": "apr_bps",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "duration_days": {
+ "name": "duration_days",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rate_model": {
+ "name": "rate_model",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'fixed'"
+ },
+ "rate_switch_count": {
+ "name": "rate_switch_count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "last_rate_switch_at": {
+ "name": "last_rate_switch_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "funded_amount": {
+ "name": "funded_amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "repaid_amount": {
+ "name": "repaid_amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "requested_at": {
+ "name": "requested_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "approved_at": {
+ "name": "approved_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "funded_at": {
+ "name": "funded_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "due_at": {
+ "name": "due_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "closed_at": {
+ "name": "closed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "defaulted_at": {
+ "name": "defaulted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_loans_borrower_id": {
+ "name": "idx_loans_borrower_id",
+ "columns": [
+ {
+ "expression": "borrower_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_loans_pool_id": {
+ "name": "idx_loans_pool_id",
+ "columns": [
+ {
+ "expression": "pool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_loans_status": {
+ "name": "idx_loans_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_loans_due_at": {
+ "name": "idx_loans_due_at",
+ "columns": [
+ {
+ "expression": "due_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_loans_borrower_status": {
+ "name": "idx_loans_borrower_status",
+ "columns": [
+ {
+ "expression": "borrower_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_loans_rate_model": {
+ "name": "idx_loans_rate_model",
+ "columns": [
+ {
+ "expression": "rate_model",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_loans_status_funded": {
+ "name": "idx_loans_status_funded",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "funded_amount",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "loans_borrower_id_profiles_id_fk": {
+ "name": "loans_borrower_id_profiles_id_fk",
+ "tableFrom": "loans",
+ "columnsFrom": [
+ "borrower_id"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ },
+ "loans_pool_id_lending_pools_id_fk": {
+ "name": "loans_pool_id_lending_pools_id_fk",
+ "tableFrom": "loans",
+ "columnsFrom": [
+ "pool_id"
+ ],
+ "tableTo": "lending_pools",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "restrict"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.notifications": {
+ "name": "notifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "read": {
+ "name": "read",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_notifications_user_id_created_at": {
+ "name": "idx_notifications_user_id_created_at",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "notifications_user_id_users_id_fk": {
+ "name": "notifications_user_id_users_id_fk",
+ "tableFrom": "notifications",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "tableTo": "users",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.pool_positions": {
+ "name": "pool_positions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "pool_id": {
+ "name": "pool_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "lender_id": {
+ "name": "lender_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "position_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'active'"
+ },
+ "principal_amount": {
+ "name": "principal_amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "earned_interest": {
+ "name": "earned_interest",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "withdrawn_amount": {
+ "name": "withdrawn_amount",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "opened_at": {
+ "name": "opened_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "closed_at": {
+ "name": "closed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_pool_positions_lender_id": {
+ "name": "idx_pool_positions_lender_id",
+ "columns": [
+ {
+ "expression": "lender_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_pool_positions_pool_id": {
+ "name": "idx_pool_positions_pool_id",
+ "columns": [
+ {
+ "expression": "pool_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "pool_positions_pool_id_lending_pools_id_fk": {
+ "name": "pool_positions_pool_id_lending_pools_id_fk",
+ "tableFrom": "pool_positions",
+ "columnsFrom": [
+ "pool_id"
+ ],
+ "tableTo": "lending_pools",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ },
+ "pool_positions_lender_id_profiles_id_fk": {
+ "name": "pool_positions_lender_id_profiles_id_fk",
+ "tableFrom": "pool_positions",
+ "columnsFrom": [
+ "lender_id"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.profiles": {
+ "name": "profiles",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "full_name": {
+ "name": "full_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "role": {
+ "name": "role",
+ "type": "app_role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'borrower'"
+ },
+ "wallet_address": {
+ "name": "wallet_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "country_code": {
+ "name": "country_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "date_of_birth": {
+ "name": "date_of_birth",
+ "type": "date",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kyc_status": {
+ "name": "kyc_status",
+ "type": "kyc_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "risk_status": {
+ "name": "risk_status",
+ "type": "risk_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'medium'"
+ },
+ "government_id_ipfs_hash": {
+ "name": "government_id_ipfs_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "government_id_url": {
+ "name": "government_id_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kyc_submitted_at": {
+ "name": "kyc_submitted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kyc_verified_at": {
+ "name": "kyc_verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kyc_rejection_reason": {
+ "name": "kyc_rejection_reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kyc_provider_id": {
+ "name": "kyc_provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "kyc_provider_status": {
+ "name": "kyc_provider_status",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "regulated_pool_access": {
+ "name": "regulated_pool_access",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "referral_code": {
+ "name": "referral_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_profiles_role": {
+ "name": "idx_profiles_role",
+ "columns": [
+ {
+ "expression": "role",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_profiles_wallet_address": {
+ "name": "idx_profiles_wallet_address",
+ "columns": [
+ {
+ "expression": "wallet_address",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_profiles_kyc_status": {
+ "name": "idx_profiles_kyc_status",
+ "columns": [
+ {
+ "expression": "kyc_status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_profiles_risk_status": {
+ "name": "idx_profiles_risk_status",
+ "columns": [
+ {
+ "expression": "risk_status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_profiles_kyc_submitted_at": {
+ "name": "idx_profiles_kyc_submitted_at",
+ "columns": [
+ {
+ "expression": "kyc_submitted_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "profiles_referral_code_key": {
+ "name": "profiles_referral_code_key",
+ "columns": [
+ {
+ "expression": "referral_code",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_profiles_kyc_provider_id": {
+ "name": "idx_profiles_kyc_provider_id",
+ "columns": [
+ {
+ "expression": "kyc_provider_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "with": {},
+ "method": "btree",
+ "where": "\"profiles\".\"kyc_provider_id\" is not null",
+ "concurrently": false
+ },
+ "idx_profiles_regulated_pool_access": {
+ "name": "idx_profiles_regulated_pool_access",
+ "columns": [
+ {
+ "expression": "regulated_pool_access",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "profiles_id_users_id_fk": {
+ "name": "profiles_id_users_id_fk",
+ "tableFrom": "profiles",
+ "columnsFrom": [
+ "id"
+ ],
+ "tableTo": "users",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.referrals": {
+ "name": "referrals",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "referrer_id": {
+ "name": "referrer_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "referee_id": {
+ "name": "referee_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "referral_code": {
+ "name": "referral_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "referral_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "qualifying_loan_id": {
+ "name": "qualifying_loan_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "bonus_amount": {
+ "name": "bonus_amount",
+ "type": "numeric(20, 7)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "payout_tx_hash": {
+ "name": "payout_tx_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "qualified_at": {
+ "name": "qualified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "paid_at": {
+ "name": "paid_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "referrals_referee_id_key": {
+ "name": "referrals_referee_id_key",
+ "columns": [
+ {
+ "expression": "referee_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_referrals_referrer_id": {
+ "name": "idx_referrals_referrer_id",
+ "columns": [
+ {
+ "expression": "referrer_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_referrals_status": {
+ "name": "idx_referrals_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_referrals_referrer_status": {
+ "name": "idx_referrals_referrer_status",
+ "columns": [
+ {
+ "expression": "referrer_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "referrals_referrer_id_profiles_id_fk": {
+ "name": "referrals_referrer_id_profiles_id_fk",
+ "tableFrom": "referrals",
+ "columnsFrom": [
+ "referrer_id"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ },
+ "referrals_referee_id_profiles_id_fk": {
+ "name": "referrals_referee_id_profiles_id_fk",
+ "tableFrom": "referrals",
+ "columnsFrom": [
+ "referee_id"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ },
+ "referrals_qualifying_loan_id_loans_id_fk": {
+ "name": "referrals_qualifying_loan_id_loans_id_fk",
+ "tableFrom": "referrals",
+ "columnsFrom": [
+ "qualifying_loan_id"
+ ],
+ "tableTo": "loans",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "set null"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.reputation_events": {
+ "name": "reputation_events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_type": {
+ "name": "source_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "source_id": {
+ "name": "source_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source_key": {
+ "name": "source_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "points_delta": {
+ "name": "points_delta",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reason": {
+ "name": "reason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_rep_events_user_id_created_at": {
+ "name": "idx_rep_events_user_id_created_at",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_rep_events_source": {
+ "name": "idx_rep_events_source",
+ "columns": [
+ {
+ "expression": "source_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_rep_events_source_key": {
+ "name": "idx_rep_events_source_key",
+ "columns": [
+ {
+ "expression": "source_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "source_key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "reputation_events_user_id_profiles_id_fk": {
+ "name": "reputation_events_user_id_profiles_id_fk",
+ "tableFrom": "reputation_events",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.reputation_snapshots": {
+ "name": "reputation_snapshots",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "score_total": {
+ "name": "score_total",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "repayment_score": {
+ "name": "repayment_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "lending_score": {
+ "name": "lending_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "consistency_score": {
+ "name": "consistency_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "external_score": {
+ "name": "external_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "reputation_level": {
+ "name": "reputation_level",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'bronze'"
+ },
+ "score_breakdown": {
+ "name": "score_breakdown",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "calculated_at": {
+ "name": "calculated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "reputation_snapshots_user_id_profiles_id_fk": {
+ "name": "reputation_snapshots_user_id_profiles_id_fk",
+ "tableFrom": "reputation_snapshots",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.risk_assessments": {
+ "name": "risk_assessments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "score": {
+ "name": "score",
+ "type": "numeric(5, 2)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "decision": {
+ "name": "decision",
+ "type": "risk_decision",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reasons": {
+ "name": "reasons",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "assessed_at": {
+ "name": "assessed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_risk_assessments_user_id_assessed_at": {
+ "name": "idx_risk_assessments_user_id_assessed_at",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "assessed_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "risk_assessments_user_id_profiles_id_fk": {
+ "name": "risk_assessments_user_id_profiles_id_fk",
+ "tableFrom": "risk_assessments",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tasks": {
+ "name": "tasks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "creator_id": {
+ "name": "creator_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_to": {
+ "name": "assigned_to",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "category": {
+ "name": "category",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reward_xlm": {
+ "name": "reward_xlm",
+ "type": "numeric(20, 6)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'0'"
+ },
+ "difficulty": {
+ "name": "difficulty",
+ "type": "task_difficulty",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'easy'"
+ },
+ "status": {
+ "name": "status",
+ "type": "task_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'open'"
+ },
+ "completion_deadline": {
+ "name": "completion_deadline",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "completion_date": {
+ "name": "completion_date",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "proof_submission": {
+ "name": "proof_submission",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "creator_rating": {
+ "name": "creator_rating",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_tasks_creator_id": {
+ "name": "idx_tasks_creator_id",
+ "columns": [
+ {
+ "expression": "creator_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_tasks_assigned_to": {
+ "name": "idx_tasks_assigned_to",
+ "columns": [
+ {
+ "expression": "assigned_to",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_tasks_status": {
+ "name": "idx_tasks_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_tasks_created_at": {
+ "name": "idx_tasks_created_at",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "tasks_creator_id_profiles_id_fk": {
+ "name": "tasks_creator_id_profiles_id_fk",
+ "tableFrom": "tasks",
+ "columnsFrom": [
+ "creator_id"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "cascade"
+ },
+ "tasks_assigned_to_profiles_id_fk": {
+ "name": "tasks_assigned_to_profiles_id_fk",
+ "tableFrom": "tasks",
+ "columnsFrom": [
+ "assigned_to"
+ ],
+ "tableTo": "profiles",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "set null"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "wallet_address": {
+ "name": "wallet_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "app_role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'borrower'"
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "last_sign_in_at": {
+ "name": "last_sign_in_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "users_wallet_address_key": {
+ "name": "users_wallet_address_key",
+ "columns": [
+ {
+ "expression": "wallet_address",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.webhook_endpoints": {
+ "name": "webhook_endpoints",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "platform": {
+ "name": "platform",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "topic": {
+ "name": "topic",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "idx_webhook_endpoints_platform": {
+ "name": "idx_webhook_endpoints_platform",
+ "columns": [
+ {
+ "expression": "platform",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ },
+ "idx_webhook_endpoints_topic": {
+ "name": "idx_webhook_endpoints_topic",
+ "columns": [
+ {
+ "expression": "topic",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "with": {},
+ "method": "btree",
+ "concurrently": false
+ }
+ },
+ "foreignKeys": {
+ "webhook_endpoints_created_by_users_id_fk": {
+ "name": "webhook_endpoints_created_by_users_id_fk",
+ "tableFrom": "webhook_endpoints",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "tableTo": "users",
+ "columnsTo": [
+ "id"
+ ],
+ "onUpdate": "no action",
+ "onDelete": "set null"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.app_role": {
+ "name": "app_role",
+ "schema": "public",
+ "values": [
+ "borrower",
+ "lender",
+ "admin"
+ ]
+ },
+ "public.kyc_status": {
+ "name": "kyc_status",
+ "schema": "public",
+ "values": [
+ "pending",
+ "submitted",
+ "verified",
+ "rejected"
+ ]
+ },
+ "public.loan_status": {
+ "name": "loan_status",
+ "schema": "public",
+ "values": [
+ "requested",
+ "approved",
+ "funded",
+ "active",
+ "repaid",
+ "defaulted",
+ "cancelled"
+ ]
+ },
+ "public.pool_status": {
+ "name": "pool_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "paused",
+ "closed"
+ ]
+ },
+ "public.position_status": {
+ "name": "position_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "closed"
+ ]
+ },
+ "public.referral_status": {
+ "name": "referral_status",
+ "schema": "public",
+ "values": [
+ "pending",
+ "qualified",
+ "paid",
+ "rejected"
+ ]
+ },
+ "public.risk_decision": {
+ "name": "risk_decision",
+ "schema": "public",
+ "values": [
+ "allow",
+ "manual_review",
+ "reject"
+ ]
+ },
+ "public.risk_status": {
+ "name": "risk_status",
+ "schema": "public",
+ "values": [
+ "low",
+ "medium",
+ "high",
+ "blocked"
+ ]
+ },
+ "public.task_difficulty": {
+ "name": "task_difficulty",
+ "schema": "public",
+ "values": [
+ "easy",
+ "medium",
+ "hard"
+ ]
+ },
+ "public.task_status": {
+ "name": "task_status",
+ "schema": "public",
+ "values": [
+ "open",
+ "assigned",
+ "completed",
+ "verified",
+ "cancelled"
+ ]
+ },
+ "public.tx_status": {
+ "name": "tx_status",
+ "schema": "public",
+ "values": [
+ "pending",
+ "confirmed",
+ "failed",
+ "cancelled"
+ ]
+ },
+ "public.verification_status": {
+ "name": "verification_status",
+ "schema": "public",
+ "values": [
+ "pending",
+ "verified",
+ "rejected",
+ "expired"
+ ]
+ }
+ },
+ "schemas": {},
+ "views": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
new file mode 100644
index 0000000..799bf34
--- /dev/null
+++ b/drizzle/meta/_journal.json
@@ -0,0 +1,20 @@
+{
+ "version": "7",
+ "dialect": "postgresql",
+ "entries": [
+ {
+ "idx": 0,
+ "version": "7",
+ "when": 1789217087472,
+ "tag": "0000_init",
+ "breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "7",
+ "when": 1789217089648,
+ "tag": "0001_functions_and_triggers",
+ "breakpoints": true
+ }
+ ]
+}
\ No newline at end of file
diff --git a/lib/analytics.ts b/lib/analytics.ts
index 099c1c5..30d5761 100644
--- a/lib/analytics.ts
+++ b/lib/analytics.ts
@@ -1,4 +1,5 @@
-import type { SupabaseClient } from "@supabase/supabase-js";
+import type { AnyDb } from "@/lib/db/pools";
+import { ledgerTransactions, loanRepayments, loans, poolPositions } from "@/lib/db/schema";
const DAY_MS = 24 * 60 * 60 * 1_000;
export const ANALYTICS_CACHE_TTL_SECONDS = 60 * 60;
@@ -70,13 +71,14 @@ export function aggregatePlatformAnalytics(
ledgerTransactions?: LedgerTransactionRow[] | null;
},
activeWindowMs: number = ANALYTICS_ACTIVE_WINDOW_MS,
+ now: number = Date.now(),
): PlatformAnalyticsMetrics {
const loans = normalizeRows(rows.loans);
const poolPositions = normalizeRows(rows.poolPositions);
const loanRepayments = normalizeRows(rows.loanRepayments);
const ledgerTransactions = normalizeRows(rows.ledgerTransactions);
- const activeWindowStart = Date.now() - activeWindowMs;
+ const activeWindowStart = now - activeWindowMs;
const tvlFromLoans = loans
.filter((loan) => isActiveLoanStatus(loan.status))
@@ -110,31 +112,32 @@ export function aggregatePlatformAnalytics(
};
}
-export async function fetchPlatformAnalytics(
- supabase: SupabaseClient,
-): Promise {
- const [loansRes, poolPositionsRes, loanRepaymentsRes, ledgerTransactionsRes] = await Promise.all([
- supabase.from("loans").select("principal_amount,status"),
- supabase.from("pool_positions").select("principal_amount,earned_interest,status"),
- supabase.from("loan_repayments").select("amount"),
- supabase.from("ledger_transactions").select("amount,user_id,status,created_at"),
+export async function fetchPlatformAnalytics(db: AnyDb): Promise {
+ const [loanRows, positionRows, repaymentRows, ledgerRows] = await Promise.all([
+ db.select({ principal_amount: loans.principalAmount, status: loans.status }).from(loans),
+ db
+ .select({
+ principal_amount: poolPositions.principalAmount,
+ earned_interest: poolPositions.earnedInterest,
+ status: poolPositions.status,
+ })
+ .from(poolPositions),
+ db.select({ amount: loanRepayments.amount }).from(loanRepayments),
+ db
+ .select({
+ amount: ledgerTransactions.amount,
+ user_id: ledgerTransactions.userId,
+ status: ledgerTransactions.status,
+ created_at: ledgerTransactions.createdAt,
+ })
+ .from(ledgerTransactions),
]);
- const firstError =
- loansRes.error ??
- poolPositionsRes.error ??
- loanRepaymentsRes.error ??
- ledgerTransactionsRes.error;
-
- if (firstError) {
- throw new Error(firstError.message);
- }
-
return aggregatePlatformAnalytics({
- loans: loansRes.data,
- poolPositions: poolPositionsRes.data,
- loanRepayments: loanRepaymentsRes.data,
- ledgerTransactions: ledgerTransactionsRes.data,
+ loans: loanRows,
+ poolPositions: positionRows,
+ loanRepayments: repaymentRows,
+ ledgerTransactions: ledgerRows,
});
}
diff --git a/lib/auth/kyc.ts b/lib/auth/kyc.ts
deleted file mode 100644
index d28c053..0000000
--- a/lib/auth/kyc.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * KYC (Know Your Customer) verification helpers
- * Manages identity verification status and document storage
- */
-
-import { getServerSupabaseClient } from "@/lib/supabase/server";
-
-export type KYCStatus = "pending" | "submitted" | "verified" | "rejected";
-
-export interface KYCData {
- status: KYCStatus;
- government_id_ipfs_hash?: string;
- government_id_url?: string;
- submitted_at?: string;
- verified_at?: string;
- rejection_reason?: string;
-}
-
-/**
- * Store KYC document hash after IPFS upload
- */
-export async function storeKYCDocument(
- userId: string,
- ipfsHash: string,
- ipfsUrl: string
-): Promise {
- const supabase = await getServerSupabaseClient();
- if (!supabase) throw new Error("Supabase not available");
-
- const { error } = await supabase
- .from("profiles")
- .update({
- government_id_ipfs_hash: ipfsHash,
- government_id_url: ipfsUrl,
- kyc_status: "submitted",
- kyc_submitted_at: new Date().toISOString(),
- })
- .eq("id", userId);
-
- if (error) throw error;
-}
-
-/**
- * Get KYC data for a user (admin only)
- */
-export async function getKYCData(userId: string): Promise {
- const supabase = await getServerSupabaseClient();
- if (!supabase) throw new Error("Supabase not available");
-
- const { data, error } = await supabase
- .from("profiles")
- .select(
- "kyc_status, government_id_ipfs_hash, government_id_url, kyc_submitted_at, kyc_verified_at, kyc_rejection_reason"
- )
- .eq("id", userId)
- .maybeSingle();
-
- if (error) throw error;
-
- return data
- ? {
- status: data.kyc_status as KYCStatus,
- government_id_ipfs_hash: data.government_id_ipfs_hash,
- government_id_url: data.government_id_url,
- submitted_at: data.kyc_submitted_at,
- verified_at: data.kyc_verified_at,
- rejection_reason: data.kyc_rejection_reason,
- }
- : null;
-}
-
-/**
- * Check if user is verified (admin checker)
- */
-export async function isUserVerified(userId: string): Promise {
- const kycData = await getKYCData(userId);
- return kycData?.status === "verified";
-}
diff --git a/lib/auth/session-token.ts b/lib/auth/session-token.ts
new file mode 100644
index 0000000..563d19c
--- /dev/null
+++ b/lib/auth/session-token.ts
@@ -0,0 +1,80 @@
+/**
+ * lib/auth/session-token.ts
+ *
+ * Stateless session tokens. A signed JWT (HS256, `jose`) is stored in an
+ * HttpOnly cookie after a successful SEP-10 sign-in. It carries only what the
+ * edge proxy needs to route requests (user id, wallet, role); everything else
+ * is looked up in the database by `lib/auth/session.ts`.
+ *
+ * This module is Edge-safe (no Node-only APIs) so proxy.ts can verify tokens
+ * without a database round-trip.
+ */
+
+import { jwtVerify, SignJWT } from "jose";
+import type { UserRole } from "@/lib/auth/roles";
+
+export const SESSION_COOKIE_NAME = "tl_session";
+
+/** Session lifetime in seconds (7 days). */
+export const SESSION_TTL_SECONDS = 7 * 24 * 60 * 60;
+
+export interface SessionClaims {
+ /** users.id */
+ sub: string;
+ /** Stellar public key (G...) */
+ wallet: string;
+ role: UserRole;
+}
+
+function secretKey(): Uint8Array {
+ const secret = process.env.SESSION_SECRET;
+ if (!secret || secret.length < 32) {
+ throw new Error(
+ "SESSION_SECRET is missing or shorter than 32 characters. Generate one with `openssl rand -base64 48`.",
+ );
+ }
+ return new TextEncoder().encode(secret);
+}
+
+/** True when a session secret is configured. */
+export function isSessionSigningConfigured(): boolean {
+ const secret = process.env.SESSION_SECRET;
+ return typeof secret === "string" && secret.length >= 32;
+}
+
+export async function signSessionToken(claims: SessionClaims): Promise {
+ return new SignJWT({ wallet: claims.wallet, role: claims.role })
+ .setProtectedHeader({ alg: "HS256", typ: "JWT" })
+ .setSubject(claims.sub)
+ .setIssuedAt()
+ .setExpirationTime(`${SESSION_TTL_SECONDS}s`)
+ .sign(secretKey());
+}
+
+/** Verify a token; returns null for anything invalid or expired. */
+export async function verifySessionToken(token: string | undefined | null): Promise {
+ if (!token) return null;
+ if (!isSessionSigningConfigured()) return null;
+ try {
+ const { payload } = await jwtVerify(token, secretKey(), { algorithms: ["HS256"] });
+ const sub = payload.sub;
+ const wallet = payload.wallet;
+ const role = payload.role;
+ if (typeof sub !== "string" || typeof wallet !== "string") return null;
+ const safeRole: UserRole = role === "lender" || role === "admin" ? role : "borrower";
+ return { sub, wallet, role: safeRole };
+ } catch {
+ return null;
+ }
+}
+
+/** Cookie attributes shared by set and clear so the browser matches them. */
+export function sessionCookieOptions() {
+ return {
+ httpOnly: true,
+ sameSite: "lax" as const,
+ secure: process.env.NODE_ENV === "production",
+ path: "/",
+ maxAge: SESSION_TTL_SECONDS,
+ };
+}
diff --git a/lib/auth/session.ts b/lib/auth/session.ts
index b152e7e..4b9216c 100644
--- a/lib/auth/session.ts
+++ b/lib/auth/session.ts
@@ -1,41 +1,155 @@
+/**
+ * lib/auth/session.ts
+ *
+ * Server-side session helpers for pages, server actions and API routes.
+ *
+ * getSessionUser() → SessionUser | null
+ * requireAuthenticatedUser() → SessionUser (redirects to /auth otherwise)
+ * requireApiUser() → SessionUser (throws UnauthorizedError — for JSON routes)
+ * requireTradeVaultAdmin() → SessionUser (allowlisted e-mail/wallet AND profiles.role = admin)
+ *
+ * The session cookie holds a signed JWT (see session-token.ts). We still read
+ * the user row on every call so a deleted or re-roled user is reflected
+ * immediately rather than at token expiry.
+ */
+
+import { cookies, headers } from "next/headers";
import { redirect } from "next/navigation";
-import {
- getDashboardPath,
- normalizeUserRole,
- type UserRole,
-} from "@/lib/auth/roles";
-import { getServerSupabaseClient } from "@/lib/supabase/server";
-
-export async function requireAuthenticatedUser(expectedRole?: UserRole) {
- const supabase = await getServerSupabaseClient();
+import { eq } from "drizzle-orm";
+import { getDb } from "@/lib/db/client";
+import { profiles, users } from "@/lib/db/schema";
+import { getDashboardPath, normalizeUserRole, type UserRole } from "@/lib/auth/roles";
+import { SESSION_COOKIE_NAME, verifySessionToken } from "@/lib/auth/session-token";
+
+/** The authenticated identity as seen by the application layer. */
+export interface SessionUser {
+ id: string;
+ walletAddress: string;
+ role: UserRole;
+ /** Display name from the profile ("" when the user never set one). */
+ fullName: string;
+ /** Optional contact e-mail (SIWS users have none unless they add one). */
+ email: string | null;
+ createdAt: string;
+ lastSignInAt: string | null;
+}
- if (!supabase) {
- redirect("/auth");
+export class UnauthorizedError extends Error {
+ constructor(message = "Not authenticated") {
+ super(message);
+ this.name = "UnauthorizedError";
}
+}
+
+const DEV_BYPASS_ENABLED =
+ process.env.NODE_ENV !== "production" && process.env.ENABLE_DEV_AUTH_BYPASS === "true";
+
+function isValidUuid(value: string): boolean {
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
+}
- const {
- data: { user },
- } = await supabase.auth.getUser();
+/**
+ * Local-dev / e2e escape hatch: `x-dev-user-id` + `x-dev-role` headers act as
+ * the session. Only honoured outside production with ENABLE_DEV_AUTH_BYPASS.
+ */
+async function devBypassUser(): Promise {
+ if (!DEV_BYPASS_ENABLED) return null;
+ const h = await headers();
+ const id = h.get("x-dev-user-id")?.trim() ?? "";
+ if (!id || !isValidUuid(id)) return null;
+ const role = normalizeUserRole(h.get("x-dev-role")?.trim());
+ const db = getDb();
+ const row = db
+ ? await db
+ .select({ wallet: users.walletAddress, fullName: profiles.fullName, email: users.email })
+ .from(users)
+ .leftJoin(profiles, eq(profiles.id, users.id))
+ .where(eq(users.id, id))
+ .limit(1)
+ .then((r) => r[0])
+ : undefined;
+ return {
+ id,
+ walletAddress: row?.wallet ?? "",
+ role,
+ fullName: row?.fullName ?? "Dev User",
+ email: row?.email ?? null,
+ createdAt: new Date(0).toISOString(),
+ lastSignInAt: null,
+ };
+}
+
+/** Resolve the current user from the session cookie, or null. */
+export async function getSessionUser(): Promise {
+ const bypass = await devBypassUser();
+ if (bypass) return bypass;
+
+ const cookieStore = await cookies();
+ const claims = await verifySessionToken(cookieStore.get(SESSION_COOKIE_NAME)?.value);
+ if (!claims) return null;
+
+ const db = getDb();
+ if (!db) return null;
+
+ const [row] = await db
+ .select({
+ id: users.id,
+ walletAddress: users.walletAddress,
+ role: users.role,
+ email: users.email,
+ createdAt: users.createdAt,
+ lastSignInAt: users.lastSignInAt,
+ fullName: profiles.fullName,
+ })
+ .from(users)
+ .leftJoin(profiles, eq(profiles.id, users.id))
+ .where(eq(users.id, claims.sub))
+ .limit(1);
+
+ if (!row) return null;
+
+ return {
+ id: row.id,
+ walletAddress: row.walletAddress,
+ role: normalizeUserRole(row.role),
+ fullName: row.fullName ?? "",
+ email: row.email ?? null,
+ createdAt: row.createdAt.toISOString(),
+ lastSignInAt: row.lastSignInAt ? row.lastSignInAt.toISOString() : null,
+ };
+}
+/**
+ * For server components / actions: redirect to /auth when signed out, or to
+ * the user's own dashboard when they are the wrong role for this page.
+ */
+export async function requireAuthenticatedUser(expectedRole?: UserRole): Promise<{
+ user: SessionUser;
+ role: UserRole;
+}> {
+ const user = await getSessionUser();
if (!user) {
redirect("/auth");
}
-
- const role = normalizeUserRole(user.user_metadata?.account_type);
-
- if (expectedRole && role !== expectedRole) {
- redirect(getDashboardPath(role));
+ if (expectedRole && user.role !== expectedRole) {
+ redirect(getDashboardPath(user.role));
}
-
- return { user, role };
+ return { user, role: user.role };
}
-function parseAllowedAdminEmails(): Set {
- const value = process.env.TRADE_VAULT_ADMIN_EMAILS;
- if (!value) {
- return new Set();
+/** For API routes: throw instead of redirecting so the caller can return JSON. */
+export async function requireApiUser(expectedRole?: UserRole): Promise {
+ const user = await getSessionUser();
+ if (!user) throw new UnauthorizedError();
+ if (expectedRole && user.role !== expectedRole) {
+ throw new UnauthorizedError(`This action requires the ${expectedRole} role.`);
}
+ return user;
+}
+function parseAllowedAdmins(): Set {
+ const value = process.env.TRADE_VAULT_ADMIN_EMAILS;
+ if (!value) return new Set();
return new Set(
value
.split(",")
@@ -44,39 +158,42 @@ function parseAllowedAdminEmails(): Set {
);
}
-export function isTradeVaultAdminUser(user: {
- email?: string;
- app_metadata?: Record;
- user_metadata?: Record;
-}) {
- const allowedAdmins = parseAllowedAdminEmails();
+/**
+ * Admin allowlist check. TRADE_VAULT_ADMIN_EMAILS accepts e-mails and Stellar
+ * addresses (comma-separated) so wallet-only accounts can be admins too.
+ */
+export function isTradeVaultAdminUser(user: Pick): boolean {
+ const allowed = parseAllowedAdmins();
const email = user.email?.toLowerCase() ?? "";
-
- // Do not trust user/app metadata claims for admin access.
- // Only allowlisted email + DB role check in requireTradeVaultAdmin grants access.
- return allowedAdmins.has(email);
+ const wallet = user.walletAddress?.toLowerCase() ?? "";
+ return (email !== "" && allowed.has(email)) || (wallet !== "" && allowed.has(wallet));
}
-export async function requireTradeVaultAdmin() {
+/** Allowlisted AND profiles.role = 'admin' — both are required. */
+export async function requireTradeVaultAdmin(): Promise<{ user: SessionUser; role: UserRole }> {
const { user, role } = await requireAuthenticatedUser();
- const emailAllowlisted = isTradeVaultAdminUser(user);
-
- const supabase = await getServerSupabaseClient();
- if (!supabase) {
- redirect(getDashboardPath(normalizeUserRole(role)));
- }
- const { data: profile } = await supabase
- .from("profiles")
- .select("role")
- .eq("id", user.id)
- .maybeSingle();
+ const db = getDb();
+ const [profile] = db
+ ? await db.select({ role: profiles.role }).from(profiles).where(eq(profiles.id, user.id)).limit(1)
+ : [];
const dbAdmin = profile?.role === "admin";
-
- if (!emailAllowlisted || !dbAdmin) {
+ if (!isTradeVaultAdminUser(user) || !dbAdmin) {
redirect(getDashboardPath(normalizeUserRole(role)));
}
-
return { user, role };
-}
\ No newline at end of file
+}
+
+/** API-route flavour of requireTradeVaultAdmin (throws instead of redirecting). */
+export async function requireApiAdmin(): Promise {
+ const user = await requireApiUser();
+ const db = getDb();
+ const [profile] = db
+ ? await db.select({ role: profiles.role }).from(profiles).where(eq(profiles.id, user.id)).limit(1)
+ : [];
+ if (!isTradeVaultAdminUser(user) || profile?.role !== "admin") {
+ throw new UnauthorizedError("Admin access required.");
+ }
+ return user;
+}
diff --git a/lib/auth/siws-client.ts b/lib/auth/siws-client.ts
index 33a507c..03a62e6 100644
--- a/lib/auth/siws-client.ts
+++ b/lib/auth/siws-client.ts
@@ -4,11 +4,10 @@
* lib/auth/siws-client.ts
*
* Browser side of Sign-In with Stellar (SEP-0010):
- * connect wallet → fetch challenge → sign with Freighter/Albedo →
- * verify on the backend → adopt the returned Supabase session.
+ * connect wallet → fetch challenge → sign with the wallet →
+ * verify on the backend (which sets the HttpOnly session cookie).
*/
-import { getBrowserSupabaseClient } from "@/lib/supabase/client";
import {
getConnectedWallet,
signTransactionWithWallet,
@@ -45,11 +44,6 @@ export async function signInWithStellar(
preferredProvider?: StellarWalletProvider,
role?: UserRole
): Promise {
- const supabase = getBrowserSupabaseClient();
- if (!supabase) {
- throw new Error("Supabase is not configured in this environment.");
- }
-
// 1. Connect the wallet (Freighter by default).
const wallet = await getConnectedWallet(preferredProvider);
const address = wallet.address;
@@ -73,7 +67,7 @@ export async function signInWithStellar(
provider: wallet.provider,
});
- // 4. Verify + obtain a Supabase session.
+ // 4. Verify — on success the server sets the session cookie.
const verify = await postJson("/api/auth/siws/verify", {
address,
signedTxXdr: signed.signedTxXdr,
@@ -82,22 +76,17 @@ export async function signInWithStellar(
if (!verify.res.ok) {
throw new Error(mapVerifyError(verify.payload));
}
- const accessToken = verify.payload.access_token as string;
- const refreshToken = verify.payload.refresh_token as string;
- if (!accessToken || !refreshToken) {
- throw new Error("Sign-in succeeded but no session was returned.");
- }
- // 5. Adopt the session (persists cookies for SSR + client).
- const { data, error } = await supabase.auth.setSession({
- access_token: accessToken,
- refresh_token: refreshToken,
- });
- if (error) {
- throw new Error(`Could not establish session: ${error.message}`);
- }
- const normalizedRole = normalizeUserRole(data.user?.user_metadata?.account_type);
- return { address, role: normalizedRole, isNewUser: Boolean(verify.payload.isNewUser) };
+ return {
+ address,
+ role: normalizeUserRole(verify.payload.role),
+ isNewUser: Boolean(verify.payload.isNewUser),
+ };
+}
+
+/** Clear the session cookie. */
+export async function signOut(): Promise {
+ await fetch("/api/auth/signout", { method: "POST" });
}
/** Map backend SIWS error codes to friendly, actionable messages. */
@@ -117,7 +106,7 @@ function mapVerifyError(payload: Record): string {
return "The sign-in challenge was invalid. Please retry.";
case "not_configured":
case "session_failed":
- return "Stellar sign-in is temporarily unavailable. Please try another method.";
+ return "Stellar sign-in is temporarily unavailable. Please try again later.";
default:
return fallback;
}
diff --git a/lib/auth/siws-server.ts b/lib/auth/siws-server.ts
index c0e284e..4c57f4e 100644
--- a/lib/auth/siws-server.ts
+++ b/lib/auth/siws-server.ts
@@ -4,15 +4,17 @@
* Server-side Sign-In with Stellar (SEP-0010) logic:
* 1. buildChallenge() — generate a signed SEP-10 challenge transaction
* 2. verifyChallenge() — validate structure, expiry and the wallet signature
- * 3. issueSessionForWallet() — mint a Supabase session for the wallet identity
+ * 3. issueSessionForWallet() — upsert the users/profiles rows for the wallet
*
- * SERVER-ONLY. Reads SIWS_SERVER_SECRET / SIWS_PASSWORD_SECRET / service-role key.
+ * SERVER-ONLY. Reads SIWS_SERVER_SECRET and the database.
*/
-import { createHmac } from "node:crypto";
+import { eq } from "drizzle-orm";
import { Keypair, StrKey, Transaction, WebAuth } from "@stellar/stellar-sdk";
-import { createClient, type Session } from "@supabase/supabase-js";
import { SIWS_NETWORK_PASSPHRASE, getSiwsDomain } from "@/lib/auth/siws-config";
+import { normalizeUserRole, type UserRole } from "@/lib/auth/roles";
+import { getDb } from "@/lib/db/client";
+import { profiles, users } from "@/lib/db/schema";
/** SEP-10 challenge validity window (seconds). */
const CHALLENGE_TIMEOUT_SECS = 300;
@@ -166,89 +168,74 @@ export function verifyChallenge(signedTxXdr: string, expectedAddress: string): s
return expectedAddress;
}
-// ─── 3. Issue a Supabase session for the wallet identity ──────────────────────
+// ─── 3. Provision the wallet identity ─────────────────────────────────────────
-/** Deterministic e-mail identity for a wallet (never receives real mail). */
-export function walletEmail(address: string): string {
- return `${address.toLowerCase()}@siws.trustlend.app`;
+export interface WalletIdentity {
+ userId: string;
+ role: UserRole;
+ isNewUser: boolean;
}
/**
- * Server-derived, deterministic password for the wallet's Supabase user.
- * Never leaves the server — used only to mint a session after SEP-10 passes.
+ * Ensure a `users` + `profiles` row exists for `address` and return the
+ * identity the API route turns into a session cookie.
+ *
+ * Idempotent: signing in again just bumps `last_sign_in_at`. The role chosen on
+ * the auth page only applies to brand-new accounts — an existing account keeps
+ * whatever role it already has.
*/
-function walletPassword(address: string): string {
- const secret = process.env.SIWS_PASSWORD_SECRET;
- if (!secret) {
- throw new SiwsError(
- "not_configured",
- "SIWS is not configured on the server (SIWS_PASSWORD_SECRET missing).",
- 503
- );
+export async function issueSessionForWallet(address: string, role?: string): Promise {
+ const db = getDb();
+ if (!db) {
+ throw new SiwsError("session_failed", "Database is not configured.", 503);
}
- return createHmac("sha256", secret).update(address).digest("hex");
-}
-
-function adminClient() {
- const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
- const key = process.env.SUPABASE_SERVICE_ROLE_KEY || process.env.SUPABASE_SERVICE_KEY;
- if (!url || !key) {
- throw new SiwsError("session_failed", "Supabase service role is not configured.", 503);
+ const requestedRole: UserRole = role === "lender" ? "lender" : "borrower";
+ const now = new Date();
+
+ const [existing] = await db
+ .select({ id: users.id, role: users.role })
+ .from(users)
+ .where(eq(users.walletAddress, address))
+ .limit(1);
+
+ if (existing) {
+ await db.update(users).set({ lastSignInAt: now }).where(eq(users.id, existing.id));
+ // Heal a missing profile row (e.g. a partially failed first sign-in).
+ await db
+ .insert(profiles)
+ .values({ id: existing.id, role: existing.role, walletAddress: address, fullName: shortName(address) })
+ .onConflictDoNothing();
+ return { userId: existing.id, role: normalizeUserRole(existing.role), isNewUser: false };
}
- return createClient(url, key, { auth: { autoRefreshToken: false, persistSession: false } });
-}
-/**
- * Ensure a Supabase user exists for `address` and return a fresh session
- * (access + refresh tokens) the client can adopt via `auth.setSession`.
- */
-export async function issueSessionForWallet(address: string, role?: string): Promise<{
- session: Session;
- isNewUser: boolean;
-}> {
- const email = walletEmail(address);
- const password = walletPassword(address);
- const admin = adminClient();
- const accountType = (role === "borrower" || role === "lender") ? role : "borrower";
-
- // Create the wallet user if it doesn't exist yet (idempotent).
- let isNewUser = false;
- const { error: createErr } = await admin.auth.admin.createUser({
- email,
- password,
- email_confirm: true,
- user_metadata: {
- wallet_address: address,
- account_type: accountType,
- auth_method: "siws",
- full_name: `Stellar ${address.slice(0, 4)}…${address.slice(-4)}`,
- },
- });
-
- if (createErr) {
- const msg = createErr.message?.toLowerCase() ?? "";
- const alreadyExists =
- msg.includes("already been registered") ||
- msg.includes("already registered") ||
- msg.includes("email_exists") ||
- (createErr as { code?: string }).code === "email_exists";
- if (!alreadyExists) {
- throw new SiwsError("session_failed", `Could not provision wallet account: ${createErr.message}`, 500);
+ const [created] = await db
+ .insert(users)
+ .values({ walletAddress: address, role: requestedRole, lastSignInAt: now })
+ .onConflictDoNothing({ target: users.walletAddress })
+ .returning({ id: users.id, role: users.role });
+
+ if (!created) {
+ // Lost a race with a concurrent first sign-in for the same wallet.
+ const [raced] = await db
+ .select({ id: users.id, role: users.role })
+ .from(users)
+ .where(eq(users.walletAddress, address))
+ .limit(1);
+ if (!raced) {
+ throw new SiwsError("session_failed", "Could not provision wallet account.", 500);
}
- } else {
- isNewUser = true;
+ return { userId: raced.id, role: normalizeUserRole(raced.role), isNewUser: false };
}
- // Mint a session with the deterministic password (never returned to client).
- const url = process.env.NEXT_PUBLIC_SUPABASE_URL!;
- const anon = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
- const authClient = createClient(url, anon, {
- auth: { autoRefreshToken: false, persistSession: false },
- });
- const { data, error } = await authClient.auth.signInWithPassword({ email, password });
- if (error || !data.session) {
- throw new SiwsError("session_failed", `Failed to issue session: ${error?.message ?? "no session"}`, 500);
- }
+ await db
+ .insert(profiles)
+ .values({ id: created.id, role: requestedRole, walletAddress: address, fullName: shortName(address) })
+ .onConflictDoNothing();
+
+ return { userId: created.id, role: requestedRole, isNewUser: true };
+}
- return { session: data.session, isNewUser };
+/** "Stellar GABC…WXYZ" — the default display name for a wallet-only account. */
+function shortName(address: string): string {
+ return `Stellar ${address.slice(0, 4)}…${address.slice(-4)}`;
}
diff --git a/lib/commitlint/validate-commit.test.ts b/lib/commitlint/validate-commit.test.ts
index bd57f08..76e78a7 100644
--- a/lib/commitlint/validate-commit.test.ts
+++ b/lib/commitlint/validate-commit.test.ts
@@ -22,7 +22,7 @@ const ALLOWED_TYPES = [
const ALLOWED_SCOPES = [
"lending", "escrow", "governance", "default-management", "multisig-admin",
"borrower-reputation", "auto-compound-vault", "treasury", "contracts",
- "frontend", "dashboard", "auth", "kyc", "api", "ci", "db", "supabase",
+ "frontend", "dashboard", "auth", "kyc", "api", "ci", "db", "neon", "drizzle",
"stellar", "soroban", "docs", "deps", "config", "landing", "hooks",
] as const;
diff --git a/lib/dashboard/metrics.ts b/lib/dashboard/metrics.ts
index b964c82..86fb56e 100644
--- a/lib/dashboard/metrics.ts
+++ b/lib/dashboard/metrics.ts
@@ -1,4 +1,6 @@
-import { getServerSupabaseClient } from "@/lib/supabase/server";
+import { and, eq, inArray, sql } from "drizzle-orm";
+import { getDb } from "@/lib/db/client";
+import { ledgerTransactions, loans, poolPositions, profiles, reputationEvents } from "@/lib/db/schema";
export interface BorrowerDashboardMetrics {
reputationScore: number;
@@ -67,34 +69,24 @@ const ACTIVE_LOAN_STATUSES = ["active", "funded", "approved"];
export async function getBorrowerDashboardMetrics(
userId: string,
): Promise {
-
- const supabase = await getServerSupabaseClient();
-
- if (!supabase) {
+ const db = getDb();
+ if (!db) {
return { reputationScore: 0, availableCredit: 0, activeLoans: 0, pendingLoans: 0, repaymentRate: 0 };
}
try {
- const [eventsRes, loansRes] = await Promise.all([
- supabase
- .from("reputation_events")
- .select("points_delta")
- .eq("user_id", userId),
- supabase
- .from("loans")
- .select("status")
- .eq("borrower_id", userId),
+ const [events, loanRows] = await Promise.all([
+ db.select({ pointsDelta: reputationEvents.pointsDelta }).from(reputationEvents).where(eq(reputationEvents.userId, userId)),
+ db.select({ status: loans.status }).from(loans).where(eq(loans.borrowerId, userId)),
]);
- const events = eventsRes.data ?? [];
- const reputationPoints = events.reduce((sum, row) => sum + Number(row.points_delta ?? 0), 0);
+ const reputationPoints = events.reduce((sum, row) => sum + Number(row.pointsDelta ?? 0), 0);
const reputation = Math.max(0, 250 + reputationPoints);
- const loans = loansRes.data ?? [];
- const pendingLoans = loans.filter((loan) => loan.status === "requested").length;
- const activeLoans = loans.filter((loan) => ACTIVE_LOAN_STATUSES.includes(loan.status)).length;
- const repaidLoans = loans.filter((loan) => loan.status === "repaid").length;
- const defaultedLoans = loans.filter((loan) => loan.status === "defaulted").length;
+ const pendingLoans = loanRows.filter((loan) => loan.status === "requested").length;
+ const activeLoans = loanRows.filter((loan) => ACTIVE_LOAN_STATUSES.includes(loan.status)).length;
+ const repaidLoans = loanRows.filter((loan) => loan.status === "repaid").length;
+ const defaultedLoans = loanRows.filter((loan) => loan.status === "defaulted").length;
const repaymentBase = repaidLoans + defaultedLoans;
const repaymentRate = repaymentBase > 0 ? (repaidLoans / repaymentBase) * 100 : 100;
@@ -110,67 +102,71 @@ export async function getBorrowerDashboardMetrics(
}
}
+type LedgerMeta = { lenderUserId?: unknown; lenderAddress?: unknown; loanId?: unknown };
+
+function parseMeta(raw: unknown): LedgerMeta {
+ if (!raw) return {};
+ try {
+ return (typeof raw === "string" ? JSON.parse(raw) : raw) as LedgerMeta;
+ } catch {
+ return {};
+ }
+}
+
export async function getLenderDashboardMetrics(
userId: string,
): Promise {
-
- const { getServerSupabaseClient, getServiceRoleClient } = await import("@/lib/supabase/server");
- const supabase = await getServerSupabaseClient();
- const srClient = getServiceRoleClient();
-
- if (!supabase || !srClient) {
+ const db = getDb();
+ if (!db) {
return { deployedCapital: 0, totalEarnings: 0, activePositions: 0, defaultRate: 0 };
}
try {
// 1. Pool positions
- const positionsRes = await supabase
- .from("pool_positions")
- .select("status, principal_amount, earned_interest")
- .eq("lender_id", userId);
-
- const positions = positionsRes.data ?? [];
- const poolDeployed = positions.reduce((s, r) => s + Number(r.principal_amount ?? 0), 0);
- const poolEarnings = positions.reduce((s, r) => s + Number(r.earned_interest ?? 0), 0);
+ const positions = await db
+ .select({
+ status: poolPositions.status,
+ principalAmount: poolPositions.principalAmount,
+ earnedInterest: poolPositions.earnedInterest,
+ })
+ .from(poolPositions)
+ .where(eq(poolPositions.lenderId, userId));
+
+ const poolDeployed = positions.reduce((s, r) => s + Number(r.principalAmount ?? 0), 0);
+ const poolEarnings = positions.reduce((s, r) => s + Number(r.earnedInterest ?? 0), 0);
const poolActive = positions.filter((r) => r.status === "active").length;
- // 2. P2P Metrics
- const { data: p2pFunds } = await supabase
- .from("ledger_transactions")
- .select("amount, ref_id")
- .eq("user_id", userId)
- .eq("ref_type", "loan_fund");
-
- const { data: p2pRepays } = await srClient
- .from("ledger_transactions")
- .select("amount, metadata")
- .eq("ref_type", "loan_repay");
-
- const lenderRepays = (p2pRepays ?? []).filter(tx => {
- try {
- const meta = JSON.parse(String(tx.metadata || "{}"));
- return String(meta.lenderUserId) === String(userId) || String(meta.lenderAddress) === String(userId);
- } catch { return false; }
+ // 2. P2P metrics
+ const [p2pFunds, p2pRepays] = await Promise.all([
+ db
+ .select({ amount: ledgerTransactions.amount, refId: ledgerTransactions.refId })
+ .from(ledgerTransactions)
+ .where(and(eq(ledgerTransactions.userId, userId), eq(ledgerTransactions.refType, "loan_fund"))),
+ db
+ .select({ amount: ledgerTransactions.amount, metadata: ledgerTransactions.metadata })
+ .from(ledgerTransactions)
+ .where(eq(ledgerTransactions.refType, "loan_repay")),
+ ]);
+
+ const lenderRepays = p2pRepays.filter((tx) => {
+ const meta = parseMeta(tx.metadata);
+ return String(meta.lenderUserId) === userId || String(meta.lenderAddress) === userId;
});
- // We must group by loan (ref_id) so a newly funded loan doesn't wipe out past profits!
+ // Group by loan so a newly funded loan doesn't wipe out past profits.
const loanProfitMap = new Map();
- for (const tx of (p2pFunds ?? [])) {
- const id = String(tx.ref_id);
+ for (const tx of p2pFunds) {
+ const id = String(tx.refId);
const cur = loanProfitMap.get(id) ?? { deployed: 0, received: 0 };
cur.deployed += Number(tx.amount || 0);
loanProfitMap.set(id, cur);
}
for (const tx of lenderRepays) {
- let id = "";
- try {
- const meta = JSON.parse(String(tx.metadata || "{}"));
- id = meta.loanId ? String(meta.loanId) : "";
- } catch {}
+ const meta = parseMeta(tx.metadata);
+ const id = meta.loanId ? String(meta.loanId) : "";
if (!id) continue;
-
const cur = loanProfitMap.get(id) ?? { deployed: 0, received: 0 };
cur.received += Number(tx.amount || 0);
loanProfitMap.set(id, cur);
@@ -181,49 +177,43 @@ export async function getLenderDashboardMetrics(
p2pProfit += Math.max(0, received - deployed);
}
- const p2pDeployed = (p2pFunds ?? []).reduce((s, t) => s + Number(t.amount || 0), 0);
+ const p2pDeployed = p2pFunds.reduce((s, t) => s + Number(t.amount || 0), 0);
- // Get active loan count from the funded loans
- const loanIds = Array.from(loanProfitMap.keys());
+ const loanIds = Array.from(loanProfitMap.keys()).filter((id) => id && id !== "null");
let p2pActiveCount = 0;
if (loanIds.length > 0) {
- const { data: loans } = await srClient
- .from("loans")
- .select("status")
- .in("id", loanIds);
- p2pActiveCount = (loans ?? []).filter(l => l.status === "active").length;
+ const loanRows = await db.select({ status: loans.status }).from(loans).where(inArray(loans.id, loanIds));
+ p2pActiveCount = loanRows.filter((l) => l.status === "active").length;
}
- const deployedCapital = poolDeployed + p2pDeployed;
- const totalEarnings = poolEarnings + p2pProfit;
- const activePositions = poolActive + p2pActiveCount;
- const defaultRate = 0;
-
- return { deployedCapital, totalEarnings, activePositions, defaultRate };
+ return {
+ deployedCapital: poolDeployed + p2pDeployed,
+ totalEarnings: poolEarnings + p2pProfit,
+ activePositions: poolActive + p2pActiveCount,
+ defaultRate: 0,
+ };
} catch {
return { deployedCapital: 0, totalEarnings: 0, activePositions: 0, defaultRate: 0 };
}
}
export async function getAdminDashboardMetrics(): Promise {
- const supabase = await getServerSupabaseClient();
- if (!supabase) return { totalUsers: 0, totalLoans: 0, activeLoans: 0, highRiskUsers: 0 };
+ const db = getDb();
+ if (!db) return { totalUsers: 0, totalLoans: 0, activeLoans: 0, highRiskUsers: 0 };
try {
-
- const [usersRes, totalLoansRes, activeLoansRes, highRiskRes] = await Promise.all([
- supabase.from("profiles").select("id", { count: "exact", head: true }),
- supabase.from("loans").select("id", { count: "exact", head: true }),
- supabase.from("loans").select("id", { count: "exact", head: true })
- .in("status", ["approved", "funded", "active", "requested"]),
- supabase.from("profiles").select("id", { count: "exact", head: true })
- .in("risk_status", ["high", "blocked"]),
+ const countOf = sql`count(*)::int`;
+ const [[usersRes], [totalLoansRes], [activeLoansRes], [highRiskRes]] = await Promise.all([
+ db.select({ count: countOf }).from(profiles),
+ db.select({ count: countOf }).from(loans),
+ db.select({ count: countOf }).from(loans).where(inArray(loans.status, ["approved", "funded", "active", "requested"])),
+ db.select({ count: countOf }).from(profiles).where(inArray(profiles.riskStatus, ["high", "blocked"])),
]);
return {
- totalUsers: usersRes.count ?? 0,
- totalLoans: totalLoansRes.count ?? 0,
- activeLoans: activeLoansRes.count ?? 0,
- highRiskUsers: highRiskRes.count ?? 0,
+ totalUsers: usersRes?.count ?? 0,
+ totalLoans: totalLoansRes?.count ?? 0,
+ activeLoans: activeLoansRes?.count ?? 0,
+ highRiskUsers: highRiskRes?.count ?? 0,
};
} catch {
return { totalUsers: 0, totalLoans: 0, activeLoans: 0, highRiskUsers: 0 };
diff --git a/lib/db/client.ts b/lib/db/client.ts
new file mode 100644
index 0000000..6343898
--- /dev/null
+++ b/lib/db/client.ts
@@ -0,0 +1,91 @@
+/**
+ * lib/db/client.ts
+ *
+ * Drizzle + Neon database access.
+ *
+ * - `getDb()` — HTTP driver. One round-trip per query, no connection to keep
+ * alive: the right choice for Vercel serverless / edge handlers and for the
+ * vast majority of reads and single-statement writes.
+ * - `getPooledDb()` — WebSocket pool driver. Required for interactive
+ * transactions (`db.transaction(...)`) and for long-running scripts (keepers,
+ * backfills) that issue many statements.
+ *
+ * Both return `null` when DATABASE_URL is not configured so callers can degrade
+ * gracefully (the app renders an empty dashboard instead of crashing at build
+ * time, matching the previous behaviour when Supabase env was absent).
+ *
+ * SERVER-ONLY. Never import from a client component. (Not guarded with the
+ * `server-only` package because keeper scripts and vitest import this too.)
+ */
+
+import { neon, neonConfig, Pool } from "@neondatabase/serverless";
+import { drizzle as drizzleHttp, type NeonHttpDatabase } from "drizzle-orm/neon-http";
+import { drizzle as drizzleWs, type NeonDatabase } from "drizzle-orm/neon-serverless";
+import * as schema from "./schema";
+
+export type Db = NeonHttpDatabase;
+export type PooledDb = NeonDatabase;
+
+let httpDb: Db | null | undefined;
+let pooledDb: PooledDb | null | undefined;
+
+function databaseUrl(): string | null {
+ const url = process.env.DATABASE_URL;
+ return url && url.trim().length > 0 ? url : null;
+}
+
+/** True when the database is configured. */
+export function isDatabaseConfigured(): boolean {
+ return databaseUrl() !== null;
+}
+
+/** Drizzle over Neon's HTTP driver. Cached per runtime. */
+export function getDb(): Db | null {
+ if (httpDb !== undefined) return httpDb;
+ const url = databaseUrl();
+ if (!url) {
+ httpDb = null;
+ return null;
+ }
+ httpDb = drizzleHttp(neon(url), { schema });
+ return httpDb;
+}
+
+/** Drizzle over a Neon WebSocket pool — use for transactions and scripts. */
+export function getPooledDb(): PooledDb | null {
+ if (pooledDb !== undefined) return pooledDb;
+ const url = databaseUrl();
+ if (!url) {
+ pooledDb = null;
+ return null;
+ }
+ // Node < 22 has no global WebSocket; the `ws` package is pulled in by
+ // @neondatabase/serverless when needed. Vercel's runtime provides one.
+ if (typeof WebSocket === "undefined") {
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
+ neonConfig.webSocketConstructor = require("ws");
+ }
+ pooledDb = drizzleWs(new Pool({ connectionString: url }), { schema });
+ return pooledDb;
+}
+
+/**
+ * Like `getDb()` but throws a descriptive error instead of returning null.
+ * Use in API routes where "database not configured" is a hard 503.
+ */
+export function requireDb(): Db {
+ const db = getDb();
+ if (!db) {
+ throw new DatabaseNotConfiguredError();
+ }
+ return db;
+}
+
+export class DatabaseNotConfiguredError extends Error {
+ constructor() {
+ super("Database is not configured (DATABASE_URL is missing).");
+ this.name = "DatabaseNotConfiguredError";
+ }
+}
+
+export { schema };
diff --git a/lib/db/metadata.ts b/lib/db/metadata.ts
new file mode 100644
index 0000000..6c7431b
--- /dev/null
+++ b/lib/db/metadata.ts
@@ -0,0 +1,28 @@
+/**
+ * lib/db/metadata.ts
+ *
+ * jsonb `metadata` columns come back from Drizzle as parsed objects. Rows
+ * written by the old Supabase code were sometimes stored as a JSON *string*
+ * inside the jsonb column, so readers must tolerate both shapes.
+ */
+
+export type Metadata = Record;
+
+export function readMetadata(raw: unknown): Metadata {
+ if (raw === null || raw === undefined) return {};
+ if (typeof raw === "string") {
+ try {
+ const parsed = JSON.parse(raw) as unknown;
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? (parsed as Metadata) : {};
+ } catch {
+ return {};
+ }
+ }
+ return typeof raw === "object" && !Array.isArray(raw) ? (raw as Metadata) : {};
+}
+
+/** Read one string field out of a metadata blob ("" when absent). */
+export function metaString(raw: unknown, key: string): string {
+ const value = readMetadata(raw)[key];
+ return value === null || value === undefined ? "" : String(value);
+}
diff --git a/lib/db/pools.test.ts b/lib/db/pools.test.ts
deleted file mode 100644
index 228c8a7..0000000
--- a/lib/db/pools.test.ts
+++ /dev/null
@@ -1,617 +0,0 @@
-/* eslint-disable @typescript-eslint/no-explicit-any */
-/**
- * Tests for optimized pool database queries
- *
- * Tests verify:
- * - Only ONE query/RPC call is made instead of multiple
- * - Correct data shape and types are returned
- * - Pagination works correctly
- * - Filtering by status works
- * - Error handling
- *
- * Run with: npm test -- pools.test.ts
- */
-
-import { describe, it, expect, vi } from "vitest";
-import { SupabaseClient } from "@supabase/supabase-js";
-import {
- fetchPools,
- fetchPoolById,
- fetchActivePoolsWithLiquidity,
- fetchAdminDashboardPools,
- type Pool,
-} from "./pools";
-
-// ─────────────────────────────────────────────────────────────────────────────
-// MOCK DATA
-// ─────────────────────────────────────────────────────────────────────────────
-
-const MOCK_POOLS: Pool[] = [
- {
- id: "pool-1",
- name: "Alpha Pool",
- description: "Premium lending pool",
- status: "active",
- apr_bps: 1500,
- total_liquidity: 50000,
- available_liquidity: 25000,
- total_borrowed: 25000,
- borrow_cap: null,
- created_at: "2024-01-01T00:00:00Z",
- updated_at: "2024-01-01T00:00:00Z",
- },
- {
- id: "pool-2",
- name: "Beta Pool",
- description: null,
- status: "active",
- apr_bps: 2000,
- total_liquidity: 100000,
- available_liquidity: 100000,
- total_borrowed: 0,
- borrow_cap: null,
- created_at: "2024-01-02T00:00:00Z",
- updated_at: "2024-01-02T00:00:00Z",
- },
- {
- id: "pool-3",
- name: "Gamma Pool",
- description: "Paused pool",
- status: "paused",
- apr_bps: 1000,
- total_liquidity: 30000,
- available_liquidity: 30000,
- total_borrowed: 0,
- borrow_cap: null,
- created_at: "2024-01-03T00:00:00Z",
- updated_at: "2024-01-03T00:00:00Z",
- },
-];
-
-// ─────────────────────────────────────────────────────────────────────────────
-// HELPER FUNCTIONS
-// ─────────────────────────────────────────────────────────────────────────────
-
-function createMockSupabaseClient(
- overrides: Partial = {}
-): SupabaseClient {
- const mockClient = {
- from: vi.fn(),
- ...overrides,
- } as unknown as SupabaseClient;
-
- return mockClient;
-}
-
-function createMockQuery(data: any[] = [], count: number | null = null) {
- const select = vi.fn();
- const eq = vi.fn();
- const gt = vi.fn();
- const order = vi.fn();
- const limit = vi.fn();
- const range = vi.fn();
- const maybeSingle = vi.fn();
-
- const queryChain = {
- select,
- eq,
- gt,
- order,
- limit,
- range,
- maybeSingle,
- data,
- error: null,
- count,
- };
-
- // Setup chainable methods
- select.mockReturnValue({
- ...queryChain,
- eq: eq.mockReturnValue(queryChain),
- gt: gt.mockReturnValue(queryChain),
- order: order.mockReturnValue(queryChain),
- limit: limit.mockReturnValue(queryChain),
- range: range.mockReturnValue(queryChain),
- maybeSingle: maybeSingle.mockReturnValue({ data: data[0] || null, error: null }),
- });
-
- eq.mockReturnValue({
- ...queryChain,
- gt: gt.mockReturnValue(queryChain),
- order: order.mockReturnValue(queryChain),
- limit: limit.mockReturnValue(queryChain),
- range: range.mockReturnValue(queryChain),
- maybeSingle: maybeSingle.mockReturnValue({ data: data[0] || null, error: null }),
- });
-
- gt.mockReturnValue({
- ...queryChain,
- order: order.mockReturnValue(queryChain),
- limit: limit.mockReturnValue(queryChain),
- range: range.mockReturnValue(queryChain),
- });
-
- order.mockReturnValue({
- ...queryChain,
- limit: limit.mockReturnValue(queryChain),
- range: range.mockReturnValue(queryChain),
- maybeSingle: maybeSingle.mockReturnValue({ data: data[0] || null, error: null }),
- });
-
- limit.mockReturnValue({
- ...queryChain,
- order: order.mockReturnValue(queryChain),
- });
-
- range.mockReturnValue(queryChain);
-
- return queryChain as any;
-}
-
-// ─────────────────────────────────────────────────────────────────────────────
-// TEST SUITES
-// ─────────────────────────────────────────────────────────────────────────────
-
-describe("fetchPools", () => {
- it("should make a single query with explicit columns", async () => {
- const mockFrom = vi.fn();
- const mockQuery = createMockQuery(MOCK_POOLS.slice(0, 2), 2);
- mockFrom.mockReturnValue(mockQuery);
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- const result = await fetchPools(client);
-
- // Verify only ONE query was made
- expect(mockFrom).toHaveBeenCalledTimes(1);
- expect(mockFrom).toHaveBeenCalledWith("lending_pools");
-
- // Verify explicit column selection (no SELECT *)
- expect(mockQuery.select).toHaveBeenCalledWith(
- "id, name, description, status, apr_bps, total_liquidity, available_liquidity, total_borrowed, borrow_cap, created_at, updated_at",
- expect.any(Object)
- );
-
- // Verify pagination was applied
- expect(mockQuery.range).toHaveBeenCalledWith(0, 9); // default limit 10
-
- // Verify result structure
- expect(result.pools).toHaveLength(2);
- expect(result.pools[0]).toEqual(MOCK_POOLS[0]);
- expect(result.hasMore).toBe(false); // got 2, wanted 10
- });
-
- it("should apply status filter when provided", async () => {
- const mockFrom = vi.fn();
- const activePools = MOCK_POOLS.filter((p) => p.status === "active");
- const mockQuery = createMockQuery(activePools, activePools.length);
- mockFrom.mockReturnValue(mockQuery);
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- await fetchPools(client, { status: "active" });
-
- // Verify status filter was applied
- expect(mockQuery.eq).toHaveBeenCalledWith("status", "active");
- });
-
- it("should handle pagination correctly", async () => {
- const mockFrom = vi.fn();
- const mockQuery = createMockQuery(MOCK_POOLS.slice(0, 1), 100);
- mockFrom.mockReturnValue(mockQuery);
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- const result = await fetchPools(client, { limit: 10, offset: 20 });
-
- // Verify correct range was requested
- expect(mockQuery.range).toHaveBeenCalledWith(20, 29);
- expect(result.hasMore).toBe(false); // Only 1 item returned, so no more on page
- });
-
- it("should throw error on query failure", async () => {
- const mockFrom = vi.fn();
- const errorQuery = {
- select: vi.fn().mockReturnThis(),
- eq: vi.fn().mockReturnThis(),
- order: vi.fn().mockReturnThis(),
- range: vi.fn().mockResolvedValue({
- data: null,
- error: { message: "Database error" },
- }),
- };
- mockFrom.mockReturnValue(errorQuery);
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- await expect(fetchPools(client)).rejects.toThrow("Failed to fetch pools");
- });
-
- it("should clamp limit to max 100", async () => {
- const mockFrom = vi.fn();
- const mockQuery = createMockQuery([], 0);
- mockFrom.mockReturnValue(mockQuery);
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- await fetchPools(client, { limit: 500 });
-
- // Should clamp to 100
- expect(mockQuery.range).toHaveBeenCalledWith(0, 99);
- });
-});
-
-describe("fetchPoolById", () => {
- it("should fetch a single pool by ID with one query", async () => {
- const mockFrom = vi.fn();
- const mockQuery = createMockQuery([MOCK_POOLS[0]]);
- mockFrom.mockReturnValue(mockQuery);
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- const result = await fetchPoolById(client, "pool-1");
-
- expect(mockFrom).toHaveBeenCalledTimes(1);
- expect(mockQuery.eq).toHaveBeenCalledWith("id", "pool-1");
- expect(result).toEqual(MOCK_POOLS[0]);
- });
-
- it("should return null if pool not found", async () => {
- const mockFrom = vi.fn();
- const mockQuery = createMockQuery([]);
- mockFrom.mockReturnValue(mockQuery);
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- const result = await fetchPoolById(client, "nonexistent");
-
- expect(result).toBeNull();
- });
-});
-
-describe("fetchActivePoolsWithLiquidity", () => {
- it("should fetch only active pools with one query", async () => {
- const mockFrom = vi.fn();
- const activePools = MOCK_POOLS.filter((p) => p.status === "active");
- const mockQuery = createMockQuery(activePools);
- mockFrom.mockReturnValue(mockQuery);
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- const result = await fetchActivePoolsWithLiquidity(client);
-
- expect(mockFrom).toHaveBeenCalledTimes(1);
- expect(mockQuery.eq).toHaveBeenCalledWith("status", "active");
- expect(result).toHaveLength(2);
- expect(result.every((p) => p.status === "active")).toBe(true);
- });
-
- it("should filter by minimum liquidity when provided", async () => {
- const mockFrom = vi.fn();
- const highLiquidityPools = MOCK_POOLS.filter(
- (p) => p.status === "active" && p.available_liquidity >= 50000
- );
- const mockQuery = createMockQuery(highLiquidityPools);
- mockFrom.mockReturnValue(mockQuery);
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- const result = await fetchActivePoolsWithLiquidity(client, 50000);
-
- expect(mockQuery.gt).toHaveBeenCalledWith("available_liquidity", 50000);
- expect(result.every((p) => p.available_liquidity >= 50000)).toBe(true);
- });
-
- it("should sort by available liquidity descending", async () => {
- const mockFrom = vi.fn();
- const mockQuery = createMockQuery(MOCK_POOLS);
- mockFrom.mockReturnValue(mockQuery);
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- await fetchActivePoolsWithLiquidity(client);
-
- expect(mockQuery.order).toHaveBeenCalledWith("available_liquidity", {
- ascending: false,
- });
- });
-});
-
-describe("fetchAdminDashboardPools", () => {
- it("should fetch pools and loans in parallel (2 queries, not sequential)", async () => {
- const mockLoans = [
- {
- id: "loan-1",
- status: "requested",
- principal_amount: 5000,
- apr_bps: 1500,
- duration_days: 30,
- requested_at: "2024-01-01T00:00:00Z",
- borrower_id: "user-1",
- profiles: { full_name: "John Doe" },
- },
- ];
-
- const mockFromPool = vi.fn();
- const mockPoolQuery = createMockQuery(MOCK_POOLS);
- mockFromPool.mockReturnValue(mockPoolQuery);
-
- const mockFromLoans = vi.fn();
- const mockLoanQuery = createMockQuery(mockLoans);
- mockFromLoans.mockReturnValue(mockLoanQuery);
-
- const client = createMockSupabaseClient({
- from: (table: string) => {
- if (table === "lending_pools") return mockFromPool();
- if (table === "loans") return mockFromLoans();
- },
- });
-
- const result = await fetchAdminDashboardPools(client);
-
- expect(result.pools).toHaveLength(3);
- expect(result.pendingLoans).toHaveLength(1);
- expect(result.pendingLoans[0].borrower_profile?.full_name).toBe("John Doe");
- });
-
- it("should handle profile relation cardinality (array format)", async () => {
- const mockLoans = [
- {
- id: "loan-1",
- status: "requested",
- principal_amount: 5000,
- apr_bps: 1500,
- duration_days: 30,
- requested_at: "2024-01-01T00:00:00Z",
- borrower_id: "user-1",
- profiles: [{ full_name: "Jane Doe" }], // Array format
- },
- ];
-
- const mockFromPool = vi.fn();
- const mockPoolQuery = createMockQuery(MOCK_POOLS);
- mockFromPool.mockReturnValue(mockPoolQuery);
-
- const mockFromLoans = vi.fn();
- const mockLoanQuery = createMockQuery(mockLoans);
- mockFromLoans.mockReturnValue(mockLoanQuery);
-
- const client = createMockSupabaseClient({
- from: (table: string) => {
- if (table === "lending_pools") return mockFromPool();
- if (table === "loans") return mockFromLoans();
- },
- });
-
- const result = await fetchAdminDashboardPools(client);
-
- expect(result.pendingLoans[0].borrower_profile?.full_name).toBe("Jane Doe");
- });
-
- it("should handle null borrower profile", async () => {
- const mockLoans = [
- {
- id: "loan-1",
- status: "requested",
- principal_amount: 5000,
- apr_bps: 1500,
- duration_days: 30,
- requested_at: "2024-01-01T00:00:00Z",
- borrower_id: "user-1",
- profiles: null,
- },
- ];
-
- const mockFromPool = vi.fn();
- const mockPoolQuery = createMockQuery(MOCK_POOLS);
- mockFromPool.mockReturnValue(mockPoolQuery);
-
- const mockFromLoans = vi.fn();
- const mockLoanQuery = createMockQuery(mockLoans);
- mockFromLoans.mockReturnValue(mockLoanQuery);
-
- const client = createMockSupabaseClient({
- from: (table: string) => {
- if (table === "lending_pools") return mockFromPool();
- if (table === "loans") return mockFromLoans();
- },
- });
-
- const result = await fetchAdminDashboardPools(client);
-
- expect(result.pendingLoans[0].borrower_profile).toBeNull();
- });
-});
-
-describe("Performance: Query Count Verification", () => {
- it("fetchPools should make exactly 1 query", async () => {
- let queryCount = 0;
- const mockFrom = vi.fn(() => {
- queryCount++;
- return createMockQuery(MOCK_POOLS);
- });
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- await fetchPools(client);
-
- expect(queryCount).toBe(1);
- });
-
- it("fetchPoolById should make exactly 1 query", async () => {
- let queryCount = 0;
- const mockFrom = vi.fn(() => {
- queryCount++;
- return createMockQuery([MOCK_POOLS[0]]);
- });
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- await fetchPoolById(client, "pool-1");
-
- expect(queryCount).toBe(1);
- });
-
- it("fetchActivePoolsWithLiquidity should make exactly 1 query", async () => {
- let queryCount = 0;
- const mockFrom = vi.fn(() => {
- queryCount++;
- return createMockQuery(MOCK_POOLS.filter((p) => p.status === "active"));
- });
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- await fetchActivePoolsWithLiquidity(client);
-
- expect(queryCount).toBe(1);
- });
-
- it("fetchAdminDashboardPools should make exactly 2 queries (parallel)", async () => {
- let queryCount = 0;
- const mockFrom = vi.fn(() => {
- queryCount++;
- return createMockQuery(queryCount === 1 ? MOCK_POOLS : []);
- });
-
- const client = createMockSupabaseClient({ from: mockFrom });
-
- await fetchAdminDashboardPools(client);
-
- // Should be 2 total: 1 for pools, 1 for loans
- expect(queryCount).toBe(2);
- });
-});
-
-describe("Pool borrow_cap field", () => {
- const POOLS_WITH_CAP: Pool[] = [
- {
- id: "pool-capped",
- name: "Capped Pool",
- description: null,
- status: "active",
- apr_bps: 1500,
- total_liquidity: 100000,
- available_liquidity: 50000,
- total_borrowed: 30000,
- borrow_cap: 50000,
- created_at: "2024-01-01T00:00:00Z",
- updated_at: "2024-01-01T00:00:00Z",
- },
- {
- id: "pool-uncapped",
- name: "Uncapped Pool",
- description: null,
- status: "active",
- apr_bps: 1500,
- total_liquidity: 100000,
- available_liquidity: 100000,
- total_borrowed: 0,
- borrow_cap: null,
- created_at: "2024-01-01T00:00:00Z",
- updated_at: "2024-01-01T00:00:00Z",
- },
- ];
-
- it("should include borrow_cap in Pool type (null for uncapped)", () => {
- const pool = POOLS_WITH_CAP[1];
- expect(pool.borrow_cap).toBeNull();
- });
-
- it("should include borrow_cap in Pool type (number for capped)", () => {
- const pool = POOLS_WITH_CAP[0];
- expect(pool.borrow_cap).toBe(50000);
- });
-
- it("should fetch borrow_cap as part of pool data from DB", async () => {
- const mockFrom = vi.fn();
- const mockQuery = createMockQuery([
- { ...POOLS_WITH_CAP[0] },
- ]);
- mockFrom.mockReturnValue(mockQuery);
-
- const client = createMockSupabaseClient({ from: mockFrom });
- const result = await fetchPoolById(client, "pool-capped");
-
- expect(result).not.toBeNull();
- expect(result?.borrow_cap).toBe(50000);
- });
-
- it("should select borrow_cap column in fetchPools", async () => {
- const mockFrom = vi.fn();
- const mockQuery = createMockQuery(POOLS_WITH_CAP, 2);
- mockFrom.mockReturnValue(mockQuery);
-
- const client = createMockSupabaseClient({ from: mockFrom });
- await fetchPools(client);
-
- // Verify borrow_cap is in the column list
- expect(mockQuery.select).toHaveBeenCalledWith(
- expect.stringContaining("borrow_cap"),
- expect.any(Object)
- );
- });
-
- it("should select borrow_cap column in fetchActivePoolsWithLiquidity", async () => {
- const mockFrom = vi.fn();
- const mockQuery = createMockQuery(POOLS_WITH_CAP);
- mockFrom.mockReturnValue(mockQuery);
-
- const client = createMockSupabaseClient({ from: mockFrom });
- await fetchActivePoolsWithLiquidity(client);
-
- expect(mockQuery.select).toHaveBeenCalledWith(
- expect.stringContaining("borrow_cap")
- );
- });
-});
-
-describe("Borrow Cap Enforcement Logic", () => {
- it("should detect when a loan would exceed pool borrow cap", () => {
- const poolBorrowCap = 50000;
- const totalBorrowed = 45000;
- const loanAmount = 6000;
-
- const wouldExceedCap = totalBorrowed + loanAmount > poolBorrowCap;
- expect(wouldExceedCap).toBe(true);
- });
-
- it("should allow a loan that fits under the borrow cap", () => {
- const poolBorrowCap = 50000;
- const totalBorrowed = 30000;
- const loanAmount = 10000;
-
- const wouldExceedCap = totalBorrowed + loanAmount > poolBorrowCap;
- expect(wouldExceedCap).toBe(false);
- });
-
- it("should allow a loan when pool has no borrow cap (null)", () => {
- const poolBorrowCap: number | null = null;
- const totalBorrowed = 999999;
- const loanAmount = 999999;
-
- const wouldExceedCap =
- poolBorrowCap !== null &&
- totalBorrowed + loanAmount > poolBorrowCap;
- expect(wouldExceedCap).toBe(false);
- });
-
- it("should allow a loan exactly at the borrow cap", () => {
- const poolBorrowCap = 50000;
- const totalBorrowed = 40000;
- const loanAmount = 10000;
-
- // Exactly at cap: 40000 + 10000 = 50000 (not exceeded)
- const wouldExceedCap = totalBorrowed + loanAmount > poolBorrowCap;
- expect(wouldExceedCap).toBe(false);
- });
-
- it("should reject a loan 1 unit over the borrow cap", () => {
- const poolBorrowCap = 50000;
- const totalBorrowed = 40000;
- const loanAmount = 10001;
-
- const wouldExceedCap = totalBorrowed + loanAmount > poolBorrowCap;
- expect(wouldExceedCap).toBe(true);
- });
-});
diff --git a/lib/db/pools.ts b/lib/db/pools.ts
index 4b9c4ff..b807f91 100644
--- a/lib/db/pools.ts
+++ b/lib/db/pools.ts
@@ -1,21 +1,19 @@
/**
- * Optimized Supabase database queries for lending pools.
+ * lib/db/pools.ts
*
- * BEFORE OPTIMIZATION (Issue #39):
- * - Multiple waterfall queries when fetching pools with related data
- * - Each fetch was a separate round-trip to Supabase
- * - No pagination or count estimation for large datasets
- * - Missing indexes on commonly filtered columns
+ * Lending-pool queries. Every function takes the Drizzle handle explicitly so
+ * callers decide whether they are on the HTTP or pooled driver, and so tests
+ * can inject a fake.
*
- * OPTIMIZATION APPROACH:
- * - Single RPC call for fetching pools with optional filters
- * - Explicit column selection (no SELECT *)
- * - Pagination support with consistent ordering
- * - Estimated row counts for large tables
- * - Proper indexes on status, created_at, and other filter columns
+ * Numeric columns come back from Postgres as strings; they are coerced to
+ * numbers at this boundary so the rest of the app never has to.
*/
-import { SupabaseClient } from "@supabase/supabase-js";
+import { asc, desc, eq, gt, sql, type SQL } from "drizzle-orm";
+import type { Db, PooledDb } from "@/lib/db/client";
+import { lendingPools, loans, profiles } from "@/lib/db/schema";
+
+export type AnyDb = Db | PooledDb;
// ─────────────────────────────────────────────────────────────────────────────
// TYPE DEFINITIONS
@@ -50,312 +48,157 @@ export interface PoolFetchResult {
hasMore: boolean;
}
+const POOL_COLUMNS = {
+ id: lendingPools.id,
+ name: lendingPools.name,
+ description: lendingPools.description,
+ status: lendingPools.status,
+ apr_bps: lendingPools.aprBps,
+ total_liquidity: lendingPools.totalLiquidity,
+ available_liquidity: lendingPools.availableLiquidity,
+ total_borrowed: lendingPools.totalBorrowed,
+ borrow_cap: lendingPools.borrowCap,
+ created_at: lendingPools.createdAt,
+ updated_at: lendingPools.updatedAt,
+};
+
// ─────────────────────────────────────────────────────────────────────────────
-// OPTIMIZED FETCH FUNCTIONS
+// FETCH FUNCTIONS
// ─────────────────────────────────────────────────────────────────────────────
-/**
- * Fetch pools with optional filtering and pagination.
- *
- * OPTIMIZATION: Uses explicit column selection and single query.
- * Previously this required multiple queries in waterfall pattern.
- *
- * Indexes used:
- * - idx_lending_pools_status (on status column)
- * - Implicit index on created_at for ordering
- *
- * @param supabase - Supabase client instance
- * @param options - Fetch options (status filter, pagination, ordering)
- * @returns Pool data with pagination metadata
- */
-export async function fetchPools(
- supabase: SupabaseClient,
- options: PoolFetchOptions = {}
-): Promise {
- const {
- status,
- limit = 10,
- offset = 0,
- orderBy = "created_at",
- orderDirection = "desc",
- } = options;
+/** Fetch pools with optional filtering and pagination. */
+export async function fetchPools(db: AnyDb, options: PoolFetchOptions = {}): Promise {
+ const { status, limit = 10, offset = 0, orderBy = "created_at", orderDirection = "desc" } = options;
- // Validate and clamp pagination parameters
const validLimit = Math.min(Math.max(Math.floor(limit) || 10, 1), 100);
const validOffset = Math.max(Math.floor(offset) || 0, 0);
- // Build the query with explicit column selection (no SELECT *)
- let query = supabase
- .from("lending_pools")
- .select(
- "id, name, description, status, apr_bps, total_liquidity, available_liquidity, total_borrowed, borrow_cap, created_at, updated_at",
- { count: "estimated" }
- );
-
- // Add status filter if provided
- if (status) {
- query = query.eq("status", status);
- }
-
- // Apply ordering (uses index on created_at or available_liquidity)
- const ascending = orderDirection === "asc";
- query = query.order(orderBy, { ascending });
+ const where: SQL | undefined = status ? eq(lendingPools.status, status) : undefined;
+ const orderColumn = orderBy === "available_liquidity" ? lendingPools.availableLiquidity : lendingPools.createdAt;
+ const order = orderDirection === "asc" ? asc(orderColumn) : desc(orderColumn);
- // Apply pagination
- query = query.range(validOffset, validOffset + validLimit - 1);
-
- const { data, error, count } = await query;
-
- if (error) {
- throw new Error(`Failed to fetch pools: ${error.message}`);
- }
-
- // Transform raw data to typed Pool objects
- const pools = (data ?? []).map(mapRawPoolToPool);
+ const [rows, [{ count }]] = await Promise.all([
+ db.select(POOL_COLUMNS).from(lendingPools).where(where).orderBy(order).limit(validLimit).offset(validOffset),
+ db.select({ count: sql`count(*)::int` }).from(lendingPools).where(where),
+ ]);
+ const pools = rows.map(mapRawPoolToPool);
return {
pools,
- totalCount: count ?? 0,
- estimatedTotalCount: count ?? 0,
- hasMore: pools.length === validLimit, // Has more if we got a full page
+ totalCount: count,
+ estimatedTotalCount: count,
+ hasMore: validOffset + pools.length < count,
};
}
-/**
- * Fetch a single pool by ID.
- *
- * OPTIMIZATION: Direct single-row lookup with explicit columns.
- * Avoids unnecessary joins or additional queries.
- *
- * @param supabase - Supabase client instance
- * @param poolId - Pool UUID
- * @returns Pool data or null if not found
- */
-export async function fetchPoolById(
- supabase: SupabaseClient,
- poolId: string
-): Promise {
- const { data, error } = await supabase
- .from("lending_pools")
- .select(
- "id, name, description, status, apr_bps, total_liquidity, available_liquidity, total_borrowed, borrow_cap, created_at, updated_at"
- )
- .eq("id", poolId)
- .maybeSingle();
-
- if (error) {
- throw new Error(`Failed to fetch pool ${poolId}: ${error.message}`);
- }
-
- return data ? mapRawPoolToPool(data) : null;
+/** Fetch a single pool by ID. */
+export async function fetchPoolById(db: AnyDb, poolId: string): Promise {
+ const [row] = await db.select(POOL_COLUMNS).from(lendingPools).where(eq(lendingPools.id, poolId)).limit(1);
+ return row ? mapRawPoolToPool(row) : null;
}
/**
- * Fetch pools with active status and available liquidity.
- *
- * OPTIMIZATION: Common query pattern optimized with index on (status, available_liquidity).
- * Used for auto-matching and loan approval.
- *
- * Previously required:
- * 1. Fetch active pools
- * 2. Filter in client code based on liquidity
- *
- * Now: Single query with both filters applied at DB level.
- *
- * @param supabase - Supabase client instance
- * @param minimumLiquidity - Minimum available liquidity required (optional)
- * @returns List of active pools with liquidity
+ * Active pools ordered by available liquidity (desc). Used for auto-matching
+ * and loan approval.
*/
-export async function fetchActivePoolsWithLiquidity(
- supabase: SupabaseClient,
- minimumLiquidity: number = 0
-): Promise {
- let query = supabase
- .from("lending_pools")
- .select(
- "id, name, description, status, apr_bps, total_liquidity, available_liquidity, total_borrowed, borrow_cap, created_at, updated_at"
- )
- .eq("status", "active");
-
- // Only add liquidity filter if minimum is > 0
+export async function fetchActivePoolsWithLiquidity(db: AnyDb, minimumLiquidity: number = 0): Promise {
+ const conditions = [eq(lendingPools.status, "active")];
if (minimumLiquidity > 0) {
- query = query.gt("available_liquidity", minimumLiquidity);
- }
-
- // Order by available liquidity descending for better allocation
- query = query.order("available_liquidity", { ascending: false });
-
- const { data, error } = await query;
-
- if (error) {
- throw new Error(`Failed to fetch active pools: ${error.message}`);
+ conditions.push(gt(lendingPools.availableLiquidity, String(minimumLiquidity)));
}
+ const rows = await db
+ .select(POOL_COLUMNS)
+ .from(lendingPools)
+ .where(sql.join(conditions, sql` and `))
+ .orderBy(desc(lendingPools.availableLiquidity));
+ return rows.map(mapRawPoolToPool);
+}
- return (data ?? []).map(mapRawPoolToPool);
+export interface PendingLoanSummary {
+ id: string;
+ status: string;
+ principal_amount: number;
+ apr_bps: number;
+ duration_days: number;
+ requested_at: string;
+ borrower_id: string;
+ borrower_profile: { full_name: string | null } | null;
}
-/**
- * Fetch pools with admin dashboard data (pools + pending loans with borrower info).
- *
- * OPTIMIZATION: Previously required 2 separate queries:
- * 1. SELECT from lending_pools
- * 2. SELECT from loans with LEFT JOIN to profiles
- *
- * Now: Fetch pools and loans separately but with explicit columns, allowing:
- * - Better caching at HTTP level
- * - Easier to scale with separate RPC calls if needed
- * - Clear separation of concerns
- *
- * @param supabase - Supabase client instance
- * @returns Object containing pools and pending loans
- */
-export async function fetchAdminDashboardPools(
- supabase: SupabaseClient
-): Promise<{
+/** Pools + pending loans (with borrower name) for the admin pool dashboard. */
+export async function fetchAdminDashboardPools(db: AnyDb): Promise<{
pools: Pool[];
- pendingLoans: Array<{
- id: string;
- status: string;
- principal_amount: number;
- apr_bps: number;
- duration_days: number;
- requested_at: string;
- borrower_id: string;
- borrower_profile: { full_name: string | null } | null;
- }>;
+ pendingLoans: PendingLoanSummary[];
}> {
- // Execute both queries in parallel (still 2 queries, but faster than sequential)
- const [poolsRes, loansRes] = await Promise.all([
- supabase
- .from("lending_pools")
- .select(
- "id, name, description, status, apr_bps, total_liquidity, available_liquidity, total_borrowed, borrow_cap, created_at, updated_at"
- )
- .order("created_at", { ascending: false }),
-
- supabase
- .from("loans")
- .select(
- "id, status, principal_amount, apr_bps, duration_days, requested_at, borrower_id, profiles:borrower_id(full_name)"
- )
- .eq("status", "requested")
- .order("requested_at", { ascending: true }),
+ const [poolRows, loanRows] = await Promise.all([
+ db.select(POOL_COLUMNS).from(lendingPools).orderBy(desc(lendingPools.createdAt)),
+ db
+ .select({
+ id: loans.id,
+ status: loans.status,
+ principal_amount: loans.principalAmount,
+ apr_bps: loans.aprBps,
+ duration_days: loans.durationDays,
+ requested_at: loans.requestedAt,
+ borrower_id: loans.borrowerId,
+ borrower_name: profiles.fullName,
+ })
+ .from(loans)
+ .leftJoin(profiles, eq(profiles.id, loans.borrowerId))
+ .where(eq(loans.status, "requested"))
+ .orderBy(asc(loans.requestedAt)),
]);
- if (poolsRes.error) {
- throw new Error(`Failed to fetch pools: ${poolsRes.error.message}`);
- }
-
- if (loansRes.error) {
- throw new Error(`Failed to fetch pending loans: ${loansRes.error.message}`);
- }
-
- const pools = (poolsRes.data ?? []).map(mapRawPoolToPool);
-
- const pendingLoans = (loansRes.data ?? []).map((loan) => {
- // Handle Supabase relation cardinality: profiles can be object or array
- const raw = loan.profiles;
- const profileData = Array.isArray(raw)
- ? (raw[0] as { full_name: string | null } | undefined) ?? null
- : (raw as { full_name: string | null } | null);
-
- return {
- id: String(loan.id),
- status: String(loan.status ?? "requested"),
+ return {
+ pools: poolRows.map(mapRawPoolToPool),
+ pendingLoans: loanRows.map((loan) => ({
+ id: loan.id,
+ status: loan.status,
principal_amount: Number(loan.principal_amount ?? 0),
- apr_bps: Number(loan.apr_bps ?? 0),
- duration_days: Number(loan.duration_days ?? 30),
- requested_at: String(loan.requested_at ?? ""),
- borrower_id: String(loan.borrower_id),
- borrower_profile: profileData
- ? { full_name: profileData.full_name ?? null }
- : null,
- };
- });
-
- return { pools, pendingLoans };
+ apr_bps: loan.apr_bps,
+ duration_days: loan.duration_days,
+ requested_at: loan.requested_at.toISOString(),
+ borrower_id: loan.borrower_id,
+ borrower_profile: loan.borrower_name !== null ? { full_name: loan.borrower_name } : null,
+ })),
+ };
}
// ─────────────────────────────────────────────────────────────────────────────
-// HELPER FUNCTIONS
+// HELPERS
// ─────────────────────────────────────────────────────────────────────────────
interface RawPool {
- id: unknown;
- name?: unknown;
- description?: unknown;
- status?: unknown;
- apr_bps?: unknown;
- total_liquidity?: unknown;
- available_liquidity?: unknown;
- total_borrowed?: unknown;
- borrow_cap?: unknown;
- created_at?: unknown;
- updated_at?: unknown;
+ id: string;
+ name: string;
+ description: string | null;
+ status: "active" | "paused" | "closed";
+ apr_bps: number;
+ total_liquidity: string | number;
+ available_liquidity: string | number;
+ total_borrowed: string | number;
+ borrow_cap: string | number | null;
+ created_at: Date | string;
+ updated_at: Date | string;
}
-/**
- * Transform raw database row to typed Pool object.
- * Ensures consistent type coercion across all fetch functions.
- */
-function mapRawPoolToPool(raw: RawPool): Pool {
+function toIso(value: Date | string): string {
+ return value instanceof Date ? value.toISOString() : String(value);
+}
+
+/** Coerce a raw row to a typed Pool (numeric → number, timestamps → ISO). */
+export function mapRawPoolToPool(raw: RawPool): Pool {
return {
- id: String(raw.id),
- name: String(raw.name ?? ""),
- description: raw.description ? String(raw.description) : null,
- status: String(raw.status ?? "paused") as "active" | "paused" | "closed",
- apr_bps: Number(raw.apr_bps ?? 0),
+ id: raw.id,
+ name: raw.name,
+ description: raw.description,
+ status: raw.status,
+ apr_bps: Number(raw.apr_bps),
total_liquidity: Number(raw.total_liquidity ?? 0),
available_liquidity: Number(raw.available_liquidity ?? 0),
total_borrowed: Number(raw.total_borrowed ?? 0),
borrow_cap: raw.borrow_cap !== null && raw.borrow_cap !== undefined ? Number(raw.borrow_cap) : null,
- created_at: String(raw.created_at ?? ""),
- updated_at: String(raw.updated_at ?? ""),
+ created_at: toIso(raw.created_at),
+ updated_at: toIso(raw.updated_at),
};
}
-
-// ─────────────────────────────────────────────────────────────────────────────
-// INDEX RECOMMENDATIONS
-// ─────────────────────────────────────────────────────────────────────────────
-
-/**
- * RECOMMENDED INDEXES FOR OPTIMAL PERFORMANCE:
- *
- * Current indexes (in 01_core_schema.sql):
- * - idx_lending_pools_status: Used by status filters ✓
- *
- * RECOMMENDED ADDITIONAL INDEXES:
- *
- * 1. Composite index for active pools with available liquidity:
- * CREATE INDEX idx_lending_pools_status_available
- * ON public.lending_pools (status, available_liquidity DESC)
- * REASON: Speeds up fetchActivePoolsWithLiquidity queries
- * Allows index-only scans for admin auto-match operations
- *
- * 2. Index on created_at for default ordering:
- * CREATE INDEX idx_lending_pools_created_at_desc
- * ON public.lending_pools (created_at DESC)
- * REASON: Default sort order in fetchPools uses created_at
- * Improves pagination performance on large tables
- *
- * 3. Index on available_liquidity for alternative sort:
- * CREATE INDEX idx_lending_pools_available_liquidity
- * ON public.lending_pools (available_liquidity DESC)
- * REASON: When users sort by available liquidity
- * Optimizes fetchPools with orderBy: 'available_liquidity'
- *
- * To apply these indexes, run in Supabase SQL editor:
- *
- * CREATE INDEX IF NOT EXISTS idx_lending_pools_status_available
- * ON public.lending_pools (status, available_liquidity DESC);
- *
- * CREATE INDEX IF NOT EXISTS idx_lending_pools_created_at_desc
- * ON public.lending_pools (created_at DESC);
- *
- * CREATE INDEX IF NOT EXISTS idx_lending_pools_available_liquidity
- * ON public.lending_pools (available_liquidity DESC);
- *
- * ESTIMATED IMPROVEMENT:
- * - Reduces query time from 50-200ms to 5-20ms for tables with 10k+ pools
- * - Compound index saves full table scans on status + liquidity queries
- */
diff --git a/lib/db/queries.ts b/lib/db/queries.ts
new file mode 100644
index 0000000..9c8f9cc
--- /dev/null
+++ b/lib/db/queries.ts
@@ -0,0 +1,171 @@
+/**
+ * lib/db/queries.ts
+ *
+ * Shared read queries for server components. Each returns the snake_case row
+ * shapes from lib/db/rows.ts. Functions accept a nullable db so pages can
+ * render an empty state when DATABASE_URL is not configured.
+ */
+
+import { and, asc, desc, eq, inArray, lt, sql } from "drizzle-orm";
+import type { Db } from "@/lib/db/client";
+import {
+ ledgerTransactions,
+ loanFundings,
+ loanRepayments,
+ loans,
+ profiles,
+ reputationEvents,
+ reputationSnapshots,
+} from "@/lib/db/schema";
+import {
+ ledgerToRow,
+ loanToRow,
+ profileToRow,
+ repaymentToRow,
+ reputationEventToRow,
+ snapshotToRow,
+ type LedgerRow,
+ type LoanRow,
+ type ProfileRow,
+ type RepaymentRow,
+ type ReputationEventRow,
+ type SnapshotRow,
+} from "@/lib/db/rows";
+
+export async function getProfile(db: Db | null, userId: string): Promise {
+ if (!db) return null;
+ const [row] = await db.select().from(profiles).where(eq(profiles.id, userId)).limit(1);
+ return row ? profileToRow(row) : null;
+}
+
+export async function getProfilesByIds(db: Db | null, ids: string[]): Promise