Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/remote-wallet-package.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"remote-wallet": minor
---

Add remote wallet CLI, protocol, browser, Node, and React package entrypoints.
8 changes: 8 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ runs:
with:
bun-version-file: .bun-version

- name: Cache Bun install cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: |
bun-${{ runner.os }}-

- name: Install dependencies
shell: bash
run: bun install
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,30 @@ jobs:
- name: Check Types
run: bun check-types

e2e:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Setup Environment
uses: ./.github/actions/setup

- name: Cache Playwright browsers
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('bun.lock') }}
restore-keys: |
playwright-${{ runner.os }}-

- name: Install Playwright Chromium
run: bun x playwright install --with-deps chromium

- name: Run E2E
run: bun run test:e2e

lint:
runs-on: ubuntu-latest
steps:
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
*.tsbuildinfo
dist
node_modules
playwright-report
test-results

# START Ruler Generated Files
/.gemini/settings.json
Expand Down
46 changes: 27 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,55 @@
# remote-wallet

TypeScript library built with [Bun](https://bun.sh/), [tsdown](https://tsdown.js.org/), [Biome](https://biomejs.dev/), and [Changesets](https://github.com/changesets/changesets).
`remote-wallet` is an agent-friendly Solana wallet CLI and protocol toolkit. It lets a dapp expose a copyable remote wallet Pairing URL, then lets an agent connect from a terminal and sign on behalf of the configured wallet key.

## Features
## CLI

- Bun-first dependency management, scripts, and tests
- TypeScript source with dual ESM/CJS output and generated types
- Biome for linting and formatting
- Example library export and CLI entrypoint
- Changesets and GitHub Actions for release automation
```bash
bunx remote-wallet "<pairing-url>"
```

## Installation
Useful options:

```bash
bun install
remote-wallet --url "<pairing-url>" --label "Agent Remote Wallet"
remote-wallet --url "<pairing-url>" --rpc-url https://api.devnet.solana.com
remote-wallet --url "<pairing-url>" --secret-key "$REMOTE_WALLET_SECRET_KEY"
```

## Browser

```ts
import { createRemoteWalletSession } from 'remote-wallet/browser'
```

## Usage
## Node

```ts
import { greet } from 'remote-wallet'
import { createRemoteWalletSigner } from 'remote-wallet/node'
```

console.log(greet('Seed'))
## Protocol

```ts
import { parseNostrAssociationUrl } from 'remote-wallet/protocol'
```

## CLI
## React

```bash
bun run src/cli.ts
bun run src/cli.ts Seed
```tsx
import { RemoteWalletPairingPanel, useRemoteWalletPairing } from 'remote-wallet/react'
```

## Development

```bash
bun install
bun run build
bun run check-types
bun run lint
bun run lint:fix
bun test
bun run test:watch
```

## License

MIT – see [LICENSE](./LICENSE).
MIT. See [LICENSE](./LICENSE).
641 changes: 640 additions & 1 deletion bun.lock

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: remote-wallet

services:
nostr-relay:
image: scsibug/nostr-rs-relay:0.9.0
ports:
- "8080:8080"
solana-test-validator:
image: beeman/solana-test-validator:4.0.0
ports:
- "8899:8899"
- "8900:8900"
security_opt:
- seccomp=unconfined
stdin_open: true
tty: true
56 changes: 47 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,55 @@
{
"bin": {
"remote-wallet": "dist/cli.mjs"
"remote-wallet": "dist/bin/remote-wallet.mjs"
},
"dependencies": {},
"description": "TypeScript library and CLI for remote wallet workflows.",
"dependencies": {
"@noble/curves": "^1.9.7",
"@noble/hashes": "^1.8.0",
"@solana/kit": "^6.8.0"
},
"description": "Agent-friendly CLI and protocol helpers for Solana remote wallet pairing",
"devDependencies": {
"@beeman/testcontainers": "1.2.3",
"@biomejs/biome": "2.4.6",
"@changesets/cli": "2.30.0",
"@intellectronica/ruler": "0.3.38",
"@playwright/test": "1.60.0",
"@solana/kit-plugins": "^0.3.0",
"@types/bun": "1.3.13",
"@types/react": "^19.2.7",
"lefthook": "2.1.5",
"pkg-pr-new": "0.0.62",
"publint": "0.3.18",
"react": "^19.2.0",
"testcontainers": "12.0.0",
"tsdown": "0.21.7",
"typescript": "6.0.2"
},
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
}
"types": "./dist/index.d.mts",
"import": "./dist/index.mjs",
"default": "./dist/index.mjs"
},
"./browser": {
"types": "./dist/browser/index.d.mts",
"import": "./dist/browser/index.mjs",
"default": "./dist/browser/index.mjs"
},
"./node": {
"types": "./dist/node/index.d.mts",
"import": "./dist/node/index.mjs",
"default": "./dist/node/index.mjs"
},
"./protocol": {
"types": "./dist/protocol/index.d.mts",
"import": "./dist/protocol/index.mjs",
"default": "./dist/protocol/index.mjs"
},
"./react": {
"types": "./dist/react/index.d.mts",
"import": "./dist/react/index.mjs",
"default": "./dist/react/index.mjs"
}
},
"files": [
Expand All @@ -30,6 +59,14 @@
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"name": "remote-wallet",
"peerDependencies": {
"react": ">=18"
},
"peerDependenciesMeta": {
"react": {
"optional": true
}
},
"repository": {
"type": "git",
"url": "git+https://github.com/wallet-ui/remote-wallet.git"
Expand All @@ -44,8 +81,9 @@
"prepare": "lefthook install",
"release": "changeset publish",
"ruler:apply": "ruler apply --local-only",
"test": "bun test test/*.test.ts",
"test:watch": "bun test test/*.test.ts --watch",
"test": "bun test test/**/*.test.ts",
"test:e2e": "playwright test",
"test:watch": "bun test test/**/*.test.ts --watch",
"version": "changeset version && bun lint:fix"
},
"type": "module",
Expand Down
15 changes: 15 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { defineConfig } from '@playwright/test'

export default defineConfig({
expect: {
timeout: 10_000,
},
fullyParallel: false,
testDir: './test/e2e',
testMatch: '**/*.e2e.ts',
timeout: 180_000,
use: {
trace: 'retain-on-failure',
},
workers: 1,
})
10 changes: 10 additions & 0 deletions src/bin/remote-wallet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bun

import { runRemoteWalletCli } from '../cli/index.ts'

try {
await runRemoteWalletCli(process.argv.slice(2))
} catch (error) {
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`)
process.exitCode = 1
}
72 changes: 72 additions & 0 deletions src/browser/create-remote-wallet-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
type CreateRemoteWalletSessionOptions,
createRemoteWalletSession,
type RemoteWalletPairingSession,
} from './create-remote-wallet-session.ts'

