DEV Community

Cover image for How to Load the Omniston Widget with @ston-fi/omniston-widget-loader

How to Load the Omniston Widget with @ston-fi/omniston-widget-loader

Use the npm loader to fetch the current Omniston Widget bundle at runtime, connect TON Connect, and mount a complete swap interface inside your application.

If you are building a modern JavaScript application, @ston-fi/omniston-widget-loader gives you programmatic control over when the Omniston Widget enters the page. You install a small npm package, call its load() method, receive the OmnistonWidget constructor, create an instance, and mount that instance into a DOM element.

The important detail is that the npm package is a loader, not a packaged copy of the complete widget. The actual widget remains CDN-hosted. That distribution model lets STON.fi deliver compatible updates inside a major version without requiring every integrator to reinstall the full widget package.

Why does Omniston use a loader package?

Why does Omniston use a loader package

A normal npm integration usually installs the complete library into your project. Your package manager resolves a particular version, your bundler includes it in the application, and that exact copy stays there until you update the dependency.

Omniston takes a different approach.

The @ston-fi/omniston-widget-loader package gives your application the code needed to load the widget, while the widget itself is distributed through STON.fi's CDN. According to the official documentation, CDN paths are major-versioned. Within one major version, integrators can receive compatible fixes and improvements without changing the integration every time the widget receives an update.

That creates a useful separation:

  • npm manages the loader inside your application.
  • The loader fetches the widget bundle when your code requests it.
  • The widget constructor is returned to your code after loading succeeds.
  • Your application decides when and where to mount it.

This is particularly useful in React, Vue, Svelte, Angular, and other bundled applications where a developer may not want a global <script> tag running immediately when the page loads.

It also means you should not assume that installing @ston-fi/omniston-widget-loader places the complete Omniston Widget inside your JavaScript bundle. The runtime loading step is part of the architecture.

What actually happens when you call load()?

What actually happens when you call  raw `load()` endraw

At the application level, the flow is simple:

Your application
      |
      v
@ston-fi/omniston-widget-loader
      |
      | load()
      v
STON.fi widget CDN
      |
      v
OmnistonWidget constructor
      |
      v
new OmnistonWidget(config)
      |
      v
widget.mount(container)
Enter fullscreen mode Exit fullscreen mode

The package exposes a loader with a load() function. When the promise resolves, you receive the constructor that you use to create the widget instance.

A minimal pattern looks like this:

import OmnistonWidgetLoader from '@ston-fi/omniston-widget-loader';

const OmnistonWidget = await OmnistonWidgetLoader.load();

const widget = new OmnistonWidget({
  // configuration
});
Enter fullscreen mode Exit fullscreen mode

Loading and creating are therefore two different operations.

load() obtains the widget constructor. new OmnistonWidget(...) creates your configured widget instance. mount(...) finally attaches that instance to a real element on the page.

The widget also brings its own styles, so the official guide does not require a separate CSS package import.

That separation matters when you integrate it into an application lifecycle. You can wait until a component exists, lazy-load the widget only where swaps are needed, and clean up the mounted instance when the surrounding page changes.

Install the loader and mount your first widget

Install the loader and mount your first widget

Install the package with your preferred package manager:

npm install @ston-fi/omniston-widget-loader
Enter fullscreen mode Exit fullscreen mode

or:

yarn add @ston-fi/omniston-widget-loader
Enter fullscreen mode Exit fullscreen mode

or:

pnpm add @ston-fi/omniston-widget-loader
Enter fullscreen mode Exit fullscreen mode

Next, add a container to your page:

<div id="omniston-widget-container"></div>
Enter fullscreen mode Exit fullscreen mode

Then load, configure, and mount the widget:

import OmnistonWidgetLoader from '@ston-fi/omniston-widget-loader';

const OmnistonWidget = await OmnistonWidgetLoader.load();

