EIP-1193 Provider
EIP-1193 defines the standard JavaScript interface for Ethereum providers — the window.ethereum API.
Interface
The provider type extends eventemitter3's EventEmitter, with typed on()
overloads per event (removeListener, off, etc. are inherited from the
emitter rather than re-declared):
import type EventEmitter from 'eventemitter3';
interface EIP1193Provider extends EventEmitter {
request(args: RequestArguments): Promise<unknown>;
on(event: 'connect', listener: (info: ProviderConnectInfo) => void): this;
on(event: 'disconnect', listener: (error: ProviderRpcError) => void): this;
on(event: 'accountsChanged', listener: (accounts: string[]) => void): this;
on(event: 'chainChanged', listener: (chainId: string) => void): this;
on(event: string, listener: (...args: any[]) => void): this;
}
chainChanged is declared for EIP-1193 completeness but is never
emitted — the chain never changes at runtime. The three live events are
accountsChanged, connect, and disconnect — see
RelayerProvider → Events.
Implemented methods
| Method | Status | Description |
|---|---|---|
eth_requestAccounts | ✅ | Prompts the backing wallet (Temple via Beacon, or the Tezos X Wallet's own signer), returns ['0xAlias'] |
eth_accounts | ✅ | Returns current connected alias |
tez_getAccounts | ✅ | Returns the connected tz1 address (non-standard, Tezos X-specific) |
eth_chainId | ✅ | Returns Tezos X chain ID |
net_version | ✅ | Chain ID as decimal string |
eth_sendTransaction | ✅ | Routes via NAC gateway, returns synthetic hash |
eth_getBalance | ✅ | Balance of the 0x alias |
eth_getTransactionByHash | ✅ | Resolves real kernel tx; scans blocks from send-time snapshot |
eth_getTransactionReceipt | ✅ | Resolves real kernel receipt; null while unresolved |
eth_getTransactionCount | ✅ | Proxied to Tezlink |
eth_call | ✅ | Proxied to Tezlink |
eth_estimateGas / eth_gasPrice / eth_maxPriorityFeePerGas / eth_feeHistory | ✅ | Short-circuited to fixed constants — see Fee model |
wallet_revokePermissions / wallet_disconnect | ✅ | Disconnects the wallet session, clears pending-op state |
| (any other method) | ✅ | Proxied to the Tezlink EVM node — deadline-exempt (the passthrough may carry writes like eth_sendRawTransaction) |
eth_sign | ❌ Rejected 4200 | Message signing is not supported |
personal_sign | ❌ Rejected 4200 | Message signing is not supported |
eth_signTypedData / _v3 / _v4 | ❌ Rejected 4200 | EIP-712 signing is not supported |
The five signing methods (eth_sign, personal_sign, eth_signTypedData,
eth_signTypedData_v3, eth_signTypedData_v4) are rejected with EIP-1193
error code 4200 (unsupported method). There is no ECDSA key behind the
0x alias — it is a kernel-computed mapping of a tz1 account — so
EVM-style message signatures cannot be produced by this provider.
Transport behavior and error codes
Reads issued by the provider run under a 15-second deadline: a timeout
throws a plain, code-less Error (Request timed out after 15000ms calling <method>), while a genuine transport loss (fetch failure, non-2xx)
throws a ProviderRpcError with code 4900. A user abort in the
backing wallet surfaces as 4001; calling eth_sendTransaction without
a session throws 4100. The unknown-method proxy row above is exempt
from the deadline. Full details and the complete code table:
RelayerProvider → Timeouts and transport errors
and Error codes.
Transaction receipts & the synthetic hash
When a dApp calls eth_sendTransaction, the relayer wraps the EVM transaction
into a Michelson runtime operation targeting the NAC gateway
(entrypoint call_evm, or call for a bare transfer).
The backing wallet then asks the user to sign the Tezos operation.
At this point the relayer only has the Michelson runtime operation hash — not the real EVM transaction hash. The EVM transaction is synthesized by the Tezos X kernel once the Michelson operation is included in a block; its hash is assigned kernel-side and cannot be derived from anything the relayer holds at signing time.
Workaround: the relayer returns a synthetic hash to the dApp right
after signing — the keccak256 of the UTF-8 bytes of the Base58 Michelson
operation hash (l1OpHashToEvmHash) — and records the EVM head block number
at that moment. When the dApp later calls
eth_getTransactionByHash(syntheticHash) or
eth_getTransactionReceipt(syntheticHash), the resolver:
- Scans EVM blocks from the recorded send-time block up to the current head.
- Picks the matching kernel-synthesized transaction. Candidates whose
fromis the user's alias are preferred, with an exactto/valuemirror of the original request winning over afrom-only match (contract calls routed through the gateway carry the real call in the receipt logs, not the top-level fields). If no sender-side candidate exists, transactions to the alias are considered, but only when their receipt carries a log emitted by the NAC precompile — this covers kernel bookkeeping shapes such as the AliasForwarder. - Caches the real hash on the pending op and proxies the original RPC call
(
eth_getTransactionByHash/eth_getTransactionReceipt) to Tezlink with the real hash, returning the real transaction and receipt — with reallogs,gasUsed, andblockNumber.
Deduplication
Two invariants keep the resolver sane under the polling load ethers.js / viem put on providers:
- Per-hash in-flight promise: concurrent calls for the same synthetic hash share the same block-scan promise instead of each starting a fresh scan.
- Claim set: once a real hash has been claimed by one pending
op, no other pending op can match the same transaction. When a
PendingOpsStoreis injected (as the wallet does), pending ops and claimed hashes are persisted so resolution state survives across provider instances.
Fallback
A resolution runs a single overall budget of 15 scan attempts spaced 2 s apart — at least ~30 s wall clock in total. Each attempt rescans the whole range from the send-time snapshot block to the current head, so later attempts cover a growing block gap. While the real transaction has not been found:
eth_getTransactionByHashreturns a pending-shaped transaction object (blockNumber: null), as EIP-1474 prescribes for a submitted-but-unmined transaction — so ethers.js / viem pollers keep polling instead of aborting.eth_getTransactionReceiptreturnsnull, the standard answer for a transaction that is not yet mined.resolveSyntheticHash()(the public wallet-facing method) resolves tonullon timeout, letting the caller fall back to the Michelson operation hash.
Example
// Connect
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
console.log(accounts); // ['0x341af4de...']
// Get chain
const chainId = await window.ethereum.request({ method: 'eth_chainId' });
// Send transaction
const hash = await window.ethereum.request({
method: 'eth_sendTransaction',
params: [{ to: '0x...', value: '0xde0b6b3a7640000' }]
});