diff --git a/package.json b/package.json
index b392a97..de5a5bb 100644
--- a/package.json
+++ b/package.json
@@ -6,6 +6,7 @@
"scripts": {
"build": "rollup -c",
"dev": "rollup -c -w",
+ "test": "node tests/stake-cap-approval.test.mjs",
"start": "sirv build --single",
"deploy-ipfs": "npm run build && npx ipfs-deploy build",
"postinstall": "patch-package"
diff --git a/src/components/modals/StakeCAP.svelte b/src/components/modals/StakeCAP.svelte
index 2e4bf18..65fae45 100644
--- a/src/components/modals/StakeCAP.svelte
+++ b/src/components/modals/StakeCAP.svelte
@@ -13,9 +13,14 @@
import { focusInput, hideModal } from '@lib/ui'
import LabelValue from '../layout/LabelValue.svelte'
- let amount, isSubmitting, walletBalance = "0.0";
+ const CAP_APPROVAL_SPENDER = 'Staking';
+
+ let amount, isSubmitting, isCheckingAllowance = true, isApproving = false, walletBalance = "0.0";
$: formattedWalletBalance = formatCAPForDisplay(walletBalance);
+ $: capAllowance = $allowances['CAP']?.[CAP_APPROVAL_SPENDER];
+ $: isAllowancePending = amount && (isCheckingAllowance || capAllowance === undefined);
+ $: requiresApproval = amount && !isAllowancePending && capAllowance * 1 <= amount * 1;
async function submit() {
@@ -31,11 +36,25 @@
}
async function checkAllowance() {
- await getAllowance('CAP', 'FundStore');
+ isCheckingAllowance = true;
+ try {
+ await getAllowance('CAP', CAP_APPROVAL_SPENDER);
+ } finally {
+ isCheckingAllowance = false;
+ }
}
async function _approveAsset() {
- const result = await approveAsset('CAP', 'FundStore');
+ isApproving = true;
+ try {
+ const success = await approveAsset('CAP', CAP_APPROVAL_SPENDER);
+ if (success) {
+ await checkAllowance();
+ await getBalance();
+ }
+ } finally {
+ isApproving = false;
+ }
}
async function getBalance() {
@@ -74,8 +93,8 @@
- {#if $allowances['CAP']?.['FundStore'] * 1 <= amount * 1}
-
+ {#if isAllowancePending || requiresApproval}
+
{:else}
{/if}
diff --git a/tests/stake-cap-approval.test.mjs b/tests/stake-cap-approval.test.mjs
new file mode 100644
index 0000000..87c0106
--- /dev/null
+++ b/tests/stake-cap-approval.test.mjs
@@ -0,0 +1,56 @@
+import assert from 'node:assert/strict';
+import { readFileSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { dirname, resolve } from 'node:path';
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const stakeModal = readFileSync(resolve(__dirname, '../src/components/modals/StakeCAP.svelte'), 'utf8');
+const capApi = readFileSync(resolve(__dirname, '../src/api/cap.js'), 'utf8');
+
+assert.match(
+ stakeModal,
+ /const CAP_APPROVAL_SPENDER = 'Staking';/,
+ 'Stake CAP approval should target the same Staking contract used by depositCAP.'
+);
+
+assert.match(
+ stakeModal,
+ /getAllowance\('CAP', CAP_APPROVAL_SPENDER\)/,
+ 'Stake CAP allowance checks should use the staking approval spender.'
+);
+
+assert.match(
+ stakeModal,
+ /approveAsset\('CAP', CAP_APPROVAL_SPENDER\)/,
+ 'Stake CAP approval transactions should use the staking approval spender.'
+);
+
+assert.doesNotMatch(
+ stakeModal,
+ /FundStore/,
+ 'Stake CAP modal must not use FundStore for CAP approval.'
+);
+
+assert.match(
+ stakeModal,
+ /isAllowancePending/,
+ 'Stake CAP modal should keep the approval path pending while allowance is loading.'
+);
+
+assert.match(
+ stakeModal,
+ /isLoading=\{isCheckingAllowance \|\| isApproving\}/,
+ 'Approve CAP button should show a loading state during allowance checks and approval transactions.'
+);
+
+assert.match(
+ stakeModal,
+ /if \(success\) \{\s+await checkAllowance\(\);\s+await getBalance\(\);/s,
+ 'Stake CAP modal should refresh allowance and wallet balance after approval succeeds.'
+);
+
+assert.match(
+ capApi,
+ /getContract\('Staking', true\)/,
+ 'depositCAP should continue staking through the Staking contract.'
+);