const widget = new OmnistonWidget({
  tonconnect: {
    type: 'standalone',
    options: {
      manifestUrl: 'https://your-app.com/tonconnect-manifest.json',
    },
  },
});

const container = document.querySelector('#omniston-widget-container');

if (container) {
  widget.mount(container);
}
Enter fullscreen mode Exit fullscreen mode

There are four operations worth noticing here:

  1. Import the loader.
  2. Await OmnistonWidgetLoader.load().
  3. Create the widget with its configuration.
  4. Mount it into an existing DOM node.

The container check is not cosmetic. mount() needs an element that actually exists, so your framework's rendering lifecycle matters. In a React component, for example, you normally wait until the component has mounted and the container ref is available.

You also need TON Connect configuration before the swap experience can connect to a wallet.

Choose the right TON Connect mode

Choose the right TON Connect mode

The Omniston Widget supports two TON Connect patterns: standalone and integrated. They solve different application problems.

Mode Wallet connection Good fit
standalone The widget manages its own TON Connect setup from your manifest A site where the widget is the main wallet-enabled feature
integrated The widget receives an existing TON Connect instance A larger dApp that already manages wallet state

Standalone mode

Standalone mode keeps the integration compact:

const widget = new OmnistonWidget({
  tonconnect: {
    type: 'standalone',
    options: {
      manifestUrl: 'https://your-app.com/tonconnect-manifest.json',
    },
  },
});
Enter fullscreen mode Exit fullscreen mode

You provide the TON Connect manifest and let the widget handle the connection flow it needs.

This works well when you are adding a swap module to a landing page, product page, dashboard, or another application that does not already maintain a separate TON Connect session.

Integrated mode

Integrated mode

If your dApp already has TON Connect, creating an independent connection just for the widget is usually unnecessary. Instead, pass the initialized instance to Omniston:

import { TonConnect } from '@tonconnect/sdk';
import OmnistonWidgetLoader from '@ston-fi/omniston-widget-loader';

const tonconnect = new TonConnect({
  manifestUrl: 'https://your-app.com/tonconnect-manifest.json',
});

const OmnistonWidget = await OmnistonWidgetLoader.load();

const widget = new OmnistonWidget({
  tonconnect: {
    type: 'integrated',
    instance: tonconnect,
  },
});
Enter fullscreen mode Exit fullscreen mode

The widget can then share the wallet connection already used by the rest of your application.

Your TON Connect manifest must also be publicly reachable. TON documentation specifies HTTPS access, no authentication requirement, and no blocking proxy challenge. STON.fi's widget guide additionally instructs integrators to host their own manifest on their application domain. Following the widget guide's deployment pattern is the safer choice for this particular integration.

Following one loader-based STON.fi integration

Imagine you already have a dashboard built with a modern frontend framework. Users can connect their TON wallet elsewhere in the application, but there is no swap interface yet.

You want to add Omniston without implementing token selectors, swap UI, routing integration, and transaction interaction from scratch.

First, install the loader:

npm install @ston-fi/omniston-widget-loader
Enter fullscreen mode Exit fullscreen mode

Your application already maintains a TON Connect instance, so the widget should use integrated mode.

After the swap component appears, load the constructor:

const OmnistonWidget = await OmnistonWidgetLoader.load();
Enter fullscreen mode Exit fullscreen mode

Now create the instance:

const widget = new OmnistonWidget({
  tonconnect: {
    type: 'integrated',
    instance: tonconnect,
  },
  widget: {
    defaultBidAsset:
      'EQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM9c',
    defaultAskAsset:
      'EQA2kCVNwVsil2EM2mB0SkXytxCqQjS4mttjDpnXmwG9T6bO',
  },
});
Enter fullscreen mode Exit fullscreen mode

The example above preselects TON as the asset to sell and STON as the asset to receive, using the addresses shown in STON.fi's current widget documentation. These defaults are optional.

Finally, mount the interface:

widget.mount(containerElement);
Enter fullscreen mode Exit fullscreen mode

