@temple-digital-group/temple-canton-js 2.0.5 → 2.0.6-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import config from "../../src/config/index.js";
2
2
  import axios from "axios";
3
3
  import { getDisclosures } from "../api/index.js";
4
4
  import { getUserId } from "../api/tokenStore.js";
5
- import { getAdapterPartyId, getWalletAdapter, submitCommand, payDueGasIfAny } from "../../src/canton/walletAdapter.js";
5
+ import { getAdapterPartyId, getWalletAdapter, submitCommand, payDueGasIfAny } from "./walletAdapter.js";
6
6
  import { normalizeAssetId, instrumentCatalog, instrumentIdToSymbol, resolveOnChainInstrumentId } from "../../src/canton/instrumentCatalog.js";
7
7
  import { randomUUID, shouldUseLedgerForMetadata, normalizeContractId, resolveInstrumentDefinition, getInstrumentRegistrar, resolveProvider, dedupeDisclosedContracts, buildHeaders, DEFAULT_UTILITY_CONTEXT_KEYS, } from "./helpers.js";
8
8
  import { resolveAmuletContext, resolveUtilityInstrumentConfiguration, resolveUtilityAllocationFactory, getAmuletHoldingsForParty, getUtilityHoldingsForParty, getUtxoCount, getAmuletRules, getOpenMiningRounds, } from "../../src/canton/index.js";
@@ -66,7 +66,11 @@ export declare function shouldUseLedgerForMetadata(): boolean;
66
66
  export declare function normalizeContractId(value: unknown): unknown;
67
67
  /** Recursively normalize contract references in an object. */
68
68
  export declare function normalizeContractReferences(value: unknown): unknown;
69
- /** Resolve provider: use the passed value, or fall back to the wallet adapter's provider. */
69
+ /**
70
+ * Resolve the active-contracts reader: the explicit provider override when given,
71
+ * otherwise the active wallet adapter (the universal wrapper). Returns null when
72
+ * no wallet source is available, so callers fall back to the ledger API.
73
+ */
70
74
  export declare function resolveProvider(provider: unknown): unknown;
71
75
  /** Resolve an instrument definition from the catalog. */
72
76
  export declare function resolveInstrumentDefinition(assetId: string): Record<string, unknown> | null;
@@ -1,7 +1,7 @@
1
1
  import config from "../../src/config/index.js";
2
2
  import axios from "axios";
3
3
  import { getJWTToken } from "../../src/auth0/index.js";
4
- import { getAdapterProvider } from "../../src/canton/walletAdapter.js";
4
+ import { getActiveContractReader } from "./wallet/service.js";
5
5
  import { instrumentCatalog, normalizeAssetId } from "../../src/canton/instrumentCatalog.js";
6
6
  // ─── Constants ───────────────────────────────────────────────────────────────
7
7
  export const DEFAULT_AMULET_CONTEXT_KEYS = {
@@ -89,9 +89,13 @@ export function normalizeContractReferences(value) {
89
89
  }
90
90
  return value;
91
91
  }
92
- /** Resolve provider: use the passed value, or fall back to the wallet adapter's provider. */
92
+ /**
93
+ * Resolve the active-contracts reader: the explicit provider override when given,
94
+ * otherwise the active wallet adapter (the universal wrapper). Returns null when
95
+ * no wallet source is available, so callers fall back to the ledger API.
96
+ */
93
97
  export function resolveProvider(provider) {
94
- return provider || getAdapterProvider() || null;
98
+ return getActiveContractReader(provider);
95
99
  }
96
100
  // ─── Instrument Catalog ──────────────────────────────────────────────────────
