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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions apps/interface/src/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { IckbSdk, type SystemState } from "@ickb/sdk";
import {
CKB,
direction2Symbol,
reservedCKB,
sanitizeAmountInput,
spendableCkb,
symbol2Direction,
toText,
} from "./utils.ts";
Expand Down Expand Up @@ -41,8 +41,8 @@ export default function Form({
setRawText(direction2Symbol(!isCkb2Udt) + text);
};

const spendableCkb = ckbAvailable > reservedCKB ? ckbAvailable - reservedCKB : 0n;
const nativeCkb = spendableCkb < ckbNative ? spendableCkb : ckbNative;
const maxCkb = spendableCkb(ckbAvailable);
const nativeCkb = maxCkb < ckbNative ? maxCkb : ckbNative;

let a = {
name: "CKB",
Expand Down
22 changes: 3 additions & 19 deletions apps/interface/src/queries.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
projectAccountAvailability,
projectConversionTransactionContext,
type SystemState,
} from "@ickb/sdk";
import {
Expand Down Expand Up @@ -88,7 +88,7 @@ export async function getL1State(
walletConfig.accountLocks,
);
const { system, user, account } = sdkState;
const projection = projectAccountAvailability(account, user.orders, {
const { projection, context: conversionContext } = projectConversionTransactionContext(system, account, user.orders, {
collectedOrdersAvailable: true,
});
const hasMatchable = user.orders.some((group) => group.order.isMatchable());
Expand All @@ -99,30 +99,14 @@ export async function getL1State(
ickbBalance,
ckbAvailable,
ickbAvailable,
readyWithdrawals,
pendingWithdrawals,
availableOrders,
pendingOrders,
} = projection;

const estimatedMaturity = [
system.tip.timestamp,
...pendingWithdrawals.map((group) => group.owned.maturity.toUnix(system.tip)),
...pendingOrders
.map((group) => group.order.maturity)
.filter((maturity): maturity is bigint => maturity !== undefined),
].reduce((best, maturity) => (best > maturity ? best : maturity));

const txContext: TransactionContext = {
system,
...conversionContext,
capacityCells: account.capacityCells,
nativeUdtCells: account.nativeUdtCells,
receipts: account.receipts,
readyWithdrawals,
availableOrders,
ckbAvailable,
ickbAvailable,
estimatedMaturity,
};

return {
Expand Down
14 changes: 8 additions & 6 deletions apps/interface/src/transaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { byte32FromByte } from "@ickb/testkit";
import { afterEach, describe, expect, it, vi } from "vitest";
import { buildTransactionPreview } from "./transaction.ts";
import type { TransactionContext } from "./transaction.ts";
import type { WalletConfig } from "./utils.ts";
import { CKB, reservedCKB, type WalletConfig } from "./utils.ts";

type BuildConversionTransactionMock = ReturnType<
typeof vi.fn<WalletConfig["sdk"]["buildConversionTransaction"]>
Expand Down Expand Up @@ -134,6 +134,8 @@ describe("buildTransactionPreview", () => {
.resolves.toMatchObject({ error: "Amount must be positive" });
await expect(buildTransactionPreview(context({ ckbAvailable: 1n }), true, 2n, config))
.resolves.toMatchObject({ error: "Not enough CKB" });
await expect(buildTransactionPreview(context({ ckbAvailable: reservedCKB + CKB }), true, reservedCKB + CKB, config))
.resolves.toMatchObject({ error: "Not enough CKB" });
await expect(buildTransactionPreview(context({ ickbAvailable: 1n }), false, 2n, config))
.resolves.toMatchObject({ error: "Not enough iCKB" });
expect(buildConversionTransaction).not.toHaveBeenCalled();
Expand All @@ -156,7 +158,7 @@ describe("buildTransactionPreview", () => {
const completeTransaction = completeTransactionMock();
vi.spyOn(ccc.Transaction.prototype, "getFee").mockResolvedValue(42n);
const txContext = context({
ckbAvailable: 7n,
ckbAvailable: reservedCKB + 7n,
system: { ...context().system, feeRate: 9n },
});
const config = walletConfigWith({
Expand Down Expand Up @@ -215,7 +217,7 @@ describe("buildTransactionPreview", () => {
sdk: { buildConversionTransaction: buildConversionTransactionMock(failedPlan(reason, 77n)) },
});

await expect(buildTransactionPreview(context({ ckbAvailable: 1n }), true, 1n, config))
await expect(buildTransactionPreview(context({ ckbAvailable: reservedCKB + 1n }), true, 1n, config))
.resolves.toMatchObject({ error: message, estimatedMaturity: 77n });
}
});
Expand All @@ -242,7 +244,7 @@ describe("buildTransactionPreview", () => {
vi.spyOn(ccc.Transaction.prototype, "getFee").mockResolvedValue(1n);

await buildTransactionPreview(
context({ ckbAvailable: 1n }),
context({ ckbAvailable: reservedCKB + 1n }),
true,
1n,
walletConfigWith({
Expand All @@ -264,7 +266,7 @@ describe("buildTransactionPreview", () => {
.mockRejectedValue(new Error("planner failed")),
},
});
await expect(buildTransactionPreview(context({ ckbAvailable: 1n }), true, 1n, plannerFailure))
await expect(buildTransactionPreview(context({ ckbAvailable: reservedCKB + 1n }), true, 1n, plannerFailure))
.resolves.toMatchObject({ error: "planner failed" });

const completionFailure = walletConfigWith({
Expand All @@ -274,7 +276,7 @@ describe("buildTransactionPreview", () => {
.mockRejectedValue(new Error("completion failed")),
},
});
await expect(buildTransactionPreview(context({ ckbAvailable: 1n }), true, 1n, completionFailure))
await expect(buildTransactionPreview(context({ ckbAvailable: reservedCKB + 1n }), true, 1n, completionFailure))
.resolves.toMatchObject({ error: "completion failed" });
});
});
3 changes: 2 additions & 1 deletion apps/interface/src/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from "@ickb/sdk";
import {
errorMessageOf,
spendableCkb,
txInfoPadding,
type TxInfo,
type WalletConfig,
Expand All @@ -26,7 +27,7 @@ export async function buildTransactionPreview(
return txInfoWithError("Amount must be positive", context.estimatedMaturity);
}

if (isCkb2Udt && amount > context.ckbAvailable) {
if (isCkb2Udt && amount > spendableCkb(context.ckbAvailable)) {
return txInfoWithError("Not enough CKB", context.estimatedMaturity);
}

Expand Down
4 changes: 4 additions & 0 deletions apps/interface/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ export const CKB = ccc.fixedPointFrom(1);
// reservedCKB are reserved for state rent in conversions
export const reservedCKB = 600n * CKB;

export function spendableCkb(ckbAvailable: bigint): bigint {
return ckbAvailable > reservedCKB ? ckbAvailable - reservedCKB : 0n;
}

export function parseWalletChain(walletChain: string): WalletChainParts {
const separatorIndex = walletChain.lastIndexOf("_");
if (separatorIndex <= 0 || separatorIndex === walletChain.length - 1) {
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"forks:ccc:plan": "node scripts/forks-ccc.mjs --plan",
"forks:ccc:smoke": "node scripts/forks-ccc-smoke.mjs",
"forks:ccc:watch": "node scripts/forks-ccc.mjs --watch",
"live:generate-config": "node scripts/ickb-generate-config.mjs",
"live:preflight": "node scripts/ickb-live-preflight.mjs",
"coworker:ask": "opencode run --pure --agent plan"
},
"engines": {
Expand Down
Loading
Loading