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
20 changes: 18 additions & 2 deletions bindings/node-adapters/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @open-wallet-standard/adapters

Framework adapters for the Open Wallet Standard — drop an OWS wallet into viem, Solana web3.js, or the Tether WDK without surfacing private keys.
Framework adapters for the Open Wallet Standard — drop an OWS wallet into viem, Solana Kit, Solana web3.js, or the Tether WDK without surfacing private keys.

[![npm](https://img.shields.io/npm/v/@open-wallet-standard/adapters)](https://www.npmjs.com/package/@open-wallet-standard/adapters)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/open-wallet-standard/core/blob/main/LICENSE)
Expand All @@ -15,6 +15,7 @@ Then install whichever framework you use alongside it:

```bash
npm install viem # for the viem adapter
npm install @solana/kit # for the Solana Kit adapter
npm install @solana/web3.js # for the Solana adapter
npm install @tetherto/wdk-wallet # for the WDK adapter
```
Expand All @@ -27,9 +28,10 @@ Framework SDKs are declared as optional peer dependencies — install only what
|---------|-------------|---------|
| viem | `@open-wallet-standard/adapters/viem` | `viem` `Account` that signs via OWS |
| Solana | `@open-wallet-standard/adapters/solana` | `@solana/web3.js` `Keypair` |
| Solana Kit | `@open-wallet-standard/adapters/solana-kit` | `@solana/kit` `KeyPairSigner` |
| WDK | `@open-wallet-standard/adapters/wdk` | WDK `IWalletAccount`-compatible object |

All three delegate signing to `@open-wallet-standard/core`, so keys remain encrypted in the OWS vault.
All four delegate signing to `@open-wallet-standard/core`, so keys remain encrypted in the OWS vault.

## viem

Expand Down Expand Up @@ -65,6 +67,20 @@ Options: `passphrase`, `vaultPath`.

The Solana adapter requires a wallet imported from a raw ed25519 private key. Mnemonic-derived wallets cannot be unwrapped into a `Keypair` — use `signMessage` / `signTransaction` from `@open-wallet-standard/core` directly.

## Solana Kit

```javascript
import { owsToSolanaKeyPairSigner } from "@open-wallet-standard/adapters/solana-kit";

const signer = await owsToSolanaKeyPairSigner("agent-treasury");

console.log(signer.address);
```

Options: `passphrase`, `vaultPath`.

The Solana Kit adapter requires a wallet imported from a raw ed25519 private key. Mnemonic-derived wallets cannot be unwrapped into a `KeyPairSigner` — use `signMessage` / `signTransaction` from `@open-wallet-standard/core` directly.

## WDK (Tether Wallet Development Kit)

```javascript
Expand Down
91 changes: 91 additions & 0 deletions bindings/node-adapters/__test__/solana-kit.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import assert from "node:assert/strict";
import { mkdtempSync, rmSync } from "node:fs";
import { createRequire } from "node:module";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { after, before, describe, it } from "node:test";
import { ed25519 } from "@noble/curves/ed25519";
import {
createWallet,
getWallet,
importWalletPrivateKey,
} from "@open-wallet-standard/core";
import {
createKeyPairSignerFromBytes,
createKeyPairSignerFromPrivateKeyBytes,
} from "@solana/kit";
import { owsToSolanaKeyPairSigner } from "../src/solana-kit.js";

const require = createRequire(import.meta.url);
const core = require("@open-wallet-standard/core");

describe("@open-wallet-standard/adapters — solana-kit", () => {
let vaultDir;
const walletName = "solana-kit-test";
const testEd25519Key = "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60";

before(() => {
vaultDir = mkdtempSync(join(tmpdir(), "ows-solana-kit-test-"));
importWalletPrivateKey(walletName, testEd25519Key, undefined, vaultDir, "solana");
});

after(() => {
rmSync(vaultDir, { force: true, recursive: true });
});

it("creates a signer with the correct address", async () => {
const wallet = getWallet(walletName, vaultDir);
const solAccount = wallet.accounts.find((account) => account.chainId.startsWith("solana:"));
const signer = await owsToSolanaKeyPairSigner(walletName, { vaultPath: vaultDir });

assert.equal(signer.address, solAccount.address);
});

it("same wallet produces the same signer address", async () => {
const signerA = await owsToSolanaKeyPairSigner(walletName, { vaultPath: vaultDir });
const signerB = await owsToSolanaKeyPairSigner(walletName, { vaultPath: vaultDir });

assert.equal(signerA.address, signerB.address);
});

it("throws for nonexistent wallets", async () => {
await assert.rejects(
() => owsToSolanaKeyPairSigner("nonexistent", { vaultPath: vaultDir }),
);
});

it("throws for mnemonic wallets", async () => {
createWallet("mnemonic-test", undefined, 12, vaultDir);

await assert.rejects(
() => owsToSolanaKeyPairSigner("mnemonic-test", { vaultPath: vaultDir }),
/Mnemonic wallets/,
);
});

it("accepts both 32-byte and 64-byte ed25519 exports", async () => {
const originalExportWallet = core.exportWallet;
const privateKeyBytes = Uint8Array.from(Buffer.from(testEd25519Key, "hex"));
const fullKeyBytes = new Uint8Array(64);
const publicKeyBytes = ed25519.getPublicKey(privateKeyBytes);

fullKeyBytes.set(privateKeyBytes, 0);
fullKeyBytes.set(publicKeyBytes, 32);

try {
core.exportWallet = () => JSON.stringify({ ed25519: Buffer.from(privateKeyBytes).toString("hex") });
const signerFromPrivateKey = await owsToSolanaKeyPairSigner(walletName, { vaultPath: vaultDir });
const expectedSignerFromPrivateKey = await createKeyPairSignerFromPrivateKeyBytes(privateKeyBytes);

assert.equal(signerFromPrivateKey.address, expectedSignerFromPrivateKey.address);

core.exportWallet = () => JSON.stringify({ ed25519: Buffer.from(fullKeyBytes).toString("hex") });
const signerFromKeypair = await owsToSolanaKeyPairSigner(walletName, { vaultPath: vaultDir });
const expectedSignerFromKeypair = await createKeyPairSignerFromBytes(fullKeyBytes);

assert.equal(signerFromKeypair.address, expectedSignerFromKeypair.address);
} finally {
core.exportWallet = originalExportWallet;
}
});
});
Loading