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
27 changes: 23 additions & 4 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 setStyle / focusWallet / addAsset / createAccount / onramp for
theming, host-driven focus mode, tracked assets, and first-party Safari create.
license: MIT
metadata:
Expand Down Expand Up @@ -232,7 +232,7 @@ Users can also add assets from the Balances tab without a host RPC. The Balances

## Custom RPC — `createAccount`

Used by the first-party **`/create/`** host page (Safari passkey create). Hosts embedding the wallet normally do **not** call this — the branding layer opens `/create/` itself when needed.
Used by the first-party **`/create/`** host page (Safari passkey create). Hosts embedding the wallet normally do **not** call this — the branding layer opens `/create/` itself when needed. Not exposed as a playground button.

```typescript
const result = await proxy.rpc("createAccount");
Expand All @@ -246,14 +246,32 @@ await proxy.rpc("createAccount", { accountName: "My Wallet" });
|--------|--------|--------|
| `createAccount` | `{ accountName?: string }` optional | Runs setup create (passkey + relayer register); returns credential id |

## Custom RPC — `onramp`

Opens Circle fiat onramp fullscreen inside the Branding Layer for the unlocked EVM address.

```typescript
await proxy.rpc("onramp", {
chainId: 8453, // optional — decimal chain id for catalog scoping
amount: "50", // optional — amount hint when supported by the kit
});
// or: await proxy.rpc("onramp", {});
```

| Method | Params | Behavior |
|--------|--------|----------|
| `onramp` | `{ chainId?: number, amount?: string }` | Shows wallet, mounts Circle AppKit onramp; session minted via Relayer `POST /wallet/onramp` |

Returns `{ ok: true }` when the user closes the onramp view. The Relayer holds the Circle kit key; the browser only receives a single-use session. Inline iframe requires Circle CSP allowlisting of the wallet origin; for local/ngrok testing the branding layer honors `localStorage.setItem("circlePopup", "true")` and uses AppKit `openWindow` instead.

## Other Host APIs

| API | Use |
|-----|-----|
| `proxy.ethereum.request(...)` | EIP-1193 (accounts, sign, chain, …) |
| `proxy.credentials.*` | OID4 offer / present (when enabled in wallet) |
| `proxy.showWallet()` / `hideWallet()` | Host-driven panel (flyout or full-screen drawer on small viewports) without an EIP-1193 call |
| `proxy.rpc(method, params)` | Custom Branding RPC (`setStyle`, `focusWallet`, `unfocusWallet`, `addAsset`, `createAccount`, …) |
| `proxy.showWallet()` / `hideWallet()` | Host-driven flyout without an EIP-1193 call |
| `proxy.rpc(method, params)` | Custom Branding RPC (`setStyle`, `focusWallet`, `unfocusWallet`, `addAsset`, `createAccount`, `onramp`, …) |

## Hard rules

Expand All @@ -262,3 +280,4 @@ await proxy.rpc("createAccount", { accountName: "My Wallet" });
- Theme with `setStyle`; 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`).
- Use `onramp` (or the in-wallet Buy button) for fiat → crypto; do not put the Circle kit key in the Host or Branding Layer.
3 changes: 3 additions & 0 deletions .github/workflows/Deploy Dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,11 @@ jobs:
gcloud --quiet auth configure-docker $GAR_LOCATION-docker.pkg.dev

- name: Build Wallet
env:
CLOUDSMITH_TOKEN: ${{ secrets.CLOUDSMITH_TOKEN }}
run: |-
docker build \
--secret id=cloudsmith_token,env=CLOUDSMITH_TOKEN \
--tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:$GITHUB_SHA" \
--tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:${GITHUB_REF##*/}" \
--tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:latest" \
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/Deploy Prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,11 @@ jobs:
gcloud --quiet auth configure-docker $GAR_LOCATION-docker.pkg.dev

- name: Build Wallet
env:
CLOUDSMITH_TOKEN: ${{ secrets.CLOUDSMITH_TOKEN }}
run: |-
docker build \
--secret id=cloudsmith_token,env=CLOUDSMITH_TOKEN \
--tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:$GITHUB_SHA" \
--tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:${GITHUB_REF##*/}" \
--tag "$GAR_LOCATION-docker.pkg.dev/$PROJECT_ID/$PRODUCT_NAME/oneshot-wallet:latest" \
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,4 @@ oneshot-wallet-*-deploy
oneshot-wallet-*-deploy.pub
*-deploy
*-deploy.pub
Circle Onramp Kit Beta testing.md
2 changes: 2 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@crcl-main:registry=https://npm.cloudsmith.io/circle/common-private/
//npm.cloudsmith.io/circle/common-private/:_authToken=${CLOUDSMITH_TOKEN}
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ Prefer **clean code over backwards compatibility**. Do not add legacy redirects,
| `src/lib/types/domain/` | Domain DTOs (e.g. `KnownAsset`, `TrackedAsset`, `WalletConfig`) |
| `src/lib/types/events/` | Domain event classes (one file each) |
| `src/lib/interfaces/{business,data,utils}/` | Layer interfaces |
| `src/lib/implementations/{business,data,utils}/` | Layer implementations |
| `src/lib/implementations/{business,data,utils}/` | Layer implementations (`CircleProvider` for AppKit onramp) |
| `src/assets/` | Static media only (SVGs, images) |

