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
13 changes: 7 additions & 6 deletions .agents/skills/oneshot-embedded-wallet/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: oneshot-embedded-wallet
description: >-
Integrate the 1Shot embedded wallet (OWS Host Layer) with @1shotapi/ows-provider.
Use when embedding wallet.1shotapi.com, wiring OWSProxy, EIP-1193, credentials,
or custom RPC such as setStyle / focusWallet / addAsset / createAccount for
or custom RPC such as configure / focusWallet / addAsset / createAccount for
theming, host-driven focus mode, tracked assets, and first-party Safari create.
license: MIT
metadata:
Expand Down Expand Up @@ -43,7 +43,7 @@ const container = document.getElementById("wallet-container")!;
const proxy = await OWSProxy.create(container, WALLET_URL);

// Optional: theme / copy before showing the flyout
await proxy.rpc("setStyle", {
await proxy.rpc("configure", {
copy: { productName: "Acme Wallet", tagline: "Powered by 1Shot" },
theme: { primary: "oklch(0.45 0.18 250)" },
});
Expand All @@ -61,12 +61,12 @@ const accounts = await proxy.ethereum.request({ method: "eth_requestAccounts" })
- Production wallet URL: **`https://wallet.1shotapi.com/`** (Signing Layer at `/signer/` on the same origin — do not embed `/signer/` from the host).
- Safari / iOS WebKit cannot create passkeys inside a **cross-origin** wallet iframe. The branding layer detects this and opens **`https://wallet.1shotapi.com/create/`** in a new tab (same origin as the wallet). Allow pop-ups from the embedding page so that handoff can complete; no host code changes are required.

## Custom RPC — `setStyle`
## Custom RPC — `configure`

1Shot-specific method registered on the Branding Layer. Call via:

```typescript
await proxy.rpc("setStyle", options);
await proxy.rpc("configure", options);
```

`options` is a partial merge (safe to call repeatedly):
Expand All @@ -84,6 +84,7 @@ await proxy.rpc("setStyle", options);
| `features.disableCredentials` | boolean | Hide Credentials tab; default `false`. Host credential flows still work |
| `features.disableDelegations` | boolean | Hide Delegations tab; default `false`. Host delegation flows still work |
| `features.allowedChains` | `string[]` (hex `0x…` chain ids) | Restrict Network dropdown to these catalog chains; omit or `[]` ⇒ all enabled |
| `destinationUrl` | string \| null | URL to receive transaction status update webhooks from the [1Shot Relayer](https://1shotapi.com/docs/relayer/get-started/overview) (≤256 chars). `null` or `""` clears |
| `copy.productName` | string | titles / chrome |
| `copy.tagline` | string | supporting line |
| `copy.connect.title` | string | connect modal title |
Expand Down Expand Up @@ -258,7 +259,7 @@ await proxy.rpc("createAccount", { accountName: "My Wallet" });
| `proxy.credentials.*` | OID4 offer / present (when enabled in wallet) |
| `proxy.analytics.on(listener)` / `.on(name, listener)` / `.off(listener)` | Branding→Host product analytics (`ows:analytics`) |
| `proxy.showWallet()` / `hideWallet()` | Host-driven flyout without an EIP-1193 call |
| `proxy.rpc(method, params)` | Custom Branding RPC (`setStyle`, `focusWallet`, `unfocusWallet`, `addAsset`, `createAccount`, …) |
| `proxy.rpc(method, params)` | Custom Branding RPC (`configure`, `focusWallet`, `unfocusWallet`, `addAsset`, `createAccount`, …) |

Subscribe so in-wallet chain/account changes update host UI without polling:

Expand Down Expand Up @@ -304,6 +305,6 @@ include a live Analytics panel fed by `proxy.analytics.on` (filter by `name`).

- Never embed the Signing Layer iframe from the Host — always Host → Branding → Signing.
- Prefer the published wallet URL in production; point at a local Branding origin only while developing this repo.
- Theme with `setStyle`; do not ask integrators to fork CSS for basic brand colors / product name.
- Theme with `configure`; do not ask integrators to fork CSS for basic brand colors / product name.
- Use `focusWallet` / `unfocusWallet` for host-driven single-asset flows; do not expose mode switching in the wallet UI.
- Use `addAsset` when the host wants a lasting Balances entry; expect a confirm modal (contrast with `focusWallet`).
4 changes: 2 additions & 2 deletions .github/workflows/Deploy Dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -119,14 +119,14 @@ jobs:

echo "---------------------------- Docker Deployment ---------------------------------------"
# Pull the latest images
docker compose pull
docker compose --env-file secrets.env pull

# Stop necessary services
docker stop wallet || true
docker rm -f wallet || true

# Reup everything
docker compose up -d
docker compose --env-file secrets.env up -d

# Prune dead resources
docker system prune -f
Expand Down
10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Prefer **clean code over backwards compatibility**. Do not add legacy redirects,
| `src/lib/implementations/{business,data,utils}/` | Layer implementations |
| `src/assets/` | Static media only (SVGs, images) |

Test Host Layer: `host/` (`npm run dev:host`). Browser extension: `extension/` (`npm run dev:extension`) — see [`extension/README.md`](extension/README.md). Style via Host RPC `setStyle`, not in-wallet debug knobs.
Test Host Layer: `host/` (`npm run dev:host`). Browser extension: `extension/` (`npm run dev:extension`) — see [`extension/README.md`](extension/README.md). Branding / host config via Host RPC `configure`, not in-wallet debug knobs.

### Form validation UX

Expand All @@ -35,13 +35,13 @@ Signing Layer WebAuthn (PRF via `OWSSigner`) shows Confirm/Cancel **inside the s

Branding `PasskeyPromptModal` + `withPasskeyPrompt` remain **only** for Branding-native WebAuthn that never enters Signing (e.g. Relayer `createRelayerAssertion` in `webauthnAuth.ts`).

### User-facing copy (`setStyle`)
### Host configuration (`configure`)

When adding or changing UI strings:
When adding or changing UI strings or host-tunable options:

1. Wire them through `style.copy` (defaults, types, and `registerSetStyle` Zod schema) so hosts can override via `setStyle`.
1. Wire them through `style` / `IStyleOptions` (defaults, types, and `registerConfigure` Zod schema) so hosts can override via `configure`.
2. Expose the same keys in **both** WalletConfigurator playgrounds: `host/` in this repo and `app/playground/` in **1Shot-API-Website-New** (`styleForm.ts` + `WalletConfigurator.tsx`).

3. Optional top-level `destinationUrl` is a URL for transaction status update webhooks from the [1Shot Relayer](https://1shotapi.com/docs/relayer/get-started/overview).
### Domain layers (assets example)

- **Utils:** `IBlockchainProvider` / `AddressUtils` (from `@1shotapi/ows-wallet-utils`) / `SupportedChainsBlockchainProvider`, `IEventBus` / `EventBus`, `ITransactionUtils` / `TransactionUtils`, `IConfigProvider` / `ConfigProvider`, `IChainRepository` / `HardcodedChainRepository`
Expand Down
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ cp .env.example .env # set NGROK_AUTHTOKEN (and optional NGROK_DOMAIN)
```bash
npm run dev # Branding Layer + ngrok HTTPS tunnel
npm run dev:local # Branding Layer, local HTTP only
npm run dev:host # Test Host Layer (setStyle knobs + EIP-1193)
npm run dev:host # Test Host Layer (configure knobs + EIP-1193)
npm run dev:extension # Browser extension (side panel + EIP-1193 shim)
```

Expand All @@ -52,7 +52,7 @@ Passkeys need HTTPS — use the printed ngrok wallet URL as the host iframe sour

By default Vite uses published `@1shotapi/ows-*` from `node_modules`. To point at a sibling `../prf-wallet` checkout, set `OWS_LOCAL_PACKAGES=1` (Firefox often breaks on the resulting `/@fs/C:` module URLs — prefer Chrome, or leave the flag unset for ngrok).

Style testing: use the **Style (setStyle RPC)** panel on the test host (`host/`), not in-wallet debug UI. See [host/README.md](host/README.md).
Style testing: use the **Style (configure RPC)** panel on the test host (`host/`), not in-wallet debug UI. See [host/README.md](host/README.md).

## Host integration

Expand All @@ -67,7 +67,7 @@ import { OWSProxy } from "@1shotapi/ows-provider";

const proxy = await OWSProxy.create(container, "https://wallet.1shotapi.com/");

await proxy.rpc("setStyle", {
await proxy.rpc("configure", {
copy: { productName: "Acme Wallet", tagline: "Powered by 1Shot" },
theme: { primary: "oklch(0.45 0.18 250)" },
});
Expand Down Expand Up @@ -102,9 +102,16 @@ The same rich payload is also POSTed fire-and-forget to the 1Shot relayer
Analytics panel fed by `proxy.analytics.on` — filter by `name` to inspect
outcomes while testing.

### `setStyle` (custom RPC)
### `configure` (custom RPC)

Additive merge of theme CSS variables + copy. Safe to call repeatedly. Schema is Zod-strict (unknown keys rejected). Full field list: [skills/oneshot-embedded-wallet/SKILL.md](skills/oneshot-embedded-wallet/SKILL.md).
Additive merge of theme CSS variables, copy, feature flags, and optional
`destinationUrl` (status webhooks from the
[1Shot Relayer](https://1shotapi.com/docs/relayer/get-started/overview)).
Safe to call repeatedly. Schema is Zod-strict (unknown keys rejected). Full field
list: [skills/oneshot-embedded-wallet/SKILL.md](skills/oneshot-embedded-wallet/SKILL.md).

When `destinationUrl` is set, the wallet asks the 1Shot Relayer to send
transaction status update webhooks to that URL.

### Agent skill

Expand Down Expand Up @@ -150,4 +157,4 @@ See [roadmap.md](roadmap.md) (ShadCN + customization phases).

## Status

Functional Branding Layer (passkey unlock, EIP-1193, signing consent, recovery, credentials) with Phase 0–1 style foundations (`setStyle` + StyleProvider + shell). ShadCN UI migration in progress.
Functional Branding Layer (passkey unlock, EIP-1193, signing consent, recovery, credentials) with Phase 0–1 style foundations (`configure` + StyleProvider + shell). ShadCN UI migration in progress.
4 changes: 2 additions & 2 deletions extension/entrypoints/sidepanel/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,12 @@ async function ensureProxy(): Promise<OWSProxy> {
});

try {
await created.rpc("setStyle", {
await created.rpc("configure", {
copy: { productName: "1Shot Wallet" },
features: { hideCloseBox: true },
});
} catch (error) {
console.warn("[1Shot sidepanel] setStyle failed", error);
console.warn("[1Shot sidepanel] configure failed", error);
}

created.ethereum.on("accountsChanged", (...params: unknown[]) => {
Expand Down
2 changes: 1 addition & 1 deletion host/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Local Host Layer for exercising Host ↔ Branding communication against this repo’s wallet (served at domain root).

Adapted from OWS [`examples/host`](https://github.com/1Shot-API/open-wallet/tree/main/examples/host), plus a **Style** panel that calls `proxy.rpc("setStyle", options)`.
Adapted from OWS [`examples/host`](https://github.com/1Shot-API/open-wallet/tree/main/examples/host), plus a **Style** panel that calls `proxy.rpc("configure", options)`.

## Run

Expand Down
2 changes: 1 addition & 1 deletion host/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@1shotapi/oneshot-wallet-host",
"version": "0.0.0",
"private": true,
"description": "Local Host Layer for testing 1Shot Wallet Branding (setStyle + EIP-1193)",
"description": "Local Host Layer for testing 1Shot Wallet Branding (configure + EIP-1193)",
"type": "module",
"scripts": {
"dev": "node scripts/dev.mjs",
Expand Down
6 changes: 3 additions & 3 deletions host/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const MAX_ANALYTICS_EVENTS = 50;
export function App() {
const flyoutContainerRef = useRef<HTMLDivElement | null>(null);
const proxyRef = useRef<OWSProxy | null>(null);
/** Last setStyle payload from Design mode — re-applied after Test recreate. */
/** Last configure payload from Design mode — re-applied after Test recreate. */
const lastStyleRef = useRef<Record<string, unknown> | null>(null);
const [previewMount, setPreviewMount] = useState<HTMLDivElement | null>(null);
const [mode, setMode] = useState<HostMode>("test");
Expand Down Expand Up @@ -107,7 +107,7 @@ export function App() {
});

if (lastStyleRef.current) {
await proxy.rpc("setStyle", lastStyleRef.current);
await proxy.rpc("configure", lastStyleRef.current);
}

if (cancelled) {
Expand All @@ -121,7 +121,7 @@ export function App() {
const connectedChain = await refreshChainFromWallet(proxy);
reportStatus(
mode === "design"
? `Design preview connected on ${connectedChain}. Apply setStyle to refresh.`
? `Design preview connected on ${connectedChain}. Apply configure to refresh.`
: mode === "analytics"
? "Live analytics — switch to Test to generate events."
: `Wallet connected on ${connectedChain}. Enter a message and click Sign.`,
Expand Down
2 changes: 1 addition & 1 deletion host/src/components/AppHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function AppHeader() {
<div className="ml-auto hidden sm:block">
<p className="text-muted-foreground max-w-md text-right text-xs">
Host Layer → Branding Layer ·{" "}
<code className="text-[0.7rem]">setStyle</code> + EIP-1193
<code className="text-[0.7rem]">configure</code> + EIP-1193
</p>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion host/src/components/AppSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const NAV_ITEMS: {
{
mode: "design",
label: "Design",
description: "Live setStyle playground",
description: "Live configure playground",
icon: PaletteIcon,
},
{
Expand Down
2 changes: 1 addition & 1 deletion host/src/components/DesignPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function DesignPanel({
</h2>
<p className="text-muted-foreground mt-1 text-sm">
Fresh inline proxy in this slot. Styles are re-applied via{" "}
<code className="text-xs">setStyle</code> when you return to Test.
<code className="text-xs">configure</code> when you return to Test.
</p>
</div>
<span className="bg-accent text-accent-foreground inline-flex items-center rounded-full border border-cyan-100 px-2.5 py-1 text-xs font-semibold">
Expand Down
35 changes: 27 additions & 8 deletions host/src/components/WalletConfigurator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
CATALOG_CHAIN_OPTIONS,
DEFAULTS_PRESET,
OCEAN_PRESET,
buildSetStylePayload,
buildConfigurePayload,
type IStyleFormState,
} from "../styleForm";

Expand All @@ -22,7 +22,7 @@ export interface IWalletConfiguratorProps {
}

/**
* Host-side style playground for `proxy.rpc("setStyle", …)`.
* Host-side style playground for `proxy.rpc("configure", …)`.
* Tabs: Basic (identity), Style (theme colors), Text (modal copy).
*/
export function WalletConfigurator({
Expand All @@ -46,13 +46,13 @@ export function WalletConfigurator({
if (next) setForm(next);
setBusy(true);
setIsError(false);
setStatus("Calling setStyle…");
setStatus("Calling configure…");
try {
await onApply(buildSetStylePayload(payloadForm));
setStatus("setStyle applied.");
await onApply(buildConfigurePayload(payloadForm));
setStatus("Configuration applied.");
} catch (error) {
setIsError(true);
setStatus(error instanceof Error ? error.message : "setStyle failed");
setStatus(error instanceof Error ? error.message : "Configuration failed");
} finally {
setBusy(false);
}
Expand Down Expand Up @@ -87,6 +87,25 @@ export function WalletConfigurator({
value={form.tagline}
onChange={(value) => patch("tagline", value)}
/>
<TextField
id="style-destination-url"
label="Status webhook URL"
value={form.destinationUrl}
mono
onChange={(value) => patch("destinationUrl", value)}
/>
<p className="-mt-2 text-muted-foreground text-xs">
URL to receive transaction status update webhooks from the{" "}
<a
href="https://1shotapi.com/docs/relayer/get-started/overview"
target="_blank"
rel="noreferrer"
className="underline underline-offset-2"
>
1Shot Relayer
</a>
. Leave empty to clear.
</p>
<fieldset className="grid gap-3 rounded-lg border p-3">
<legend className="px-1 text-sm font-medium">Features</legend>
<div className="flex items-center justify-between gap-3">
Expand Down Expand Up @@ -151,7 +170,7 @@ export function WalletConfigurator({
Leave all unchecked to allow every catalog network. Checking any
restricts the wallet Network dropdown via{" "}
<code className="text-[0.7rem]">
setStyle.features.allowedChains
configure.features.allowedChains
</code>
.
</p>
Expand Down Expand Up @@ -281,7 +300,7 @@ export function WalletConfigurator({
void apply();
}}
>
Apply setStyle
Apply configuration
</Button>
<Button
type="button"
Expand Down
2 changes: 1 addition & 1 deletion host/src/hooks/useHostTestActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,7 +530,7 @@ export function useHostTestActions({
if (!proxy) {
throw new Error("Wallet not connected");
}
await proxy.rpc("setStyle", options);
await proxy.rpc("configure", options);
};

const handleFocusUsdcArc = () => {
Expand Down
16 changes: 13 additions & 3 deletions host/src/styleForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export interface IStyleFormState {
disableCredentials: boolean;
/** Pass as `features.disableDelegations`. */
disableDelegations: boolean;
/**
* Status webhook URL for the 1Shot Relayer (`destinationUrl` on configure).
* Empty ⇒ omit / clear when applying a full features payload.
* @see https://1shotapi.com/docs/relayer/get-started/overview
*/
destinationUrl: string;

// Text — Connect
connectTitle: string;
Expand Down Expand Up @@ -231,6 +237,7 @@ export const ACME_PRESET: IStyleFormState = {
hideCloseBox: false,
disableCredentials: false,
disableDelegations: false,
destinationUrl: "",
connectTitle: "Connect to Acme",
connectBody: "Acme is requesting your wallet address.",
connectContinue: "Allow",
Expand Down Expand Up @@ -363,7 +370,7 @@ export const ACME_PRESET: IStyleFormState = {
export const OCEAN_PRESET: IStyleFormState = {
...ACME_PRESET,
productName: "Ocean Wallet",
tagline: "Host setStyle preset",
tagline: "Host configure preset",
primary: "#0e7490",
primaryForeground: "#ffffff",
background: "#f0f9ff",
Expand Down Expand Up @@ -517,8 +524,8 @@ function put(
if (trimmed) target[key] = trimmed;
}

/** Build a `setStyle` RPC payload from the flat form (omit empty theme/copy keys). */
export function buildSetStylePayload(
/** Build a `configure` RPC payload from the flat form (omit empty theme/copy keys). */
export function buildConfigurePayload(
form: IStyleFormState,
): Record<string, unknown> {
const theme: Record<string, string> = {};
Expand Down Expand Up @@ -787,5 +794,8 @@ export function buildSetStylePayload(
disableDelegations: form.disableDelegations,
allowedChains: [...form.allowedChainIds],
};
const destinationUrl = form.destinationUrl.trim();
// Always send so Apply can clear a previously configured URL.
payload.destinationUrl = destinationUrl.length > 0 ? destinationUrl : null;
return payload;
}
4 changes: 2 additions & 2 deletions infra/scripts/deploy-prod-wallet-vm-remote.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ echo "----------------------- Start of Wallet Deployment Script ----------------
echo "----------------------------------------------------------------------------------------"

echo "---------------------------- Docker Deployment ---------------------------------------"
docker compose pull
docker compose --env-file secrets.env pull

docker stop wallet || true
docker rm -f wallet || true

docker compose up -d
docker compose --env-file secrets.env up -d

docker system prune -f
Loading