经过一段时间的准备,各种文档查看和代码验证,我觉得可以完成这个项目。所以在这里记录展示出来,目的是有很多开发的感悟, cardano太特殊了,lisp函数式样的技术路线。与以太坊的solidity语言开发智能合约是天差地别,很多人可能会犯和我一样的错误,走一些弯路,我觉得可能我的分享或许可以帮到一些人。
我计划几天发帖一部分开发或是感悟的内容,希望能在工作日每天更新,看情况吧。
废话不多说,我的开发技术工具清单说下
vs code工具,很顺手,各种支持
智能合约语言: opshin
智能合约交互脚本包:pycardano
web框架:react
钱包交互框架: Lucid
python: 3.11
xiaojun_weng1:
保存
标题: 如何查看链上的智能合约脚本 (更新003)
每次提交脚本后,我们一般在cardano scan上查看,提交合约后有返回一个tx,就是交易编号,获取编号在https://preprod.cardanoscan.io 查询。这里查看一个我的例子
https://preprod.cardanoscan.io/transaction/2af9b342e92a88be47da9e41eba470da23c7a860b9c420ec4e5a1c4b1cdf1e21?tab=utxo
这个就是datum,上次说的保存在链上的常量。
这里的script就是智能合约的脚本,链上执行的合约就是这个。
下次整理一个全流程最小的最简单的智能合约和可以发布的脚本的github项目。
标题:创建合约上链需要的第一个对象 Context (更新004)
context = .......
builder = TransactionBuilder(context)
上面的没有完成的代码里context 有几种途径。
第一种,使用blockfrost.com提供,第二种使用ogmios, 第三种使用kupo。
那context是什么? 就和以太坊里 Alchemy 作用类似。blockfrost.com可以网上注册就可以。ogmios和kupo在Demeter - The turnkey solution for Cardano dApp infrastructure
blockfrost获取context代码
blockfrost_key = os.getenv("BET_BLOCKFROST_PROJECT_ID", None)
network = Network.TESTNET
def get_blockfrost_chain_context():
return BlockFrostChainContext(
project_id=blockfrost_key,
network=network,
)
ogmios获得context
from pycardano import Network
from pycardano import (
BlockFrostChainContext,
KupoOgmiosV6ChainContext,
OgmiosV6ChainContext,
)
ogmios_host = os.getenv("OGMIOS_API_HOST", "localhost")
ogmios_port = os.getenv("OGMIOS_API_PORT", "443")
ogmios_protocol = os.getenv("OGMIOS_API_PROTOCOL", "wss")
ogmios_url = f"{ogmios_protocol}://{ogmios_host}:{ogmios_port}"
def get_OGMIOS_chain_context():
return OgmiosV6ChainContext(
host=ogmios_host,
port=int(ogmios_port),
secure=True,
network=network,
)
注意:这里ws协议端口是1337,wss端口是443.
ogmios的context对象函数更多一点。
最后再补充下,如果你想获取更多,更灵活的cardano链上数据,可以使用Cardano DB-Sync
。
三种input , 消费utxo的地方 (更新005)
突然发现上面的context没有说仔细,context就是一个真正通往cardano主链的通道接口。
好进入正题,先看下代码
builder = TransactionBuilder(context)
builder.add_input_address(source_address)
builder.add_script_input(utxo_to_spend, gift_script, datum, redeemer)
builder.reference_inputs.add(sc_utxo)
所有的合约都要消费utxo的,如果没有utxo就没有智能合约的“出生证”,而这里要说的三种input就是出生证。
第一个是 add_input_address(address) ,这里是普通的utxo,在这个address上由算法选择那个或是哪些utxo作为可以消费的utxo
第二个是add_script_input , 这个是如果需要调用智能合约的脚本,就需要用这个入口,这里utxo_to_spend一定是有datum的,否则会报错。utxo_to_spend是具体的utxo
第三个是reference_inputs,这个是唯一不能消费的utxo,但是它非常重要,一般的预言机oracle就是用这个,用这个的datum。当然他也是一个script address的utxo。
今天东西不多,但是都是非常有用的知识点,cardano的智能合约开发其实真正代码的知识点不多,主要是围绕合约的代码, 如果代码是2,那么周边就是8。
铺垫了一些基础知识,但是如果没有代码把玩估计会很无趣,后面我出一个简短的全流程合约github 项目,一个基于官方改的。不过好像我还要说一期redeemer
一个完整的全流程的cardano智能合约可运行的github repository(更新006)
不多废话,直接访问 GitHub - malakaw/publish_gifts
里面是两个简单的智能合约执行方式的不同实现例子。
花了我半天时间,是可以运行的,完整的,不过准备的东西比较多,还要装cardano-cli。
具体的内容不再这里说,具体看github.
下注网站的预言机 C3目前啥情况 (更新007)
说了这么些技术的知识点,好像对于下注网具体业务的还没有提及 ,今天就来一段这块的整理。说到下注那必须要有预言机来触发或是cardano 的智能合约必须要用的预言机信息。cardano上目前可行的oracle就只有 C3了。预言机主要分两类,一类是Push Oracle Node****,另一类是Pull Oracle Node (ODV) ,第一类其实就是主要指各种币token 的价格, 第二类类似我们需求的下注类信息,单个的信息验证类。目前的情况看c3的文档Charli3 Docs - Technical Guide of Oracle Integration 第一种已经上生成,第二种我们要的pull模式还是Alpha状态,相关的Charli3 ODV nodes搭建的文档都是没有的, 后来我不死心,通过电报上的 cardnao founder lab群协助联系到c3官方,好像也是没啥消息,就问我为啥要Charli3 ODV nodes,然后没有正式恢复。好吧,死心了。那就用我自己手动oracle了。
如果要做一个有竞争力的bet Dapp那么就必须要用Hydra (更新008)
了解cardano 的一段时间的应该是都知道Hydra-九头蛇,据说是可以大幅度提高性能。是的今天我来说说Hydra。
在说Hydra之前,先提一下polymarket, 我用过,战绩惨淡,目前下注的2025年cardano是否EFT通过估计要黄了。目前polygan的作为以太坊的lay2速度和交易费用肯定是各种便宜和高速的。那在cardano上如果做一个bet网站需要长时间等待交易完成肯定是没啥意思的,何必呢,慢又贵,相比polymarket。好吧,我是这么想,我也想做bet网站dapp,但是实话实说,我没想过做另外一个polymarket, 我只是想给我的cardano stake pool : cmore pool 的用户提供各种趣味性,或则吸引潜在的用户通过bet dapp来质押到我的cmore pool。但是不能因为是mini 版本的下注网站,就考虑产品设计的合理性,对不。所以,这就是我考虑hydra 的原因。但是估计第一个版本不会使用hydra.
好的,现在开始讲hydra,我刚开始的时候寻找各种平台有没有免费的测试环境的hydra, 后来发现是没有,好吧,自己安装,Getting started | Hydra Head protocol documentation 参考这个,用docker安装,比我想象中简单,很容易。不过,后来没做太多测试。
总结下我的学习知识点或是内容吧, hydra可以让我有免费的uxto来消费,其实hydra就是lay2, 如果你不提交的主网,那你就不需要付费,在提交主网前,你的各种操作和在操作主网的一摸一样。在类似polymarket的用户只有在sell和claim 的时候真正最后提交到主网,之前都可以在hydra上操作,各种费用都是0. 还有在Hydra Head 初始化(开头)的时候也要付费。这个是费用的理解。 那速度呢? 不用上主网,不需要各个cardano node 来确认,那么不就是毫秒级别的速度么,超级快。那这样安全么?想象一下吧, polygan不就是这么干的么,lay2; 当然一定是一定规模hydra才是安全的,这个肯定;但是cardano目前是可以做到这些。
为啥用Opshin在合约里不能验证签名cardano-cli生成的钱包签名(更新009)
记录下,最近测试给合约传redeemer里有签名信息,但是在用opshin的verify_ed25519_signature验证签名的时候死活不行。后来问下chatGPT,了解了这里记录下。
总的来说就是cardnao-cli生成的是“助记词”钱包,一种Ed25519-extended 钱包,不纯的ed25519。不过后来也查到可以cardnao-cli生成纯 ed25519钱包,就是命令再带上一个参数“ --normal-key”。不管怎么说,所以后来我用pycardano生成纯的ed25519钱包就可以verify_ed25519_signature验证签名信息了。
下面是pycardano钱包生成代码。
from pycardano import (
PaymentSigningKey,
PaymentVerificationKey,
Address,
Network
)
# 生成 Ed25519 私钥
sk = PaymentSigningKey.generate()
vk = PaymentVerificationKey.from_signing_key(sk)
# 将 keys 保存为文件
with open("oracle.sk", "wb") as f:
f.write(sk.to_cbor())
with open("oracle.vk", "wb") as f:
f.write(vk.to_cbor())
# 生成地址
address = Address(payment_part=vk.hash(), network=Network.TESTNET)
with open("oracle.addr", "w") as f:
f.write(str(address))
print("Private key (hex):", sk.to_cbor().hex())
print("Public key (hex):", vk.to_cbor().hex())
print("Address:", address)
放弃Lucid,拥抱meshjs(更新010)
还是那个原因,文档和使用例子较多。下面奉上reference 执行合约的例子,官方目前也没有提供的。我这个折腾老长时间。
提交ada和合约script到链上
const script: PlutusScript = {
code: cbor
.encode(Buffer.from(plutusScript.validators[0].compiledCode, "hex"))
.toString("hex"),
version: "V3",
};
const scriptAddress = resolvePlutusScriptAddress(script, 0);
async function sendLMSR(){
console.log("connected:",connected)
if(connected){
console.log("scriptAddress:",scriptAddress)
const address = (await wallet.getUsedAddresses())[0];
const addressPubKey = deserializeAddress(address);
const changeAddress = await wallet.getChangeAddress();
const utxos = await wallet.getUtxos();
const unsignedTx = await txBuilder
.spendingPlutusScriptV3()
.setNetwork("preprod")
.txOut(scriptAddress, [{ unit: "lovelace", quantity: "15300000" }])
.txOutInlineDatumValue(mConStr0([addressPubKey.pubKeyHash]))
.txOutReferenceScript(script.code,"V3")
.changeAddress(changeAddress)
.selectUtxosFrom(utxos)
.complete();
const signedTx = await wallet.signTx(unsignedTx);
const txHash = await wallet.submitTx(signedTx);
console.log("txHash", txHash);
}
}
reference方式经济执行合约
const unsignedTx = await txBuilder
.spendingPlutusScriptV3()
.txIn(assetUtxo!.input.txHash, assetUtxo!.input.outputIndex)
.txInInlineDatumPresent()
.txInRedeemerValue(mConStr0([]))
.txOut(test_receive_address, [{ unit: "lovelace", quantity: "1700000" }])
.spendingTxInReference(reference_script_tx,0,undefined,scriptHash_)
.changeAddress(changeAddress)
.txInCollateral(
collateral[0]?.input.txHash,
collateral[0]?.input.outputIndex,
collateral[0]?.output.amount,
collateral[0]?.output.address,
)
.requiredSignerHash(addressPubKey.pubKeyHash)
.complete();
const signedTx = await wallet.signTx(unsignedTx, true);
const txHash = await wallet.submitTx(signedTx);
客观面对函数式智能合约的一些设计思路。(更新011)
最近在使用 opshin ed25519签名的时候,发现还是有些难搞的,虽然也有clojure 函数式编程的经验,但是,有些基本的方法不能用还是不爽的,稍微有点,我觉得他们还是有优化的空间。
整理如下。做了一些对以太坊的Solidity的对比。
最不爽的是不能string.split
如何在合约中判断具体某个address 方式 (更新012)
如何在合约中判断具体某个address 方式;不是 address1 == address2这种,而是使用credential_hash 。 看下普通的address,如下图,具体不展开,就看 Payment credentials ,这个就是一个PubKeyCredential,我们只要比较PubKeyCredential的hash就可以了,塞在28 bytes里。展开来说**白名单和黑名单就可以用这个来判断。
下面是****pycardano** 获取的方式。
from pycardano import Address
addr = Address.from_primitive("addr_test1vqmwcgg90hkz2urw4nl9y9c8cq6m32htj3m2tvueetydtqsdxp35j")
payment_hash = addr.payment_part.payload # bytes (28 bytes)
print(payment_hash.hex())
print(bytes.fromhex(payment_hash.hex()))
合约内
m_hash_bytes = b'6\xec!\x05}\xec%pn\xac\xfeR\x17\x07\xc05\xb8\xaa\xeb\x94v\xa5\xb3\x99\xca\xc8\xd5\x82'
def sum_ada_excluding_target(outputs: List[TxOut]) -> int:
total = 0
for out in outputs:
cred = out.address.payment_credential
if isinstance(cred,PubKeyCredential):
if cred.credential_hash != m_hash_bytes:
不做带LP token的预测市场(更新013)
这期更新很简短,就几句话,但是这个是前前后后思考决定的结论,不做带lp token 的预测市场,也没有订单簿,至少开始是这样的。
测试到airdrop场景用户判断 (更新015)
测试到airdrop场景—其实我有这么一个场景,就是在测试期间大家用preprod net , 测试多的用户我想在以后main net上给一些奖励token, 那么是不是可以按照原先他们使用的preprod net的address找到 main net的地址,并发送给他们token。
不可以,也可以,怎么说呢, 从preprod net address反推到main net address是不行的,但是大家都共用一个公钥, 公钥签名的信息是一样的,如果用户在测试的使用使用了签名,那dapp上main net后,再签名和preprod的是一样的,那么就可以判断是同一个用户了, 两个net的address是配对的,同一个用户。
用bodega算是高消费么?(更新016)
参考文章( Bodega Tokenomics):https://medium.com/@bodegacardano/bodega-tokenomics-41ba9d917aad
我读到文章里的 Users will need 50,000 BODEGA tokens to make each bet. ,不禁感叹真TM贵啊,是每个 bet, 或则说这个不适合用户来创建bet?就适合一些做市商来创建bet? 用户就只要来predict就可以了? 如果按照AMM算法来买卖 ,那就没有太多成本的担忧。毕竟不是 LMSR,肯定会有亏损的风险。
一家cardano社区的恶心商业公司 (更新017)
InfrasVen Studio 1, LDA 成立于葡萄牙,税号:517901285, 提供培训课程(在我看来就是一些无聊的产品设计课程,没人在乎具体学习怎么样),但是要有一份签约, 作为获得培训项目访问权和咨询支持的回报,参与者授予公司: (i) 投资权: 公司拥有向参与者投资最高 1,000,000 欧元(壹佰万欧元) 的选择权(期权)。 (ii) 后续跟投权: 公司拥有后续按比例参与任何轮次融资的选择权,投资比例与其最初持有的股权比例一致。
培训可以,不错,投资也很好,关键是啥; 培训内容很烂,学不到东西,投资条款很坑,他可以投也可以不投,但是他有这个权利, 等比例跟投权 (Successive pro rata option…) 。
他们小算盘打的好精明。本事也大的,找到cardano社区的 kol 饺子站台,真是可以的。
合约脚本可以存到不是合约地址 (更新018)
今天长见识了,原来合约的脚本可以存到不是这个合约的地址,脑洞大开,以太坊上更加不可能这么想吧,这个是gemin告诉我的。触发这次技术探索的是,我这边的设计两个合约要发布,两个合约都要reference执行的,那么就有疑问了,两个reference的合约,那执行的时候运行那个合约的validator呢?毕竟有两个合约的validator,最终我得到答案,执行的refernece 用spendingTxInReference, 不执行的refernece用readOnlyTxInReference。再回到开始说的合约的脚本可以存到不是这个合约的地址,听上去很奇怪,但是真可以,验证过了,pycardano deploy合约,meshjs调用,都是可行的。当然可以。
勤俭持家的交易费必须知道的几个函数-pycardano (更新019)
我们在提交一个交易的时候,cardnao需要你计算出交易费,第一要知道的就是min_lovelace
参考定义和使用
定义
#Calculate minimum lovelace a transaction output needs to hold
def min_lovelace(
context: ChainContext,
output: Optional[TransactionOutput] = None,
amount: Optional[Union[int, Value]] = None,
has_datum: bool = False,
) -> int:
使用
min_ada = min_lovelace(context,output)
min_lovelace就是计算出需要最小的花费fee.
第二个TransactionBuilder的fee_buffer和fee
两个都是这是transaction的fee,无非就是是fee设置是精准的,如果世纪小于这个就报错FeeTooSmallUTxO,而fee_buffer会把实际设置的花费多出来的会返回给用户. 这是最推荐 的做法,
预测列表页面开发中(更新020)
主要是后台状态条件判断和测试数据准备,meshjs的钱包hook还是很好用,有“last_signer_stake”可以判断当前的钱包。
还有处理了批量mint token 的功能。
cardano智能合约的时间戳太出乎我的意料了 (更新021)
其实我在一个月前就想写这个了,由于忙其他的,把这个给忘了,cardano的智能合约时间戳有点奇葩,或则直接一点,就是不喜欢,不好用。他不像以太坊的时间戳,以太坊好歹是个时间点,cardano是一个时间范围,当然你可以定义一个datum数据是POSIXTime 类型,就和python的time.time() 得到的一样的 int 类型,但是你要在合约里使用,举例说你给的一个POSIXTime 和 当前时间点比较是不可能的,你只能和一个时间范围比较tx_info.valid_range。 ok,其实开始我还是接受的,时间范围就时间范围,毕竟是区块链,但是我在preprod环境,这个时间有延后行,整个时间范围延后 3小时以上,我不得不在测试的数据上加上4小时的延后来测试。我后面会在mainnet主网测试下这个,当然以最小的成本。怎么测试我还没想好,后面会有更新给出。
cardano 的 utxo有他的优势,但是我要说,有些地方还是有点简陋和不方便的,这里的时间戳算是一个。