export interface RemoteWalletProvider {
cancelPairing: () => void
getSnapshot: () => RemoteWalletProviderSnapshot
notifyPairingChange?: () => void
startPairing: (options?: Partial<CreateRemoteWalletSessionOptions>) => Promise<RemoteWalletPairingSession>
subscribe: (listener: RemoteWalletProviderListener) => () => void
}

export interface RemoteWalletProviderOptions {
relayUrl?: string
}

export interface RemoteWalletProviderSnapshot {
session?: RemoteWalletPairingSession
}

export type RemoteWalletProviderListener = () => void

export function createRemoteWalletProvider({
relayUrl = 'wss://relay.damus.io',
}: RemoteWalletProviderOptions = {}): RemoteWalletProvider {
const listeners = new Set<RemoteWalletProviderListener>()
let snapshot: RemoteWalletProviderSnapshot = {}

const emit = () => {
for (const listener of listeners) {
listener()
}
}

return {
cancelPairing() {
snapshot.session?.cancel()
snapshot = {}
emit()
},
getSnapshot() {
return snapshot
},
notifyPairingChange() {
emit()
},
async startPairing(options = {}) {
snapshot.session?.cancel()
const session = await createRemoteWalletSession({
chains: options.chains,
connect: options.connect,
identity: options.identity,
relayUrl: options.relayUrl ?? relayUrl,
timeoutMs: options.timeoutMs,
})

snapshot = { session }
emit()

return session
},
subscribe(listener) {
listeners.add(listener)

return () => {
listeners.delete(listener)
}
},
}
}
Loading
Loading