Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion bindings/node-adapters/__test__/viem.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import assert from 'node:assert/strict';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createWallet, getWallet, signMessage as owsSignMessage } from '@open-wallet-standard/core';
import { createWallet, getWallet, signMessage as owsSignMessage, importWalletPrivateKey } from '@open-wallet-standard/core';
import { privateKeyToAccount } from 'viem/accounts';
import { owsToViemAccount } from '../src/viem.js';

describe('@open-wallet-standard/adapters — viem', () => {
Expand Down Expand Up @@ -59,6 +60,27 @@ describe('@open-wallet-standard/adapters — viem', () => {
const td = { domain: { name: 'T', version: '1', chainId: '1', verifyingContract: '0x0000000000000000000000000000000000000001' }, types: { EIP712Domain: [{ name: 'name', type: 'string' }, { name: 'version', type: 'string' }, { name: 'chainId', type: 'uint256' }, { name: 'verifyingContract', type: 'address' }], M: [{ name: 'c', type: 'string' }] }, primaryType: 'M', message: { c: 'D' } };
assert.equal(await account.signTypedData(td), await account.signTypedData(td));
});
it('signTypedData matches viem for a uint256-bearing message', async () => {
// A wallet imported from a known key so the signature can be compared to
// viem's own privateKeyToAccount. The message carries uint256 bigints and
// omits EIP712Domain, matching how a real viem walletClient signs.
const pkVault = mkdtempSync(join(tmpdir(), 'ows-viem-pk-'));
const pk = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80';
importWalletPrivateKey('viem-pk-test', pk.slice(2), undefined, pkVault, 'evm');
const account = owsToViemAccount('viem-pk-test', { chain: 'eip155:1', vaultPath: pkVault });
const reference = privateKeyToAccount(pk);
const typedData = {
domain: { name: 'Test', version: '1', chainId: 1, verifyingContract: '0x0000000000000000000000000000000000000001' },
types: { Msg: [{ name: 'id', type: 'uint256' }, { name: 'ids', type: 'uint256[]' }] },
primaryType: 'Msg',
message: { id: 2n ** 200n, ids: [0n, 1n, 2n ** 256n - 1n] },
};
try {
assert.equal(await account.signTypedData(typedData), await reference.signTypedData(typedData));
} finally {
rmSync(pkVault, { recursive: true, force: true });
}
});
it('signTransaction returns RLP-encoded signed transaction', async () => {
const account = owsToViemAccount(walletName, { vaultPath: vaultDir });
const tx = { to: '0x0000000000000000000000000000000000000001', value: 0n, chainId: 1, type: 'eip1559', maxFeePerGas: 1000000000n, maxPriorityFeePerGas: 1000000000n };
Expand Down
27 changes: 26 additions & 1 deletion bindings/node-adapters/src/viem.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
const { getWallet, signMessage, signTypedData, signTransaction } = require("@open-wallet-standard/core");
const { toAccount } = require("viem/accounts");

// Encode a bigint as the core's EIP-712 parser expects a uint value: even-length
// hex. Decimal uints above 2^128 are rejected, and hex must have an even number
// of digits. Negative values fall back to decimal (int types).
function bigintToOwsHex(value) {
if (value < 0n) return value.toString();
const hex = value.toString(16);
return `0x${hex.length % 2 === 1 ? `0${hex}` : hex}`;
}

function owsToViemAccount(walletNameOrId, options = {}) {
const chain = options.chain ?? "eip155:1";
const wallet = getWallet(walletNameOrId, options.vaultPath);
Expand Down Expand Up @@ -33,7 +42,23 @@ function owsToViemAccount(walletNameOrId, options = {}) {
return serializeTransaction(transaction, { r, s, yParity });
},
async signTypedData(typedData) {
const result = signTypedData(walletNameOrId, chain, JSON.stringify(typedData), options.passphrase, options.index, options.vaultPath);
const { getTypesForEIP712Domain } = require("viem");
// viem's signTypedData action adds EIP712Domain to `types` before calling an
// account; a direct account.signTypedData() call does not. Add it when absent
// so the core resolves the domain type. A caller-supplied EIP712Domain wins.
const payload = {
...typedData,
types: {
EIP712Domain: getTypesForEIP712Domain({ domain: typedData.domain }),
...typedData.types,
},
};
// The core parses the JSON payload and expects uint values as even-length hex.
// JSON.stringify cannot serialize bigints, so encode them here.
const json = JSON.stringify(payload, (_key, value) =>
typeof value === "bigint" ? bigintToOwsHex(value) : value
);
const result = signTypedData(walletNameOrId, chain, json, options.passphrase, options.index, options.vaultPath);
return result.signature.startsWith("0x") ? result.signature : `0x${result.signature}`;
},
});
Expand Down