Skip to main content

Connect and Authenticate

Step-by-step guide for establishing a connection to the wallet-gateway and sending your first authenticated message.

Prerequisites: read Connection Model and Message Authentication first.


What You're Doing

Connecting to the wallet-gateway involves three things:

  1. Opening a WSS WebSocket connection to the gateway endpoint.
  2. Constructing a signed message envelope for your first request.
  3. Sending it within the authentication timeout window.

There is no separate login step. Your first signed message is your authentication.


Step 1 — Open the WebSocket Connection

const ws = new WebSocket('wss://gateway.your-processor.example/ws');

ws.addEventListener('open', () => {
console.log('Connection open. Sending first message...');
sendGetBalance(); // or any valid signed message
});

ws.addEventListener('message', (event) => {
const msg = JSON.parse(event.data);
handleMessage(msg);
});

ws.addEventListener('close', (event) => {
console.warn(`Connection closed: ${event.code} ${event.reason}`);
// Implement reconnection logic here if needed
});

The connection endpoint is published in the Basic Data Service (basic-data-server). Check with your processor for the correct URL.


Step 2 — Build the Message Envelope

Every message you send must be wrapped in the standard envelope:

interface GatewayMessage {
type: string;
callerAddress: string; // Your wallet address
deadline: number; // Unix timestamp (seconds) — a few minutes from now
payload: object; // The operation-specific data
signature: MessageSig;
}

Set deadline to a point a few minutes in the future. Messages with a deadline in the past are rejected.


Step 3 — Sign the Message

The signature object requires an EIP-712 digest of the message, signed with your wallet's private key.

import { ethers } from 'ethers';

async function buildSignedMessage(signer, type, payload) {
const callerAddress = await signer.getAddress();
const deadline = Math.floor(Date.now() / 1000) + 300; // 5 minutes

// Define the EIP-712 domain for gateway messages.
// Obtain name, version, and chainId from the Basic Data Service.
const domain = {
name: 'WalletGateway',
version: '1',
chainId: YOUR_CHAIN_ID,
};

const messageTypes = {
GatewayMessage: [
{ name: 'type', type: 'string' },
{ name: 'callerAddress', type: 'address' },
{ name: 'deadline', type: 'uint256' },
{ name: 'payloadHash', type: 'bytes32' },
],
};

// Hash the payload separately so the outer type stays fixed.
const payloadHash = ethers.keccak256(
ethers.toUtf8Bytes(JSON.stringify(payload))
);

const messageData = { type, callerAddress, deadline, payloadHash };

const sig = await signer.signTypedData(domain, messageTypes, messageData);
const { v, r, s } = ethers.Signature.from(sig);
const hash = ethers.TypedDataEncoder.hash(domain, messageTypes, messageData);

return {
type,
callerAddress,
deadline,
payload,
signature: {
hash,
v: v < 27 ? v + 27 : v,
r,
s,
},
};
}

Note: The exact EIP-712 type definition for gateway messages is specified in the formal specification, Section 5.2. Verify the domain parameters with the Basic Data Service before signing.


Step 4 — Send Your First Message

Use any valid operation as your first message. A balance query is a natural starting point:

async function sendGetBalance(ws, signer, domainSeparators) {
const payload = {
requestId: crypto.randomUUID(),
domainSeparators,
};

const message = await buildSignedMessage(signer, 'GET_BALANCE', payload);
ws.send(JSON.stringify(message));
}

Step 5 — Handle Responses

All responses follow the same pattern: a type field identifies the message, and a requestId echoes the one you sent.

function handleMessage(msg) {
switch (msg.type) {
case 'BALANCE_RESULT':
console.log('Balances:', msg.payload.balances);
break;
case 'ERROR':
console.error(`Error [${msg.payload.errorCode}]: ${msg.payload.message}`);
break;
default:
console.log('Received:', msg.type, msg.payload);
}
}

Handling the Idle Timeout

The gateway closes connections that have been idle for too long. To keep a connection alive, respond to ping frames:

// The browser WebSocket API handles ping/pong automatically.
// In Node.js (using the 'ws' library):
ws.on('ping', () => ws.pong());

Handling Disconnection

If the connection closes unexpectedly, reconnect and re-register any subscriptions:

ws.addEventListener('close', async () => {
await delay(2000); // Simple backoff — use exponential in production
reconnect();
});

async function reconnect() {
// Re-open connection and re-register subscriptions
openConnection();
subscribeToTokens(myTokenList);
}

Common Mistakes

MistakeResultFix
Deadline set in the pastEXPIRED_DEADLINE errorSet deadline to at least 60 seconds from now
Reusing a signed messageRejected after deadline passesGenerate a fresh signature for each message
Sending before connection is openMessage lostWait for the open event before sending
Not re-subscribing after reconnectNo push notifications receivedRe-send SUBSCRIBE_BALANCE / SUBSCRIBE_TRANSFERS after each reconnection
Wrong v valueINVALID_SIGNATURE errorNormalise: if v < 27, add 27