At that point, the loader's job is effectively finished. It got the constructor into your application. The configured OmnistonWidget instance now owns the widget lifecycle.

This distinction is useful when debugging. If load() fails, investigate the runtime loading stage. If construction succeeds but wallet interaction fails, inspect TON Connect configuration. If the instance exists but nothing appears, check the DOM container and mount timing.

A compact debugging checklist

When a loader-based integration does not behave as expected, check these areas separately:

  • Did OmnistonWidgetLoader.load() resolve successfully?
  • Does the mount container exist at the moment mount() runs?
  • Is the TON Connect manifest publicly accessible?
  • Are you using the intended standalone or integrated mode?
  • If using integrated mode, are you passing the initialized TON Connect instance?
  • Does your application remove or recreate the container during navigation?

Separating loading, configuration, and mounting usually makes the source of a problem much easier to identify.

Control mounting, unmounting, and runtime behavior

Loading a widget once does not mean it must remain mounted forever.

The official guide exposes mount and unmount lifecycle methods, along with events for mount, unmount, and error.

For example:

widget.on('mount', ({ container }) => {
  console.info('Omniston Widget mounted', container);
});

widget.on('unmount', () => {
  console.info('Omniston Widget unmounted');
});

widget.on('error', (error) => {
  console.error('Omniston Widget error', error);
});
Enter fullscreen mode Exit fullscreen mode

You can explicitly remove the widget when it is no longer needed:

widget.unmount();
Enter fullscreen mode Exit fullscreen mode

This becomes important in single-page applications. A route change can remove the original DOM node while your JavaScript still holds a reference to the widget instance.

A React component, for example, can load the widget inside an effect and unmount it during cleanup:

useEffect(() => {
  let active = true;
  let widget;

  OmnistonWidgetLoader.load().then((OmnistonWidget) => {
    if (!active || !containerRef.current) return;

    widget = new OmnistonWidget({
      tonconnect: {
        type: 'integrated',
        instance: tonconnect,
      },
    });

    widget.mount(containerRef.current);
  });

  return () => {
    active = false;
    widget?.unmount();
  };
}, [tonconnect]);
Enter fullscreen mode Exit fullscreen mode

The active guard prevents asynchronous loading from mounting into a component that disappeared before the promise resolved.

That is one of the practical advantages of the loader approach. Widget loading can participate in the same lifecycle logic as the rest of your application instead of depending on a permanently declared global script.

Loader or direct CDN script?

Loader or Direct CDN

STON.fi supports both approaches. They eventually give you access to the same widget constructor, but they fit different environments.

With the npm loader:

import OmnistonWidgetLoader from '@ston-fi/omniston-widget-loader';

const OmnistonWidget = await OmnistonWidgetLoader.load();
Enter fullscreen mode Exit fullscreen mode

With the direct CDN approach, the script creates a browser global:

<script src="https://widget.ston.fi/v0/index.js"></script>
Enter fullscreen mode Exit fullscreen mode

and your page uses:

const widget = new window.OmnistonWidget({
  // configuration
});
Enter fullscreen mode Exit fullscreen mode

The direct CDN method is attractive for a small static page because there is almost nothing to install.

The loader is usually the more natural choice when your project already has a build system and application lifecycle. You can import it through your package manager, decide exactly when loading starts, keep the integration inside your JavaScript modules, and coordinate mounting with your framework.

One detail should not be overlooked: using the npm loader does not replace the CDN distribution model. The loader is a controlled way to access that model from a bundled application.

What should you verify before shipping?

A working local demo is only the first stage. Before deploying a loader-based Omniston Widget integration, test the complete path from page load to wallet interaction.

Loading

Confirm that the widget loads successfully in the actual production environment, not only through your development server.

Manifest

Verify that your TON Connect manifest and icon remain publicly available over HTTPS. TON documentation notes that inaccessible or invalid manifests can cause manifest-related connection errors.

Lifecycle

