一键导入
goldbot-sachs
Earn yield on idle USDC. Gasless USDC-to-ETH via CoW Swap, deploy a Gnosis Safe, and deposit into Beefy Morpho (~4% APY) on Base.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Earn yield on idle USDC. Gasless USDC-to-ETH via CoW Swap, deploy a Gnosis Safe, and deposit into Beefy Morpho (~4% APY) on Base.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | goldbot-sachs |
| description | Earn yield on idle USDC. Gasless USDC-to-ETH via CoW Swap, deploy a Gnosis Safe, and deposit into Beefy Morpho (~4% APY) on Base. |
| metadata | {"openclaw":{"emoji":"🏦","homepage":"https://github.com/publu/goldbotsachs","requires":{"bins":["node"],"env":["PRIVATE_KEY"]},"install":[{"id":"npm","kind":"node","packages":["viem","@safe-global/protocol-kit","@cowprotocol/cow-sdk","@cowprotocol/sdk-viem-adapter"],"label":"Install DeFi dependencies (npm)"}]}} |
Earn ~4% APY on idle USDC via Beefy Morpho vaults on Base. Start with just USDC — no ETH needed.
| Contract | Address |
|---|---|
| USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
| Beefy Morpho Steakhouse USDC | 0xF1C55b6E063ee90A33FFE62deBe618962bae021e |
| CoW Relayer | 0xC92E8bdf79f0507f65a392b0ab4667716BFE0110 |
| Native ETH (CoW sentinel) | 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE |
| Variable | Required | Description |
|---|---|---|
PRIVATE_KEY | Always | EOA private key (hex, 0x prefix) |
SAFE_ADDRESS | After step 2 | Gnosis Safe address. Printed by the deploy step |
SELL_AMOUNT | No | USDC to swap in raw units (default: 1000000 = 1 USDC) |
Follow this sequence when starting from an EOA with USDC and no ETH:
1. Refuel ──────── Gasless USDC → ETH via CoW Swap (permit pre-hook)
Now the EOA has ETH for gas.
2. Deploy Safe ─── Use ETH to deploy a 1-of-1 Gnosis Safe.
Save the SAFE_ADDRESS.
3. Fund Safe ───── Transfer USDC from EOA to Safe (standard ERC-20 transfer).
4. Deposit ─────── Approve + deposit into Beefy Morpho via Safe.
EOA pays gas (~$0.001 on Base).
5. Check ──────── Read balances (ETH, USDC, vault shares). No gas needed.
To check current state at any point, read balances (step 5). To refuel again later, repeat step 1.
This is the most complex operation. An EIP-2612 permit is signed off-chain (free) and attached as a CoW pre-hook so no prior approval transaction is needed. If the wallet already has sufficient allowance, the permit is skipped.
Non-obvious details an agent must know:
name: 'USD Coin', version: '2', chainId: 8453@cowprotocol/cow-sdk v7+ — v5 has broken ESM exports and missing _signTypedData bridgingsetGlobalAdapter is re-exported from @cowprotocol/cow-sdk in v7 (in v5 it was only in @cowprotocol/sdk-common)appData.metadata.hooks.pre in advanced settingsimport { createPublicClient, createWalletClient, http, encodeFunctionData } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
import { setGlobalAdapter, SupportedChainId, TradingSdk, OrderKind } from '@cowprotocol/cow-sdk'
import { ViemAdapter } from '@cowprotocol/sdk-viem-adapter'
const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
const COW_RELAYER = '0xC92E8bdf79f0507f65a392b0ab4667716BFE0110'
const NATIVE_ETH = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'
const account = privateKeyToAccount(process.env.PRIVATE_KEY)
const publicClient = createPublicClient({ chain: base, transport: http() })
const walletClient = createWalletClient({ account, chain: base, transport: http() })
const sellAmount = BigInt(process.env.SELL_AMOUNT || '1000000')
// 1. Check existing allowance — skip permit if sufficient
const allowance = await publicClient.readContract({
address: USDC,
abi: [{ type: 'function', name: 'allowance', inputs: [{ name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view' }],
functionName: 'allowance',
args: [account.address, COW_RELAYER]
})
let permitHook = undefined
if (allowance < sellAmount) {
// 2. Sign USDC permit off-chain (free, no gas)
const nonce = await publicClient.readContract({
address: USDC,
abi: [{ type: 'function', name: 'nonces', inputs: [{ name: 'owner', type: 'address' }], outputs: [{ type: 'uint256' }], stateMutability: 'view' }],
functionName: 'nonces',
args: [account.address]
})
const deadline = BigInt(Math.floor(Date.now() / 1000) + 3600)
const permitValue = 2n ** 256n - 1n // max approval
const sig = await walletClient.signTypedData({
domain: { name: 'USD Coin', version: '2', chainId: 8453, verifyingContract: USDC },
types: {
Permit: [
{ name: 'owner', type: 'address' }, { name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' }, { name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
},
primaryType: 'Permit',
message: { owner: account.address, spender: COW_RELAYER, value: permitValue, nonce, deadline }
})
const r = sig.slice(0, 66)
const s = '0x' + sig.slice(66, 130)
const v = parseInt(sig.slice(130, 132), 16)
const permitCalldata = encodeFunctionData({
abi: [{
type: 'function', name: 'permit',
inputs: [
{ name: 'owner', type: 'address' }, { name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' }, { name: 'deadline', type: 'uint256' },
{ name: 'v', type: 'uint8' }, { name: 'r', type: 'bytes32' }, { name: 's', type: 'bytes32' }
],
outputs: [], stateMutability: 'nonpayable'
}],
functionName: 'permit',
args: [account.address, COW_RELAYER, permitValue, deadline, v, r, s]
})
permitHook = { target: USDC, callData: permitCalldata, gasLimit: '80000' }
}
// 3. Init CoW SDK
const adapter = new ViemAdapter({ provider: publicClient, signer: account })
setGlobalAdapter(adapter)
const sdk = new TradingSdk({ chainId: SupportedChainId.BASE, signer: account, appCode: 'GoldbotSachs-Refuel' })
// 4. Get quote and post order (with permit pre-hook if needed)
const advancedSettings = permitHook
? { appData: { metadata: { hooks: { pre: [permitHook] } } } }
: undefined
const quote = await sdk.getQuote({
kind: OrderKind.SELL,
sellToken: USDC,
buyToken: NATIVE_ETH,
amount: sellAmount.toString(),
sellTokenDecimals: 6,
buyTokenDecimals: 18,
owner: account.address,
receiver: account.address,
slippageBps: 100,
}, advancedSettings)
const result = await quote.postSwapOrderFromQuote()
const orderId = result.orderId
// Track at: https://explorer.cow.fi/base/orders/{orderId}
Deploy a 1-of-1 Gnosis Safe controlled by the EOA. Uses the predictedSafe pattern for a deterministic address.
import Safe from '@safe-global/protocol-kit'
import { createWalletClient, http } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'viem/chains'
const account = privateKeyToAccount(process.env.PRIVATE_KEY)
const walletClient = createWalletClient({ account, chain: base, transport: http('https://base-rpc.publicnode.com') })
const protocolKit = await Safe.init({
provider: 'https://base-rpc.publicnode.com',
signer: process.env.PRIVATE_KEY,
predictedSafe: {
safeAccountConfig: { owners: [account.address], threshold: 1 },
safeDeploymentConfig: { saltNonce: '1' }
}
})
const safeAddress = await protocolKit.getAddress()
const isDeployed = await protocolKit.isSafeDeployed()
if (!isDeployed) {
const deployTx = await protocolKit.createSafeDeploymentTransaction()
await walletClient.sendTransaction({
to: deployTx.to,
value: BigInt(deployTx.value),
data: deployTx.data
})
}
// Save safeAddress as SAFE_ADDRESS for subsequent steps
Transfer USDC from EOA to Safe. Standard ERC-20 transfer — no code example needed:
walletClient.writeContract({ address: USDC, abi: erc20Abi, functionName: 'transfer', args: [SAFE_ADDRESS, amount] })
Batches approve + deposit into a single Safe transaction. The EOA pays gas (~$0.001 on Base). Protocol Kit handles signing, MultiSend batching, and submission automatically.
Non-obvious details:
deposit(uint256), NOT ERC-4626's deposit(uint256, address) — wrong signature silently fails and Safe reports GS013import Safe from '@safe-global/protocol-kit'
import { encodeFunctionData, parseAbi } from 'viem'
const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'
const BEEFY_VAULT = '0xF1C55b6E063ee90A33FFE62deBe618962bae021e'
const protocolKit = await Safe.init({
provider: 'https://base-rpc.publicnode.com',
signer: process.env.PRIVATE_KEY,
safeAddress: process.env.SAFE_ADDRESS
})
const amount = BigInt(process.env.DEPOSIT_AMOUNT || '1000000') // 1 USDC
const transactions = [
{
to: USDC,
data: encodeFunctionData({
abi: parseAbi(['function approve(address spender, uint256 amount)']),
functionName: 'approve',
args: [BEEFY_VAULT, amount]
}),
value: '0'
},
{
to: BEEFY_VAULT,
data: encodeFunctionData({
abi: parseAbi(['function deposit(uint256 _amount)']),
functionName: 'deposit',
args: [amount]
}),
value: '0'
}
]
const safeTx = await protocolKit.createTransaction({ transactions })
const result = await protocolKit.executeTransaction(safeTx)
// result.hash is the on-chain transaction hash
// View at: https://basescan.org/tx/{result.hash}
All read-only, no gas needed. Use readContract with balanceOf on:
0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913) — 6 decimals0xF1C55b6E063ee90A33FFE62deBe618962bae021e) — 18 decimals for sharesgetPricePerFullShare() on the vault to convert shares to USDC: usdcValue = shares * pricePerFullShare / 1e18 / 1e12 (divide by 1e12 to convert from 18-decimal share value to 6-decimal USDC)getBalanceNote: The Beefy vault is NOT ERC-4626 — it does not have convertToAssets, totalAssets, maxDeposit, etc. Use the Beefy-specific API: deposit(uint256), withdrawAll(), balanceOf(address), getPricePerFullShare().
deposit(uint256) not deposit(uint256, address). Wrong function signature silently reverts and Safe reports misleading GS013 error.checkNSignatures, (2) inner call failed with safeTxGas=0 and gasPrice=0 in execTransaction. If you see GS013, check the inner call first.name: 'USD Coin', version: '2', chainId: 8453. Getting these wrong silently produces an invalid signature.explorer.cow.fi.base-rpc.publicnode.com instead of mainnet.base.org to avoid 429 errors from the public Base RPC."Safe not deployed": Run the deploy step (step 2) first. The Safe address is deterministic — you'll get the same address for the same owner + salt.
"Insufficient USDC allowance": The deposit step batches approve + deposit. If it fails mid-batch, run approve separately then deposit.