| name | dapp-connect-agenticwallet |
| version | 1.4.0 |
| author | okx |
| description | Upgrade an existing DApp Skill into one compatible with the Onchain OS routing
contract. The scaffold reads the source skill, classifies its tools, wraps
transaction/signing tools in a pending_sign shell, injects required spec
sections, and runs self-checks.
This scaffold does **not** generate skills from scratch. Bring an existing
DApp skill — the scaffold's job is to convert, not to invent.
[Trigger phrases]
Use the scaffold to upgrade <skill-dir>
Upgrade DApp Skill at <skill-dir>
用脚手架升级 <skill-dir>
升级 DApp Skill:路径 <skill-dir>
Examples:
"Use the scaffold to upgrade ~/.agents/skills/my-dex"
"用脚手架升级 ~/.agents/skills/uniswap-ai-test"
The scaffold reads the directory (Form A = has index.ts; Form B = markdown
only) and injects pending_sign wrappers + next_action.tool routing +
requiredTools + the 3 fixed spec sections per the Onchain OS schema.
[Scope] Works with any agent that reads from ~/.agents/skills/ —
Claude Code, OpenClaw, Cursor, Codex, Gemini CLI, etc.
(Claude Code also picks it up via the ~/.claude/skills/ symlink.)
|
| triggers | ["用脚手架升级","升级 DApp Skill","Use the scaffold to upgrade","Upgrade DApp Skill","upgrade Onchain OS DApp"] |
Onchain OS DApp Integration Scaffold
This Skill is an AI workflow definition. The agent reads this file and
executes the upgrade workflow described below.
Scope & runtime model (must tell the user before generating)
Output works in: Any agent that reads from ~/.agents/skills/ —
Claude Code, OpenClaw, Cursor, Codex, Gemini CLI, and others.
(Claude Code picks it up automatically via the ~/.claude/skills/ symlink
created by the installer.)
Runtime model: index.ts is never executed by any process. A Claude Code
Skill is a Markdown instruction. The actual execution path is:
LLM reads index.ts → simulates the function body during reasoning (calling
Bash/WebFetch tools to fetch real data) → returns JSON conforming to the
pending_sign contract → Claude routes per next_action.tool to Onchain OS.
Therefore this approach fits: pure-function tools that build calldata
(swap route lookup + encode, transfer address validation, etc.).
Does NOT fit: multi-step retry, WebSocket subscriptions, complex state
machines, heavy business logic — use the MCP scaffold instead.
Workflow: upgrade an existing DApp Skill
When the user provides any of these inputs (any combination is recognized as
the upgrade flow):
- The scaffold spec doc (this SKILL.md, or the Onchain OS DApp integration
guide § 3 "Skill integration")
- The scaffold itself (
dapp-connect-agenticwallet — this skill)
- The third-party DApp's existing Skill (path like
~/.agents/skills/<dapp-name>/)
Example trigger phrase:
I have an existing DApp Skill at ~/.agents/skills/my-dex.
Upgrade it per the Onchain OS Skill spec.
Workflow (7 steps, includes a form fork)
Step 1 — Read the source DApp Skill
<src>/SKILL.md → parse frontmatter (name / version / author / tools array)
<src>/index.ts → if present, identify each function signature, return
type, and any existing signing logic
<src>/README.md (if present) → extract DApp business description
<src>/references/ or other subdirectories → record paths; may need full
copy later
Step 1.5 — Form detection (critical fork)
The form determines the rest of the flow. Do not skip this step.
| Detection | Form | Path |
|---|
<src>/index.ts exists AND has exported business functions | Form A | Steps 2a → 3a → 4a → 5 → 6 (original code-bearing flow) |
Only <src>/SKILL.md, no index.ts, OR index.ts has no exports | Form B | Steps 2b → 3b → 4b → 5 → 6 (markdown-merge flow) |
| Local-signing code already detected (see § Hard constraints item 3 for full keyword list) | Violation | Stop immediately, ask the user to clean the source first, then retry |
Form-detection command:
test -f <src>/index.ts && grep -q '^export' <src>/index.ts && echo "Form A" || echo "Form B"
Violation-scan command (must run BEFORE form detection):
grep -rn \
'ethers\.Wallet\|new Wallet(\|signTransaction\|sendTransaction\|privateKey\|Keypair\.fromSecret\|nacl\.sign\|forge script.*--broadcast\|cast send\|useSignMessage\|useWriteContract\|signUserOp\|HDNodeWallet\|mnemonicToAccount\|window\.ethereum\.request.*eth_send\|walletClient\.writeContract\|walletClient\.sendTransaction' \
<src>/ 2>/dev/null | grep -v '^.*:.*\/\/' | head -20
Form A path (source has executable code)
Step 2a — Classify each tool (decides whether to wrap in pending_sign)
Classify per tool:
| Detection | Class | Action |
|---|
Function name contains build / swap / mint / deposit etc. + returns tx/calldata | Transaction | Wrap with pending_sign |
Function name contains send / transfer + has to+amount params | Transfer | Wrap with pending_sign → onchainos wallet send |
Function name contains sign / login / authorize + returns message | Signing | Wrap with pending_sign → onchainos wallet sign-message |
Function name contains get / query / search / list + returns plain JSON | Read-only | Untouched, pass through |
Already contains ethers.Wallet / signTransaction / privateKey | Violation — source signs locally | Tell user: must remove local signing first before integrating Onchain OS |
Step 3a — Synthesize new SKILL.md (merge strategy, v1.4)
Use a merge strategy, not a rewrite — every original frontmatter field
must be preserved; only inject the scaffold's required fields:
- Read all original frontmatter fields (including
allowed-tools,
model, license, metadata, and any non-standard fields)
- Override these fields (scaffold values win):
name = original name + -onchainos suffix (don't double-append if
already suffixed)
version = original patch +1 (e.g. 1.2.0 → 1.2.1)
description = inject [Onchain OS dependency] + [signing constraint]
blocks (append to original description, don't replace)
requiredTools = union of Onchain OS tools matched by the transaction /
transfer / signing tools (union with existing requiredTools if any)
- Preserve every other original field (
author, chains, tools,
allowed-tools, model, license, metadata, etc.) — must not silently
drop
- Post-merge check: new SKILL.md frontmatter field count ≥ original;
if it decreases, list the dropped fields and warn the user
- Inject
## Pre-flight Checks + ## Signing Constraint sections (copied
verbatim from templates/SKILL.md.template)
- Original SKILL.md body (markdown after the frontmatter) is kept in
full, appended after the new frontmatter as the business description
Step 4a — Synthesize new index.ts
- Inject the
RUNTIME MODEL warning at the top (copied from
templates/index.ts.template)
- Also inject the
toSafeInt helper at the top (copied from
templates/index.ts.template) — inject unconditionally regardless of
whether sign-message tools are present, to avoid omission
- Original business functions → rename with
_impl suffix (e.g.
my_build_swap → _my_build_swap_impl), kept as internal helpers
- Each transaction tool → generate a new outer function, wrap in
pending_sign skeleton, internally call the _impl to get unsigned_tx
- Signing tools (sign-message) → during wrapping, additionally:
- Replace every EIP-712
uint*/int* field in the _impl return's message
with toSafeInt(...) calls
- If the original
_impl returns non-standard {domain, types, values},
inject the standard-format conversion in the outer function (see
Appendix B / sign-message skeleton comments)
- Read-only tools → pass through (export as-is)
export { ... } lists every public tool (outer + read-only)
Form B path (markdown-only source, no index.ts)
When this applies: the source Skill is an AI guidance / instruction
document — markdown only, describing "the Agent should call API X like Y" —
with no executable business functions. This is the majority of skills in the
Claude Code ecosystem (e.g. all 10 skills in uniswap/uniswap-ai).
Step 2b — Identify the "tool call chain" in the original SKILL.md
Scan the original SKILL.md body for:
- HTTP endpoints (e.g.
POST /v1/quote, POST /v1/swap)
- Returned fields that may be
unsigned_tx candidates: to / data /
value / calldata / unsignedTx / transaction
- Local-signing keywords (
walletClient.sendTransaction /
ethers.Wallet.signTransaction / privateKey)
If any local-signing keyword is found → output the violation list, stop
immediately, and ask the user to clean up the source first.
Step 3b — Synthesize new SKILL.md (markdown-merge strategy, v1.4)
- Original SKILL.md body kept in full (everything after the frontmatter
is left untouched)
- Frontmatter uses the merge strategy (same rules as Step 3a): preserve
all original fields, inject/override only the scaffold's 3 fixed fields:
description += [Onchain OS dependency] + [signing constraint] (append,
don't replace)
requiredTools = union
- Version: patch +1
- Inject in the new frontmatter:
[Onchain OS dependency] /
[signing constraint] / requiredTools / ## Pre-flight Checks /
## Signing Constraint — these are the 5 fixed positions
- Append a new section at the end of the original body:
## Onchain OS routing instructions (read before executing any tool). Contents:
- Line numbers in the original SKILL.md where local-signing examples
appear, paired with the Onchain OS replacement for each
- The
pending_sign structure + next_action.tool routing rules
- "Runtime LLM MUST read: do not execute the local-signing examples in the
body above; return pending_sign instead and let the Onchain OS tool named
in next_action.tool sign for you"
Step 4b — Do NOT generate a stub index.ts
- For Form B, don't fabricate an index.ts. The source skill has no
business functions, so the new skill shouldn't either
- Claude Code skills support a pure-markdown form; the LLM reads the SKILL.md
body directly and follows the API instructions
- If the user insists on an index.ts, respond: "Recommend adding
index.ts
business functions to the source skill first, then re-running the upgrade
on the Form A path. A stub index.ts here has no operational value."
Step 4b extension — Copy references/ and other resource directories
- If the source has
<src>/references/ / <src>/_shared/ / similar
directories, copy them whole to the upgraded output
- Don't trim contents
Steps shared by both forms
Step 5 — Synthesize new README.md
- Copy
templates/README.md.template
{{DESCRIPTION}} = first sentence of the source SKILL.md description
{{EXAMPLE_USER_PROMPT}} = pick a test phrase based on the first
transaction tool's businessType
Step 6 — Self-check
Run the 5 self-check items and output a consolidated report to the user.
Include a migration table so the user can confirm each tool was classified
and routed correctly:
Original tool New tool Class Onchain OS tool
my_build_swap → my_build_swap Transaction onchainos wallet contract-call
my_get_price → my_get_price Read-only — (pass-through)
my_search_token → my_search_token Read-only —
Workflow hard constraints
- Do not modify the original Skill's business-logic semantics (the
_impl function bodies must stay unchanged; only the outer wrappers are
added)
- Do not infer
unsigned_tx's to / data — those must be returned by
the original _impl function. The workflow only wraps them into a
pending_sign outer shell.
- If the source already contains local-signing code (ethers.Wallet etc.),
abort at Step 2 and ask the user to clean the source first.
Self-check (mandatory after every upgrade, do not skip)
Run mechanical checks against the upgrade output. Run each Bash command and
report the consolidated result to the user:
D=<outputDir>
grep -l '{{' $D/* && echo FAIL:A1 || echo PASS:A1
python3 -c "import yaml,sys; yaml.safe_load(open('$D/SKILL.md').read().split('---',2)[1])" && echo PASS:A2 || echo FAIL:A2
grep -q '\[Onchain OS dependency\]' $D/SKILL.md && grep -q '\[signing constraint\]' $D/SKILL.md && grep -q '## Pre-flight Checks' $D/SKILL.md && grep -q '## Signing Constraint' $D/SKILL.md && echo PASS:A3 || echo FAIL:A3
grep -v '^//' $D/index.ts 2>/dev/null | grep -qE \
'ethers\.Wallet|new Wallet\(|signTransaction|sendTransaction|privateKey|mnemonic|keystore|\
Keypair\.fromSecret|nacl\.sign|forge\s+script.*--broadcast|cast\s+send|anchor\s+deploy|\
useSignMessage|useWriteContract|useWalletClient|writeContract\(\
|signUserOp|UserOperation|signTypedData\(|eth_sendTransaction|eth_sendRawTransaction|\
HDNodeWallet|mnemonicToAccount|deriveChild|\
window\.ethereum\.request.*eth_send|window\.ethereum\.send\b' \
&& echo FAIL:A4 || echo PASS:A4
grep -q 'pending_sign' $D/index.ts 2>/dev/null && grep -q 'next_action' $D/index.ts 2>/dev/null && echo PASS:A5 || echo FAIL:A5
Expected: all 5 PASS for Form A. Form B: A1/A2/A3 PASS, A4/A5 SKIP (no index.ts).
Any FAIL → tell the user which item failed and propose a fix; do not claim
the upgrade succeeded.
pending_sign skeletons (used by Step 4a's wrappers)
For each transaction-class tool wrapped in Form A's Step 4a, use one of these
skeletons by businessType. Outside the // TODO [third-party] markers, copy
the skeleton verbatim:
async function <NAME>(params: any): Promise<PendingSign> {
const TODO = (field: string): never => {
throw new Error(`[SCAFFOLD-UNFILLED] ${field} unfilled — fill in business logic or have the LLM substitute at runtime per the comment`);
};
const unsigned_tx = {
to: TODO('to'),
data: TODO('data'),
value: '0',
chain: '<PRIMARY_CHAIN>',
};
return {
status: 'pending_sign',
unsigned_tx,
description: TODO('description'),
next_action: { tool: '<NEXT_ACTION_TOOL>' },
};
}
async function <NAME>(params: any): <> {
= (: ): { (); };
unsigned_tx = {
: (),
: ,
: (),
: ,
};
{
: ,
unsigned_tx,
: (),
: { : },
};
}
<>(: ) {
= (: ): { (); };
{
: ,
: (),
: (),
: { : },
};
}
Why throw-style placeholders:
- The legacy
'0xTODO' string is a valid literal — the LLM in
"simulate-execute" mode would return it as-is, then Onchain OS would get an
invalid address and signing would fail
TODO('to') throws immediately, forcing the LLM to either substitute a
real value (per the comment, by calling the appropriate API) or ask the
user for an API key — never fall through with a literal placeholder
- For DApp developers filling stubs by hand:
'0xTODO' is easy to miss; a
thrown error is impossible to ignore
Composition rules:
- Multiple tools: separate function bodies with one blank line
- End of file: append
export { <name1>, <name2>, ... };
<PRIMARY_CHAIN> = first element of the source's chains array
<NEXT_ACTION_TOOL> = mapped from businessType per the table:
| businessType | next_action.tool | requiredTools |
|---|
swap | onchainos wallet contract-call | onchainos wallet contract-call, onchainos gateway broadcast |
transfer | onchainos wallet send | onchainos wallet send, onchainos gateway broadcast |
contract-call | onchainos wallet contract-call | onchainos wallet contract-call, onchainos gateway broadcast |
sign-message | onchainos wallet sign-message | onchainos wallet sign-message |
pipeline | per-tool — see below | union of the above |
pipeline businessType handling
businessType = pipeline means a Skill mixes transaction tools + read-only
tools (e.g. search_token / get_price / build_swap triple). When
wrapping:
- For each tool, classify per Step 2a's table (transaction / transfer /
sign-message / read-only)
- Wrap transaction tools per their respective skeletons
- Read-only tools use this skeleton (no pending_sign):
async function <NAME>(params: any) {
return { };
}
requiredTools = union (e.g. [onchainos wallet contract-call, onchainos gateway broadcast]); read-only tools don't contribute to requiredTools
next_action.tool is per-tool, not shared
- When declaring read-only tools in SKILL.md
tools, prefix description
with [read-only] so the Agent can route correctly
Hard constraints (must hold during every upgrade)
-
SKILL.md must contain the 3 fixed positions: description's
[Onchain OS dependency] + [signing constraint] blocks, the
requiredTools array, and the ## Pre-flight Checks + ## Signing Constraint
sections
-
Every index.ts tool function's return value must conform to the
pending_sign contract:
{ status, unsigned_tx: {to, data, value, chain}, description, next_action: {tool} }
-
Forbidden (must never appear in upgrade output):
JS / TS inline signing
ethers.Wallet / new Wallet(...) / privateKey
signTransaction / sendTransaction
signTypedData(...) / eth_sendRawTransaction
- Direct
window.ethereum write calls (window.ethereum.request({method:'eth_send*'}) etc.)
Solana
Keypair.fromSecretKey(...) / nacl.sign(...) / nacl.sign.keyPair.fromSeed(...)
bs58.decode(<privateKey>)
Foundry / Hardhat / Anchor CLI
forge script ... --broadcast / forge script ... --private-key
cast send / cast wallet sign
anchor deploy / anchor test --provider.wallet
wagmi / viem hooks
useSignMessage / useWriteContract / useWalletClient
walletClient.writeContract(...) / walletClient.sendTransaction(...)
ERC-4337 / Account Abstraction
signUserOp(...) / direct UserOperation send
HD wallet derivation
v1.3 → v1.4 roadmap (known gaps · next-version fixes)
Based on real-world Mode-B-style upgrades against the 10 skills in
Uniswap/uniswap-ai, v1.2 surfaced these gaps. v1.3 fixed most; v1.4 ships
remaining fixes as part of the from-scratch removal.
Gap 1 · Step 2b scanner only catches JS signing, misses CLI / API relay
The current Step 2b scanner covers JS inline signing (sendTransaction /
walletClient.writeContract / ethers.Wallet, etc.). Missed cases:
| Signing medium | Example pattern | Typical skill |
|---|
| Foundry / Hardhat CLI | forge script .* --broadcast / cast send .* --account / --private-key | Any deployer-type skill (e.g. uniswap-cca/deployer) |
| Third-party wallet CLI | tempo pay / tempo wallet send / rainbow send | Any external wallet integration (e.g. pay-with-any-token) |
| Direct RPC | eth_sendTransaction / native JSON-RPC | Low-level EVM integrations |
| API-relayed unsigned tx | External API returns {to, data, value} + docs say "send / broadcast" | Aggregators like Uniswap Trading API, 1inch API |
Fix: expand the keyword set with 3 new scan patterns (CLI / RPC / API-relay);
any hit triggers the routing-conversion section.
Gap 2 · Routing-conversion section had only one template
v1.2's "line-replacement" template (good for inline JS) didn't cover CLI or
API-relay scenarios.
v1.4 ships three templates:
Pattern 1 · Line replacement (existing) — for JS inline signing:
- L123: `walletClient.sendTransaction(...)` → pending_sign + onchainos wallet contract-call
- L456: `signTypedData(...)` → pending_sign + onchainos wallet sign-message
Pattern 2 · Flow replacement (new) — for CLI-driven signing:
Original flow: forge script ... --broadcast
Converted:
1. forge build only (drop --broadcast)
2. Extract constructor calldata from the artifact
3. Wrap in pending_sign:
{ status, unsigned_tx: {to, data, value, chain}, next_action: {tool: 'onchainos wallet contract-call'} }
4. Onchain OS takes over signing + broadcasting
Pattern 3 · Adapter wrap (new) — for API-relayed unsigned tx:
const { swap } = await externalApi.getSwap(quote); // unsigned tx structure
return {
status: 'pending_sign',
unsigned_tx: { to: swap.to, data: swap.data, value: swap.value, chain: ... },
description: ..., next_action: { tool: 'onchainos wallet contract-call' },
};
Step 3b generation logic: pick the matching template(s) per the Step 2b
scanner's signing-medium classification (multiple may apply).
Gap 3 · Routing-conversion injection used a "violation count threshold"
v1.2 in practice: 23/14/4 violations → injected; 3/3 → not injected. This
threshold caused deployer / pay-with-any-token to skip injection.
Hard rule:
if requiredTools is non-empty → MUST inject the routing-conversion section,
regardless of violation count
if requiredTools is empty AND no signing/transaction keywords → skip the
upgrade entirely (see Gap 4)
Gap 4 · Pure-doc / config / planning skills got irrelevant injections
v1.2 injected [Onchain OS dependency] + requiredTools: [] into 5 skills
that don't produce transactions: uniswap-cca/configurator,
uniswap-driver/{liquidity,swap}-planner,
uniswap-hooks/{v4-hook-generator, v4-security-foundations}. This created a
contradiction: "dependency declared + empty dependency list".
Step 1.5 prefilter (runs before form detection):
Scan the original SKILL.md for any "transaction signal" keyword:
- sign / signature / unsigned_tx / transaction / broadcast
- sendTransaction / writeContract / forge script --broadcast / cast send
- tempo pay / onchainos wallet send / any wallet CLI write
- pending_sign / onchainos wallet contract-call / send / sign-message
Any hit → proceed to form detection (full upgrade flow)
No hits → classify as "pure-doc skill" → skip upgrade, append at end of SKILL.md:
> This skill is pure documentation / config / planning; it doesn't directly
> produce on-chain transactions or signing messages, so it has no direct
> integration with Onchain OS. If downstream skills (deployer / executor
> types) emit transactions, those downstream skills are responsible for
> the pending_sign routing.
Gap 5 · Repo-level batch upgrade
Uniswap/uniswap-ai is a monorepo (5 plugins × 10 skills). Single-skill
upgrades require 10 manual passes.
Optional in v1.4: support a "repo-level batch" trigger phrase:
"Run the upgrade flow on every skill under <monorepo-path>, output to <dest>"
→ Agent globs all SKILL.md → runs Step 1.5 prefilter on each → classifies →
produces a consolidated migration table
References
templates/ — three rendering templates (now used for fixed-section
snippets only, not for from-scratch generation)
examples/my-dex-swap/ — complete sample of an upgrade output
- Upstream spec: third-party DApp Onchain OS integration quickstart, § 3
"Skill integration"