Navigate away from the widget and back again. Make sure your application does not accidentally create duplicate instances or retain a widget mounted to an obsolete container.

Wallet state

If your application already manages TON Connect, verify that the widget uses the same intended connection through integrated mode.

Swap configuration

If you set default assets, custom assets, or referral parameters, verify those values rather than assuming that a successfully rendered interface means every option is correct.

Error handling

Capture the widget's error event during development. A visible swap box is only one part of the integration. Wallet connection and transaction flows need testing as well.

The practical takeaway is simple: treat @ston-fi/omniston-widget-loader as the runtime boundary between your application and the CDN-delivered widget. Test the loader, widget configuration, wallet layer, and DOM lifecycle independently. Once those pieces are separated mentally, integrating Omniston becomes much easier to reason about.

Frequently Asked Questions

What is @ston-fi/omniston-widget-loader?

It is an npm package for programmatically loading the Omniston Widget bundle. Your application calls load(), waits for the bundle to become available, and receives the OmnistonWidget constructor. The complete widget itself uses STON.fi's CDN distribution model rather than being bundled as a fixed widget copy inside the loader package.

Does npm install @ston-fi/omniston-widget-loader install the complete widget?

Not in the conventional sense. The package acts as a loader for the CDN-hosted widget. STON.fi documents this as an intentional distribution model so compatible widget updates can be delivered within a major-version path while applications keep using the same integration pattern.

Do I need to import the Omniston Widget CSS separately?

No separate widget stylesheet import is required according to the current STON.fi integration guide. The widget bundle loads its own styles. You can then customize supported visual properties through CSS variables scoped to the element used for widget.mount(...).

Should I use standalone or integrated TON Connect mode?

Use standalone when the widget can manage the wallet connection required for its own swap flow. Use integrated when your application already maintains TON Connect and you want Omniston to share that existing instance. The second approach avoids treating the widget as a separate wallet-connected application inside your dApp.

Can I lazy-load the Omniston Widget?

Yes. Programmatic runtime loading is one of the main reasons to use the loader. You can call load() when a swap component opens, when a relevant route becomes active, or at another point chosen by your application rather than declaring the widget globally with a script tag.

What does load() return?

It resolves with the OmnistonWidget constructor. You still need to instantiate it with new OmnistonWidget(configuration) and then call mount() with an existing DOM element. Loading the constructor and rendering the configured widget are separate stages.

Can I remove the widget after mounting it?

Yes. The documented widget lifecycle includes unmount(), and the widget exposes lifecycle events including mount, unmount, and error. This is especially useful in applications where routes or components appear and disappear without a full browser refresh.

When should I use the loader instead of the CDN script?

Use the loader when your application already uses npm and a modern build system, especially when you want programmatic control over loading and component lifecycle. Use the direct CDN script when a small static integration benefits more from minimal setup. Both approaches ultimately expose the Omniston Widget constructor.

Sources and Further Reading

  • STON.fi Omniston Widget - Official overview of the widget, npm loader, CDN distribution, TON Connect modes, configuration, and customization
  • STON.fi Full Guide & Reference - Official installation guide covering @ston-fi/omniston-widget-loader, load(), configuration, lifecycle methods, assets, and TON Connect integration
  • STON.fi Omniston Widget GitHub repository - Official repository explaining the CDN distribution model and programmatic loader usage
  • npm package registry - Package information and usage example for @ston-fi/omniston-widget-loader
  • STON.fi Help Center - Official overview of embedding the swap widget, TON Connect modes, theming, assets, and referral configuration
  • TON Documentation - Official TON Connect guidance covering manifest hosting, accessibility requirements, and available SDK packages

Top comments (1)

Collapse
 
ivan_cryptovazimazima profile image
Ivan “Crypto Vazima” Zimanov

Hello. If you find an error in the text or code, please write about it in the comments below. This will greatly help others who read this article and encounter the same problem you've already solved. Thank you for your support and assistance, best regards!