Skip to content

About

Injected provider API for connecting Stellar dApps to the Scopuly mobile wallet

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

@scopuly/signer-extension-api

Zero-dependency TypeScript API for Stellar dApps using the Scopuly wallet provider in either:

  • the Scopuly mobile in-app dApp browser;
  • the Scopuly browser extension.

Both environments inject the same provider as window.scopuly. This package supplies its types and small delegate functions. It does not inject a provider, handle secret keys, sign locally, or submit requests without the user's approval in Scopuly.

Install

npm install @scopuly/signer-extension-api

Importing the package also adds the typed window.scopuly and window.stellar.scopuly discovery namespaces to TypeScript projects.

Live playground

Try the provider flows in the Scopuly Provider Playground. It demonstrates provider detection and connection, SEP-53 message signing, transaction signing and submission, Soroban authorization signing, and x402 receipt reporting.

The playground uses the same window.scopuly contract described by this package. Signing flows require the Scopuly browser extension paired with Scopuly Mobile. Check the selected Stellar network and review every request before approving it; the playground uses Mainnet by default.

Detect and connect

The provider can be injected after the page starts. Check for it immediately and listen for scopuly#initialized when it is not available yet.

import {
  getNetwork,
  getProvider,
  requestAccess,
} from "@scopuly/signer-extension-api";

const connect = async () => {
  const provider = getProvider();
  const {address} = await requestAccess();
  const {network, networkPassphrase} = await getNetwork();

  return {
    address,
    network,
    networkPassphrase,
    platform: provider.platform, // "mobile" | "extension"
    providerVersion: provider.__scopulyProviderVersion,
  };
};

if (window.scopuly?.isScopuly) {
  await connect();
}
else {
  window.addEventListener("scopuly#initialized", () => void connect(), {once: true});
}

requestAccess() asks the user to select or approve one public account for the current dApp origin. A dApp never receives the user's secret key or unrelated accounts.

Sign a Stellar transaction

Build an unsigned transaction in the dApp—for example with @stellar/stellar-sdk—then pass its XDR to Scopuly:

import {
  getNetwork,
  requestAccess,
  signTransaction,
} from "@scopuly/signer-extension-api";

const {address} = await requestAccess();
const {networkPassphrase} = await getNetwork();

// Build this with @stellar/stellar-sdk or another Stellar transaction builder.
const unsignedXdr = transaction.toXDR();

const {signedTxXdr, signerAddress} = await signTransaction(unsignedXdr, {
  address,
  networkPassphrase,
});

// Submit signedTxXdr through your own Horizon/RPC flow if required.
console.log({signedTxXdr, signerAddress});

Every signing request opens a Scopuly review flow. The provider verifies the requested network and signer before signing.

To let Scopuly sign and submit through its configured Stellar endpoint:

import {signAndSubmitTransaction} from "@scopuly/signer-extension-api";

const {status, hash, signedTxXdr} = await signAndSubmitTransaction(unsignedXdr, {
  address,
  networkPassphrase,
});

Custom submit URLs are not accepted.

All signing methods accept only address and networkPassphrase as common options. signTransaction additionally accepts submit. Derivation paths and custom submission URLs are not part of the Scopuly provider contract.

Sign a Soroban authorization entry

Use signAuthEntry when a Soroban flow needs a non-invoker authorization signature:

import {signAuthEntry} from "@scopuly/signer-extension-api";

const {signedAuthEntry, signerAddress} = await signAuthEntry(authEntryXdr, {
  address,
  networkPassphrase,
});

Scopuly validates the entry's network ID and CAP-71 bound address. The returned signature is the raw 64-byte Ed25519 signature encoded as padded base64.

Sign a SEP-53 message

import {signMessage} from "@scopuly/signer-extension-api";

const {signedMessage, signerAddress} = await signMessage(
  "Approve order #42",
  {address, networkPassphrase},
);

signedMessage is a lowercase, hex-encoded Ed25519 signature using SEP-53 domain separation.

Report an x402 receipt

After a paid request through Scopuly's x402 gateway, report its public receipt so the wallet can verify and attach the settlement to its local activity:

import {reportX402Receipt} from "@scopuly/signer-extension-api";

const receiptId = response.headers.get("X-SCOPULY-X402-RECEIPT");

if (receiptId) {
  const receipt = await reportX402Receipt({receiptId});
  console.log(receipt.status, receipt.transaction);
}

You can alternatively pass the canonical receiptUrl, or both values. Scopuly accepts only https://api.scopuly.com/x402/receipt/<32-lowercase-hex>, verifies that both values match, and associates the receipt with the connected account and dApp origin. This method does not create a new signature.

React to provider changes

import {onChange} from "@scopuly/signer-extension-api";

const removeListener = onChange((event) => {
  console.log(event.address, event.network, event.isConnected, event.changed);
});

// Call when the component or integration is disposed.
removeListener();

Events are emitted when the connected account, permission, or active network changes.

API

  • getProvider
  • isConnected
  • requestAccess
  • getAddress
  • getPublicKey
  • getNetwork
  • signTransaction
  • signAndSubmitTransaction
  • signAuthEntry
  • signMessage
  • reportX402Receipt
  • disconnect
  • onChange

All provider, option, result, error, event, platform, and x402 receipt types are exported from the package root.

Errors

Provider calls reject with an object containing a numeric code and human-readable message:

  • -1 — internal error;
  • -2 — bridge or external-service error;
  • -3 — unavailable provider, invalid request, timeout, or unsupported method;
  • -4 — user rejection.
try {
  await signTransaction(unsignedXdr, {address, networkPassphrase});
}
catch (error) {
  if (typeof error === "object" && error && "code" in error && error.code === -4) {
    console.log("The user rejected the request");
  }
}

Treat code as the stable programmatic value and message as display text.

Developer guide

For a detailed integration walkthrough, see
How to Connect a Stellar dApp to Scopuly — A Developer Guide.

Compatibility and security

  • Package 0.3.x describes Scopuly provider contract 0.3.x on mobile and extension.
  • Older provider builds may not expose reportX402Receipt or the platform marker.
  • The extension may reject reportX402Receipt when its paired mobile signer does not advertise that capability.
  • Account access is scoped to the dApp origin.
  • Every signing request requires explicit approval in Scopuly.
  • Transaction XDR, network, signer, Soroban authorization, and returned signatures are checked by the provider.
  • Secret keys never enter this package or the dApp.

License

MIT

About

Injected provider API for connecting Stellar dApps to the Scopuly mobile wallet

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages