@logswap/sdk 0.5.55 → 0.5.57

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.
package/dist/write.d.ts CHANGED
@@ -12,7 +12,7 @@
12
12
  * raw `minOut`/`maxIn` the contract wants. `withSlippage()` derives one from a quote when that is
13
13
  * what you have — but the bound is what ships.
14
14
  */
15
- import type { Address, Hash } from "viem";
15
+ import type { Address, Hash, Hex } from "viem";
16
16
  import type { LogswapClient } from "./client.js";
17
17
  /** Disables the deadline check. Deliberate opt-out, never a default. */
18
18
  export declare const NEVER: bigint;
@@ -73,6 +73,18 @@ export declare function sendRouterWrite(c: LogswapClient, args: {
73
73
  args: readonly unknown[];
74
74
  bufferPct?: bigint;
75
75
  }): Promise<Hash>;
76
+ /**
77
+ * Whether an error is the user saying no in their wallet (EIP-1193 code 4001, viem's
78
+ * `UserRejectedRequestError`), however deep viem nested it.
79
+ */
80
+ export declare function isUserRejection(e: unknown): boolean;
81
+ /**
82
+ * The permit signatures are an optimisation: when they cannot be produced (a wallet without
83
+ * `signTypedData`, a read that failed) the write goes out plain and relies on a standing
84
+ * allowance. A REJECTED signature is not that — it was swallowed too, and the user who had just
85
+ * said no was shown a transaction prompt, which then reverted with a Permit2 error that hid why.
86
+ */
87
+ export declare function fallBackUnlessRejected(e: unknown): Hex[];
76
88
  /** Simulate a router write and return its decoded result, without sending. */
77
89
  export declare function simulateRouterWrite<T>(c: LogswapClient, args: {
78
90
  abi: readonly unknown[];
package/dist/write.js CHANGED
@@ -92,6 +92,31 @@ export async function sendRouterWrite(c, args) {
92
92
  const gas = (estimate * (100n + (args.bufferPct ?? 30n))) / 100n;
93
93
  return c.wallet.writeContract({ ...params, gas });
94
94
  }
95
+ /**
96
+ * Whether an error is the user saying no in their wallet (EIP-1193 code 4001, viem's
97
+ * `UserRejectedRequestError`), however deep viem nested it.
98
+ */
99
+ export function isUserRejection(e) {
100
+ const seen = new Set();
101
+ for (let x = e; x && typeof x === "object" && !seen.has(x); x = x.cause) {
102
+ seen.add(x);
103
+ const o = x;
104
+ if (o.name === "UserRejectedRequestError" || o.code === 4001)
105
+ return true;
106
+ }
107
+ return false;
108
+ }
109
+ /**
110
+ * The permit signatures are an optimisation: when they cannot be produced (a wallet without
111
+ * `signTypedData`, a read that failed) the write goes out plain and relies on a standing
112
+ * allowance. A REJECTED signature is not that — it was swallowed too, and the user who had just
113
+ * said no was shown a transaction prompt, which then reverted with a Permit2 error that hid why.
114
+ */
115
+ export function fallBackUnlessRejected(e) {
116
+ if (isUserRejection(e))
117
+ throw e;
118
+ return [];
119
+ }
95
120
  /** Simulate a router write and return its decoded result, without sending. */
96
121
  export async function simulateRouterWrite(c, args) {
97
122
  const account = args.account ?? c.wallet?.account?.address;
@@ -112,7 +137,7 @@ export async function simulateRouterWrite(c, args) {
112
137
  */
113
138
  export async function sendRouterWriteWithPermits(c, args, pullTokens) {
114
139
  const { permit2SigCalls } = await import("./onboard.js");
115
- const sigCalls = await permit2SigCalls(c, pullTokens).catch(() => []);
140
+ const sigCalls = await permit2SigCalls(c, pullTokens).catch(fallBackUnlessRejected);
116
141
  if (sigCalls.length === 0)
117
142
  return sendRouterWrite(c, args);
118
143
  const { encodeFunctionData } = await import("viem");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@logswap/sdk",
3
- "version": "0.5.55",
3
+ "version": "0.5.57",
4
4
  "description": "TypeScript SDK for Logswap, the liquidity primitive introducing the logarithmic invariant — typed wrappers over the CPoolManager / FPoolManager / Router / Lens surface.",
5
5
  "license": "GPL-2.0-or-later",
6
6
  "type": "module",
@@ -45,5 +45,6 @@
45
45
  "repository": {
46
46
  "type": "git",
47
47
  "url": "git+https://github.com/logswap/logswap-sdk.git"
48
- }
48
+ },
49
+ "homepage": "https://logswap.org"
49
50
  }
package/src/actions.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * actions.ts — the router's transaction: a BATCH (decisions 010).
2
+ * actions.ts — the router's transaction: a BATCH.
3
3
  *
4
4
  * `execute(actions, settle)` runs a list of actions — mint, burn, update, claim, swap, path, in any
5
5
  * order, across any C markets — under ONE manager lock. Each action accrues deltas on the router
package/src/admin.ts CHANGED
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * admin — the protocol's writes: the C manager's cut of the mint fee (the F manager's is a
3
- * constant, decisions 049), the policies a pool may be born with (the registry's), the
3
+ * constant), the policies a pool may be born with (the registry's), the
4
4
  * policies' own numbers, and the two-step collector handover. Plain writes to the managers,
5
5
  * the registry and the policies; no router.
6
6
  *
7
- * One vocabulary (decisions 040): both managers call the protocol's cut of the mint fee
7
+ * One vocabulary: both managers call the protocol's cut of the mint fee
8
8
  * `MINT_FEE_PROTOCOL_CUT`; C sets it through `setFeeParams(uint256)`, F holds it as a constant
9
- * (049). The harvest's cut is the policy's answer at every harvest (decisions 044), set on the
9
+ * (049). The harvest's cut is the policy's answer at every harvest, set on the
10
10
  * policy (`setHarvestCut`) and capped by the manager (`HARVEST_CUT_MAX`); the release's
11
11
  * protocol share likewise (`POT_CUT_MAX`).
12
12
  */
@@ -29,12 +29,12 @@ function write(c: LogswapClient, address: Address, abi: readonly unknown[], func
29
29
  export async function setFeeParamsC(c: LogswapClient, mintFeeProtocolCutWad: bigint): Promise<Hash> {
30
30
  return write(c, c.addresses.cPoolManager, cPoolManagerAbi, "setFeeParams", [mintFeeProtocolCutWad]);
31
31
  }
32
- /** The F manager's `MINT_FEE_PROTOCOL_CUT` — a constant since F version 18 (decisions 049); there is no setter. */
32
+ /** The F manager's `MINT_FEE_PROTOCOL_CUT` — a constant since F version 18; there is no setter. */
33
33
  export async function fMintFeeProtocolCut(c: LogswapClient): Promise<bigint> {
34
34
  return c.public.readContract({ address: c.addresses.fPoolManager as Address, abi: fPoolManagerAbi, functionName: "MINT_FEE_PROTOCOL_CUT" } as never) as Promise<bigint>;
35
35
  }
36
36
 
37
- // ─── the registry (decisions 046): the LOG authority's, not the collector's ─────────────────
37
+ // ─── the registry: the LOG authority's, not the collector's ─────────────────
38
38
 
39
39
  const ZERO = "0x0000000000000000000000000000000000000000" as const;
40
40
 
@@ -58,7 +58,7 @@ export async function policyApproved(c: LogswapClient, policy: Address): Promise
58
58
  /** One policy on the registry's list, with what it says it is. */
59
59
  export interface RegistryEntry {
60
60
  policy: Address;
61
- /** The policy's own `kind()` tag (decisions 049) — what a pool born under it is. */
61
+ /** The policy's own `kind()` tag — what a pool born under it is. */
62
62
  kind: FPoolKind;
63
63
  name: string;
64
64
  /** The block it closes to new pools from; 0 = open. */
@@ -68,7 +68,7 @@ export interface RegistryEntry {
68
68
  }
69
69
 
70
70
  /**
71
- * Every policy listed, sunset ones included — one flat list (decisions 049), each with the kind
71
+ * Every policy listed, sunset ones included — one flat list, each with the kind
72
72
  * it says it is; `kind` narrows it to one, which is what a create form's dropdown reads. Empty
73
73
  * with no registry.
74
74
  */
@@ -138,7 +138,7 @@ export async function setPolicyApproved(c: LogswapClient, policy: Address, appro
138
138
  }
139
139
 
140
140
  /**
141
- * Move a pool to another approved policy of the same kind (decisions 046, 049): the registry
141
+ * Move a pool to another approved policy of the same kind: the registry
142
142
  * owner's alone, orchestrated on the registry — `migrate(manager, poolId, to)`. The new policy
143
143
  * takes the authority and the name from the old; the operator and the lists void; no swap lands
144
144
  * in the block.
package/src/claims.ts CHANGED
@@ -91,42 +91,20 @@ export async function checkReadiness(
91
91
  owner: Address,
92
92
  need: { base?: bigint; quote?: bigint; positions?: boolean } = {},
93
93
  ): Promise<Readiness> {
94
- const erc20 = [
95
- {
96
- type: "function",
97
- name: "allowance",
98
- stateMutability: "view",
99
- inputs: [
100
- { name: "owner", type: "address" },
101
- { name: "spender", type: "address" },
102
- ],
103
- outputs: [{ type: "uint256" }],
104
- },
105
- ] as const;
106
-
107
- const [operator, baseAllow, quoteAllow] = await Promise.all([
94
+ // A token is ready when BOTH legs of its pull path are: the ERC-20 allowance to whoever pulls
95
+ // (Permit2 when wired) and, with Permit2, its unexpired allowance to the router. This used to
96
+ // read the ERC-20 allowance to the ROUTER, which on a Permit2 deployment is never granted — so
97
+ // it said "approve" forever, and approving the router changed nothing.
98
+ const { checkTokenReadiness } = await import("./onboard.js");
99
+ const ready = async (token: Address, amount?: bigint) => {
100
+ if (amount === undefined) return true;
101
+ const r = await checkTokenReadiness(c, token, owner, amount);
102
+ return !r.needsTokenApproval && !r.needsPermit2Approval;
103
+ };
104
+ const [operator, baseOk, quoteOk] = await Promise.all([
108
105
  need.positions ? isRouterApproved(c, owner) : Promise.resolve(true),
109
- need.base
110
- ? (c.public.readContract({
111
- address: key.base,
112
- abi: erc20,
113
- functionName: "allowance",
114
- args: [owner, c.addresses.router],
115
- }) as Promise<bigint>)
116
- : Promise.resolve(0n),
117
- need.quote
118
- ? (c.public.readContract({
119
- address: key.quote,
120
- abi: erc20,
121
- functionName: "allowance",
122
- args: [owner, c.addresses.router],
123
- }) as Promise<bigint>)
124
- : Promise.resolve(0n),
106
+ ready(key.base, need.base),
107
+ ready(key.quote, need.quote),
125
108
  ]);
126
-
127
- return {
128
- needsOperator: !operator,
129
- needsBaseAllowance: need.base !== undefined && baseAllow < need.base,
130
- needsQuoteAllowance: need.quote !== undefined && quoteAllow < need.quote,
131
- };
109
+ return { needsOperator: !operator, needsBaseAllowance: !baseOk, needsQuoteAllowance: !quoteOk };
132
110
  }
package/src/client.ts CHANGED
@@ -24,7 +24,7 @@ export interface LogswapAddresses {
24
24
  */
25
25
  fPoolManager?: Address;
26
26
  /**
27
- * The protocol's four F policies, one per kind (decisions 044): every F pool names one in its key,
27
+ * The protocol's four F policies, one per kind: every F pool names one in its key,
28
28
  * and the pool's roles, gates and terms live on it. A deployment predating them carries none.
29
29
  */
30
30
  publicPolicy?: Address;
@@ -32,7 +32,7 @@ export interface LogswapAddresses {
32
32
  polPolicy?: Address;
33
33
  padPolicy?: Address;
34
34
  /**
35
- * The policy registry (decisions 046): what a pool of each kind may be born under, and the
35
+ * The policy registry: what a pool of each kind may be born under, and the
36
36
  * owner that may move a pool between policies. Read from the F manager (`registry()`) when
37
37
  * absent; a deployment with none asks nobody.
38
38
  */
@@ -52,7 +52,38 @@ export function createLogswapClient(args: {
52
52
  wallet?: WalletClient;
53
53
  addresses: LogswapAddresses;
54
54
  }): LogswapClient {
55
- return { public: args.public, wallet: args.wallet, addresses: args.addresses };
55
+ return { public: args.public, wallet: args.wallet && guardChain(args.wallet, args.public), addresses: args.addresses };
56
+ }
57
+
58
+ /**
59
+ * The wallet, with every signing call first checking that it is on the chain the reads come from.
60
+ *
61
+ * An injected wallet switched to another network would otherwise estimate and simulate against
62
+ * the public client's chain and then sign and send on its own — with deterministic deploy
63
+ * addresses, possibly into a different contract. Checked at each call, not once: a user can
64
+ * switch networks mid-session.
65
+ */
66
+ function guardChain(wallet: WalletClient, pub: PublicClient): WalletClient {
67
+ if (typeof wallet.getChainId !== "function") return wallet; // a test double
68
+ let readChain: number | undefined = pub.chain?.id;
69
+ const check = async () => {
70
+ readChain ??= await pub.getChainId();
71
+ const walletChain = await wallet.getChainId();
72
+ if (walletChain !== readChain) {
73
+ throw new Error(`logswap: the wallet is on chain ${walletChain} but this app reads chain ${readChain} — switch the wallet's network`);
74
+ }
75
+ };
76
+ const guarded = new Set<PropertyKey>(["writeContract", "sendTransaction", "signTypedData"]);
77
+ return new Proxy(wallet, {
78
+ get(target, prop, receiver) {
79
+ const v = Reflect.get(target, prop, receiver);
80
+ if (!guarded.has(prop) || typeof v !== "function") return v;
81
+ return async (...a: unknown[]) => {
82
+ await check();
83
+ return (v as (...x: unknown[]) => unknown).apply(target, a);
84
+ };
85
+ },
86
+ });
56
87
  }
57
88
 
58
89
  /**
@@ -69,18 +100,18 @@ export function createLogswapClient(args: {
69
100
  * manager sailed through `assertVersion` while `FEE_MAX()` still answered on it. Move this in the
70
101
  * same change as `CParams.VERSION`, never after it.
71
102
  *
72
- * **3** since 2026-09-03 (contracts `d8406fb`): `burnById` and `update` take an `owner`, `update`
103
+ * **3** since 2026-09-03: `burnById` and `update` take an `owner`, `update`
73
104
  * mints to `to`, `positions()` returns `aps` as uint128 — and a returned quote flow now carries the
74
- * swept fees (decisions 018), which is why `exit`'s settle-to-one-asset math changed with it.
105
+ * swept fees, which is why `exit`'s settle-to-one-asset math changed with it.
75
106
  *
76
- * **4** since 2026-09-18 (decisions 042 + 040): `PoolKey` is `(base, quote, tickSpacing, phi)` —
107
+ * **4** since 2026-09-18: `PoolKey` is `(base, quote, tickSpacing, phi)` —
77
108
  * the variance kernel and its two key fields are gone, so every pool id changed; `phiEff` is
78
109
  * `phiOf`; `getPool` lost `sigma2Ema`; `Initialize` lost two fields; the cut is
79
110
  * `MINT_FEE_PROTOCOL_CUT` / `setFeeParams`.
80
111
  */
81
112
  export const EXPECTED_VERSION = 4n;
82
113
  /**
83
- * The F manager's own tag (contracts 25a7bcf: 5 — the key's salt, pool names, the sponsor registry;
114
+ * The F manager's own tag (5 — the key's salt, pool names, the sponsor registry;
84
115
  * 4 was the income-only harvest cut).
85
116
  *
86
117
  * A FLOOR, not an equality — unlike `EXPECTED_VERSION` above. The F primitive is iterating fast and
@@ -92,43 +123,43 @@ export const EXPECTED_VERSION = 4n;
92
123
  */
93
124
  // 7 (2026-09-15): the reserves are the state and the marks derived, `lockStrike`, the dividend
94
125
  // harvest, the relaunch (`dissolve(poolId, successor)`, `rollIn`), the reserve-named seed, the
95
- // sponsor facet — decisions 025–031.
96
- // 8 (2026-09-15): the relaunch is in place (decisions 032) — every share id carries the
126
+ // sponsor facet.
127
+ // 8 (2026-09-15): the relaunch is in place — every share id carries the
97
128
  // generation (`fPoolShareId(poolId, gen)`), `dissolve(poolId)`, `redeem`, `claim` / `dividendOf`
98
129
  // / `rollIn` / `previewRollIn` take the generation, `Pool.gen` replaces `dissolved` and
99
130
  // `successor`. A 7 deployment's share ids do not even resolve with this SDK.
100
- // 9 (2026-09-16): `deepen` is gone, `Pool.leverTheta` is `harvestedTheta` (decisions 034).
131
+ // 9 (2026-09-16): `deepen` is gone, `Pool.leverTheta` is `harvestedTheta`.
101
132
  // 10 (2026-09-17): no floor guard — `Pool.minBuffer`, `raiseMinBuffer`, `LEVER_FLOOR` gone;
102
- // `dissolve` needs an empty bid on every pool (decisions 035); the key bit is the CLASS, `pad`
133
+ // `dissolve` needs an empty bid on every pool; the key bit is the CLASS, `pad`
103
134
  // (036): `seed` mints a pad's shares to 0xdead, and the operator, the gates and the list are
104
135
  // refused on it — `fPoolLaunch` no longer transfers the seed itself.
105
- // 11 (2026-09-17): roles and entries (decisions 037) — the operator runs the pool (`harvest`,
136
+ // 11 (2026-09-17): roles and entries — the operator runs the pool (`harvest`,
106
137
  // `dissolve`, `setLegs`) and is the authority until appointed, two-step (`acceptOperator`,
107
138
  // `Pool.pendingOperator`); `setLegL`/`admitLeg` are gone, `setLegs` edits the next generation's
108
139
  // table while unseeded; `fPoolRelaunch` takes `legs`.
109
140
  // `getPool` decodes one field fewer, so a 9 deployment's struct no longer matches this SDK.
110
- // 12 (2026-09-17): one contract (decisions 039) — the sponsor facet is folded back into the
141
+ // 12 (2026-09-17): one contract — the sponsor facet is folded back into the
111
142
  // manager; `sponsor()` and the fallback are gone, every entry, error and event unchanged, so
112
143
  // `fPoolManagerAbi` is the manager's own ABI and no longer a union. Layout unchanged.
113
- // 13 (2026-09-18): one vocabulary (decisions 040) — `HARVEST_FEE` is `HARVEST_PROTOCOL_CUT` (a
144
+ // 13 (2026-09-18): one vocabulary — `HARVEST_FEE` is `HARVEST_PROTOCOL_CUT` (a
114
145
  // cut of harvested income, not a fee on the harvest) and the exemption is `harvestCutExempt` /
115
146
  // `setHarvestCutExempt`; `acceptAuthority` clears the operator, pending and accepted (a mandate
116
147
  // is voided by the handover of the role that granted it); `setLegs` needs a generation to have
117
148
  // lived (`gen > 0`); `initialize` refuses a codeless base and `mint` refuses `to = address(0)`
118
149
  // on both managers. Layout unchanged.
119
- // 14 (2026-09-20): the policy (decisions 041) — `FPoolKey.policy`, every pool id moves; `dissolve` is
150
+ // 14 (2026-09-20): the policy — `FPoolKey.policy`, every pool id moves; `dissolve` is
120
151
  // `retire`, `regenerate` releases the dead shares' pot, `harvestable`/`unharvested` are the
121
152
  // readouts; `feePerL`, `edgePerL`, `HARVEST_PROTOCOL_CUT`, `harvestCutExempt` are gone.
122
- // 15 (2026-09-20): the policy answers every question (decisions 044) — `FPoolKey.authority` is
153
+ // 15 (2026-09-20): the policy answers every question — `FPoolKey.authority` is
123
154
  // `creator` (every pool id moves again); the roles, gates and list are the pool's POLICY's
124
155
  // (`FPolicyBase` / `PrivatePolicy`), not the manager's: `getPool` has `policy` and `asks` and no
125
156
  // role; `proposeAuthority`, `appointOperator`, `setGates`, `setAllowed` (`setMinters` / `setTakers` since 17) are policy writes;
126
157
  // `policyApproved(kind, policy)` / `setPolicyApproved(kind, policy, ok)`; `Regenerated` names the payee;
127
158
  // `HARVEST_CUT_MAX` / `POT_CUT_MAX` cap what a policy may route to the protocol.
128
- // 16 (2026-09-21): the word is POLICY (decisions 045) — a rename, no pool id moves: `FPoolKey.policy`,
159
+ // 16 (2026-09-21): the word is POLICY — a rename, no pool id moves: `FPoolKey.policy`,
129
160
  // `IFPolicy`, `FPolicyBase`, `PublicPolicy` … `PadPolicy`, `policyApproved` / `setPolicyApproved`,
130
161
  // `PolicyApproved`; `PolPolicy.setPolicy` / `PolicySet` are `setRules` / `RulesSet`.
131
- // 17 (2026-09-21): the registry, the move, the stamp, two lists, one base (decisions 046–048) — no
162
+ // 17 (2026-09-21): the registry, the move, the stamp, two lists, one base — no
132
163
  // pool id moves: the F manager takes a `PolicyRegistry` at construction and asks it at a birth
133
164
  // (`policyApproved` / `setPolicyApproved` left the manager: `registryApprove` …); the key's policy
134
165
  // is the policy at birth, `migrate` moves a pool for the registry's owner; `stamp` / `setAsks` are
@@ -149,7 +180,7 @@ export async function assertVersion(c: LogswapClient, expected = EXPECTED_VERSIO
149
180
  `against version ${expected}. Upgrade the SDK or point at the matching deployment.`,
150
181
  );
151
182
  }
152
- // the F manager carries its own tag since contracts c782811; assert it the same way when deployed
183
+ // the F manager carries its own tag; assert it the same way when deployed
153
184
  if (c.addresses.fPoolManager) {
154
185
  const { fPoolManagerAbi } = await import("./generated.js");
155
186
  const gotF = await c.public.readContract({
@@ -164,4 +195,32 @@ export async function assertVersion(c: LogswapClient, expected = EXPECTED_VERSIO
164
195
  );
165
196
  }
166
197
  }
198
+ await assertWiring(c);
199
+ }
200
+
201
+ /**
202
+ * The versions say the managers are the right code; this says the rest of the addresses belong
203
+ * to THEM. A stale router or lens from a previous deployment passes every version check and then
204
+ * fails in the worst way: approvals go to a router that pulls for another manager, or every pull
205
+ * reverts because the router's Permit2 is not the one the user approved.
206
+ */
207
+ export async function assertWiring(c: LogswapClient): Promise<void> {
208
+ const { logswapRouterAbi, logswapLensAbi } = await import("./generated.js");
209
+ const rd = (address: Address, abi: readonly unknown[], functionName: string) =>
210
+ c.public.readContract({ address, abi, functionName } as never) as Promise<Address>;
211
+ const ZERO = "0x0000000000000000000000000000000000000000";
212
+ const want: Array<[string, Promise<Address>, Address]> = [
213
+ ["router.mgr()", rd(c.addresses.router, logswapRouterAbi, "mgr"), c.addresses.cPoolManager],
214
+ ["router.permit2()", rd(c.addresses.router, logswapRouterAbi, "permit2"), c.addresses.permit2 ?? ZERO],
215
+ ["lens.mgr()", rd(c.addresses.lens, logswapLensAbi, "mgr"), c.addresses.cPoolManager],
216
+ ];
217
+ if (c.addresses.fPoolManager) {
218
+ want.push(["router.fmgr()", rd(c.addresses.router, logswapRouterAbi, "fmgr"), c.addresses.fPoolManager]);
219
+ want.push(["lens.fmgr()", rd(c.addresses.lens, logswapLensAbi, "fmgr"), c.addresses.fPoolManager]);
220
+ }
221
+ const got = await Promise.all(want.map((w) => w[1]));
222
+ const bad = want.flatMap(([what, , expected], i) =>
223
+ got[i]!.toLowerCase() === expected.toLowerCase() ? [] : [`${what} is ${got[i]}, expected ${expected}`],
224
+ );
225
+ if (bad.length) throw new Error(`logswap: the deployment's addresses do not belong together — ${bad.join("; ")}`);
167
226
  }