
Telegram Mini Apps电报小程序开发文档
2022年4月Telegram的MiniApp(之前为Web App,6.0版后改名为Mini App)上线,Mini Apps(简称 TMAs,中文名:小程序)很可能会变成一个类似于微信小程序的平台,使得Telegram 更接近一个“超级应用”。目前,电报小程序推出不久,版本还在快速迭代中,开发人员也较少,但电报庞大的用户群基础很可能会产生大量的小程序。 作为Web3的开发者,大多数应用都是前端和区块链直接交互,但电报bot只支持消息通过电报服务和bot所在的服务器进行交互,导致大量DAPP无法给到用户可靠的账户安全保障。电报小程序在电报应用中“嵌入”了Web前端应用,通过它与区块链和智能合约直接交互,将账户信息通过安全策略在本地进行保存,大幅度提高账户安全性。同时,将与区块链无关的业务逻辑通过bot与服务器进行交互,提高用户体验。 所以,Telegram+小程序+bot+智能合约的开发模式,可能会称为一种全新的Web3开发技术栈。事实上,从时间上看,电报小程序与TON链同时推出,也可能有这方面的用意。但是这种开发模式不仅仅适用于电报和TON链,更适用于用户量庞大的各种EVM链...
FERC20:一个更公平的ERC20方案
简介我们非常高兴地宣布,erc20.cash 上线了。这是一个更公平的的ERC20代币方案,我们将它命名为:Fair ERC-20,简称FERC20。 今年3月8日,BRC20代币在比特币链上通过Ordinals部署成功,在短短一两个月内吸引了大量关注和资金的参与。BRC20代币的成功得益于以下几个原因:简洁的Ordinals协议使得BRC20发行方无法在代币上做过多的编程,避免了在以太坊合约中各种安全风险和一些自私的设计。人人平等的铸币权。BRC20的发行方或项目团队,无法像在以太坊智能合约中通常做的那样,给自己或相关利益方预留一部分免费(低价)代币。在铸造BRC20时,所有人都站在同一起跑线上,即使发行方和团队也是如此。比特币的UTXO机制和低性能,让很多具有速度优势的智能合约机器人无法在比特币网络上工作,从而防止了通过技术手段获得比正常参与者更大的优势以及由此造成的不公平。上述原因使得BRC20对社区参与者来说,更公平,从而吸引了更多人参与。 但是,即使如此,有个非常有意思的现象是:大多数以太坊社区的成员尚未参与BRC20。 所以,我们想,是否能将BRC20的公平发售(Fa...
关于使用合约批量铸造的原因分析与解决方案
Cirth.meme上线4天,各方面表现良好。但铸造代码中,发现存在使用合约来批量铸造的可能,这严重影响了公平铸造。 类似的交易记录见:https://etherscan.io/tx/0xa58e8039740892f59b25ec0e14ce63d3d5c62eba0b16cdcd61c65553d5c67874。原因社区提交上述交易后,经技术团队研究,复现了其操作,大概的方式为:写两个智能合约,一个为主控制合约,一个为铸造合约,在主控制合约中创建多个铸造合约,通过铸造合约伪造正常用户,去调用Cirth合约的mint方法。 下面是实现类似功能的智能合约源码:// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; import "@openzeppelin/contracts/proxy/Clones.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; interface IFERC721C { function mint(address to) exter...

Telegram Mini Apps电报小程序开发文档
2022年4月Telegram的MiniApp(之前为Web App,6.0版后改名为Mini App)上线,Mini Apps(简称 TMAs,中文名:小程序)很可能会变成一个类似于微信小程序的平台,使得Telegram 更接近一个“超级应用”。目前,电报小程序推出不久,版本还在快速迭代中,开发人员也较少,但电报庞大的用户群基础很可能会产生大量的小程序。 作为Web3的开发者,大多数应用都是前端和区块链直接交互,但电报bot只支持消息通过电报服务和bot所在的服务器进行交互,导致大量DAPP无法给到用户可靠的账户安全保障。电报小程序在电报应用中“嵌入”了Web前端应用,通过它与区块链和智能合约直接交互,将账户信息通过安全策略在本地进行保存,大幅度提高账户安全性。同时,将与区块链无关的业务逻辑通过bot与服务器进行交互,提高用户体验。 所以,Telegram+小程序+bot+智能合约的开发模式,可能会称为一种全新的Web3开发技术栈。事实上,从时间上看,电报小程序与TON链同时推出,也可能有这方面的用意。但是这种开发模式不仅仅适用于电报和TON链,更适用于用户量庞大的各种EVM链...
FERC20:一个更公平的ERC20方案
简介我们非常高兴地宣布,erc20.cash 上线了。这是一个更公平的的ERC20代币方案,我们将它命名为:Fair ERC-20,简称FERC20。 今年3月8日,BRC20代币在比特币链上通过Ordinals部署成功,在短短一两个月内吸引了大量关注和资金的参与。BRC20代币的成功得益于以下几个原因:简洁的Ordinals协议使得BRC20发行方无法在代币上做过多的编程,避免了在以太坊合约中各种安全风险和一些自私的设计。人人平等的铸币权。BRC20的发行方或项目团队,无法像在以太坊智能合约中通常做的那样,给自己或相关利益方预留一部分免费(低价)代币。在铸造BRC20时,所有人都站在同一起跑线上,即使发行方和团队也是如此。比特币的UTXO机制和低性能,让很多具有速度优势的智能合约机器人无法在比特币网络上工作,从而防止了通过技术手段获得比正常参与者更大的优势以及由此造成的不公平。上述原因使得BRC20对社区参与者来说,更公平,从而吸引了更多人参与。 但是,即使如此,有个非常有意思的现象是:大多数以太坊社区的成员尚未参与BRC20。 所以,我们想,是否能将BRC20的公平发售(Fa...
关于使用合约批量铸造的原因分析与解决方案
Cirth.meme上线4天,各方面表现良好。但铸造代码中,发现存在使用合约来批量铸造的可能,这严重影响了公平铸造。 类似的交易记录见:https://etherscan.io/tx/0xa58e8039740892f59b25ec0e14ce63d3d5c62eba0b16cdcd61c65553d5c67874。原因社区提交上述交易后,经技术团队研究,复现了其操作,大概的方式为:写两个智能合约,一个为主控制合约,一个为铸造合约,在主控制合约中创建多个铸造合约,通过铸造合约伪造正常用户,去调用Cirth合约的mint方法。 下面是实现类似功能的智能合约源码:// SPDX-License-Identifier: MIT pragma solidity ^0.8.18; import "@openzeppelin/contracts/proxy/Clones.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; interface IFERC721C { function mint(address to) exter...

Subscribe to jackygu's blog

Subscribe to jackygu's blog
Share Dialog
Share Dialog


<100 subscribers
<100 subscribers
最近电报自动交易机器人和各种SocialFi很火,这些产品给用户带来了类似Web2的良好用户体验。但火爆的背后,也发生了多起安全事件。为此,很多新上线的平台开始使用更先进的账户安全技术来保护用户资产,比如@tomo_social使用了ERC-4337账户抽象技术,有些电报机器人采用了MPC钱包技术。
尽管账户抽象钱包(AA钱包)已经具备了零gas费(服务商代付gas费),多签,社交登录等强大功能,并大幅度提升用户体验,但是因为ERC-4337属于在现有以太坊共识基础上的补丁方案,与链交互签名时仍旧需要私钥,各种方案只是在私钥保存和签名环节采取各种安全措施。
所以,虽然很多代用户签名交互的电报机器人,SocialFi平台通过MPC钱包或AA钱包来保障客户的私钥安全,但实际上,因为最终还是要通过钱包主私钥来进行签名,本质上还是私钥的验证模式,所以仍旧有私钥泄露的风险。
今天看到AA钱包创新项目ZeroDev的Session Key(对话密钥)解决方案,可以让AA钱包授权生成一个或若干个Session Key(也是一种私钥),来受控的执行经授权的操作。这种授权模式有别于ERC-20或ERC-721的合约资产的使用额度授权,更有别于私钥的验证模式,一旦授权Session Key后,可以通过观察者(如服务器)自动执行授权范围内的合约或链上交互操作,从而可以带来更安全更便捷的用户体验。
下面内容参考:https://docs.zerodev.app/use-wallets/use-session-keys
会话密钥(Session keys)是ZeroDev的AA钱包中最强大的功能之一,它具有许多很多实际的应用场景,能解决很多痛点。
传统的EOA钱包只有一个用来为钱包签署交易的密钥(私钥)。如果拥有该密钥,就拥有了该钱包。这就是为什么绝对不能丢失或泄露助记词(seed phrase)或私钥的原因。
然而,使用AA钱包时,钱包与密钥是分离的,即:钱包所有者可以为任意密钥指定发起交易的权限,并随时撤销这些密钥的访问权限,甚至还可以限定密钥的范围,使其只能在特定条件和特定时间窗口内发送有限的交易。
我们称这些受到主密钥控制的密钥为会话密钥。
有一篇ZeroDev创始人写的文章可以参考:《会话密钥是 Web3 的 JWT》(Session Keys are the JWTs of Web3)
Session Key有几个核心用途:
如果您正在构建一个高频交互的Dapp,可能希望用户不必频繁的手动确认每个交易。这时,可以为用户的当前“会话”创建一个Session Key,并限定该Key的使用时间和范围,使其只能发送您所允许的交易,并且该Key在当前会话结束后失效。设定后,可以使用该Session Key与Dapp进行交互,而无需使用他们的主密钥对每一个交易都进行手动确认。
通常情况下,交易需要由钱包所有者主动发起。然而,有时候“自动化”交易能实现更佳的用户体验(如电报机器人等)。例如,如果您正在构建一个电报交易bot,您可能希望为用户提供一个功能,即当价格接近设定的目标价格时,自动执行买入或卖出的交易。在这种情况下,可以创建一个Session Key,仅当价格确实接近目标价时才允许执行。把这个bot部署到一个“观察者”(比如服务器)共享会话密钥,当条件发生时,观察者(服务器)会为用户自动执行交易。
这类应用场景非常大,不仅仅包括自动交易机器人实现的挂单交易,跟随交易,自动抢单,还包括大量的Defi场景。
笔者实际用下来,感觉Session Key有点像ChainLink的Keep,但是它比Keep更灵活,且更便宜。
const { LocalAccountSigner } = require("@alchemy/aa-core")
const { generatePrivateKey } = require('viem/accounts')
const sessionKey = LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey())
以上使用了viem和@alchemy/aa-core两个库。
先看代码,我直接在代码里做注解了:
const { SessionKeyProvider, Operation, ParamCondition } = require('@zerodev/sdk')
const { getFunctionSelector, pad, zeroAddress } = require('viem')
const sessionKeyProvider = await SessionKeyProvider.init({
projectId, // 在ZeroDev平台上注册并获得的ProjectId
defaultProvider: ecdsaProvider, // 默认provider与signer方式,这里使用ECDSA作为签名方式
sessionKey, // 上面生成的Session Key
sessionKeyData: { // Session Key配置参数
validAfter: 0, // 启动任务时间戳
validUntil: 0, // 任务截止时间戳
permissions: [ // 许可条件,是个数组,可以设置多个许可条件
{
target: contractAddress, // 交互的合约地址
valueLimit: 0, // 最大发送的value值
sig: getFunctionSelector( // 调用合约方法和参数
"mint(address)"
),
operation: Operation.Call, // 调用方式,建议使用Call,最好不要用DelegateCall
rules: [ // 调用参数的条件,也是一个数组,如果有多个参数,可以配置多个参数条件
{
condition: ParamCondition.EQUAL, // 条件为“等于”, 其他条件:EQUAL, GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL, NOT_EQUAL
offset: 0, // 因为是第一个参数,偏移量设为0
param: pad(address, { size: 32 }),// 规定地址的长度为32字节
},
],
},
],
paymaster: zeroAddress, //见下面
}
})
关于paymaster属性:该属性规定了由谁来支付gas费。
当字段为空(或zeroAddress常量时,会话密钥可以在有或没有支付主体的情况下使用(即可以进行任何操作)。注意,这可能是不安全的,因为拥有会话密钥的人可以通过燃气spam 交易并浪费您所有的以太币,因此只有在某种程度上信任会话密钥的用户时才会这样做。
当字段为address(1)(或者constants.oneAddress常量)时,会话密钥必须与支付主体一起使用,但可以是任何支付主体。
当字段为支付主体地址时,则由主账户支付Gas费。
类似与标准的ecdsaProvider一样,见下面代码:
const { hash } = await sessionKeyProvider.sendUserOperation({
target: contractAddress,
data: encodeFunctionData({
abi: contractABI,
functionName: "mint",
args: [address],
}),
})
await sessionKeyProvider.waitForUserOperationTransaction(hash)
由于Session Key是由钱包所有者创建并与持有会话密钥的用户共享的,因此自然会想知道如果所有者和会话密钥用户运行在不同节点的情况下,它们如何通过网络传输。
这里,我们将会话密钥用户称为“代理”,即代表钱包所有者通过会话密钥委托操作的代理。
一般来说,有两种方法可以实现:
所有者创建会话密钥并将其发送给代理。
代理创建一个公私钥对,将公钥发送给所有者以将其“注册”为会话密钥,最后通过私钥使用会话密钥。
第一种方法需要所有者和代理之间较少的通信,但第二种方法更安全,因为会话密钥的私钥部分不离开代理(甚至所有者也无法看到它),因此会话密钥泄漏的可能性较小。
来看一下如何实施这两种方法:
// sessionPrivateKey is the private key of the session key
const serializedSessionKeyParams = await sessionKeyProvider.serializeSessionKeyParams(sessionPrivateKey)
const sessionKeyParams = SessionKeyProvider.deserializeSessionKeyParams(serializedSessionKeyParams)
const sessionKeyProvider = await SessionKeyProvider.fromSessionKeyParams({
projectId,
sessionKeyParams
})
第一步:代理创建公私钥对
const { LocalAccountSigner } = require("@alchemy/aa-core")
const sessionPrivateKey = generatePrivateKey()
const sessionKey = LocalAccountSigner.privateKeyToAccountSigner(sessionPrivateKey)
const sessionPublicKey = await sessionKey.getAddress()
第二步:代理将公钥发给钱包所有人,钱包所有人注册该“公钥”
const { EmptyAccountSigner } = require('@zerodev/sdk')
// Create an "empty signer" with the public key alone
const sessionKey = new EmptyAccountSigner(sessionPublicKey)
// create the provider
const sessionKeyProvider = await SessionKeyProvider.init({
sessionKey,
// the other params, such as the permissions...
})
const serializedSessionKeyParams = sessionKeyProvider.serializeSessionKeyParams()
第三步:所有人将序列化后的Session Key发还给代理,代理解包Session Key
const sessionKeyParams = {
...SessionKeyProvider.deserializeSessionKeyParams(serializedSessionKey),
sessionPrivateKey,
}
const sessionKeyProvider = await SessionKeyProvider.fromSessionKeyParams({
projectId,
sessionKeyParams,
})
const { hash } = await sessionKeyProvider.sendUserOperation({
// ...use the session key provider as you normally would
})
zeroDev官方推荐使用第二种方法。
最近加密市场进入深熊,很多人都在预测下一轮牛市中的机会赛道。笔者在之前文章中提出:基于Secure Intent-centric Account的Bot,SocialFi等大幅度提升用户体验的应用,很可能是非常重要的机会。甚至用这个思路把之前Web3的东西重做一遍,都是有价值的。至少最近发生的一切,已经让人感觉到一点东西了。
最近电报自动交易机器人和各种SocialFi很火,这些产品给用户带来了类似Web2的良好用户体验。但火爆的背后,也发生了多起安全事件。为此,很多新上线的平台开始使用更先进的账户安全技术来保护用户资产,比如@tomo_social使用了ERC-4337账户抽象技术,有些电报机器人采用了MPC钱包技术。
尽管账户抽象钱包(AA钱包)已经具备了零gas费(服务商代付gas费),多签,社交登录等强大功能,并大幅度提升用户体验,但是因为ERC-4337属于在现有以太坊共识基础上的补丁方案,与链交互签名时仍旧需要私钥,各种方案只是在私钥保存和签名环节采取各种安全措施。
所以,虽然很多代用户签名交互的电报机器人,SocialFi平台通过MPC钱包或AA钱包来保障客户的私钥安全,但实际上,因为最终还是要通过钱包主私钥来进行签名,本质上还是私钥的验证模式,所以仍旧有私钥泄露的风险。
今天看到AA钱包创新项目ZeroDev的Session Key(对话密钥)解决方案,可以让AA钱包授权生成一个或若干个Session Key(也是一种私钥),来受控的执行经授权的操作。这种授权模式有别于ERC-20或ERC-721的合约资产的使用额度授权,更有别于私钥的验证模式,一旦授权Session Key后,可以通过观察者(如服务器)自动执行授权范围内的合约或链上交互操作,从而可以带来更安全更便捷的用户体验。
下面内容参考:https://docs.zerodev.app/use-wallets/use-session-keys
会话密钥(Session keys)是ZeroDev的AA钱包中最强大的功能之一,它具有许多很多实际的应用场景,能解决很多痛点。
传统的EOA钱包只有一个用来为钱包签署交易的密钥(私钥)。如果拥有该密钥,就拥有了该钱包。这就是为什么绝对不能丢失或泄露助记词(seed phrase)或私钥的原因。
然而,使用AA钱包时,钱包与密钥是分离的,即:钱包所有者可以为任意密钥指定发起交易的权限,并随时撤销这些密钥的访问权限,甚至还可以限定密钥的范围,使其只能在特定条件和特定时间窗口内发送有限的交易。
我们称这些受到主密钥控制的密钥为会话密钥。
有一篇ZeroDev创始人写的文章可以参考:《会话密钥是 Web3 的 JWT》(Session Keys are the JWTs of Web3)
Session Key有几个核心用途:
如果您正在构建一个高频交互的Dapp,可能希望用户不必频繁的手动确认每个交易。这时,可以为用户的当前“会话”创建一个Session Key,并限定该Key的使用时间和范围,使其只能发送您所允许的交易,并且该Key在当前会话结束后失效。设定后,可以使用该Session Key与Dapp进行交互,而无需使用他们的主密钥对每一个交易都进行手动确认。
通常情况下,交易需要由钱包所有者主动发起。然而,有时候“自动化”交易能实现更佳的用户体验(如电报机器人等)。例如,如果您正在构建一个电报交易bot,您可能希望为用户提供一个功能,即当价格接近设定的目标价格时,自动执行买入或卖出的交易。在这种情况下,可以创建一个Session Key,仅当价格确实接近目标价时才允许执行。把这个bot部署到一个“观察者”(比如服务器)共享会话密钥,当条件发生时,观察者(服务器)会为用户自动执行交易。
这类应用场景非常大,不仅仅包括自动交易机器人实现的挂单交易,跟随交易,自动抢单,还包括大量的Defi场景。
笔者实际用下来,感觉Session Key有点像ChainLink的Keep,但是它比Keep更灵活,且更便宜。
const { LocalAccountSigner } = require("@alchemy/aa-core")
const { generatePrivateKey } = require('viem/accounts')
const sessionKey = LocalAccountSigner.privateKeyToAccountSigner(generatePrivateKey())
以上使用了viem和@alchemy/aa-core两个库。
先看代码,我直接在代码里做注解了:
const { SessionKeyProvider, Operation, ParamCondition } = require('@zerodev/sdk')
const { getFunctionSelector, pad, zeroAddress } = require('viem')
const sessionKeyProvider = await SessionKeyProvider.init({
projectId, // 在ZeroDev平台上注册并获得的ProjectId
defaultProvider: ecdsaProvider, // 默认provider与signer方式,这里使用ECDSA作为签名方式
sessionKey, // 上面生成的Session Key
sessionKeyData: { // Session Key配置参数
validAfter: 0, // 启动任务时间戳
validUntil: 0, // 任务截止时间戳
permissions: [ // 许可条件,是个数组,可以设置多个许可条件
{
target: contractAddress, // 交互的合约地址
valueLimit: 0, // 最大发送的value值
sig: getFunctionSelector( // 调用合约方法和参数
"mint(address)"
),
operation: Operation.Call, // 调用方式,建议使用Call,最好不要用DelegateCall
rules: [ // 调用参数的条件,也是一个数组,如果有多个参数,可以配置多个参数条件
{
condition: ParamCondition.EQUAL, // 条件为“等于”, 其他条件:EQUAL, GREATER_THAN, LESS_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN_OR_EQUAL, NOT_EQUAL
offset: 0, // 因为是第一个参数,偏移量设为0
param: pad(address, { size: 32 }),// 规定地址的长度为32字节
},
],
},
],
paymaster: zeroAddress, //见下面
}
})
关于paymaster属性:该属性规定了由谁来支付gas费。
当字段为空(或zeroAddress常量时,会话密钥可以在有或没有支付主体的情况下使用(即可以进行任何操作)。注意,这可能是不安全的,因为拥有会话密钥的人可以通过燃气spam 交易并浪费您所有的以太币,因此只有在某种程度上信任会话密钥的用户时才会这样做。
当字段为address(1)(或者constants.oneAddress常量)时,会话密钥必须与支付主体一起使用,但可以是任何支付主体。
当字段为支付主体地址时,则由主账户支付Gas费。
类似与标准的ecdsaProvider一样,见下面代码:
const { hash } = await sessionKeyProvider.sendUserOperation({
target: contractAddress,
data: encodeFunctionData({
abi: contractABI,
functionName: "mint",
args: [address],
}),
})
await sessionKeyProvider.waitForUserOperationTransaction(hash)
由于Session Key是由钱包所有者创建并与持有会话密钥的用户共享的,因此自然会想知道如果所有者和会话密钥用户运行在不同节点的情况下,它们如何通过网络传输。
这里,我们将会话密钥用户称为“代理”,即代表钱包所有者通过会话密钥委托操作的代理。
一般来说,有两种方法可以实现:
所有者创建会话密钥并将其发送给代理。
代理创建一个公私钥对,将公钥发送给所有者以将其“注册”为会话密钥,最后通过私钥使用会话密钥。
第一种方法需要所有者和代理之间较少的通信,但第二种方法更安全,因为会话密钥的私钥部分不离开代理(甚至所有者也无法看到它),因此会话密钥泄漏的可能性较小。
来看一下如何实施这两种方法:
// sessionPrivateKey is the private key of the session key
const serializedSessionKeyParams = await sessionKeyProvider.serializeSessionKeyParams(sessionPrivateKey)
const sessionKeyParams = SessionKeyProvider.deserializeSessionKeyParams(serializedSessionKeyParams)
const sessionKeyProvider = await SessionKeyProvider.fromSessionKeyParams({
projectId,
sessionKeyParams
})
第一步:代理创建公私钥对
const { LocalAccountSigner } = require("@alchemy/aa-core")
const sessionPrivateKey = generatePrivateKey()
const sessionKey = LocalAccountSigner.privateKeyToAccountSigner(sessionPrivateKey)
const sessionPublicKey = await sessionKey.getAddress()
第二步:代理将公钥发给钱包所有人,钱包所有人注册该“公钥”
const { EmptyAccountSigner } = require('@zerodev/sdk')
// Create an "empty signer" with the public key alone
const sessionKey = new EmptyAccountSigner(sessionPublicKey)
// create the provider
const sessionKeyProvider = await SessionKeyProvider.init({
sessionKey,
// the other params, such as the permissions...
})
const serializedSessionKeyParams = sessionKeyProvider.serializeSessionKeyParams()
第三步:所有人将序列化后的Session Key发还给代理,代理解包Session Key
const sessionKeyParams = {
...SessionKeyProvider.deserializeSessionKeyParams(serializedSessionKey),
sessionPrivateKey,
}
const sessionKeyProvider = await SessionKeyProvider.fromSessionKeyParams({
projectId,
sessionKeyParams,
})
const { hash } = await sessionKeyProvider.sendUserOperation({
// ...use the session key provider as you normally would
})
zeroDev官方推荐使用第二种方法。
最近加密市场进入深熊,很多人都在预测下一轮牛市中的机会赛道。笔者在之前文章中提出:基于Secure Intent-centric Account的Bot,SocialFi等大幅度提升用户体验的应用,很可能是非常重要的机会。甚至用这个思路把之前Web3的东西重做一遍,都是有价值的。至少最近发生的一切,已经让人感觉到一点东西了。
No activity yet