Test Host Layer: `host/` (`npm run dev:host`). Style via Host RPC `setStyle`, not in-wallet debug knobs.

Fiat onramp: Asset Details **Buy** and host RPC `onramp({ chainId?, amount? })` open `OnrampView` (Circle AppKit). Sessions come from Relayer `POST /wallet/onramp` — never put the Circle kit key in this SPA. Default UI is `mountIframe`; for local/ngrok before Circle CSP allowlisting, set `localStorage.circlePopup = "true"` to use `openWindow` (prefetch session, then a sync click). Cloudsmith: set `CLOUDSMITH_TOKEN` before `npm install` (see README).

### Form validation UX

Primary submit actions (e.g. Send in `TransferTokensModal`) stay **disabled until every required field is valid**. Do not leave the button enabled and only reject on click. Empty fields show no error text; invalid non-empty input shows inline errors; the CTA enables only when the whole form is ready.
Expand Down
8 changes: 7 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ WORKDIR /app

COPY package.json package-lock.json ./
COPY host/package.json ./host/
RUN npm ci
COPY .npmrc ./

# Circle @crcl-main/* requires CLOUDSMITH_TOKEN BuildKit secret
RUN --mount=type=secret,id=cloudsmith_token \
CLOUDSMITH_TOKEN="$(cat /run/secrets/cloudsmith_token)" \
npm ci \
&& rm -f .npmrc

COPY index.html vite.config.ts tsconfig.json tsconfig.node.json components.json ./
COPY src ./src
Expand Down
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,36 @@ Production deliverable: a static **nginx** Docker image (no server-side runtime)

## Setup

Circle’s private AppKit canary requires Cloudsmith auth. The repo includes `.npmrc`;
export your token before installing:

```bash
# Linux / macOS
export CLOUDSMITH_TOKEN=<your-cloudsmith-token>
```

```powershell
# Windows PowerShell
$env:CLOUDSMITH_TOKEN = "<your-cloudsmith-token>"
```

CI Docker builds expect GitHub Actions secret `CLOUDSMITH_TOKEN`.

```bash
npm install
cp .env.example .env # set NGROK_AUTHTOKEN (and optional NGROK_DOMAIN)
```

Fiat onramp uses Circle AppKit (`Buy` in Asset Details, or host RPC `onramp`).
Sessions are minted by the Relayer (`POST /wallet/onramp`); the kit key never
ships in this SPA. Inline iframe onramp requires your wallet domain to be
registered with Circle for CSP — see Circle’s beta docs. For local/ngrok
testing before CSP allowlisting, force the popup flow:

```js
localStorage.setItem("circlePopup", "true")
```

## Develop

```bash
Expand Down
20 changes: 20 additions & 0 deletions host/src/components/WalletActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export interface IWalletActionsProps {
onUnfocusWallet: () => void;
onAddUsdcArc: () => void;
onAddUsdtBase: () => void;
onOnramp: () => void;
}

export function WalletActions({
Expand Down Expand Up @@ -78,6 +79,7 @@ export function WalletActions({
onUnfocusWallet,
onAddUsdcArc,
onAddUsdtBase,
onOnramp,
}: IWalletActionsProps) {
const meta = hostChainMeta(chainId);

Expand Down Expand Up @@ -310,6 +312,24 @@ export function WalletActions({
</Button>
</div>
</div>

<div className="flex flex-col gap-1.5">
<Label>Onramp</Label>
<p className="text-muted-foreground text-xs">
Open Circle fiat onramp via <code>onramp</code> (Buy crypto into the
unlocked wallet address).
</p>
<div className="flex flex-wrap gap-2">
<Button
type="button"
variant="outline"
disabled={!ready || busy}
onClick={onOnramp}
>
Onramp
</Button>
</div>
</div>
</section>
);
}
23 changes: 23 additions & 0 deletions host/src/hooks/useHostTestActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,28 @@ export function useHostTestActions({
})();
};

const handleOnramp = () => {
const proxy = proxyRef.current;
if (!proxy) return;
setBusy(true);
reportStatus("Opening onramp…");
void (async () => {
try {
await proxy.rpc("onramp", {});
proxy.showWallet();
setWalletVisible(true);
reportStatus("Onramp closed.");
} catch (error) {
reportStatus(
error instanceof Error ? error.message : "onramp failed",
true,
);
} finally {
setBusy(false);
}
})();
};

const walletActionProps = {
ready,
busy,
Expand Down Expand Up @@ -510,6 +532,7 @@ export function useHostTestActions({
onUnfocusWallet: handleUnfocusWallet,
onAddUsdcArc: handleAddUsdcArc,
onAddUsdtBase: handleAddUsdtBase,
onOnramp: handleOnramp,
};

return {
Expand Down
Loading