Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
8f0d226
refactor: replace WASM dependency in SessionProvider with pure TypeSc…
kronosapiens Apr 6, 2026
4ed815f
refactor: rename TsSessionAccount to CartridgeSessionAccount
kronosapiens Apr 6, 2026
3e44217
refactor: rename session-account.ts to account.ts and outside-executi…
kronosapiens Apr 6, 2026
7e84415
refactor: fold signerToGuid from guid.ts into shared.ts
kronosapiens Apr 6, 2026
99b8f2b
refactor: rename session/ts/ to session/internal/
kronosapiens Apr 6, 2026
e69e29b
refactor: route node/ imports through session public API
kronosapiens Apr 6, 2026
32772de
refactor: remove Js prefixes from session types
kronosapiens Apr 6, 2026
c2fa74d
refactor: restore Felt type alias in session types
kronosapiens Apr 6, 2026
4f2ee2c
refactor: rename session/internal/shared.ts to utils.ts
kronosapiens Apr 7, 2026
9508593
fix: correct session fallback, sorting, and time-bound validation
kronosapiens Apr 7, 2026
e44e446
test: add utils and multi-call execution coverage
kronosapiens Apr 13, 2026
d6f3ba0
refactor: use glob exports for session/internal modules
kronosapiens Apr 13, 2026
7b69b26
refactor: import session types through public API in utils.ts
kronosapiens Apr 13, 2026
b402e82
fix: correct merkle leaf hashing for TypedDataPolicy and ApprovalPolicy
kronosapiens Apr 13, 2026
e06e90e
docs: add context for execute() fallback removal in session accounts
kronosapiens Apr 13, 2026
369cced
refactor: flip SNIP-9 error check to positive condition
kronosapiens Apr 13, 2026
3f960aa
fix: guard unauthorized policies, harden subscribe, fix test naming
kronosapiens Apr 13, 2026
0091463
test: trim redundant tests without losing coverage
kronosapiens Apr 13, 2026
09f6702
style: fix prettier formatting in subscribe test
kronosapiens Apr 13, 2026
497bfb9
fix: enable CapacitorHttp to bypass CORS in native WebView
kronosapiens Apr 15, 2026
927d166
refactor: extract STRK contract address constant in tests
kronosapiens Apr 15, 2026
1777365
chore: remove unused @cartridge/controller-wasm dependency from contr…
kronosapiens Apr 15, 2026
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
5 changes: 5 additions & 0 deletions examples/capacitor/capacitor.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ const config: CapacitorConfig = {
appId: "gg.cartridge.controller.capacitor",
appName: "Cartridge Session",
webDir: "dist",
plugins: {
CapacitorHttp: {
enabled: true,
},
},
server: {
hostname: "controller-capacitor", // Recommended: Set a custom hostname for production
androidScheme: "https",
Expand Down
6 changes: 3 additions & 3 deletions examples/capacitor/ios/App/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ EXTERNAL SOURCES:
:path: "../../../../node_modules/.pnpm/@capacitor+ios@6.2.1_@capacitor+core@6.2.1/node_modules/@capacitor/ios"

SPEC CHECKSUMS:
Capacitor: c95400d761e376be9da6be5a05f226c0e865cebf
CapacitorApp: a19ccb4f5f32d2534be5c6f524d5a9b614a1e8a7
CapacitorBrowser: 825163a1d6adce944ac7b9e3cab101146b5a9df8
Capacitor: 1e0d0e7330dea9f983b50da737d8918abcf273f8
CapacitorApp: 22c94146dfaae1159ab7059cbd51e76d9f2d45ec
CapacitorBrowser: 9e9a268445f9472b04b68320930c59bdc4d360fa
CapacitorCordova: 8d93e14982f440181be7304aa9559ca631d77fff

PODFILE CHECKSUM: f6877332d3ea075bd6f7d8f89d3ecbc9a0bb06a3
Expand Down
1 change: 0 additions & 1 deletion packages/controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
}
},
"dependencies": {
"@cartridge/controller-wasm": "catalog:",
"@cartridge/penpal": "catalog:",
"micro-sol-signer": "^0.5.0",
"bs58": "^6.0.0",
Expand Down
186 changes: 186 additions & 0 deletions packages/controller/src/__tests__/account.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
import { hash } from "starknet";
import { CartridgeSessionAccount } from "../session/internal/account";
import { isSnip9CompatibilityError } from "../session/internal/errors";
import type { CallPolicy, Session } from "../session/internal/types";
import { normalizeFelt } from "../session/internal/utils";

// Mock global fetch
const mockFetch = jest.fn();
(global as any).fetch = mockFetch;

beforeEach(() => {
mockFetch.mockReset();
});

const TRANSFER_SELECTOR = normalizeFelt(hash.getSelectorFromName("transfer"));
const TEST_RPC = "https://rpc.test";
const TEST_PRIVATE_KEY = "0x1";
const TEST_ADDRESS = "0x1234";
const TEST_OWNER_GUID = "0x5678";
const TEST_CHAIN_ID = "SN_SEPOLIA";
const STRK_CONTRACT_ADDRESS =
"0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7";

const TEST_POLICIES: CallPolicy[] = [
{
target: STRK_CONTRACT_ADDRESS,
method: TRANSFER_SELECTOR,
authorized: true,
},
];

const TEST_SESSION: Session = {
policies: TEST_POLICIES,
expiresAt: 9999999999,
metadataHash: "0x0",
sessionKeyGuid: "0x0",
guardianKeyGuid: "0x0",
};

describe("CartridgeSessionAccount", () => {
test("executeFromOutside sends correct RPC request", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
jsonrpc: "2.0",
id: 1,
result: { transaction_hash: "0xtxhash" },
}),
});

