Message Signing
For signing messages (personal signatures), encode your data as base64:TypeScript
const personalSign = async (data: string) => {
// Encode message as base64
const message = Buffer.from(data, 'utf-8').toString('base64');
const body = { message_base64: message };
return await fetch('/v2/wallet/sign/message', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${YOUR_JWT_TOKEN}`,
'X-Magic-API-Key': 'YOUR_MAGIC_API_KEY',
'X-OIDC-Provider-ID': 'YOUR_OIDC_PROVIDER_ID',
'X-Magic-Chain': 'ETH'
},
body: JSON.stringify(body)
});
};
Data Signing
For/v2/wallet/sign/data, send the hash together with transaction_data, the content it was computed from. Magic recomputes the hash and rejects the request with 400 INVALID_SIGNING_PARAMS if they don’t match.
import { typedSignatureHash } from '@metamask/eth-sig-util';
const signTypedDataV1 = async (data: TypedDataV1) => {
// Compute hash for typed data V1
const rawDataHash = typedSignatureHash(data);
const body = {
raw_data_hash: rawDataHash,
transaction_data: { method: 'eth_signTypedData', typedData: data },
};
return await fetch('/v2/wallet/sign/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${YOUR_JWT_TOKEN}`,
'X-Magic-API-Key': 'YOUR_MAGIC_API_KEY',
'X-OIDC-Provider-ID': 'YOUR_OIDC_PROVIDER_ID',
'X-Magic-Chain': 'ETH'
},
body: JSON.stringify(body)
});
};
import {
MessageTypes,
SignTypedDataVersion,
TypedDataUtils,
TypedMessage,
} from '@metamask/eth-sig-util';
const computeEip712Hash = (
data: TypedMessage<MessageTypes>,
version: SignTypedDataVersion.V3 | SignTypedDataVersion.V4,
): string => {
const hashBuffer = TypedDataUtils.eip712Hash(data, version);
return '0x' + hashBuffer.toString('hex');
};
const signTypedDataV3 = async (data: TypedMessage<MessageTypes>) => {
// Compute EIP-712 hash for typed data V3
const rawDataHash = computeEip712Hash(data, SignTypedDataVersion.V3);
const body = {
raw_data_hash: rawDataHash,
transaction_data: { method: 'eth_signTypedData_v3', typedData: data },
};
return await fetch('/v2/wallet/sign/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${YOUR_JWT_TOKEN}`,
'X-Magic-API-Key': 'YOUR_MAGIC_API_KEY',
'X-OIDC-Provider-ID': 'YOUR_OIDC_PROVIDER_ID',
'X-Magic-Chain': 'ETH'
},
body: JSON.stringify(body)
});
};
const signTypedDataV4 = async (data: TypedMessage<MessageTypes>) => {
// Compute EIP-712 hash for typed data V4
const rawDataHash = computeEip712Hash(data, SignTypedDataVersion.V4);
const body = {
raw_data_hash: rawDataHash,
transaction_data: { method: 'eth_signTypedData_v4', typedData: data },
};
return await fetch('/v2/wallet/sign/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${YOUR_JWT_TOKEN}`,
'X-Magic-API-Key': 'YOUR_MAGIC_API_KEY',
'X-OIDC-Provider-ID': 'YOUR_OIDC_PROVIDER_ID',
'X-Magic-Chain': 'ETH'
},
body: JSON.stringify(body)
});
};
import { resolveProperties, Signature, Transaction, TransactionLike, TransactionRequest } from 'ethers';
const signTransaction = async (tx: TransactionRequest) => {
// Resolve any promises in transaction properties
const resolvedTx = await resolveProperties(tx);
const txForSigning = { ...resolvedTx };
delete txForSigning.from;
// Create transaction object and get unsigned hash
const btx = Transaction.from(txForSigning as TransactionLike);
// The transaction's fields, so Magic can recompute the unsigned hash.
// Integers go as decimal strings, since JSON can't carry bigints.
const transaction_data = {
method: 'eth_signTransaction',
to: btx.to,
value: btx.value.toString(),
data: btx.data,
chainId: Number(btx.chainId),
nonce: btx.nonce,
gasLimit: btx.gasLimit.toString(),
...(btx.maxFeePerGas != null
? {
maxFeePerGas: btx.maxFeePerGas.toString(),
maxPriorityFeePerGas: btx.maxPriorityFeePerGas!.toString(),
}
: { gasPrice: btx.gasPrice!.toString() }),
};
const body = { raw_data_hash: btx.unsignedHash, transaction_data };
const res = await fetch('/v2/wallet/sign/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${YOUR_JWT_TOKEN}`,
'X-Magic-API-Key': 'YOUR_MAGIC_API_KEY',
'X-OIDC-Provider-ID': 'YOUR_OIDC_PROVIDER_ID',
'X-Magic-Chain': 'ETH'
},
body: JSON.stringify(body)
});
const { r, s, v } = await res.json();
// Attach signature to transaction
btx.signature = Signature.from({ r, s, v });
return btx.serialized;
};
import { getBytes, hashMessage, isHexString } from 'ethers';
const ethSign = async (message: string) => {
// EIP-191 hash: hex strings are signed as bytes, anything else as text.
const rawDataHash = hashMessage(isHexString(message) ? getBytes(message) : message);
const body = {
raw_data_hash: rawDataHash,
transaction_data: { method: 'eth_sign', data: message },
};
return await fetch('/v2/wallet/sign/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${YOUR_JWT_TOKEN}`,
'X-Magic-API-Key': 'YOUR_MAGIC_API_KEY',
'X-OIDC-Provider-ID': 'YOUR_OIDC_PROVIDER_ID',
'X-Magic-Chain': 'ETH'
},
body: JSON.stringify(body)
});
};
Transactions with an access list, blobs or an authorization list can’t be described in
transaction_data yet. To sign an EIP-7702 authorization, use Sign EIP-7702 Authorization, which computes the hash for you.