Skip to main content

Wallets

In any decentralized application, the user's wallet is their identity, their key, and their signature. It's the fundamental tool that allows them to interact with the blockchain and prove ownership of their assets and actions.

A major challenge in building multi-chain dApps is that every blockchain ecosystem has its own wallet standards and connection methods. EffectStream solves this problem by providing a single, unified interface that handles the complexity for you.

The @effectstream/wallets Package

The @effectstream/wallets package is the core frontend library for managing user identity. It provides a single, easy-to-use API for connecting to and interacting with a wide variety of blockchain wallets, abstracting away the chain-specific implementation details.

AddressType

This table is a EffectStream numeric representation of wallet address type. Normally addresses will be used with it's corresponding address type, so the correct cryptographic signer/verifiers can be selected.

Address TypeNumber
EVM0
Cardano1
Substrate2
Algorand3
Mina4
Midnight5
Avail6
Polkadot7

Connecting a Wallet in Your Frontend

Integrating wallet connectivity into your dApp is streamlined with a single walletLogin function. You simply specify which type of wallet you want to connect to using the WalletMode enum, and the library handles the rest.

import { WalletMode, walletLogin, signMessage } from '@effectstream/wallets';

async function connectWallet() {
// walletLogin prompts the user with their chosen wallet extension and
// returns a Result<Wallet>.
const result = await walletLogin({
mode: WalletMode.EvmInjected,
preference: { name: 'MetaMask' },
});

if (!result.success) {
console.error('Failed to connect wallet:', result.errorMessage);
return;
}

const wallet = result.result;
console.log('Connected wallet:', wallet.walletAddress);

// Use the wallet to sign messages, or sendTransaction to submit inputs.
const signature = await signMessage(wallet, 'hello effectstream');
}

The WalletMode enum allows you to support a broad range of ecosystems, enabling a truly multi-chain experience:

WalletModeEcosystemDescription
EvmInjectedEVMStandard EVM injected browser wallets. e.g., MetaMask, Phantom, etc.
EvmEthersEVMConnect using an EVM Compatible provider instance. e.g., Viem, Ethers, thirdweb Local Wallet, etc.
CardanoCardanoConnects to Cardano wallets. e.g., Nami, Eternl, etc.
PolkadotPolkadotConnects to wallets in the Polkadot ecosystem.
AlgorandAlgorandConnects to Algorand wallets. e.g., Exodus, etc.
MinaMinaConnects to the Auro wallet for the Mina Protocol.
AvailJsAvailConnects to wallets for the Avail network.
MidnightMidnightConnects to Lace Wallet
CardanoLocalCardanoIn-browser local wallet: a BIP-39 key-pair generated and managed by the library. No extension required.
MidnightLocalMidnightIn-browser local wallet from a generated hex seed. No extension required.
EvmViemEVMIn-browser local wallet from a viem 0x private key. No extension required.

Local (in-browser) wallets

For Cardano you have two options, both managed by @effectstream/wallets:

  1. Connect a real wallet (WalletMode.Cardano) - the user signs with their CIP-30 extension (Lace, Eternl, Nami, NuFi, ...).
  2. Use a local wallet (WalletMode.CardanoLocal) - the library generates and manages a key-pair in the browser, with no extension. The code for this lives in packages/effectstream-sdk/wallets/src/cardano/.
import { WalletMode, walletLogin, signMessage } from '@effectstream/wallets';

// A fresh BIP-39 seed is generated in the browser when `seedPhrase` is omitted.
const wallet = await walletLogin({
mode: WalletMode.CardanoLocal,
network: 'Preview', // 'Mainnet' | 'Preprod' | 'Preview' | 'Custom'
});
if (!wallet.success) throw new Error(wallet.errorMessage);

console.log('Local address:', wallet.result.walletAddress); // addr_test1...
const signature = await signMessage(wallet.result, 'hello effectstream');

