背景
PW-Core (github)是个为前端设计的 sdk。有了 pw-core,您可以直接使用各种加密钱包作为 CKB 钱包。此外,我们提供了许多友好的模型和更原生的方式来建立您的交易。如果您是CKB dAp p开发的新手,请务必来尝试一下pw-core(可以先从我们的支付演示开始)。
最近,我们收到了在后端开发中使用 pw-core 来为 dApp 开发人员提供统一体验的要求,我们也认为这是一个好主意。然而,我们自己也缺乏工程师,没有时间去做适配。因此,我们提出了一个建议来让这些新增的组件可以添加到 pw-core 中以供后端使用。
RawProvider
在后端开发中,如果开发者需要用他的密钥签署一笔交易,他应该都要是自动完成的。所以,我们需要的提供者的最基本的一个功能是,它允许导入私钥并能够对交易进行签名。我们称之为RawProvider
。RawProvider的 实现可能是这样的:
- 在
Constructor
中传递一个私钥字符串;
- 在
sign
方法中实现 CKB 的默认的签名方法(也就是使用内置的Blake2bSigner
);
就这样,不是很难对吧?
IndexerCollector
另一个缺失的拼图是一个基于官方的 ckb-indexer的收集器
目前 pw-core 中的默认收集器是 PwCollector
,它基于 Portal Wallet 的后端服务器。对于开发人员来说,将 dApp 的数据服务依赖于其他项目的后端并不是一个好的选择,但是构建一个服务器来缓存和分析 CKB 的交易也是非常昂贵的。因此,最好的解决方案是构建一个适应 公共 ckb-indexer 服务 的收集器。
要构建 IdexerCollector
,您需要读取 ckb-indexer 的 rpc 的方法,并将它们适配到收集器的接口中。您可以参考内置的 PwCollector 和 SUDTCollector 的实现。
来自 Lay2 的小福利
为了增加些让你开始 CKB 探险的激励,我们提供5万CKB(现在约等于 500美元 ,也许不久的将来会等值特斯拉 Model Y )来奖励任何可以来实现这两个组件,并让你的 PR 被我们的开发团队接受的人。当然,如果你有任何问题,请随时在这里提出,我们也会邀请你加入我们的聊天群以获得更多的即时支持。
译者注:
【1】 Model Y
【2】还有大大继续加码,各位勇者们快来~
2 Likes
我运行Node项目十之八九都是编译不通过的(所以,不好测试):
所以在此针对 RawProvider简单参与一下。抛砖引玉~
1、更新pw-core/src/hashers/blake2b-hasher.ts的构造函数,以支持传入私钥,其它不变:
constructor(key: string = null) {
const keyData = !key || key.length === 0 ? null : Buffer.from(key);
const h = blake2b(
32,
keyData,
null,
new Uint8Array(Reader.fromRawString('ckb-default-hash').toArrayBuffer())
);
super(h);
}
gist: https://gist.github.com/taurenshaman/86f96ae81a3f26188bffa735218a70c0
2、增加文件:pw-core/src/signers/blake2b-signer.ts
import { Signer, Message } from '.';
import { Blake2bHasher } from '../hashers';
import { Provider } from '../providers';
export class Blake2bSigner extends Signer {
constructor(public readonly provider: Provider, key: string = null) {
super(new Blake2bHasher(key));
}
signMessage(message: string): Promise<string>{
return this.provider.sign(message);
}
async signMessages(messages: Message[]): Promise<string[]> {
const sigs = [];
for (const message of messages) {
if (
this.provider.address.toLockScript().toHash() === message.lock.toHash()
) {
sigs.push(await this.provider.sign(message.message));
} else {
sigs.push('0x');
}
}
return sigs;
}
}
gist: https://gist.github.com/taurenshaman/ed4dfd5019ade10e78a2dbacf24ac955
3、增加文件:pw-core/src/providers/raw-provider.ts,基于DummyProvider修改:
import { Provider, Platform } from './provider';
import { Address, AddressType } from '..';
import { Blake2bSigner } from '../signers/blake2b-signer';
export class RawProvider extends Provider {
secret: string;
signer: Blake2bSigner;
sign(message: string): Promise<string> {
console.log('message', message);
return this.signer.signMessage(message);
}
constructor(secret: string, platform: Platform = Platform.ckb) {
super(platform);
this.secret = secret;
this.signer = new Blake2bSigner(this, secret);
}
async init(): Promise<Provider> {
if (this.platform === Platform.eth) {
this.address = new Address(
'0x26C5F390FF2033CbB44377361c63A3Dd2DE3121d',
AddressType.eth
);
} else {
this.address = new Address(
'ckt1qyqxpayn272n8km2k08hzldynj992egs0waqnr8zjs',
AddressType.ckb
);
}
return this;
}
async close() {
console.log('do nothing');
}
}
gist: https://gist.github.com/taurenshaman/de4cb3ce973ac53799723124cb1b703b
4 Likes
针对IndexerCollector 抛砖引玉~
- 实现getBalance。根据ckb-indexer,可以直接调用
get_cells_capacity
,输入是script和scrip_type,输出的capacity的单位应该是ckb。
// get capacity by lock script
async getBalance(address: Address): Promise<Amount> {
const data = {
data: {
script: address.toLockScript().serializeJson(),
scrip_type: "lock",
}
};
const res = await axios.post(
`${
this.apiBase
}/get_cells_capacity`,
data
);
/* result data: {
capacity - total capacity
block_hash - indexed tip block hash
block_number - indexed tip block number
}
*/
//PwCollector: return new Amount(res.data.data, AmountUnit.shannon);
return new Amount(res.data.capacity, AmountUnit.ckb);
}
- 实现collect。根据含义应该是get unspent cells。可以根据上面的代码调用rpc的
get_cells
// get unspent cells
async collect(address: Address, options: CollectorOptions): Promise<Cell[]> {
if (!options || !options.neededAmount) {
throw new Error("'neededAmount' in options must be provided");
}
const addressLockScript = address.toLockScript();
const cells: Cell[] = [];
// 怎么把capacity=${options.neededAmount.toHexString()}传进去?
const reqData = {
data: {
script: address.toLockScript().serializeJson(),
scrip_type: "lock",
}
};
const res = await axios.post(
`${
this.apiBase
}/get_cells`,
reqData
);
/* result data: {
objects - live cells
last_cursor - pagination parameter
}
*/
for (let liveCell of res.data.objects) {
// capacity = new Amount(capacity, AmountUnit.shannon);
// outPoint = new OutPoint(outPoint.txHash, outPoint.index);
//cells.push(new Cell(capacity, addressLockScript, null, outPoint));
cells.push(Cell.fromRPC(liveCell)); // 应该可以直接fromRPC吧?
}
return cells;
}
- 浏览时从ckb-js-toolkit中发现了RPC,尝试直接调用rpc.collect如下,最后对数据遍历时,没有找到怎么获取capacity:
constructor(public apiBase: string = "https://mainnet.ckb.dev/indexer") {
super();
this.apiBase = apiBase;
this.rpc = new RPC(this.apiBase);
}
// get unspent cells
async collect(address: Address, options: CollectorOptions): Promise<Cell[]> {
if (!options || !options.neededAmount) {
throw new Error("'neededAmount' in options must be provided");
}
const addressLockScript = address.toLockScript();
const rpcc = new cell_collectors.RPCCollector(this.rpc, addressLockScript.toHash(), {
skipCellWithContent: true,
loadData: true
});
const data = rpcc.collect();
const cells: Cell[] = [];
(async () => {
for await (const x of data) {
// 这里的capacity怎么读取?
const cap= new Amount(capacity, AmountUnit.shannon);
const outPoint = OutPoint.fromRPC(x.data);
cells.push(new Cell(cap, addressLockScript, null, outPoint));
}
})();
return cells;
}
-
找资料时,找到了 CKBRPC in ckb-sdk-core
当前项目中没找到@nervosnetwork/ckb-sdk-core
,尝试安装,失败。
CKBRPC中的getCapacityByLockHash
应该就是IndexerCollector的getBalance需要的方法;没有找到可直接用的通过neededAmount查询的方法。
不知道为什么没有引入@nervosnetwork/ckb-sdk-core
,有了这个库,就不需要通过axios进行查询了。
-
关于如何实现collect的一个思路。
在wile(true)循环中,可以通过第2条或者CKBRPC. getLiveCellsByLockHash获取一个分页的cells,然后比较neededAmount和分页中所有cell的capacity之和:小于,取分页中最小够用的cells集合,跳出循环;大于,继续获取下一个分页的数据。
const resultCells = [];
while(true){
const cells = ..... // 第2条的axios调用,或者CKBRPC. getLiveCellsByLockHash
const totalCapacity = ... // 遍历cells数组计算capacity之和
if(totalCapacity >= neededAmount){
// cells --> resultCells: 可能只需要cells中的一部分而非全部
break;
}
}
2 Likes
homura
5
实验性地实现了一版
底下是测试用的代码
const {
default: PWCore,
AddressType,
Address,
Amount,
DefaultSigner,
Blake2bHasher,
} = require('./build/main');
const { RawProvider } = require('./build/main/providers/raw-provider');
const { SimpleBuilder } = require('./build/main/builders/simple-builder');
const {
IndexerCollector,
} = require('./build/main/collectors/indexer-collector');
async function main() {
const pw = new PWCore('https://aggron.ckb.dev');
await pw.init(
new RawProvider(
'0xprivate key'
),
new IndexerCollector()
);
const builder = new SimpleBuilder(
new Address(
'ckt...',
AddressType.ckb
),
new Amount('100')
);
const tx = await builder.build();
const signer = new DefaultSigner(PWCore.provider);
signer.hasher = new Blake2bHasher();
const txHash = await pw.sendTransaction(tx, signer);
console.log(txHash);
}
main();
3 Likes