关于 compose-bitcoin-oracle
compose-bitcoin-oracle 是一项技能,用于在用户自己的 Goldsky 账户中部署比特币价格预言机示例。它定时获取 CoinGecko 报价,将编码后的数值写入 PriceOracle 合约,并把价格存入 Compose collection。
主要功能
- 01搭建 bitcoin-oracle 示例
- 02定时获取 CoinGecko 报价
- 03向 PriceOracle 合约写入价格
- 04在 collection 中保存历史价格
- 05支持共享或自有合约
适用场景
- 01运行定时 BTC/USD 预言机示例
- 02记录价格历史以供后续查询
- 03使用共享演示合约搭建预言机
MCP 相关
该技能指导在 Compose 上配置和部署应用。应用通过 Compose 管理的钱包向 PriceOracle 合约写入数据,并使用 Compose collection 保存历史价格。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ goldsky-io/ goldsky-agent --skill compose-bitcoin-oracle安装后在 Claude Code 中输入/compose-bitcoin-oracle确认已加载。
详细介绍
构建:Compose bitcoin-oracle
在用户自己的 Goldsky 账户下搭建 bitcoin-oracle 示例。定时任务从 CoinGecko 获取 BTC/USD,并通过 Compose 管理的钱包,将 (timestamp, price * 100) 作为两个 bytes32 值写入 PriceOracle 合约。它还会将价格追加到 Compose collection,以便进行历史查询。
此模板仅提供 bitcoin-oracle 应用特有的内容——其工作原理和源代码。推荐的方案使用 Base Sepolia 上一个共享、完全不受权限限制的 PriceOracle,因此用户无需部署任何内容,Compose 智能钱包会自动创建,并由赞助方承担 Gas 费用。
步骤 0a — 先加载基础技能
在做任何事情之前——在回答、提问、搭建文件或调用 deployComposeApp 之前——先加载此模板所依赖的两项基础技能:
Skill(compose)— 始终适用的 Compose 指南:基本规则(绝不替用户擅自假设应用的任何情况;不确定时应先询问)和通用构建指南。Skill(compose-reference)— 清单、字段和 API 参考;在编写任何compose.yaml或任务文件之前先查阅。
此模板特意省略了这些规则和参考资料——正确构建时必须遵循它们,且此处不会重复。两项技能都加载完毕后才能继续。
模式检测
根据你可用的工具选择模式:
-
有
deployComposeApp工具(Goldsky Web 应用聊天机器人)——这是首选的应用内流程。 不要输出goldsky终端命令或cliCommand卡片,也不要使用步骤 0b /degit/forge/goldsky compose deploy。先用 2~3 句话进行简单说明,然后通过askUser提问(用recommendedIndex标记推荐选项):- 应用名称——首先询问,其他事项一概先不做:"应用应该叫什么名字?(建议使用
bitcoin-oracle)"。名称之后很难更改——它会限定具名钱包的作用范围,并参与每次deployContract的 CREATE2 盐值,因此必须在任何钱包或合约步骤之前确定。用户犹豫不决时,接受默认名称bitcoin-oracle,并将所选名称设置为生成的compose.yaml顶层name:。 - 合约——明确询问,不要自行假设:"你有自己的
PriceOracle合约吗?还是应该使用 Base Sepolia 上的共享演示预言机,以便快速运行?" 选项:“使用 Base Sepolia 上的共享演示预言机(推荐——无需部署)”和“我会使用自己的合约。” 使用共享路径时,ORACLE_CONTRACT是baseSepolia上的硬编码地址0x53deB3fF6E6e82A3b5E96f14E185e3Fe66BF5113——请逐字符照抄;并在说明中提及它仅供演示,不适用于生产环境。使用自有合约的路径时,请用户粘贴其合约地址和链,并严格使用用户粘贴的内容(其PriceOracle必须允许 Compose 钱包写入)。 - 更新频率(建议每分钟更新一次,
* * * * *)。
Compose 智能钱包会在运行时自动创建并由系统赞助 Gas——绝不要告知用户创建或为钱包充值。完成提问后,在内存中生成文件(不要运行 degit):
compose.yaml(按所选计划配置单个 cron 任务)、src/contracts/PriceOracle.json(步骤 3 中的原样 ABI)和src/tasks/bitcoin-oracle.ts(从 CoinGecko 获取 BTC/USD,然后通过 Gas 由系统赞助的智能钱包调用evm.contracts.PriceOracle.write(toBytes32(timestamp), toBytes32(Math.round(price*100))),并将每个价格追加到bitcoin_prices集合)。ORACLE_CONTRACT是任务文件顶部的硬编码const,不是环境变量(本示例中将其内联;/compose也允许在清单中使用env:——两种方式都可以,但不要把纯地址放进secrets:)。输出文件前,请遵循/compose-reference了解清单格式,并遵循沙盒导入规则(按照/compose中的黄金规则——不要凭记忆生成清单)。然后在同一轮中调用deployComposeApp;不要先请用户确认,也不要输出任何goldsky命令。在此模式下,忽略下面的步骤 0~8——那些是 CLI/本地操作流程。 - 应用名称——首先询问,其他事项一概先不做:"应用应该叫什么名字?(建议使用
-
**有
Bash(本地 CLI / 编码 Agent):**直接执行以下步骤,并解析输出以供后续命令使用。 -
**两者都没有(纯参考问答):**说明应用的功能;如需逐步操作指导,请引导用户运行
npx skills add goldsky-io/goldsky-agent,以便在本地使用 Bash 运行。
不可妥协的事项
- Base Sepolia 上地址为
0x53deB3fF6E6e82A3b5E96f14E185e3Fe66BF5113的共享预言机完全不受权限限制——任何人都可以向其写入。 它仅用于入门和演示。请在说明中告知用户,绝对不能将其用于生产环境。它仅存在于 Base Sepolia 上。 - 执行
goldsky compose deployContract、goldsky compose deploy、git push或gh repo create之前,必须先展示确切命令并获得明确确认。 - 仅限自行部署路径: 合约的授权写入者必须是 Compose 钱包,否则每次
write()都会回滚。共享预言机路径没有写入者限制,因此不适用此要求。 - 示例仅包含
src/contracts/PriceOracle.json(ABI),不包含 Solidity 源码。 如果要全新部署,请使用本技能中的参考合约。ABI 必须逐字写入——见步骤 3。 - 不要修改
src/lib/utils.ts。toBytes32与合约存储值的方式紧密相关。
以下步骤 0–8 是 Bash/本地 CLI 流程。如果有
deployComposeApp工具可用(Web 应用聊天机器人),请勿遵循这些步骤——改用上文模式检测中的部署工具流程。
步骤 0b — 搭建示例项目
将 bitcoin-oracle 示例单独拉取到一个全新的目录中(不包含 Git 历史记录):
npx -y degit goldsky-io/documentation-examples/compose/bitcoin-oracle#6abe62878cb92f2569538ba9572b049ed5949a01 bitcoin-oracle
cd bitcoin-oracle
如果无法使用 npx degit,则改用稀疏克隆:
git clone --filter=blob:none --sparse https://github.com/goldsky-io/documentation-examples.git
cd documentation-examples && git checkout 6abe62878cb92f2569538ba9572b049ed5949a01 && git sparse-checkout set compose/bitcoin-oracle && cd compose/bitcoin-oracle
如果用户已克隆该示例,请跳过此步骤并进入该目录。
预检
如果 goldsky compose --version 输出 compose CLI is not installed,请运行 goldsky compose install(在 stable 通道上可重复执行),然后再次检查。
版本(compose 0.8.1)。 运行 goldsky compose --version——输出应为 goldsky compose 0.8.1。如果版本低于 0.8.1,或者 deployContract/writeContract 是未知命令,则自行部署路径(步骤 3 的分支 B)无法运行——请主动提出为用户运行 goldsky compose update,然后重新检查 --version。(分支 A——共享预言机——只需要 compose deploy,因此使用较旧的 CLI 也没问题。)
goldsky CLI、身份验证和 deno 检查属于标准 Compose 预检——请参阅 /compose 和 /auth-setup。bitcoin-oracle 特有说明:自行部署路径(步骤 3,分支 B)使用 goldsky compose deployContract,它自带编译器——不需要 Foundry。
步骤 1 — 配置
根据 /compose 中的基本规则,只询问无法自行确定的信息——并先询问应用名称:
- “应用应该叫什么名字?(建议使用
bitcoin-oracle)”——必须先询问,在任何钱包(步骤 2)或合约(步骤 3)步骤之前。名称之后很难更改:它决定命名钱包的作用域,也会参与每次deployContract的 CREATE2 盐值计算,因此现在就必须确定。用户没有特别意见时,使用默认名称bitcoin-oracle,并将其设为compose.yaml顶层的name:。 - “使用哪条链?”——Base Sepolia(推荐),因为它提供了现成且完全不受权限限制的共享预言机(无需部署)。其他选项(Base、Arbitrum、Polygon Amoy 等)则需要部署自己的预言机。在 TS 中使用驼峰形式(
baseSepolia)。 - “使用哪个 PriceOracle 合约?”(选择链后立即询问)——有两个选项:
- 复用 Base Sepolia 上的共享预言机(推荐)——无需部署。连接到
0x53deB3fF6E6e82A3b5E96f14E185e3Fe66BF5113(在说明文字中提及该地址,不要放在任何选项标签中)。仅用于演示和入门,不可用于生产环境。 - 部署自己的预言机——见步骤 3,分支 B。(选择 Base Sepolia 以外的任何链时都必须使用此选项。)
- 复用 Base Sepolia 上的共享预言机(推荐)——无需部署。连接到
- “Cron 应多久运行一次?”——每分钟一次(推荐,
* * * * *)、每 5 分钟一次(*/5 * * * *)或每小时一次。在compose.yaml中设置cron触发器下的expression:。
步骤 2 — 钱包
- 共享预言机路径(推荐): 无需操作。Compose 智能钱包会在运行时自动创建,并在 Base Sepolia 上获得全额 Gas 赞助。不要告知用户创建或充值钱包。
- 自行部署路径(分支 B): 在部署任何内容之前先创建钱包,作为步骤 3 中分支 B 顺序的第 (1) 步;将其地址记录为
$COMPOSE_WALLET。命名钱包bitcoin-oracle-wallet与src/tasks/bitcoin-oracle.ts中的evm.wallet({ name: "bitcoin-oracle-wallet" })相匹配。
步骤 3 — 合约
分支 A — 复用共享预言机(推荐)。 在 Base Sepolia 上,$CONTRACT_ADDRESS = 0x53deB3fF6E6e82A3b5E96f14E185e3Fe66BF5113。无需部署,也无需授权写入者——但“无需部署”不等于“无需配置”。脚手架自带的默认值已过时(Polygon Amoy + 不同的预言机地址),因此即使选择分支 A,也必须完成步骤 4 的配置。跳到步骤 4。
分支 B — 自行部署。 分支 B 的顺序固定:创建钱包 → 部署合约 → 生成代码 → 配置(步骤 4)→ 部署应用(步骤 7)。 请按顺序完成以下步骤 (1)–(5)。deployContract 会将完整的已编译 ABI 写入 src/contracts/PriceOracle.json,无需手动确认。以下 JSON 是任务实际使用的子集——write、两个 view getter 和 PriceUpdated 事件(编译后的输出还会公开构造函数、writer、setWriter 和 OnlyWriter)。在应用内或脚手架内联路径中,此子集足以生成代码;切勿自行编造 ABI 字段:
[{"inputs":[{"internalType":"bytes32","name":"timestamp","type":"bytes32"},{"internalType":"bytes32","name":"price","type":"bytes32"}],"name":"write","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"latestTimestamp","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"latestPrice","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"timestamp","type":"bytes32"},{"indexed":false,"internalType":"bytes32","name":"price","type":"bytes32"}],"name":"PriceUpdated","type":"event"}]
(1) 创建钱包(可在部署任何内容之前完成),并将其地址记录为 $COMPOSE_WALLET。它与 src/tasks/bitcoin-oracle.ts 中的 evm.wallet({ name: "bitcoin-oracle-wallet" }) 相匹配:
COMPOSE_WALLET=$(goldsky compose wallet create bitcoin-oracle-wallet --json | jq -r .address)
⚠ 切勿使用
wallet create --env local:这是一个 LOCAL tevm 钱包,云运行时不会使用它进行签名,最终会导致预言机无法使用,而下方的故障排查部分也无法诊断此问题。请使用默认的(云端)钱包。
(2) 写入源代码,然后部署合约。 运行 mkdir -p contracts,并将以下参考 Solidity 代码写入 contracts/PriceOracle.sol:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract PriceOracle {
address public writer;
bytes32 public latestTimestamp;
bytes32 public latestPrice;
event PriceUpdated(bytes32 indexed timestamp, bytes32 price);
error OnlyWriter();
constructor(address _writer) { writer = _writer; }
function setWriter(address newWriter) external {
if (msg.sender != writer) revert OnlyWriter();
writer = newWriter;
}
function write(bytes32 timestamp, bytes32 price) external {
if (msg.sender != writer) revert OnlyWriter();
latestTimestamp = timestamp;
latestPrice = price;
emit PriceUpdated(timestamp, price);
}
}
然后通过 Compose 钱包部署。deployContract 会在 CLI 中编译,并通过由获得 Gas 赞助的 Compose 钱包签名的 CREATE2 代理进行部署,因此在 Base / Base Sepolia 上无需有余额的 EOA,也无需 RPC URL。每次调用 deployContract / writeContract 都需要项目 API 密钥。身份验证:在 shell 中导出一次 GOLDSKY_API_TOKEN=<project API key>(推荐:避免密钥出现在 argv 和 shell 历史记录中),或在每条命令中传入 -t "$GOLDSKY_PROJECT_KEY",或使用已登录的 goldsky login 会话。优先级:--token > GOLDSKY_API_TOKEN > ~/.goldsky/auth_token。请在控制面板的 Settings > API Keys 中创建密钥。构造函数参数会将步骤 1 中的钱包授权为写入者:
ADDR=$(goldsky compose deployContract contracts/PriceOracle.sol \
--chain-id <CHAIN_ID> \
--constructor-args $COMPOSE_WALLET \
--wallet bitcoin-oracle-wallet \
--json | jq -r .address)
链 ID:baseSepolia → 84532、base → 8453、polygonAmoy → 80002、polygon → 137、arbitrum → 42161、optimism → 10。ABI 会自动保存到 src/contracts/PriceOracle.json。--json 会隐藏 ascii art;如果失败,命令会以状态码 1 退出,并在 stderr 输出 {"error":true,"code":"...","message":"..."},因此如果捕获结果为空,请检查 stderr。设置 CONTRACT_ADDRESS=$ADDR。
非 Base 链(forge 回退方案)。 deployContract 会通过云端的 Alchemy bundler 路由,覆盖 Ethereum、Sepolia、Polygon、Polygon Amoy、Arbitrum、Arbitrum Sepolia、Optimism、Optimism Sepolia、Base 和 Base Sepolia(链 ID 分别为 1、11155111、137、80002、42161、421614、10、11155420、8453、84532)。如果链不在此范围内,部署会因 No Alchemy bundler URL for chain <id> 而失败,因此请在这种情况下使用 forge create 回退方案。多提供商覆盖情况由 FOU-991 跟踪。在 forge 路径中,写入者仍是 $COMPOSE_WALLET,因此任务代码无需更改;之后提取纯 ABI(见命令下方的说明)。不要向用户索要私钥;主动提出生成一个临时注资 EOA:直接将其生成到模型永远不会读取的 chmod-600 文件中,只显示地址:
cast wallet new --json > /tmp/k.json
umask 077 && printf 'FUNDED_EOA_KEY=%s\n' "$(jq -r '.[0].private_key' /tmp/k.json)" > .eoa.env
cast wallet address "$(jq -r '.[0].private_key' /tmp/k.json)" # the ONLY thing ever printed
shred -u /tmp/k.json
引导用户前往所选链对应的水龙头,然后主动提出在部署前使用 cast balance <address> --rpc-url <RPC> 检查余额。如果用户拒绝,请明确告知他们,另一种选择是使用自己已注资的钱包进行部署:
source .eoa.env # provides FUNDED_EOA_KEY; keep this in the SAME shell invocation as the command
forge create contracts/PriceOracle.sol:PriceOracle \
--rpc-url <RPC_URL> \
--private-key "$FUNDED_EOA_KEY" \
--broadcast \
--constructor-args $COMPOSE_WALLET
必须使用 --broadcast(forge ≥1.0 默认只进行试运行,什么也不会部署,但看起来像是成功了),而且 --constructor-args 必须是最后一个标志——它是贪婪的可变参数,因此如果将它放在 --rpc-url / --private-key 之前,forge 会把这些参数一并吞掉并连接到 localhost。已部署地址位于 Deployed to: 行——将其保存为 $CONTRACT_ADDRESS。然后提取纯 ABI(输入完整的 {abi, bytecode, ...} artifact 会导致代码生成静默失败):
jq .abi out/PriceOracle.sol/PriceOracle.json > src/contracts/PriceOracle.json
(3)运行代码生成。 deployContract 已保存上面的 ABI(或者在 forge 路径中,jq .abi 提取命令已保存 ABI);codegen 会生成任务所导入的类型化 evm.contracts.PriceOracle 类(CLI 自身的成功输出也会提示这一点):
goldsky compose codegen
(4)接入合约地址和链到任务中(步骤 4)。(5)部署应用,使接入后的任务上线(步骤 7)。只需部署一次,无需重新部署:钱包和合约已提前创建并部署,因此这一步会使接入后的任务上线。
无论使用哪种方式(deployContract 或 forge create 回退方案),$COMPOSE_WALLET——构造函数参数——都是授权写入者的凭证,因此无需再做其他操作。(如果已有一个符合 PriceOracle 结构的合约?请从所有者 EOA 调用 setWriter($COMPOSE_WALLET),授予 Compose 钱包写入权限,并改用该合约地址。)
步骤 4 — 将合约地址和链接入任务
编辑 src/tasks/bitcoin-oracle.ts — 使用 grep 锚点:
- 在文件顶部附近找到
const ORACLE_CONTRACT = "0x...",并将地址替换为$CONTRACT_ADDRESS。 - 找到
new evm.contracts.PriceOracle(...)调用中的evm.chains.*引用,并将其设为evm.chains.<所选链的驼峰命名>(例如baseSepolia)。 - 如果接入的是其他链,也要更新过时的脚手架注释块,其中仍写着“Polygon Amoy”,使文件注释保持一致。
如果用户更改了 cron 频率,请编辑 compose.yaml 中 cron 触发器下的 expression:。
- 将
compose.yaml顶层的name:设为步骤 1 中选定的应用名称(degit 脚手架可能会提供不同的默认值——请覆盖它,使compose deploy和 CREATE2 salt 保持一致)。
步骤 5 — Gas(自行部署,仅限非赞助链)
Compose 管理的钱包在赞助链(Base、Base Sepolia、Polygon、Polygon Amoy 等)上默认启用 sponsorGas: true。在这些链上,钱包无需充值;跳过此步骤。在非赞助链上,请向 $COMPOSE_WALLET 发送原生 Gas 代币(可使用测试网水龙头;在主网上则需按 cron 频率准备预算:每分钟写入约为 1,440 笔交易/天)。请注意,这种运行时任务 Gas 赞助覆盖的链远多于云端 deployContract / writeContract 的 部署 路径;后者通过云端的 Alchemy bundler 执行,覆盖 Ethereum、Sepolia、Polygon、Polygon Amoy、Arbitrum、Arbitrum Sepolia、Optimism、Optimism Sepolia、Base 和 Base Sepolia(链 ID:1、11155111、137、80002、42161、421614、10、11155420、8453、84532)。在此列表之外的链上部署会因 No Alchemy bundler URL for chain <id> 而失败,因此请在此类链上使用 forge create 回退方案(步骤 3)。多提供商覆盖情况由 FOU-991 跟踪。随后让运行时赞助(或已充值的钱包)承担 cron 写入的 Gas 费用。
步骤 6 — 可选:发布到新的 GitHub 仓库
git init
git add .
if git ls-files --cached | grep -qiE '(keypair\.json|\.env|private[._-]?key|\.pem|id_rsa)'; then
echo "⚠ Secret-shaped file is staged — remove it before committing. Aborting publish."
else
git commit -m "Initial commit: Compose bitcoin-oracle"
gh repo create <user's repo name> --<public|private> --source=. --push
fi
步骤 7 — 部署到 Goldsky
goldsky compose deploy
对于分支 A(共享预言机),这是首次也是唯一一次部署。对于分支 B,这同样是唯一一次部署:钱包和合约已先行创建和部署(步骤 3),因此这次部署就能让已接线的任务上线。
步骤 8 — 冒烟测试
流式查看日志,并等待下一次 cron 触发(最多 1 分钟):
goldsky compose logs -f
-f / --follow 会实时流式输出,让你捕获下一次 cron 触发;不带参数的 goldsky compose logs 则只输出最近 100 行(--tail,默认值为 100)后返回。
另请检查运行记录(Agent 以非交互方式运行,因此查看 runs 比观看实时流更可靠):
goldsky compose runs --task bitcoin_oracle --since 5m --json
理想的输出是返回负载中包含 success: true 和带 0x 前缀的 oracleHash 交易哈希,并按设定频率重复执行且不重试。
在链上验证(Base Sepolia 浏览器:https://sepolia.basescan.org/address/$CONTRACT_ADDRESS#events):每次 cron 触发都应看到一个 PriceUpdated 事件,并且 latestPrice() / latestTimestamp() 应返回近期的 bytes32 值。
故障排查
- 重新部署后,编辑
compose.yaml或源文件没有生效。 本地.compose/bundle 缓存已过时。运行rm -rf .compose/,然后重新部署。 - 每次 cron 运行都会回滚(仅适用于自行部署)。 Compose 钱包不是已授权的写入者。重新运行
setWriter($COMPOSE_WALLET)(自有合约的分支 A),或重新检查deployContract构造函数参数。(共享预言机没有写入者限制,因此原因不会是这个。) insufficient funds for gas。 仅在非赞助链上自行部署时才可能发生。为$COMPOSE_WALLET充值。- CoinGecko 返回 429 / 被限流。 默认重试配置(尝试 3 次并退避)可处理临时限流。若持续发生,请降低 cron 频率或改用付费 API。
- 任务已运行,但链上没有事件。 确认
evm.chains.*引用与合约所在的链相符。在错误链上的钱包签署的交易不会出现在目标链上。 - 出现
error: Unknown command "deployContract". Did you mean command "deploy"?(或writeContract变体)。 原因:CLI 版本过旧(低于 0.8.1)。解决方法:提供goldsky compose update,然后重新检查goldsky compose --version。 - 重新运行
deployContract时出现This contract has already been deployed with this app。 CREATE2 会根据合约、构造函数参数和应用名称进行去重,因此相同的重复运行会在发送交易前被拒绝,且--json输出中没有deployBlock。重新运行无法找回区块。可采取的措施:更改构造函数参数、更改源代码,或使用不同的应用名称。新名称不能仅在大小写或-/_上有所区别:名称会被规范化(转为小写,将[-_]+替换为-),因此my-app、My_App和my__app会冲突,部署会返回 409。PriceOracle 接受构造函数参数,因此建议优先采用构造函数参数方案。⚠ 重命名应用会更改所有后续部署地址,并与运行compose deploy时使用的应用名称冲突,因此除非确有需要,否则请避免这样做。
不应采取的做法
- 不要更改
src/lib/utils.ts中的toBytes32helper。合约会将price读取为bytes32,示例按 100(分)进行缩放;更改任一侧都会悄无声息地破坏存储值。 - 不要将共享的 Base Sepolia 预言机用作生产目标——任何人都可以向其中写入。
- 不要自行编造
PriceOracleABI。请使用步骤 3 中的 ABI(其中的 JSON 是任务涉及的子集;deployContract会写入完整版本)。
相关内容
/compose— 从头构建新的/自定义 Compose 应用,或说明 Compose 是什么。/compose-reference— 清单、CLI、TaskContext API、钱包、Gas 赞助、代码生成。/compose-doctor— 诊断并修复损坏的 Compose 应用。/auth-setup—goldsky login操作指南。