Skip to content

Instantly share code, notes, and snippets.

@WilfredTA
Last active August 20, 2019 08:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save WilfredTA/9f822b3ef598faa612289adff794aafd to your computer and use it in GitHub Desktop.
Save WilfredTA/9f822b3ef598faa612289adff794aafd to your computer and use it in GitHub Desktop.
Some generator code extending js sdk - experiences errors
const fs = require('fs')
const Core = require('@nervosnetwork/ckb-sdk-core').default
class CKBInterface {
constructor(core, nodeUrl, privKey) {
this.core = new Core(nodeUrl)
this.privKey = privKey
}
getAddressObject() {
if (!this.address) {
this.address = this.core.generateAddress(this.privKey)
}
return this.address
}
getLockScriptOf(target=null) {
if (!this.systemCell) {
throw "Must cache system cell info prior to using lock scripts"
}
let identifier = this.getAddressObject().identifier
if (target) {
identifier = target
}
return {
codeHash: this.systemCodeHash,
args: [`0x${identifier}`]
}
}
getLockHashOf(lockScript=null) {
if (!lockScript) {
lockScript = this.getLockScriptOf()
}
return this.core.utils.lockScriptToHash(lockScript)
}
async cacheSystemCell() {
if (!this.systemCell) {
this.systemCell = await this.core.loadSystemCell()
this.systemCodeHash = this.core.rpc.paramsFormatter.toHash(this.systemCell.codeHash)
this.systemCellOutPoint = {
blockHash: this.core.rpc.paramsFormatter.toHash(this.systemCell.outPoint.blockHash),
cell: {
txHash: this.core.rpc.paramsFormatter.toHash(this.systemCell.outPoint.cell.txHash),
index: this.systemCell.outPoint.cell.index,
},
}
}
console.log(this.systemCell, " SYS CELL")
return this.systemCell
}
// bytes must be in BigInt
bytesToShannons(bytes) {
return bytes * BigInt((10**8))
}
async installDuktape(address) {
try {
const duktapeBuildLocation = process.env.DUKTAPE_BUILD_LOCATION || '../ckb-duktape/build/duktape'
let duktapeData = fs.readFileSync(duktapeBuildLocation)
const duktapeDataSize = duktapeData.byteLength
let duktapeDataHash = this.core.utils.blake2b(32, null, null, this.core.utils.PERSONAL);
duktapeDataHash = duktapeDataHash.update(duktapeData).digest('hex');
duktapeDataHash = this.core.rpc.paramsFormatter.toHash(duktapeDataHash)
duktapeData = this.core.rpc.paramsFormatter.toHash(this.core.utils.bytesToHex(duktapeData))
console.log(duktapeDataSize, 'duktape size')
let duktapeCell = {
capacity: this.bytesToShannons(BigInt(duktapeDataSize) + 100n),
lock: this.getLockScriptOf(),
data: duktapeData
}
console.log(duktapeCell.capacity, "<< REQ cAPACITY")
const tx = await this.generateTransaction([duktapeCell])
// const identifier = this.core.rpc.paramsFormatter.toHash(this.getAddressObject().identifier)
const signedTx = await this.core.signTransaction(this.getAddressObject())(tx)
const duktapeTxHash = await this.core.rpc.sendTransaction(signedTx)
this.duktapeDataHash = duktapeDataHash
this.duktapeOutPoint = {
cell: {
txHash: this.core.rpc.paramsFormatter.toHash(duktapeTxHash),
index: '0'
}
}
} catch(error) {
console.warn(error)
}
}
async getUnspentCells(_lockHash, from, to) {
const cellsGroup = []
const STEP = 100
const slidingWindow = async (_lockhash, from, to) => {
try {
if (from + STEP < to) {
const subsetOfCells = await this.core.rpc.getCellsByLockHash(_lockHash, from, from + STEP)
cellsGroup.push(subsetOfCells)
await slidingWindow(_lockHash, from + STEP, to)
} else {
const finalSetOfCells = await this.core.rpc
.getCellsByLockHash(_lockHash, from, to)
cellsGroup.push(finalSetOfCells)
}
} catch(error) {
console.warn(error)
}
}
await slidingWindow(_lockHash, from, to)
return cellsGroup.flat()
}
async gatherAllCells() {
try {
const lockHash = this.getLockHashOf();
const tipBlockNumber = await this.core.rpc.getTipBlockNumber()
const cells = await this.getUnspentCells(lockHash, 0, tipBlockNumber)
return cells
} catch (error) {
console.warn(error);
}
}
// Outputs capacity is expected to be in shannons already, so we do not convert them to shannons
// During needed capacity calculation
async generateTransaction(outputs, additionalDeps=null) {
try {
let neededCapacity = 0n
let inputs = []
let inputCapacity = 0n
let witnesses = []
let cells = await this.gatherAllCells()
let deps = [this.systemCellOutPoint]
if (additionalDeps) {
deps = deps.concat(additionalDeps)
}
const changeOutput = {
capacity: 0n,
lock: this.getLockScriptOf(),
data: '0x'
}
// Collect needed capacity for each output
outputs.forEach((output) => {
neededCapacity += output.capacity
})
for (let i = 0; i < cells.length; i++) {
const currCell = cells[i]
inputs.push({
previousOutput: currCell.outPoint,
since: '0',
args: []
})
console.log(this.bytesToShannons(BigInt(currCell.capacity)), "<< PUSHED TO INPUTS")
inputCapacity += this.bytesToShannons(BigInt(currCell.capacity))
if (inputCapacity >= neededCapacity) {
break
}
}
// Check if enough capacity exists
if (inputCapacity < neededCapacity) {
throw new Error('Not enough capacity to make this transaction')
}
// Make change
if (inputCapacity > neededCapacity) {
changeOutput.capacity = inputCapacity - neededCapacity
}
if (changeOutput.capacity > 0n) {
//outputs.push(changeOutput)
}
console.log(this.bytesToShannons(changeOutput.capacity, "CAPACITY OF CHANGE OUTPUT"))
// Format capacities to strings for rpc
outputs.forEach((out) => {
out.capacity = out.capacity.toString()
})
// Add witnesses for every input
witnesses = inputs.map((input) => {
return { data: [] }
})
const tx = {
version: '0',
deps,
inputs,
outputs,
witnesses
}
console.log(outputs.length, " NUM OF OUTPUTS")
return tx
} catch(error) {
console.warn(error)
}
}
async sendCapacity(target, capacity) {
try {
const output = {
capacity: BigInt(capacity),
lock: {
codeHash: this.systemCodeHash,
args: [`0x${target}`]
},
data: '0x'
}
const tx = await this.generateTransaction([output])
const signedTx = await this.core.signTransaction(this.getAddressObject())(tx)
console.log(signedTx)
const capacityTransferTxHash = await this.core.rpc.sendTransaction(signedTx)
console.log(capacityTransferTxHash)
} catch(error) {
console.warn(error)
}
}
}
const bootstrap = async () => {
const nodeUrl = process.env.NODE_URL || 'http://localhost:8114' // example node url
const privateKey = process.env.PRIV_KEY || '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' // example private key
let ckbGateway = new CKBInterface(Core, nodeUrl, privateKey)
await ckbGateway.cacheSystemCell()
const targetId = ckbGateway.getAddressObject().identifier
//const unspentCells = await ckbGateway.gatherAllCells()
await ckbGateway.installDuktape() // This results in Error: {"code":-3,"message":"InvalidTx(InsufficientCellCapacity)"} Sometimes
// Other times, it results in Error: {"code":-3,"message":"InvalidTx(OutputsSumOverflow)"}
//await ckbGateway.sendCapacity(targetId, 600000000) // this works
}
bootstrap()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment