@cartridge/controller 0.11.2 → 0.11.3

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.
Files changed (59) hide show
  1. package/.turbo/turbo-build$colon$deps.log +18 -18
  2. package/.turbo/turbo-build.log +16 -16
  3. package/dist/iframe/base.d.ts +0 -1
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1854 -811
  6. package/dist/index.js.map +1 -1
  7. package/dist/node/index.cjs +1 -1
  8. package/dist/node/index.cjs.map +1 -1
  9. package/dist/node/index.d.cts +1 -1
  10. package/dist/node/index.d.ts +1 -1
  11. package/dist/node/index.js +1 -1
  12. package/dist/node/index.js.map +1 -1
  13. package/dist/{provider-s-80NdXp.js → provider-DCZ6z1Gs.js} +38 -24
  14. package/dist/provider-DCZ6z1Gs.js.map +1 -0
  15. package/dist/session.js +2 -2
  16. package/dist/stats.html +1 -1
  17. package/dist/toast/components/close-button.d.ts +1 -0
  18. package/dist/toast/components/progress-bar.d.ts +6 -0
  19. package/dist/toast/index.d.ts +57 -0
  20. package/dist/toast/styles.d.ts +1 -0
  21. package/dist/toast/types.d.ts +39 -0
  22. package/dist/toast/utils/progress-bar.d.ts +9 -0
  23. package/dist/toast/utils.d.ts +8 -0
  24. package/dist/toast/variants/achievement.d.ts +3 -0
  25. package/dist/toast/variants/error.d.ts +3 -0
  26. package/dist/toast/variants/index.d.ts +6 -0
  27. package/dist/toast/variants/marketplace.d.ts +3 -0
  28. package/dist/toast/variants/network-switch.d.ts +3 -0
  29. package/dist/toast/variants/quest.d.ts +3 -0
  30. package/dist/toast/variants/transaction.d.ts +3 -0
  31. package/dist/types.d.ts +3 -3
  32. package/dist/utils.d.ts +1 -0
  33. package/dist/wallets/index.d.ts +1 -0
  34. package/dist/wallets/phantom-evm/index.d.ts +7 -0
  35. package/dist/wallets/types.d.ts +2 -2
  36. package/package.json +2 -3
  37. package/src/iframe/base.ts +2 -20
  38. package/src/index.ts +1 -0
  39. package/src/toast/components/close-button.ts +82 -0
  40. package/src/toast/components/progress-bar.ts +60 -0
  41. package/src/toast/index.ts +263 -0
  42. package/src/toast/styles.ts +142 -0
  43. package/src/toast/types.ts +67 -0
  44. package/src/toast/utils/progress-bar.ts +23 -0
  45. package/src/toast/utils.ts +65 -0
  46. package/src/toast/variants/achievement.ts +244 -0
  47. package/src/toast/variants/error.ts +107 -0
  48. package/src/toast/variants/index.ts +6 -0
  49. package/src/toast/variants/marketplace.ts +145 -0
  50. package/src/toast/variants/network-switch.ts +72 -0
  51. package/src/toast/variants/quest.ts +164 -0
  52. package/src/toast/variants/transaction.ts +256 -0
  53. package/src/types.ts +1 -0
  54. package/src/utils.ts +15 -0
  55. package/src/wallets/bridge.ts +4 -0
  56. package/src/wallets/index.ts +1 -0
  57. package/src/wallets/phantom-evm/index.ts +8 -0
  58. package/src/wallets/types.ts +5 -1
  59. package/dist/provider-s-80NdXp.js.map +0 -1