const account = CartridgeSessionAccount.newAsRegistered(
TEST_RPC,
TEST_PRIVATE_KEY,
TEST_ADDRESS,
TEST_OWNER_GUID,
TEST_CHAIN_ID,
TEST_SESSION,
);

const result = await account.executeFromOutside([
{
contractAddress: STRK_CONTRACT_ADDRESS,
entrypoint: "transfer",
calldata: ["0x1", "0x2", "0x0"],
},
]);

expect(result.transaction_hash).toBe("0xtxhash");
expect(mockFetch).toHaveBeenCalledTimes(1);

const [url, options] = mockFetch.mock.calls[0];
expect(url).toBe(TEST_RPC);
const body = JSON.parse(options.body);
expect(body.method).toBe("cartridge_addExecuteOutsideTransaction");
expect(body.params.address).toBe(TEST_ADDRESS);
expect(body.params.outside_execution).toBeDefined();
expect(body.params.signature).toBeDefined();
expect(Array.isArray(body.params.signature)).toBe(true);
});

test("executeFromOutside throws on RPC error", async () => {
mockFetch.mockResolvedValueOnce({
ok: true,
json: async () => ({
jsonrpc: "2.0",
id: 1,
error: { code: -32000, message: "execution failed" },
}),
});

const account = CartridgeSessionAccount.newAsRegistered(
TEST_RPC,
TEST_PRIVATE_KEY,
TEST_ADDRESS,
TEST_OWNER_GUID,
TEST_CHAIN_ID,
TEST_SESSION,
);

await expect(
account.executeFromOutside([
{
contractAddress: STRK_CONTRACT_ADDRESS,
entrypoint: "transfer",
calldata: [],
},
]),
).rejects.toThrow("execution failed");
});

test("internal CartridgeSessionAccount only exposes executeFromOutside, not execute", () => {
const account = CartridgeSessionAccount.newAsRegistered(
TEST_RPC,
TEST_PRIVATE_KEY,
TEST_ADDRESS,
TEST_OWNER_GUID,
TEST_CHAIN_ID,
TEST_SESSION,
);
expect((account as any).execute).toBeUndefined();
expect(account.executeFromOutside).toBeDefined();
});

test("executeFromOutside propagates non-SNIP-9 errors without swallowing", async () => {
const networkError = new Error("network timeout");
mockFetch.mockRejectedValueOnce(networkError);

const account = CartridgeSessionAccount.newAsRegistered(
TEST_RPC,
TEST_PRIVATE_KEY,
TEST_ADDRESS,
TEST_OWNER_GUID,
TEST_CHAIN_ID,
TEST_SESSION,
);

await expect(
account.executeFromOutside([
{
contractAddress: STRK_CONTRACT_ADDRESS,
entrypoint: "transfer",
calldata: [],
},
]),
).rejects.toThrow("network timeout");
});

describe("isSnip9CompatibilityError", () => {
test("returns true for OUTSIDE_EXECUTION_NOT_SUPPORTED code", () => {
const err = Object.assign(new Error("failed"), {
code: "OUTSIDE_EXECUTION_NOT_SUPPORTED",
});
expect(isSnip9CompatibilityError(err)).toBe(true);
});

test("returns true for snip-9 message patterns", () => {
expect(
isSnip9CompatibilityError(
new Error("account is not compatible with snip-9"),
),
).toBe(true);
expect(
isSnip9CompatibilityError(new Error("entrypoint does not exist")),
).toBe(true);
});

test("returns false for generic errors", () => {
expect(isSnip9CompatibilityError(new Error("network timeout"))).toBe(
false,
);
});

test("returns false for null/undefined", () => {
expect(isSnip9CompatibilityError(null)).toBe(false);
expect(isSnip9CompatibilityError(undefined)).toBe(false);
});

test("checks nested cause for error code", () => {
const cause = Object.assign(new Error("inner"), {
code: "OUTSIDE_EXECUTION_UNSUPPORTED",
});
const err = Object.assign(new Error("outer"), { cause });
expect(isSnip9CompatibilityError(err)).toBe(true);
});
});
});
Loading
Loading