97
101
  /** Resolve an instrument definition from the catalog. */
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Universal Wallet Adapter contract.
3
+ *
4
+ * Every wallet (Loop, and future providers such as Console or BitGo) is
5
+ * represented as a *normalized adapter* implementing the capability interface
6
+ * defined here. The rest of the SDK never talks to a wallet/provider SDK
7
+ * directly — it goes through the wallet service (./service.js), which dispatches
8
+ * to the active adapter.
9
+ *
10
+ * Capabilities are split into two tiers:
11
+ *
12
+ * - REQUIRED: getPartyId, getActiveContracts, submitCommand.
13
+ * A wallet that cannot provide these is not usable. `defineWalletAdapter`
14
+ * throws if a factory omits one, and the service throws if a required
15
+ * capability is invoked while no adapter is configured.
16
+ *
17
+ * - OPTIONAL: payDueGasIfAny, getHolding, getAmuletRules, getOpenMiningRounds,
18
+ * disconnect. Not every wallet supports these (e.g. a custodial wallet may
19
+ * handle fees itself, a non-amulet wallet has no mining rounds). When a
20
+ * factory omits an optional capability, it is filled with a safe no-op so
21
+ * callers can invoke it unconditionally — it simply "passes".
22
+ *
23
+ * Add a new wallet by writing a `create<Name>WalletAdapter()` factory that
24
+ * builds a definition object and returns `defineWalletAdapter(definition)`.
25
+ */
26
+ /** A normalized wallet adapter produced by {@link defineWalletAdapter}. */
27
+ export interface WalletAdapter {
28
+ /** Provider id, e.g. "loop". */
29
+ readonly id: string;
30
+ /** True for server-side adapters that sign/submit locally. */
31
+ readonly isServer: boolean;
32
+ /** Resolve the wallet's primary party id. */
33
+ getPartyId(): string | null;
34
+ /** Read active contracts matching `args` via the wallet. */
35
+ getActiveContracts(args: unknown): Promise<unknown>;
36
+ /** Sign and submit a ledger command via the wallet. */
37
+ submitCommand(command: unknown): Promise<unknown>;
38
+ /** Pay any outstanding network gas. No-op when unsupported. */
39
+ payDueGasIfAny(): Promise<void>;
40
+ /** Read a holding via the wallet. Resolves null when unsupported. */
41
+ getHolding(...args: unknown[]): Promise<unknown>;
42
+ /** Resolve amulet rules via the wallet. Resolves null when unsupported. */
43
+ getAmuletRules(...args: unknown[]): Promise<unknown>;
44
+ /** Resolve open mining rounds via the wallet. Resolves null when unsupported. */
45
+ getOpenMiningRounds(...args: unknown[]): Promise<unknown>;
46
+ /** Disconnect the wallet. No-op when unsupported. */
47
+ disconnect(): Promise<void>;
48
+ /** Raw provider object for legacy getAdapterProvider() consumers, or null. */
49
+ getRawProvider(): unknown;
50
+ }
51
+ /** Capability definition supplied by a `create<Name>WalletAdapter()` factory. */
52
+ export interface WalletAdapterDefinition {
53
+ id?: string;
54
+ isServer?: boolean;
55
+ getPartyId(): string | null;
56
+ getActiveContracts(args: unknown): Promise<unknown> | unknown;
57
+ submitCommand(command: unknown): Promise<unknown> | unknown;
58
+ payDueGasIfAny?(): Promise<void> | void;
59
+ getHolding?(...args: unknown[]): Promise<unknown> | unknown;
60
+ getAmuletRules?(...args: unknown[]): Promise<unknown> | unknown;
61
+ getOpenMiningRounds?(...args: unknown[]): Promise<unknown> | unknown;
62
+ disconnect?(): Promise<void> | void;
63
+ getRawProvider?(): unknown;
64
+ }
65
+ /** Required capabilities — a usable wallet adapter must implement all of these. */
66
+ export declare const REQUIRED_CAPABILITIES: readonly ["getPartyId", "getActiveContracts", "submitCommand"];
67
+ /** Optional capabilities — filled with safe no-ops when a wallet does not support them. */
68
+ export declare const OPTIONAL_CAPABILITIES: readonly ["payDueGasIfAny", "getHolding", "getAmuletRules", "getOpenMiningRounds", "disconnect"];
69
+ /** Returns true if `value` was produced by {@link defineWalletAdapter}. */
70
+ export declare function isWalletAdapter(value: unknown): value is WalletAdapter;
71
+ /**
72
+ * Build a normalized wallet adapter from a factory definition.
73
+ *
74
+ * Validates that every required capability is implemented and fills any missing
75
+ * optional capability with a safe no-op. The returned object is branded so the
76
+ * service and facade can distinguish it from a raw provider SDK instance.
77
+ */
78
+ export declare function defineWalletAdapter(definition: WalletAdapterDefinition): WalletAdapter;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Universal Wallet Adapter contract.
3
+ *
4
+ * Every wallet (Loop, and future providers such as Console or BitGo) is
5
+ * represented as a *normalized adapter* implementing the capability interface
6
+ * defined here. The rest of the SDK never talks to a wallet/provider SDK
7
+ * directly — it goes through the wallet service (./service.js), which dispatches
8
+ * to the active adapter.
9
+ *
10
+ * Capabilities are split into two tiers:
11
+ *
12
+ * - REQUIRED: getPartyId, getActiveContracts, submitCommand.
13
+ * A wallet that cannot provide these is not usable. `defineWalletAdapter`
14
+ * throws if a factory omits one, and the service throws if a required
15
+ * capability is invoked while no adapter is configured.
16
+ *
17
+ * - OPTIONAL: payDueGasIfAny, getHolding, getAmuletRules, getOpenMiningRounds,
18
+ * disconnect. Not every wallet supports these (e.g. a custodial wallet may
19
+ * handle fees itself, a non-amulet wallet has no mining rounds). When a
20
+ * factory omits an optional capability, it is filled with a safe no-op so
21
+ * callers can invoke it unconditionally — it simply "passes".
22
+ *
23
+ * Add a new wallet by writing a `create<Name>WalletAdapter()` factory that
24
+ * builds a definition object and returns `defineWalletAdapter(definition)`.
25
+ */
26
+ /** Required capabilities — a usable wallet adapter must implement all of these. */
27
+ export const REQUIRED_CAPABILITIES = ["getPartyId", "getActiveContracts", "submitCommand"];
28
+ /** Optional capabilities — filled with safe no-ops when a wallet does not support them. */
29
+ export const OPTIONAL_CAPABILITIES = ["payDueGasIfAny", "getHolding", "getAmuletRules", "getOpenMiningRounds", "disconnect"];
30
+ /** Brand used to recognize objects produced by {@link defineWalletAdapter}. */
31
+ const ADAPTER_BRAND = Symbol.for("temple.canton.walletAdapter");
32
+ /**
33
+ * Safe no-op implementations for optional capabilities. Each returns a benign
34
+ * "nothing to do" value so a wallet that lacks the capability still passes when
35
+ * a caller invokes it. The comments explain why a no-op is the correct default.
36
+ */
37
+ const OPTIONAL_NO_OPS = {
38
+ // Networks/wallets with no programmatic gas concept (or where fees are paid
39
+ // wallet-side, e.g. a custodial provider) have nothing to settle here.
40
+ payDueGasIfAny: async () => undefined,
41
+ // Wallet does not expose its own holdings read — callers fall back to the
42
+ // ledger API, so "no wallet-side holdings" is represented as null.
43
+ getHolding: async () => null,
44
+ // Amulet-specific context. A non-Canton-amulet wallet simply has no rules to
45
+ // provide; null lets callers fall back to the ledger/scan lookups.
46
+ getAmuletRules: async () => null,
47
+ getOpenMiningRounds: async () => null,
48
+ // Stateless/headless wallets hold no connection to tear down.
49
+ disconnect: async () => undefined,
50
+ };
51
+ /** Returns true if `value` was produced by {@link defineWalletAdapter}. */
52
+ export function isWalletAdapter(value) {
53
+ return Boolean(value && value[ADAPTER_BRAND] === true);
54
+ }
55
+ /**
56
+ * Build a normalized wallet adapter from a factory definition.
57
+ *
58
+ * Validates that every required capability is implemented and fills any missing
59
+ * optional capability with a safe no-op. The returned object is branded so the
60
+ * service and facade can distinguish it from a raw provider SDK instance.
61
+ */
62
+ export function defineWalletAdapter(definition) {
63
+ if (!definition || typeof definition !== "object") {
64
+ throw new Error("[Temple SDK] Wallet adapter definition must be an object.");
65
+ }
66
+ const id = typeof definition.id === "string" && definition.id ? definition.id : "unknown";
67
+ for (const capability of REQUIRED_CAPABILITIES) {
68
+ if (typeof definition[capability] !== "function") {
69
+ throw new Error(`[Temple SDK] Wallet adapter "${id}" is missing required capability: ${capability}().`);
70
+ }
71
+ }
72
+ const adapter = {
73
+ [ADAPTER_BRAND]: true,
74
+ id,
75
+ isServer: Boolean(definition.isServer),
76
+ // Required capabilities (validated above).
77
+ getPartyId: definition.getPartyId,
78
+ getActiveContracts: definition.getActiveContracts,
79
+ submitCommand: definition.submitCommand,
80
+ // Optional capabilities — fall back to a safe no-op when a wallet omits them.
81
+ payDueGasIfAny: typeof definition.payDueGasIfAny === "function" ? definition.payDueGasIfAny : OPTIONAL_NO_OPS.payDueGasIfAny,
82
+ getHolding: typeof definition.getHolding === "function" ? definition.getHolding : OPTIONAL_NO_OPS.getHolding,
83
+ getAmuletRules: typeof definition.getAmuletRules === "function" ? definition.getAmuletRules : OPTIONAL_NO_OPS.getAmuletRules,
84
+ getOpenMiningRounds: typeof definition.getOpenMiningRounds === "function" ? definition.getOpenMiningRounds : OPTIONAL_NO_OPS.getOpenMiningRounds,
85
+ disconnect: typeof definition.disconnect === "function" ? definition.disconnect : OPTIONAL_NO_OPS.disconnect,
86
+ // Escape hatch: the raw provider object for legacy consumers of
87
+ // getAdapterProvider(). Defaults to null when a wallet exposes no such object.
88
+ getRawProvider: typeof definition.getRawProvider === "function" ? definition.getRawProvider : (() => null),
89
+ };
90
+ return adapter;
91
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Loop wallet adapter factory.
3
+ *
4
+ * Wraps a raw Loop SDK instance in the universal wallet adapter interface so the
5
+ * rest of the SDK interacts with Loop through the same contract as every other
6
+ * wallet. This preserves the exact behavior that previously lived inline in
7
+ * walletAdapter.js:
8
+ *
9
+ * - Server-side (Loop): signs locally via executeTransaction().
10
+ * - Client-side (Loop): delegates signing to the Loop Wallet via the provider's
11
+ * submitTransaction().
12
+ * - Reads go through the provider sub-object (loop.provider) when present.
13
+ *
14
+ * Server vs client is detected from the Loop connection: a server-side instance
15
+ * has a null connection.ticketId and connection.ws.
16
+ */
17
+ import { type WalletAdapter } from "./adapter.js";
18
+ /**
19
+ * Create a universal wallet adapter backed by a Loop SDK instance.
20
+ */
21
+ export declare function createLoopWalletAdapter(loop: unknown): WalletAdapter;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Loop wallet adapter factory.
3
+ *
4
+ * Wraps a raw Loop SDK instance in the universal wallet adapter interface so the
5
+ * rest of the SDK interacts with Loop through the same contract as every other
6
+ * wallet. This preserves the exact behavior that previously lived inline in
7
+ * walletAdapter.js:
8
+ *
9
+ * - Server-side (Loop): signs locally via executeTransaction().
10
+ * - Client-side (Loop): delegates signing to the Loop Wallet via the provider's
11
+ * submitTransaction().
12
+ * - Reads go through the provider sub-object (loop.provider) when present.
13
+ *
14
+ * Server vs client is detected from the Loop connection: a server-side instance
15
+ * has a null connection.ticketId and connection.ws.
16
+ */
17
+ import { defineWalletAdapter } from "./adapter.js";
18
+ /**
19
+ * Detect whether a Loop instance is server-side.
20
+ * Loop-specific: server-side has null connection.ticketId and connection.ws.
21
+ * With no connection object, assume server-side (headless/API usage).
22
+ */
23
+ function detectServerSide(loop) {
24
+ if (loop.connection) {
25
+ return loop.connection.ticketId == null && loop.connection.ws == null;
26
+ }
27
+ return true;
28
+ }
29
+ /**
30
+ * Create a universal wallet adapter backed by a Loop SDK instance.
31
+ */
32
+ export function createLoopWalletAdapter(loop) {
33
+ if (!loop || typeof loop !== "object") {
34
+ throw new Error("[Temple SDK] createLoopWalletAdapter requires a Loop wallet instance.");
35
+ }
36
+ const sdk = loop;
37
+ const isServer = detectServerSide(sdk);
38
+ // The provider sub-object handles reads (getActiveContracts) and client-side
39
+ // submission (submitTransaction). Fall back to the instance itself.
40
+ const provider = sdk.provider || sdk;
41
+ return defineWalletAdapter({
42
+ id: "loop",
43
+ isServer,
44
+ getRawProvider: () => provider,
45
+ // ── Required capabilities ──
46
+ getPartyId: () => sdk.provider?.party_id || sdk.session?.partyId || null,
47
+ getActiveContracts: (args) => {
48
+ if (typeof provider.getActiveContracts !== "function") {
49
+ throw new Error("[Temple SDK] Loop provider does not support getActiveContracts().");
50
+ }
51
+ return provider.getActiveContracts(args);
52
+ },
53
+ submitCommand: (command) => {
54
+ // Server-side: sign locally with the private key.
55
+ if (isServer && typeof sdk.executeTransaction === "function") {
56
+ return sdk.executeTransaction(command);
57
+ }
58
+ // Client-side: delegate signing to the Loop Wallet via WebSocket.
59
+ if (typeof provider.submitTransaction === "function") {
60
+ return provider.submitTransaction(command);
61
+ }
62
+ throw new Error("[Temple SDK] Loop wallet adapter does not support transaction submission.");
63
+ },
64
+ // ── Optional capabilities ──
65
+ // Loop pays network gas programmatically only on the server side; the
66
+ // client-side flow is handled by the Loop Wallet UI, so it is a no-op here.
67
+ payDueGasIfAny: async () => {
68
+ if (!isServer)
69
+ return;
70
+ if (typeof sdk.checkDueGas !== "function" || typeof sdk.payGas !== "function")
71
+ return;
72
+ try {
73
+ const dueGas = await sdk.checkDueGas();
74
+ const trackingId = dueGas?.tracking_id;
75
+ if (dueGas?.pending && trackingId) {
76
+ console.log(`[Temple SDK] Paying outstanding network gas (tracking_id=${trackingId})`);
77
+ await sdk.payGas(trackingId);
78
+ }
79
+ }
80
+ catch (error) {
81
+ console.warn(`[Temple SDK] checkDueGas/payGas failed: ${error?.message || String(error)}`);
82
+ }
83
+ },
84
+ // getHolding / getAmuletRules / getOpenMiningRounds / disconnect: not
85
+ // provided by Loop here — the adapter inherits the safe no-op defaults.
86
+ });
87
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Universal wallet service.
3
+ *
4
+ * Holds the single active wallet adapter and dispatches capability calls to it.
5
+ * This is the one chokepoint the rest of the SDK uses to reach a wallet — no
6
+ * module talks to a provider/wallet SDK directly.
7
+ *
8
+ * - Required capabilities (getPartyId, submitCommand) throw a clear error when
9
+ * invoked while no adapter is configured.
10
+ * - Active-contract reads are resolved through `getActiveContractReader`, which
11
+ * supports an explicit per-call provider override and returns null when no
12
+ * wallet source is available so callers can fall back to the ledger API.
13
+ * - Optional capabilities (payDueGasIfAny) become safe no-ops when no adapter
14
+ * is set.
15
+ */
16
+ import { type WalletAdapter } from "./adapter.js";
17
+ /** Register the active wallet adapter. Pass null to clear it. */
18
+ export declare function setActiveAdapter(adapter: WalletAdapter | null): void;
19
+ /** Get the active wallet adapter, or null when none is set. */
20
+ export declare function getActiveAdapter(): WalletAdapter | null;
21
+ /** True when a wallet adapter is configured. */
22
+ export declare function hasActiveAdapter(): boolean;
23
+ /** Resolve the active wallet's party id. Throws when no adapter is configured. */
24
+ export declare function getPartyId(): string | null;
25
+ /** Sign and submit a ledger command. Throws when no adapter is configured. */
26
+ export declare function submitCommand(command: unknown): Promise<unknown>;
27
+ /**
28
+ * Resolve the object used to read active contracts.
29
+ *
30
+ * An explicit `providerOverride` wins (a caller intentionally bypassing the
31
+ * active wallet); otherwise the active adapter — the universal wrapper — is
32
+ * returned, exposing its normalized `getActiveContracts` capability. Returns
33
+ * null when neither is available, signalling callers to fall back to the
34
+ * ledger API.
35
+ */
36
+ export declare function getActiveContractReader(providerOverride?: unknown): unknown;
37
+ /**
38
+ * Pay any outstanding network gas before a read/submit. Safe no-op when no
39
+ * adapter is configured (e.g. pure ledger/server mode without a wallet).
40
+ */
41
+ export declare function payDueGasIfAny(): Promise<void>;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Universal wallet service.
3
+ *
4
+ * Holds the single active wallet adapter and dispatches capability calls to it.
5
+ * This is the one chokepoint the rest of the SDK uses to reach a wallet — no
6
+ * module talks to a provider/wallet SDK directly.
7
+ *
8
+ * - Required capabilities (getPartyId, submitCommand) throw a clear error when
9
+ * invoked while no adapter is configured.
10
+ * - Active-contract reads are resolved through `getActiveContractReader`, which
11
+ * supports an explicit per-call provider override and returns null when no
12
+ * wallet source is available so callers can fall back to the ledger API.
13
+ * - Optional capabilities (payDueGasIfAny) become safe no-ops when no adapter
14
+ * is set.
15
+ */
16
+ import { isWalletAdapter } from "./adapter.js";
17
+ /** The single active, normalized wallet adapter (or null when none is set). */
18
+ let _activeAdapter = null;
19
+ /** Register the active wallet adapter. Pass null to clear it. */
20
+ export function setActiveAdapter(adapter) {
21
+ if (adapter && !isWalletAdapter(adapter)) {
22
+ throw new Error("[Temple SDK] setActiveAdapter expects a normalized wallet adapter created by a create*WalletAdapter() factory.");
23
+ }
24
+ _activeAdapter = adapter || null;
25
+ }
26
+ /** Get the active wallet adapter, or null when none is set. */
27
+ export function getActiveAdapter() {
28
+ return _activeAdapter;
29
+ }
30
+ /** True when a wallet adapter is configured. */
31
+ export function hasActiveAdapter() {
32
+ return _activeAdapter !== null;
33
+ }
34
+ function requireAdapter() {
35
+ if (!_activeAdapter) {
36
+ throw new Error("[Temple SDK] No wallet adapter configured. Call initialize() with WALLET_ADAPTER or setWalletAdapter() first.");
37
+ }
38
+ return _activeAdapter;
39
+ }
40
+ // ── Required capabilities ──────────────────────────────────────────────────
41
+ /** Resolve the active wallet's party id. Throws when no adapter is configured. */
42
+ export function getPartyId() {
43
+ return requireAdapter().getPartyId();
44
+ }
45
+ /** Sign and submit a ledger command. Throws when no adapter is configured. */
46
+ export function submitCommand(command) {
47
+ return requireAdapter().submitCommand(command);
48
+ }
49
+ /**
50
+ * Resolve the object used to read active contracts.
51
+ *
52
+ * An explicit `providerOverride` wins (a caller intentionally bypassing the
53
+ * active wallet); otherwise the active adapter — the universal wrapper — is
54
+ * returned, exposing its normalized `getActiveContracts` capability. Returns
55
+ * null when neither is available, signalling callers to fall back to the
56
+ * ledger API.
57
+ */
58
+ export function getActiveContractReader(providerOverride = null) {
59
+ if (providerOverride)
60
+ return providerOverride;
61
+ return _activeAdapter || null;
62
+ }
63
+ // ── Optional capabilities ──────────────────────────────────────────────────
64
+ /**
65
+ * Pay any outstanding network gas before a read/submit. Safe no-op when no
66
+ * adapter is configured (e.g. pure ledger/server mode without a wallet).
67
+ */
68
+ export async function payDueGasIfAny() {
69
+ if (!_activeAdapter)
70
+ return;
71
+ return _activeAdapter.payDueGasIfAny();
72
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Wallet Adapter Facade
3
+ *
4
+ * Backward-compatible public surface for wallet interactions. The real wallet
5
+ * architecture lives in ./wallet/ — a universal adapter contract (adapter.ts),
6
+ * a service registry that dispatches capability calls (service.ts), and one
7
+ * factory per wallet (loop.ts, and future providers). This module is a thin
8
+ * facade over that service so existing imports keep working unchanged.
9
+ *
10
+ * Adding a new wallet does not touch this file: write a create<Name>WalletAdapter()
11
+ * factory and pass its result to setWalletAdapter().
12
+ */
13
+ import type { WalletAdapter } from "./wallet/adapter.js";
14
+ export { createLoopWalletAdapter } from "./wallet/loop.js";
15
+ export { defineWalletAdapter, isWalletAdapter } from "./wallet/adapter.js";
16
+ export type { WalletAdapter, WalletAdapterDefinition } from "./wallet/adapter.js";
17
+ /**
18
+ * Set the active wallet adapter.
19
+ *
20
+ * Accepts either a normalized adapter (from a create*WalletAdapter() factory) or
21
+ * a raw Loop SDK instance, which is auto-wrapped for backward compatibility.
22
+ * Pass a falsy value to clear the adapter.
23
+ */
24
+ export declare function setWalletAdapter(adapter: unknown): void;
25
+ /**
26
+ * Get the active (normalized) wallet adapter, or null when none is set.
27
+ */
28
+ export declare function getWalletAdapter(): WalletAdapter | null;
29
+ /**
30
+ * Returns the provider sub-object used for read operations (getActiveContracts).
31
+ * Kept for backward compatibility; resolves the active adapter's raw provider.
32
+ */
33
+ export declare function getAdapterProvider(): unknown;
34
+ /**
35
+ * Check if the active adapter runs in server mode.
36
+ */
37
+ export declare function isServerMode(): boolean;
38
+ /**
39
+ * Get the party ID from the active wallet adapter, or null when none is set.
40
+ */
41
+ export declare function getAdapterPartyId(): string | null;
42
+ /**
43
+ * Pay any outstanding network gas before submitting a transaction or reading
44
+ * balances. Safe no-op when no adapter is configured or the wallet handles gas
45
+ * itself.
46
+ */
47
+ export declare function payDueGasIfAny(): Promise<void>;
48
+ /**
49
+ * Submit a command via the active wallet adapter.
50
+ * Throws when no adapter is configured.
51
+ */
52
+ export declare function submitCommand(command: unknown): Promise<unknown>;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Wallet Adapter Facade
3
+ *
4
+ * Backward-compatible public surface for wallet interactions. The real wallet
5
+ * architecture lives in ./wallet/ — a universal adapter contract (adapter.ts),
6
+ * a service registry that dispatches capability calls (service.ts), and one
7
+ * factory per wallet (loop.ts, and future providers). This module is a thin
8
+ * facade over that service so existing imports keep working unchanged.
9
+ *
10
+ * Adding a new wallet does not touch this file: write a create<Name>WalletAdapter()
11
+ * factory and pass its result to setWalletAdapter().
12
+ */
13
+ import { setActiveAdapter, getActiveAdapter, submitCommand as serviceSubmitCommand, payDueGasIfAny as servicePayDueGasIfAny, } from "./wallet/service.js";
14
+ import { isWalletAdapter } from "./wallet/adapter.js";
15
+ import { createLoopWalletAdapter } from "./wallet/loop.js";
16
+ // Re-export the adapter toolkit so consumers can build/register custom wallets.
17
+ export { createLoopWalletAdapter } from "./wallet/loop.js";
18
+ export { defineWalletAdapter, isWalletAdapter } from "./wallet/adapter.js";
19
+ /**
20
+ * Set the active wallet adapter.
21
+ *
22
+ * Accepts either a normalized adapter (from a create*WalletAdapter() factory) or
23
+ * a raw Loop SDK instance, which is auto-wrapped for backward compatibility.
24
+ * Pass a falsy value to clear the adapter.
25
+ */
26
+ export function setWalletAdapter(adapter) {
27
+ if (!adapter) {
28
+ setActiveAdapter(null);
29
+ return;
30
+ }
31
+ const normalized = isWalletAdapter(adapter) ? adapter : createLoopWalletAdapter(adapter);
32
+ setActiveAdapter(normalized);
33
+ }
34
+ /**
35
+ * Get the active (normalized) wallet adapter, or null when none is set.
36
+ */
37
+ export function getWalletAdapter() {
38
+ return getActiveAdapter();
39
+ }
40
+ /**
41
+ * Returns the provider sub-object used for read operations (getActiveContracts).
42
+ * Kept for backward compatibility; resolves the active adapter's raw provider.
43
+ */
44
+ export function getAdapterProvider() {
45
+ const adapter = getActiveAdapter();
46
+ if (!adapter)
47
+ return null;
48
+ return adapter.getRawProvider() || adapter;
49
+ }
50
+ /**
51
+ * Check if the active adapter runs in server mode.
52
+ */
53
+ export function isServerMode() {
54
+ const adapter = getActiveAdapter();
55
+ return adapter ? Boolean(adapter.isServer) : false;
56
+ }
57
+ /**
58
+ * Get the party ID from the active wallet adapter, or null when none is set.
59
+ */
60
+ export function getAdapterPartyId() {
61
+ const adapter = getActiveAdapter();
62
+ return adapter ? adapter.getPartyId() : null;
63
+ }
64
+ /**
65
+ * Pay any outstanding network gas before submitting a transaction or reading
66
+ * balances. Safe no-op when no adapter is configured or the wallet handles gas
67
+ * itself.
68
+ */
69
+ export function payDueGasIfAny() {
70
+ return servicePayDueGasIfAny();
71
+ }
72
+ /**
73
+ * Submit a command via the active wallet adapter.
74
+ * Throws when no adapter is configured.
75
+ */
76
+ export function submitCommand(command) {
77
+ return serviceSubmitCommand(command);
78
+ }
@@ -2,7 +2,7 @@ import config from "../../src/config/index.js";
2
2
  import axios from "axios";
3
3
  import { getDisclosures, createWithdrawalRequest, getWithdrawalRequestStatus, getDelegation, } from "../api/index.js";
4
4
  import { getUserId } from "../api/tokenStore.js";
5
- import { getAdapterPartyId, getWalletAdapter, submitCommand, payDueGasIfAny } from "../../src/canton/walletAdapter.js";
5
+ import { getAdapterPartyId, getWalletAdapter, submitCommand, payDueGasIfAny } from "./walletAdapter.js";
6
6
  import { randomUUID, shouldUseLedgerForMetadata, normalizeContractId, resolveInstrumentDefinition, getInstrumentRegistrar, resolveProvider, dedupeDisclosedContracts, buildHeaders, DEFAULT_AMULET_CONTEXT_KEYS, DEFAULT_UTILITY_CONTEXT_KEYS, } from "./helpers.js";
7
7
  import { resolveAmuletContext, resolveUtilityInstrumentConfiguration, resolveUtilityAllocationFactory, getUtxoCount, } from "../../src/canton/index.js";
8
8
  import { normalizeAssetId } from "../../src/canton/instrumentCatalog.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@temple-digital-group/temple-canton-js",
3
- "version": "2.0.5",
3
+ "version": "2.0.6-beta",
4
4
  "description": "JavaScript library for interacting with Temple Canton blockchain",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -2,7 +2,7 @@ import config from "../../src/config/index.js";
2
2
  import axios from "axios";
3
3
  import { getDisclosures } from "../api/index.js";
4
4
  import { getUserId } from "../api/tokenStore.js";
5
- import { getAdapterPartyId, getWalletAdapter, submitCommand, payDueGasIfAny } from "../../src/canton/walletAdapter.js";
5
+ import { getAdapterPartyId, getWalletAdapter, submitCommand, payDueGasIfAny } from "./walletAdapter.js";
6
6
  import { normalizeAssetId, instrumentCatalog, instrumentIdToSymbol, resolveOnChainInstrumentId } from "../../src/canton/instrumentCatalog.js";
7
7
  import {
8
8
  randomUUID,
@@ -1,7 +1,7 @@
1
1
  import config from "../../src/config/index.js";
2
2
  import axios from "axios";
3
3
  import { getJWTToken } from "../../src/auth0/index.js";
4
- import { getAdapterProvider } from "../../src/canton/walletAdapter.js";
4
+ import { getActiveContractReader } from "./wallet/service.js";
5
5
  import { instrumentCatalog, normalizeAssetId } from "../../src/canton/instrumentCatalog.js";
6
6
 
7
7
  // ─── Constants ───────────────────────────────────────────────────────────────
@@ -141,9 +141,13 @@ export function normalizeContractReferences(value: unknown): unknown {
141
141
  return value;
142
142
  }
143
143
 
144
- /** Resolve provider: use the passed value, or fall back to the wallet adapter's provider. */
144
+ /**
145
+ * Resolve the active-contracts reader: the explicit provider override when given,
146
+ * otherwise the active wallet adapter (the universal wrapper). Returns null when
147
+ * no wallet source is available, so callers fall back to the ledger API.
148
+ */
145
149
  export function resolveProvider(provider: unknown): unknown {
146
- return provider || getAdapterProvider() || null;
150
+ return getActiveContractReader(provider);
147
151
  }
148
152
 
149
153
  // ─── Instrument Catalog ──────────────────────────────────────────────────────
@@ -2,7 +2,7 @@ import config, { NETWORK_LOCALHOST } from "../config/index.js";
2
2
  import axios from "axios";
3
3
  import { getDisclosures, getDelegation } from "../../dist/api/index.js";
4
4
  import { getUserId } from "../../dist/api/tokenStore.js";
5
- import { getAdapterPartyId, getWalletAdapter, submitCommand, payDueGasIfAny } from "./walletAdapter.js";
5
+ import { getAdapterPartyId, getWalletAdapter, submitCommand, payDueGasIfAny } from "../../dist/canton/walletAdapter.js";
6
6
 
7
7
  // Import shared helpers from compiled TypeScript module
8
8
  import {
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Universal Wallet Adapter contract.
3
+ *
4
+ * Every wallet (Loop, and future providers such as Console or BitGo) is
5
+ * represented as a *normalized adapter* implementing the capability interface
6
+ * defined here. The rest of the SDK never talks to a wallet/provider SDK
7
+ * directly — it goes through the wallet service (./service.js), which dispatches
8
+ * to the active adapter.
9
+ *
10
+ * Capabilities are split into two tiers:
11
+ *
12
+ * - REQUIRED: getPartyId, getActiveContracts, submitCommand.
13
+ * A wallet that cannot provide these is not usable. `defineWalletAdapter`
14
+ * throws if a factory omits one, and the service throws if a required
15
+ * capability is invoked while no adapter is configured.
16
+ *
17
+ * - OPTIONAL: payDueGasIfAny, getHolding, getAmuletRules, getOpenMiningRounds,
18
+ * disconnect. Not every wallet supports these (e.g. a custodial wallet may
19
+ * handle fees itself, a non-amulet wallet has no mining rounds). When a
20
+ * factory omits an optional capability, it is filled with a safe no-op so
21
+ * callers can invoke it unconditionally — it simply "passes".
22
+ *
23
+ * Add a new wallet by writing a `create<Name>WalletAdapter()` factory that
24
+ * builds a definition object and returns `defineWalletAdapter(definition)`.
25
+ */
26
+
27
+ /** A normalized wallet adapter produced by {@link defineWalletAdapter}. */
28
+ export interface WalletAdapter {
29
+ /** Provider id, e.g. "loop". */
30
+ readonly id: string;
31
+ /** True for server-side adapters that sign/submit locally. */
32
+ readonly isServer: boolean;
33
+
34
+ // ── Required capabilities ──
35
+ /** Resolve the wallet's primary party id. */
36
+ getPartyId(): string | null;
37
+ /** Read active contracts matching `args` via the wallet. */
38
+ getActiveContracts(args: unknown): Promise<unknown>;
39
+ /** Sign and submit a ledger command via the wallet. */
40
+ submitCommand(command: unknown): Promise<unknown>;
41
+
42
+ // ── Optional capabilities (always present after normalization) ──
43
+ /** Pay any outstanding network gas. No-op when unsupported. */
44
+ payDueGasIfAny(): Promise<void>;
45
+ /** Read a holding via the wallet. Resolves null when unsupported. */
46
+ getHolding(...args: unknown[]): Promise<unknown>;
47
+ /** Resolve amulet rules via the wallet. Resolves null when unsupported. */
48
+ getAmuletRules(...args: unknown[]): Promise<unknown>;
49
+ /** Resolve open mining rounds via the wallet. Resolves null when unsupported. */
50
+ getOpenMiningRounds(...args: unknown[]): Promise<unknown>;
51
+ /** Disconnect the wallet. No-op when unsupported. */
52
+ disconnect(): Promise<void>;
53
+
54
+ /** Raw provider object for legacy getAdapterProvider() consumers, or null. */
55
+ getRawProvider(): unknown;
56
+ }
57
+
58
+ /** Capability definition supplied by a `create<Name>WalletAdapter()` factory. */
59
+ export interface WalletAdapterDefinition {
60
+ id?: string;
61
+ isServer?: boolean;
62
+ getPartyId(): string | null;
63
+ getActiveContracts(args: unknown): Promise<unknown> | unknown;
64
+ submitCommand(command: unknown): Promise<unknown> | unknown;
65
+ payDueGasIfAny?(): Promise<void> | void;
66
+ getHolding?(...args: unknown[]): Promise<unknown> | unknown;
67
+ getAmuletRules?(...args: unknown[]): Promise<unknown> | unknown;
68
+ getOpenMiningRounds?(...args: unknown[]): Promise<unknown> | unknown;
69
+ disconnect?(): Promise<void> | void;
70
+ getRawProvider?(): unknown;
71
+ }
72
+
73
+ /** Required capabilities — a usable wallet adapter must implement all of these. */
74
+ export const REQUIRED_CAPABILITIES = ["getPartyId", "getActiveContracts", "submitCommand"] as const;
75
+
76
+ /** Optional capabilities — filled with safe no-ops when a wallet does not support them. */
77
+ export const OPTIONAL_CAPABILITIES = ["payDueGasIfAny", "getHolding", "getAmuletRules", "getOpenMiningRounds", "disconnect"] as const;
78
+
79
+ /** Brand used to recognize objects produced by {@link defineWalletAdapter}. */
80
+ const ADAPTER_BRAND = Symbol.for("temple.canton.walletAdapter");
81
+
82
+ /**
83
+ * Safe no-op implementations for optional capabilities. Each returns a benign
84
+ * "nothing to do" value so a wallet that lacks the capability still passes when
85
+ * a caller invokes it. The comments explain why a no-op is the correct default.
86
+ */
87
+ const OPTIONAL_NO_OPS = {
88
+ // Networks/wallets with no programmatic gas concept (or where fees are paid
89
+ // wallet-side, e.g. a custodial provider) have nothing to settle here.
90
+ payDueGasIfAny: async (): Promise<void> => undefined,
91
+ // Wallet does not expose its own holdings read — callers fall back to the
92
+ // ledger API, so "no wallet-side holdings" is represented as null.
93
+ getHolding: async (): Promise<unknown> => null,
94
+ // Amulet-specific context. A non-Canton-amulet wallet simply has no rules to
95
+ // provide; null lets callers fall back to the ledger/scan lookups.
96
+ getAmuletRules: async (): Promise<unknown> => null,
97
+ getOpenMiningRounds: async (): Promise<unknown> => null,
98
+ // Stateless/headless wallets hold no connection to tear down.
99
+ disconnect: async (): Promise<void> => undefined,
100
+ };
101
+
102
+ /** Returns true if `value` was produced by {@link defineWalletAdapter}. */
103
+ export function isWalletAdapter(value: unknown): value is WalletAdapter {
104
+ return Boolean(value && (value as Record<symbol, unknown>)[ADAPTER_BRAND] === true);
105
+ }
106
+
107
+ /**
108
+ * Build a normalized wallet adapter from a factory definition.
109
+ *
110
+ * Validates that every required capability is implemented and fills any missing
111
+ * optional capability with a safe no-op. The returned object is branded so the
112
+ * service and facade can distinguish it from a raw provider SDK instance.
113
+ */
114
+ export function defineWalletAdapter(definition: WalletAdapterDefinition): WalletAdapter {
115
+ if (!definition || typeof definition !== "object") {
116
+ throw new Error("[Temple SDK] Wallet adapter definition must be an object.");
117
+ }
118
+
119
+ const id = typeof definition.id === "string" && definition.id ? definition.id : "unknown";
120
+
121
+ for (const capability of REQUIRED_CAPABILITIES) {
122
+ if (typeof definition[capability] !== "function") {
123
+ throw new Error(`[Temple SDK] Wallet adapter "${id}" is missing required capability: ${capability}().`);
124
+ }
125
+ }
126
+
127
+ const adapter = {
128
+ [ADAPTER_BRAND]: true,
129
+ id,
130
+ isServer: Boolean(definition.isServer),
131
+
132
+ // Required capabilities (validated above).
133
+ getPartyId: definition.getPartyId,
134
+ getActiveContracts: definition.getActiveContracts,
135
+ submitCommand: definition.submitCommand,
136
+
137
+ // Optional capabilities — fall back to a safe no-op when a wallet omits them.
138
+ payDueGasIfAny: typeof definition.payDueGasIfAny === "function" ? definition.payDueGasIfAny : OPTIONAL_NO_OPS.payDueGasIfAny,
139
+ getHolding: typeof definition.getHolding === "function" ? definition.getHolding : OPTIONAL_NO_OPS.getHolding,
140
+ getAmuletRules: typeof definition.getAmuletRules === "function" ? definition.getAmuletRules : OPTIONAL_NO_OPS.getAmuletRules,
141
+ getOpenMiningRounds: typeof definition.getOpenMiningRounds === "function" ? definition.getOpenMiningRounds : OPTIONAL_NO_OPS.getOpenMiningRounds,
142
+ disconnect: typeof definition.disconnect === "function" ? definition.disconnect : OPTIONAL_NO_OPS.disconnect,
143
+
144
+ // Escape hatch: the raw provider object for legacy consumers of
145
+ // getAdapterProvider(). Defaults to null when a wallet exposes no such object.
146
+ getRawProvider: typeof definition.getRawProvider === "function" ? definition.getRawProvider : ((): unknown => null),
147
+ };
148
+
149
+ return adapter as unknown as WalletAdapter;
150
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Loop wallet adapter factory.
3
+ *
4
+ * Wraps a raw Loop SDK instance in the universal wallet adapter interface so the
5
+ * rest of the SDK interacts with Loop through the same contract as every other
6
+ * wallet. This preserves the exact behavior that previously lived inline in
7
+ * walletAdapter.js:
8
+ *
9
+ * - Server-side (Loop): signs locally via executeTransaction().
10
+ * - Client-side (Loop): delegates signing to the Loop Wallet via the provider's
11
+ * submitTransaction().
12
+ * - Reads go through the provider sub-object (loop.provider) when present.
13
+ *
14
+ * Server vs client is detected from the Loop connection: a server-side instance
15
+ * has a null connection.ticketId and connection.ws.
16
+ */
17
+
18
+ import { defineWalletAdapter, type WalletAdapter } from "./adapter.js";
19
+
20
+ /** The Loop read/submit provider sub-object (or the Loop instance itself). */
21
+ interface LoopProvider {
22
+ party_id?: string | null;
23
+ getActiveContracts?: (args: unknown) => unknown;
24
+ submitTransaction?: (command: unknown) => unknown;
25
+ }
26
+
27
+ /** Outstanding-gas descriptor returned by Loop's checkDueGas(). */
28
+ interface DueGas {
29
+ pending?: boolean;
30
+ tracking_id?: string;
31
+ }
32
+
33
+ /** Structural shape of the Loop SDK instance this adapter relies on. */
34
+ interface LoopLike extends LoopProvider {
35
+ provider?: LoopProvider | null;
36
+ session?: { partyId?: string | null } | null;
37
+ connection?: { ticketId?: unknown; ws?: unknown } | null;
38
+ executeTransaction?: (command: unknown) => unknown;
39
+ checkDueGas?: () => Promise<DueGas | null | undefined> | DueGas | null | undefined;
40
+ payGas?: (trackingId: string) => Promise<unknown> | unknown;
41
+ }
42
+
43
+ /**
44
+ * Detect whether a Loop instance is server-side.
45
+ * Loop-specific: server-side has null connection.ticketId and connection.ws.
46
+ * With no connection object, assume server-side (headless/API usage).
47
+ */
48
+ function detectServerSide(loop: LoopLike): boolean {
49
+ if (loop.connection) {
50
+ return loop.connection.ticketId == null && loop.connection.ws == null;
51
+ }
52
+ return true;
53
+ }
54
+
55
+ /**
56
+ * Create a universal wallet adapter backed by a Loop SDK instance.
57
+ */
58
+ export function createLoopWalletAdapter(loop: unknown): WalletAdapter {
59
+ if (!loop || typeof loop !== "object") {
60
+ throw new Error("[Temple SDK] createLoopWalletAdapter requires a Loop wallet instance.");
61
+ }
62
+
63
+ const sdk = loop as LoopLike;
64
+ const isServer = detectServerSide(sdk);
65
+ // The provider sub-object handles reads (getActiveContracts) and client-side
66
+ // submission (submitTransaction). Fall back to the instance itself.
67
+ const provider: LoopProvider = sdk.provider || sdk;
68
+
69
+ return defineWalletAdapter({
70
+ id: "loop",
71
+ isServer,
72
+ getRawProvider: () => provider,
73
+
74
+ // ── Required capabilities ──
75
+ getPartyId: () => sdk.provider?.party_id || sdk.session?.partyId || null,
76
+
77
+ getActiveContracts: (args) => {
78
+ if (typeof provider.getActiveContracts !== "function") {
79
+ throw new Error("[Temple SDK] Loop provider does not support getActiveContracts().");
80
+ }
81
+ return provider.getActiveContracts(args);
82
+ },
83
+
84
+ submitCommand: (command) => {
85
+ // Server-side: sign locally with the private key.
86
+ if (isServer && typeof sdk.executeTransaction === "function") {
87
+ return sdk.executeTransaction(command);
88
+ }
89
+ // Client-side: delegate signing to the Loop Wallet via WebSocket.
90
+ if (typeof provider.submitTransaction === "function") {
91
+ return provider.submitTransaction(command);
92
+ }
93
+ throw new Error("[Temple SDK] Loop wallet adapter does not support transaction submission.");
94
+ },
95
+
96
+ // ── Optional capabilities ──
97
+ // Loop pays network gas programmatically only on the server side; the
98
+ // client-side flow is handled by the Loop Wallet UI, so it is a no-op here.
99
+ payDueGasIfAny: async () => {
100
+ if (!isServer) return;
101
+ if (typeof sdk.checkDueGas !== "function" || typeof sdk.payGas !== "function") return;
102
+ try {
103
+ const dueGas = await sdk.checkDueGas();
104
+ const trackingId = dueGas?.tracking_id;
105
+ if (dueGas?.pending && trackingId) {
106
+ console.log(`[Temple SDK] Paying outstanding network gas (tracking_id=${trackingId})`);
107
+ await sdk.payGas(trackingId);
108
+ }
109
+ } catch (error) {
110
+ console.warn(`[Temple SDK] checkDueGas/payGas failed: ${(error as Error)?.message || String(error)}`);
111
+ }
112
+ },
113
+ // getHolding / getAmuletRules / getOpenMiningRounds / disconnect: not
114
+ // provided by Loop here — the adapter inherits the safe no-op defaults.
115
+ });
116
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Universal wallet service.
3
+ *
4
+ * Holds the single active wallet adapter and dispatches capability calls to it.
5
+ * This is the one chokepoint the rest of the SDK uses to reach a wallet — no
6
+ * module talks to a provider/wallet SDK directly.
7
+ *
8
+ * - Required capabilities (getPartyId, submitCommand) throw a clear error when
9
+ * invoked while no adapter is configured.
10
+ * - Active-contract reads are resolved through `getActiveContractReader`, which
11
+ * supports an explicit per-call provider override and returns null when no
12
+ * wallet source is available so callers can fall back to the ledger API.
13
+ * - Optional capabilities (payDueGasIfAny) become safe no-ops when no adapter
14
+ * is set.
15
+ */
16
+
17
+ import { isWalletAdapter, type WalletAdapter } from "./adapter.js";
18
+
19
+ /** The single active, normalized wallet adapter (or null when none is set). */
20
+ let _activeAdapter: WalletAdapter | null = null;
21
+
22
+ /** Register the active wallet adapter. Pass null to clear it. */
23
+ export function setActiveAdapter(adapter: WalletAdapter | null): void {
24
+ if (adapter && !isWalletAdapter(adapter)) {
25
+ throw new Error("[Temple SDK] setActiveAdapter expects a normalized wallet adapter created by a create*WalletAdapter() factory.");
26
+ }
27
+ _activeAdapter = adapter || null;
28
+ }
29
+
30
+ /** Get the active wallet adapter, or null when none is set. */
31
+ export function getActiveAdapter(): WalletAdapter | null {
32
+ return _activeAdapter;
33
+ }
34
+
35
+ /** True when a wallet adapter is configured. */
36
+ export function hasActiveAdapter(): boolean {
37
+ return _activeAdapter !== null;
38
+ }
39
+
40
+ function requireAdapter(): WalletAdapter {
41
+ if (!_activeAdapter) {
42
+ throw new Error("[Temple SDK] No wallet adapter configured. Call initialize() with WALLET_ADAPTER or setWalletAdapter() first.");
43
+ }
44
+ return _activeAdapter;
45
+ }
46
+
47
+ // ── Required capabilities ──────────────────────────────────────────────────
48
+
49
+ /** Resolve the active wallet's party id. Throws when no adapter is configured. */
50
+ export function getPartyId(): string | null {
51
+ return requireAdapter().getPartyId();
52
+ }
53
+
54
+ /** Sign and submit a ledger command. Throws when no adapter is configured. */
55
+ export function submitCommand(command: unknown): Promise<unknown> {
56
+ return requireAdapter().submitCommand(command);
57
+ }
58
+
59
+ /**
60
+ * Resolve the object used to read active contracts.
61
+ *
62
+ * An explicit `providerOverride` wins (a caller intentionally bypassing the
63
+ * active wallet); otherwise the active adapter — the universal wrapper — is
64
+ * returned, exposing its normalized `getActiveContracts` capability. Returns
65
+ * null when neither is available, signalling callers to fall back to the
66
+ * ledger API.
67
+ */
68
+ export function getActiveContractReader(providerOverride: unknown = null): unknown {
69
+ if (providerOverride) return providerOverride;
70
+ return _activeAdapter || null;
71
+ }
72
+
73
+ // ── Optional capabilities ──────────────────────────────────────────────────
74
+
75
+ /**
76
+ * Pay any outstanding network gas before a read/submit. Safe no-op when no
77
+ * adapter is configured (e.g. pure ledger/server mode without a wallet).
78
+ */
79
+ export async function payDueGasIfAny(): Promise<void> {
80
+ if (!_activeAdapter) return;
81
+ return _activeAdapter.payDueGasIfAny();
82
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Wallet Adapter Facade
3
+ *
4
+ * Backward-compatible public surface for wallet interactions. The real wallet
5
+ * architecture lives in ./wallet/ — a universal adapter contract (adapter.ts),
6
+ * a service registry that dispatches capability calls (service.ts), and one
7
+ * factory per wallet (loop.ts, and future providers). This module is a thin
8
+ * facade over that service so existing imports keep working unchanged.
9
+ *
10
+ * Adding a new wallet does not touch this file: write a create<Name>WalletAdapter()
11
+ * factory and pass its result to setWalletAdapter().
12
+ */
13
+
14
+ import {
15
+ setActiveAdapter,
16
+ getActiveAdapter,
17
+ submitCommand as serviceSubmitCommand,
18
+ payDueGasIfAny as servicePayDueGasIfAny,
19
+ } from "./wallet/service.js";
20
+ import { isWalletAdapter } from "./wallet/adapter.js";
21
+ import { createLoopWalletAdapter } from "./wallet/loop.js";
22
+ import type { WalletAdapter } from "./wallet/adapter.js";
23
+
24
+ // Re-export the adapter toolkit so consumers can build/register custom wallets.
25
+ export { createLoopWalletAdapter } from "./wallet/loop.js";
26
+ export { defineWalletAdapter, isWalletAdapter } from "./wallet/adapter.js";
27
+ export type { WalletAdapter, WalletAdapterDefinition } from "./wallet/adapter.js";
28
+
29
+ /**
30
+ * Set the active wallet adapter.
31
+ *
32
+ * Accepts either a normalized adapter (from a create*WalletAdapter() factory) or
33
+ * a raw Loop SDK instance, which is auto-wrapped for backward compatibility.
34
+ * Pass a falsy value to clear the adapter.
35
+ */
36
+ export function setWalletAdapter(adapter: unknown): void {
37
+ if (!adapter) {
38
+ setActiveAdapter(null);
39
+ return;
40
+ }
41
+ const normalized = isWalletAdapter(adapter) ? adapter : createLoopWalletAdapter(adapter);
42
+ setActiveAdapter(normalized);
43
+ }
44
+
45
+ /**
46
+ * Get the active (normalized) wallet adapter, or null when none is set.
47
+ */
48
+ export function getWalletAdapter(): WalletAdapter | null {
49
+ return getActiveAdapter();
50
+ }
51
+
52
+ /**
53
+ * Returns the provider sub-object used for read operations (getActiveContracts).
54
+ * Kept for backward compatibility; resolves the active adapter's raw provider.
55
+ */
56
+ export function getAdapterProvider(): unknown {
57
+ const adapter = getActiveAdapter();
58
+ if (!adapter) return null;
59
+ return adapter.getRawProvider() || adapter;
60
+ }
61
+
62
+ /**
63
+ * Check if the active adapter runs in server mode.
64
+ */
65
+ export function isServerMode(): boolean {
66
+ const adapter = getActiveAdapter();
67
+ return adapter ? Boolean(adapter.isServer) : false;
68
+ }
69
+
70
+ /**
71
+ * Get the party ID from the active wallet adapter, or null when none is set.
72
+ */
73
+ export function getAdapterPartyId(): string | null {
74
+ const adapter = getActiveAdapter();
75
+ return adapter ? adapter.getPartyId() : null;
76
+ }
77
+
78
+ /**
79
+ * Pay any outstanding network gas before submitting a transaction or reading
80
+ * balances. Safe no-op when no adapter is configured or the wallet handles gas
81
+ * itself.
82
+ */
83
+ export function payDueGasIfAny(): Promise<void> {
84
+ return servicePayDueGasIfAny();
85
+ }
86
+
87
+ /**
88
+ * Submit a command via the active wallet adapter.
89
+ * Throws when no adapter is configured.
90
+ */
91
+ export function submitCommand(command: unknown): Promise<unknown> {
92
+ return serviceSubmitCommand(command);
93
+ }
@@ -7,7 +7,7 @@ import {
7
7
  getDelegation,
8
8
  } from "../api/index.js";
9
9
  import { getUserId } from "../api/tokenStore.js";
10
- import { getAdapterPartyId, getWalletAdapter, submitCommand, payDueGasIfAny } from "../../src/canton/walletAdapter.js";
10
+ import { getAdapterPartyId, getWalletAdapter, submitCommand, payDueGasIfAny } from "./walletAdapter.js";
11
11
  import {
12
12
  randomUUID,
13
13
  shouldUseLedgerForMetadata,
@@ -19,6 +19,9 @@ export function getConfigValue(key: any): any;
19
19
  export function setWalletAdapter(adapter: any): void;
20
20
  export function getWalletAdapter(): any;
21
21
  export function getAdapterProvider(): any;
22
+ export function createLoopWalletAdapter(loop: any): any;
23
+ export function defineWalletAdapter(definition: any): any;
24
+ export function isWalletAdapter(value: any): boolean;
22
25
  export const NETWORK_LOCALHOST: "localhost";
23
26
  export const NETWORK_TESTNET: "testnet";
24
27
  export const NETWORK_MAINNET: "mainnet";
@@ -6,8 +6,15 @@ if (typeof process !== 'undefined' && process.env) {
6
6
  // Global config object that can be set by the consuming application
7
7
  let appConfig = null;
8
8
 
9
- // Re-export wallet adapter functions
10
- export { setWalletAdapter, getWalletAdapter, getAdapterProvider } from "../canton/walletAdapter.js";
9
+ // Re-export wallet adapter functions and the adapter toolkit (factory + helpers).
10
+ export {
11
+ setWalletAdapter,
12
+ getWalletAdapter,
13
+ getAdapterProvider,
14
+ createLoopWalletAdapter,
15
+ defineWalletAdapter,
16
+ isWalletAdapter,
17
+ } from "../../dist/canton/walletAdapter.js";
11
18
 
12
19
  /**
13
20
  * Initialize the library with a configuration object.
@@ -1,7 +0,0 @@
1
- export function setWalletAdapter(adapter: unknown): void;
2
- export function getWalletAdapter(): unknown;
3
- export function getAdapterProvider(): unknown;
4
- export function isServerMode(): boolean;
5
- export function getAdapterPartyId(): string | null;
6
- export function submitCommand(command: unknown): Promise<unknown>;
7
- export function payDueGasIfAny(): Promise<void>;
@@ -1,112 +0,0 @@
1
- /**
2
- * Wallet Adapter Module
3
- *
4
- * Provides a unified interface for wallet interactions (reads and writes).
5
- * Currently supports Loop Wallet. Designed to support additional wallets in the future.
6
- *
7
- * Server-side (Loop): uses executeTransaction() — signs locally with private key
8
- * Client-side (Loop): uses submitTransaction() — delegates signing to Loop Wallet via WebSocket
9
- *
10
- * Detection: Loop server-side has null connection.ticketId and connection.ws
11
- */
12
-
13
- let _adapter = null;
14
- let _isServer = false;
15
-
16
- /**
17
- * Set the wallet adapter (e.g., Loop SDK instance).
18
- * Auto-detects server vs client mode.
19
- */
20
- export function setWalletAdapter(adapter) {
21
- _adapter = adapter;
22
- _isServer = adapter ? detectServerSide(adapter) : false;
23
- }
24
-
25
- /**
26
- * Get the current wallet adapter.
27
- */
28
- export function getWalletAdapter() {
29
- return _adapter;
30
- }
31
-
32
- /**
33
- * Returns the provider sub-object used for read operations (getActiveContracts).
34
- * For Loop, this is adapter.provider.
35
- */
36
- export function getAdapterProvider() {
37
- if (!_adapter) return null;
38
- return _adapter.provider || _adapter;
39
- }
40
-
41
- /**
42
- * Check if running in server mode.
43
- */
44
- export function isServerMode() {
45
- return _isServer;
46
- }
47
-
48
- /**
49
- * Detect if the adapter is server-side.
50
- * Loop-specific: server-side has null connection.ticketId and connection.ws
51
- */
52
- function detectServerSide(adapter) {
53
- if (adapter?.connection) {
54
- return adapter.connection.ticketId == null && adapter.connection.ws == null;
55
- }
56
- // If no connection object, assume server-side (e.g., headless/API usage)
57
- return true;
58
- }
59
-
60
- /**
61
- * Get the party ID from the wallet adapter.
62
- * For Loop, this is on adapter.connection.partyId or adapter.partyId.
63
- */
64
- export function getAdapterPartyId() {
65
- if (!_adapter) return null;
66
- return _adapter.provider?.party_id || _adapter.session?.partyId || null;
67
- }
68
-
69
- /**
70
- * Pay any outstanding network gas before submitting a transaction or reading balances.
71
- * Server-side only — client-side gas is handled by the Loop Wallet UI.
72
- */
73
- export async function payDueGasIfAny() {
74
- if (!_isServer || !_adapter) return;
75
- if (typeof _adapter.checkDueGas !== "function" || typeof _adapter.payGas !== "function") return;
76
-
77
- try {
78
- const dueGas = await _adapter.checkDueGas();
79
- if (dueGas?.pending && dueGas?.tracking_id) {
80
- console.log(`[Temple SDK] Paying outstanding network gas (tracking_id=${dueGas.tracking_id})`);
81
- await _adapter.payGas(dueGas.tracking_id);
82
- }
83
- } catch (error) {
84
- console.warn(`[Temple SDK] checkDueGas/payGas failed: ${error?.message || error}`);
85
- }
86
- }
87
-
88
- /**
89
- * Submit a command via the wallet adapter.
90
- * Server-side: calls adapter.executeTransaction(command)
91
- * Client-side: calls adapter.provider.submitTransaction(command)
92
- *
93
- * Note: callers of deposit/withdrawal/delegation should call payDueGasIfAny()
94
- * at the start of their action to ensure any outstanding gas is paid before
95
- * pulling UTXOs or submitting the command.
96
- */
97
- export async function submitCommand(command) {
98
- if (!_adapter) {
99
- throw new Error("[Temple SDK] No wallet adapter configured. Call initialize() with WALLET_ADAPTER or setWalletAdapter() first.");
100
- }
101
-
102
- if (_isServer && typeof _adapter.executeTransaction === 'function') {
103
- return _adapter.executeTransaction(command);
104
- }
105
-
106
- const provider = _adapter.provider || _adapter;
107
- if (typeof provider.submitTransaction === 'function') {
108
- return provider.submitTransaction(command);
109
- }
110
-
111
- throw new Error("[Temple SDK] Wallet adapter does not support transaction submission.");
112
- }