@@ -0,0 +1 @@
1
+ export declare const CloseButton: (translucent?: boolean) => HTMLDivElement;
@@ -0,0 +1,6 @@
1
+ export interface ProgressBarOptions {
2
+ duration: number;
3
+ onComplete?: () => void;
4
+ borderRadius?: number;
5
+ }
6
+ export declare const ProgressBar: (options: ProgressBarOptions) => HTMLDivElement;
@@ -0,0 +1,57 @@
1
+ import { ToastOptions } from './types';
2
+ /**
3
+ * Show a toast notification
4
+ *
5
+ * The toast will always appear on the parent page, even if called from within an iframe.
6
+ * This ensures toasts are visible above all content, including the keychain iframe.
7
+ *
8
+ * @param options - Toast options with variant-specific properties
9
+ * @returns A function to manually dismiss the toast
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { toast } from "@cartridge/controller";
14
+ *
15
+ * // Error toast
16
+ * toast({
17
+ * variant: "error",
18
+ * message: "Transaction failed",
19
+ * });
20
+ *
21
+ * // Transaction toast
22
+ * toast({
23
+ * variant: "transaction",
24
+ * hash: "0x1234...",
25
+ * status: "success",
26
+ * amount: "100",
27
+ * token: "ETH"
28
+ * });
29
+ *
30
+ * // Network switch toast
31
+ * toast({
32
+ * variant: "network-switch",
33
+ * networkName: "Mainnet",
34
+ * networkIcon: <url to icon image>
35
+ * });
36
+ *
37
+ * // Achievement toast
38
+ * toast({
39
+ * variant: "achievement",
40
+ * itemName: "First Achievement",
41
+ * itemImage: "https://example.com/trophy.png"
42
+ * action: "purchased" | "sold",
43
+ * });
44
+ *
45
+ * // Marketplace toast
46
+ * toast({
47
+ * variant: "marketplace",
48
+ * itemName: "Cool NFT #123",
49
+ * action: "purchased",
50
+ * price: "0.5",
51
+ * currency: "ETH",
52
+ * image: "https://example.com/nft.png"
53
+ * });
54
+ * ```
55
+ */
56
+ export declare function toast(options: ToastOptions): () => void;
57
+ export * from './types';
@@ -0,0 +1 @@
1
+ export declare function injectStyles(targetDoc: Document): void;
@@ -0,0 +1,39 @@
1
+ export type ToastPosition = "top-left" | "top-right" | "top-center" | "bottom-left" | "bottom-right" | "bottom-center";
2
+ export interface BaseToastOptions {
3
+ duration?: number;
4
+ position?: ToastPosition;
5
+ }
6
+ export interface ErrorToastOptions extends BaseToastOptions {
7
+ variant: "error";
8
+ message: string;
9
+ }
10
+ export interface TransactionToastOptions extends BaseToastOptions {
11
+ variant: "transaction";
12
+ status: "confirming" | "confirmed";
13
+ isExpanded?: boolean;
14
+ label?: string;
15
+ }
16
+ export interface NetworkSwitchToastOptions extends BaseToastOptions {
17
+ variant: "network-switch";
18
+ networkName: string;
19
+ networkIcon?: string;
20
+ }
21
+ export interface AchievementToastOptions extends BaseToastOptions {
22
+ variant: "achievement";
23
+ title: string;
24
+ subtitle?: string;
25
+ xpAmount: number;
26
+ isDraft?: boolean;
27
+ }
28
+ export interface QuestToastOptions extends BaseToastOptions {
29
+ variant: "quest";
30
+ title: string;
31
+ subtitle: string;
32
+ }
33
+ export interface MarketplaceToastOptions extends BaseToastOptions {
34
+ variant: "marketplace";
35
+ itemName: string;
36
+ itemImage: string;
37
+ action: "purchased" | "sold";
38
+ }
39
+ export type ToastOptions = ErrorToastOptions | TransactionToastOptions | NetworkSwitchToastOptions | AchievementToastOptions | QuestToastOptions | MarketplaceToastOptions;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Add a progress bar to a toast element
3
+ *
4
+ * @param toast - The toast element to add the progress bar to
5
+ * @param duration - Duration in milliseconds
6
+ * @param onComplete - Callback when progress completes
7
+ * @param borderRadius - Optional border radius in pixels (default: 8px)
8
+ */
9
+ export declare function addProgressBarToToast(toast: HTMLElement, duration: number, onComplete: () => void, borderRadius?: number): void;
@@ -0,0 +1,8 @@
1
+ export declare const TOAST_CONTAINER_ID = "cartridge-toast-container";
2
+ export declare const DEFAULT_DURATION = 3000;
3
+ export declare const DEFAULT_POSITION = "bottom-right";
4
+ export declare const TOAST_MESSAGE_TYPE = "cartridge-toast-show";
5
+ export declare function isInIframe(): boolean;
6
+ export declare function getTargetDocument(): Document | null;
7
+ export declare function getToastContainer(targetDoc: Document, position: string): HTMLElement;
8
+ export declare function removeToast(toast: HTMLElement): void;
@@ -0,0 +1,3 @@
1
+ import { AchievementToastOptions } from '../types';
2
+ export declare function injectAchievementStyles(targetDoc: Document): void;
3
+ export declare function createAchievementToast(options: AchievementToastOptions): HTMLElement;
@@ -0,0 +1,3 @@
1
+ import { ErrorToastOptions } from '../types';
2
+ export declare function injectErrorStyles(targetDoc: Document): void;
3
+ export declare function createErrorToast(options: ErrorToastOptions): HTMLElement;
@@ -0,0 +1,6 @@
1
+ export * from './error';
2
+ export * from './transaction';
3
+ export * from './network-switch';
4
+ export * from './achievement';
5
+ export * from './marketplace';
6
+ export * from './quest';
@@ -0,0 +1,3 @@
1
+ import { MarketplaceToastOptions } from '../types';
2
+ export declare function injectMarketplaceStyles(targetDoc: Document): void;
3
+ export declare function createMarketplaceToast(options: MarketplaceToastOptions): HTMLElement;
@@ -0,0 +1,3 @@
1
+ import { NetworkSwitchToastOptions } from '../types';
2
+ export declare function injectNetworkSwitchStyles(targetDoc: Document): void;
3
+ export declare function createNetworkSwitchToast(options: NetworkSwitchToastOptions): HTMLElement;
@@ -0,0 +1,3 @@
1
+ import { QuestToastOptions } from '../types';
2
+ export declare function injectQuestStyles(targetDoc: Document): void;
3
+ export declare function createQuestToast(options: QuestToastOptions): HTMLElement;
@@ -0,0 +1,3 @@
1
+ import { TransactionToastOptions } from '../types';
2
+ export declare function injectTransactionStyles(targetDoc: Document): void;
3
+ export declare function createTransactionToast(options: TransactionToastOptions): HTMLElement;
package/dist/types.d.ts CHANGED
@@ -15,9 +15,9 @@ export type KeychainSession = {
15
15
  };