The local key never leaves the browser. Combined with the Account System's &linkAddress delegation, a real wallet can authorise the local key to sign non-financial inputs - so the player signs once and subsequent actions need no pop-up. The same pattern is available on EVM (EvmViem) and Midnight (MidnightLocal).

EffectstreamConfig

The EffectstreamConfig is used to configure the EffectStream.

Settings:

  • Security Namespace: A string the wallet signs into every batched message; must match the server's security namespace.
  • EffectStream L2 Sync Protocol Name: The name of the effectstream l2 sync protocol defined in your config.
  • EffectStream L2 Contract Address: The address of the effectstream l2 contract to target.
  • EffectStream L2 Chain: The chain of the effectstream l2 contract.
  • EffectStream L2 ABI: (Optional) The abi of the effectstream l2 contract.
  • Batcher URL: The url of the batcher to use.
  • Prefer Batched Mode: If true use batcher by default, otherwise use self-sequenced transaction.

See the EffectstreamConfig in the @effectstream/wallets package for more details.

const effectstreamConfig = new EffectstreamConfig(
"my-app", // security namespace
"effectstream-l2-sync-protocol-name", // effectstream l2 sync protocol name
"0x1234567890abcdef", // effectstream l2 contract address
hardhat, // effectstream l2 chain
undefined, // if undefined, use default effectstream l2 abi
"http://localhost:3334", // batcher url
true, // if true use batcher by default
);

Primary Uses of a Connected Wallet

Once a user has connected their wallet, your frontend can use the returned walletClient for different uses.

1. Sending Concise Inputs to EffectStream L2 Contract

Send a transaction to the EffectStream L2 contract. This will automatically decide whether to use the batcher or the self-sequenced transaction based on the preferBatchedMode flag.

const conciseInput = ["my-action", "0x1", "0x2"]; // Your grammar-formatted input
const result = await sendTransaction(walletClient, conciseInput, effectstreamConfig);

See the sendTransaction function in the @effectstream/wallets package for more details.

2. Manually Signing Messages for the Batcher

To provide a gasless, cross-chain experience, the user's wallet is used to sign a message containing their game input. This signed message is then sent to the Batcher, which handles the on-chain submission. This is the core mechanism that allows a Cardano user to play a game on an EVM chain without needing an EVM wallet or gas.

const conciseInput = ["my-action", "0x3", "0x4"]; // Your grammar-formatted input
const result = await sendBatcherTransaction(walletClient, conciseInput, effectstreamConfig);

3. Sending On-Chain Transactions

For specific, high-stakes actions, or if your dApp doesn't use a Batcher, you can use the wallet to send traditional on-chain transactions.

  • Direct EffectStream L2 Contract Interaction: Call the submitInput function on the EffectstreamL2Contract to send a game move directly. This can be done using the sendSelfSequencedTransaction function.
const result = await sendSelfSequencedTransaction(walletClient, conciseInput, effectstreamConfig);
  • Other Contract Interactions: Call any function on any other smart contract, such as minting an NFT or transferring an ERC20 token.

4. User Identification

The user's walletAddress is their primary identifier within the EffectStream. When your State Machine receives an input, it knows which user performed the action based on the signerAddress. This address is used to query the database for the user's state, inventory, and other relevant information.

4. Signing Messages

You can sign custom messages with the wallet.

const message = "my-message";
const signature = await walletClient.signMessage({ message });

Wallets and the EffectStream Account System

A wallet address is not just an identifier; it's also the key to EffectStream's flexible L2 Account System. While a wallet can act as a standalone identity, it can also be linked to a higher-level EffectStream Account.

This allows for an "account abstraction" experience where:

  • A single EffectStream Account can be controlled by multiple wallet addresses.
  • The primary (controlling) wallet of an account can be changed.

A user manages their account by sending built-in system commands (like &linkAddress) to the EffectstreamL2Contract, using their connected wallet to authorize the action with a signature.

Learn more about the EffectStream Account System