16
16
  export declare const EMBEDDED_WALLETS: readonly ["google", "webauthn", "discord", "walletconnect", "password"];
17
17
  export type EmbeddedWallet = (typeof EMBEDDED_WALLETS)[number];
18
- export declare const ALL_AUTH_OPTIONS: readonly ["google", "webauthn", "discord", "walletconnect", "password", "metamask", "rabby", "argent", "braavos", "phantom", "base"];
18
+ export declare const ALL_AUTH_OPTIONS: readonly ["google", "webauthn", "discord", "walletconnect", "password", "metamask", "rabby", "phantom-evm", "argent", "braavos", "phantom", "base"];
19
19
  export type AuthOption = (typeof ALL_AUTH_OPTIONS)[number];
20
- export declare const IMPLEMENTED_AUTH_OPTIONS: ("metamask" | "rabby" | "google" | "webauthn" | "discord" | "walletconnect" | "password")[];
20
+ export declare const IMPLEMENTED_AUTH_OPTIONS: ("metamask" | "rabby" | "phantom-evm" | "google" | "webauthn" | "discord" | "walletconnect" | "password")[];
21
21
  export type AuthOptions = (typeof IMPLEMENTED_AUTH_OPTIONS)[number][];
22
22
  export declare enum ResponseCodes {
23
23
  SUCCESS = "SUCCESS",
@@ -155,7 +155,7 @@ export type KeychainOptions = IFrameOptions & {
155
155
  /** When true, defer iframe mounting until connect() is called. Reduces initial load and resource fetching. */
156
156
  lazyload?: boolean;
157
157
  };
158
- export type ProfileContextTypeVariant = "inventory" | "trophies" | "achievements" | "leaderboard" | "activity";
158
+ export type ProfileContextTypeVariant = "inventory" | "trophies" | "achievements" | "quests" | "leaderboard" | "activity";
159
159
  export type Token = "eth" | "strk" | "lords" | "usdc" | "usdt";
160
160
  export type Tokens = {
161
161
  erc20?: Token[];
package/dist/utils.d.ts CHANGED
@@ -14,3 +14,4 @@ export declare function toArray<T>(val: T | T[]): T[];
14
14
  export declare function humanizeString(str: string): string;
15
15
  export declare function parseChainId(url: URL): ChainId;
16
16
  export declare function isMobile(): boolean;
17
+ export declare function sanitizeImageSrc(src: string): string;
@@ -5,6 +5,7 @@ export * from './bridge';
5
5
  export * from './ethereum-base';
6
6
  export * from './metamask';
7
7
  export * from './phantom';
8
+ export * from './phantom-evm';
8
9
  export * from './rabby';
9
10
  export * from './types';
10
11
  export * from './platform';
@@ -0,0 +1,7 @@
1
+ import { ExternalWalletType } from '../types';
2
+ import { EthereumWalletBase } from '../ethereum-base';
3
+ export declare class PhantomEVMWallet extends EthereumWalletBase {
4
+ readonly type: ExternalWalletType;
5
+ readonly rdns = "app.phantom";
6
+ readonly displayName = "Phantom";
7
+ }
@@ -1,8 +1,8 @@
1
- export declare const AUTH_EXTERNAL_WALLETS: readonly ["metamask", "rabby"];
1
+ export declare const AUTH_EXTERNAL_WALLETS: readonly ["metamask", "rabby", "phantom-evm"];
2
2
  export type AuthExternalWallet = (typeof AUTH_EXTERNAL_WALLETS)[number];
3
3
  export declare const EXTRA_EXTERNAL_WALLETS: readonly ["argent", "braavos", "phantom", "base"];
4
4
  export type ExtraExternalWallet = (typeof EXTRA_EXTERNAL_WALLETS)[number];
5
- export declare const EXTERNAL_WALLETS: readonly ["metamask", "rabby", "argent", "braavos", "phantom", "base"];
5
+ export declare const EXTERNAL_WALLETS: readonly ["metamask", "rabby", "phantom-evm", "argent", "braavos", "phantom", "base"];
6
6
  export type ExternalWalletType = (typeof EXTERNAL_WALLETS)[number];
7
7
  export type ExternalPlatform = "starknet" | "ethereum" | "solana" | "base" | "arbitrum" | "optimism";
8
8
  export interface ExternalWallet {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cartridge/controller",
3
- "version": "0.11.2",
3
+ "version": "0.11.3",
4
4
  "description": "Cartridge Controller",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -50,7 +50,7 @@
50
50
  "vite-plugin-node-polyfills": "^0.23.0",
51
51
  "vite-plugin-top-level-await": "^1.4.4",
52
52
  "vite-plugin-wasm": "^3.4.1",
53
- "@cartridge/tsconfig": "0.11.2"
53
+ "@cartridge/tsconfig": "0.11.3"
54
54
  },
55
55
  "scripts": {
56
56
  "build:deps": "pnpm build",
@@ -58,7 +58,6 @@
58
58
  "build:browser": "vite build",
59
59
  "build:node": "tsup --config tsup.node.config.ts",
60
60
  "build": "pnpm build:browser && pnpm build:node",
61
- "build:compat": "pnpm build:browser && pnpm build:node",
62
61
  "format": "prettier --write \"src/**/*.ts\"",
63
62
  "format:check": "prettier --check \"src/**/*.ts\"",
64
63
  "test": "jest",
@@ -14,7 +14,6 @@ export class IFrame<CallSender extends {}> implements Modal {
14
14
  private iframe?: HTMLIFrameElement;
15
15
  private container?: HTMLDivElement;
16
16
  private onClose?: () => void;
17
- private child?: AsyncMethodReturns<CallSender>;
18
17
  private closeTimeout?: NodeJS.Timeout;
19
18
 
20
19
  constructor({
@@ -55,7 +54,7 @@ export class IFrame<CallSender extends {}> implements Modal {
55
54
  iframe.sandbox.add("allow-scripts");
56
55
  iframe.sandbox.add("allow-same-origin");
57
56
  iframe.allow =
58
- "publickey-credentials-create *; publickey-credentials-get *; clipboard-write";
57
+ "publickey-credentials-create *; publickey-credentials-get *; clipboard-write; local-network-access *; payment *";
59
58
  iframe.style.scrollbarWidth = "none";
60
59
  iframe.style.setProperty("-ms-overflow-style", "none");
61
60
  iframe.style.setProperty("-webkit-scrollbar", "none");
@@ -118,20 +117,6 @@ export class IFrame<CallSender extends {}> implements Modal {
118
117
  { passive: false },
119
118
  );
120
119
 
121
- // Add click event listener to close iframe when clicking outside
122
- container.addEventListener("click", (e) => {
123
- if (e.target === container) {
124
- // Attempting to reset(clear context) for keychain iframe (identified by ID)
125
- if (id === "controller-keychain" && this.child) {
126
- // Type assertion for keychain child only
127
- (this.child as any)
128
- .reset?.()
129
- .catch((e: any) => console.error("Error resetting context:", e));
130
- }
131
- this.close();
132
- }
133
- });
134
-
135
120
  this.iframe = iframe;
136
121
  this.container = container;
137
122
 
@@ -142,10 +127,7 @@ export class IFrame<CallSender extends {}> implements Modal {
142
127
  reload: (_origin: string) => () => window.location.reload(),
143
128
  ...methods,
144
129
  },
145
- }).promise.then((child) => {
146
- this.child = child;
147
- onConnect(child);
148
- });
130
+ }).promise.then(onConnect);
149
131
 
150
132
  this.resize();
151
133
  window.addEventListener("resize", () => this.resize());
package/src/index.ts CHANGED
@@ -4,4 +4,5 @@ export * from "./types";
4
4
  export * from "./lookup";
5
5
  export * from "./utils";
6
6
  export * from "./wallets";
7
+ export * from "./toast";
7
8
  export * from "@cartridge/presets";
@@ -0,0 +1,82 @@
1
+ export const CloseButton = (translucent = false): HTMLDivElement => {
2
+ const container = document.createElement("div");
3
+ container.id = "close-button";
4
+ container.style.display = "flex";
5
+ container.style.alignItems = "center";
6
+ container.style.justifyContent = "center";
7
+
8
+ const button = document.createElement("button");
9
+ button.className = translucent
10
+ ? "cartridge-close-button translucent"
11
+ : "cartridge-close-button";
12
+
13
+ const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
14
+ svg.setAttribute("width", "20");
15
+ svg.setAttribute("height", "20");
16
+ svg.setAttribute("viewBox", "0 0 20 20");
17
+ svg.setAttribute("fill", "none");
18
+ svg.style.pointerEvents = "none"; // Ensure clicks pass through to button
19
+
20
+ const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
21
+ path.setAttribute(
22
+ "d",
23
+ "M15.5465 14.343C15.8881 14.6837 15.8881 15.2364 15.5465 15.5772C15.2049 15.9179 14.6506 15.9178 14.309 15.5772L10.0006 11.2484L5.66162 15.5757C5.32001 15.9164 4.76575 15.9164 4.4241 15.5757C4.08245 15.235 4.08249 14.6822 4.4241 14.3415L8.76455 10.0157L4.4229 5.65573C4.08128 5.31504 4.08128 4.76227 4.4229 4.42155C4.76451 4.08082 5.31877 4.08086 5.66042 4.42155L10.0006 8.78299L14.3396 4.45573C14.6812 4.11504 15.2355 4.11504 15.5771 4.45573C15.9188 4.79642 15.9187 5.34918 15.5771 5.68991L11.2367 10.0157L15.5465 14.343Z",
24
+ );
25
+ path.setAttribute("class", "cartridge-close-icon");
26
+
27
+ svg.appendChild(path);
28
+ button.appendChild(svg);
29
+
30
+ // Inline critical button styles
31
+ button.style.display = "flex";
32
+ button.style.alignItems = "center";
33
+ button.style.justifyContent = "center";
34
+ button.style.border = "none";
35
+ button.style.background = "transparent";
36
+ button.style.cursor = "pointer";
37
+ button.style.borderRadius = "4px";
38
+ button.style.padding = "10px";
39
+ button.style.gap = "4px";
40
+ button.style.transition = "background-color 0.2s ease";
41
+
42
+ // Add styles dynamically to the correct document
43
+ const targetDoc = container.ownerDocument;
44
+ if (!targetDoc.getElementById("cartridge-close-button-style")) {
45
+ const style = targetDoc.createElement("style");
46
+ style.id = "cartridge-close-button-style";
47
+ style.textContent = `
48
+ .cartridge-close-button .cartridge-close-icon {
49
+ fill: rgba(0, 0, 0, 0.48);
50
+ transition: fill 0.2s ease;
51
+ }
52
+
53
+ .cartridge-close-button:not(.translucent):hover {
54
+ background-color: #181c19;
55
+ }
56
+
57
+ .cartridge-close-button:not(.translucent):hover .cartridge-close-icon {
58
+ fill: rgba(255, 255, 255, 0.72);
59
+ }
60
+
61
+ .cartridge-close-button.translucent .cartridge-close-icon {
62
+ fill: rgba(0, 0, 0, 0.48);
63
+ }
64
+
65
+ .cartridge-close-button.translucent:hover {
66
+ background-color: rgba(0, 0, 0, 0.04);
67
+ }
68
+
69
+ .cartridge-close-button.translucent:hover .cartridge-close-icon {
70
+ fill: rgba(0, 0, 0, 0.72);
71
+ }
72
+
73
+ .cartridge-close-button:active {
74
+ transform: scale(0.95);
75
+ }
76
+ `;
77
+ targetDoc.head.appendChild(style);
78
+ }
79
+
80
+ container.appendChild(button);
81
+ return container;
82
+ };
@@ -0,0 +1,60 @@
1
+ export interface ProgressBarOptions {
2
+ duration: number; // in milliseconds (0 or Infinity for static full bar)
3
+ onComplete?: () => void;
4
+ borderRadius?: number; // Optional border radius in pixels (default: 8px)
5
+ }
6
+
7
+ export const ProgressBar = (options: ProgressBarOptions): HTMLDivElement => {
8
+ const borderRadius = options.borderRadius ?? 8;
9
+ const isInfiniteDuration =
10
+ !isFinite(options.duration) || options.duration <= 0;
11
+
12
+ const container = document.createElement("div");
13
+ container.className = "cartridge-toast-progress-bar";
14
+ container.style.position = "absolute";
15
+ container.style.bottom = "0";
16
+ container.style.left = "0";
17
+ container.style.right = "0";
18
+ container.style.height = "4px";
19
+ container.style.overflow = "hidden";
20
+ container.style.borderBottomLeftRadius = `${borderRadius}px`;
21
+ container.style.borderBottomRightRadius = `${borderRadius}px`;
22
+ container.style.backgroundColor = "rgba(255, 255, 255, 0.2)";
23
+
24
+ const inside = document.createElement("div");
25
+ inside.className = "cartridge-toast-progress-bar-fill";
26
+ inside.style.position = "absolute";
27
+ inside.style.bottom = "0";
28
+ inside.style.left = "0";
29
+ inside.style.height = "100%";
30
+ inside.style.backgroundColor = "rgba(255, 255, 255, 0.8)";
31
+ inside.style.borderBottomLeftRadius = `${borderRadius}px`;
32
+
33
+ if (isInfiniteDuration) {
34
+ // For infinite duration, show full bar immediately without animation
35
+ inside.style.width = "100%";
36
+ inside.style.transition = "none";
37
+ } else {
38
+ // For finite duration, animate the progress bar
39
+ inside.style.width = "0%";
40
+ inside.style.transition = `width ${options.duration}ms linear`;
41
+
42
+ // Start animation after a brief delay to ensure styles are applied
43
+ requestAnimationFrame(() => {
44
+ requestAnimationFrame(() => {
45
+ inside.style.width = "100%";
46
+ });
47
+ });
48
+
49
+ // Call onComplete when animation finishes
50
+ if (options.onComplete) {
51
+ setTimeout(() => {
52
+ options.onComplete?.();
53
+ }, options.duration);
54
+ }
55
+ }
56
+
57
+ container.appendChild(inside);
58
+
59
+ return container;
60
+ };