-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adds
transferMultipleSpore
and meltMultipleThenCreateSpore
,…
… with its tests
- Loading branch information
1 parent
6b2589c
commit 7f25b9f
Showing
7 changed files
with
599 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { BI } from '@ckb-lumos/lumos'; | ||
import { describe, it } from 'vitest'; | ||
import { createMultipleSpores, getSporeById, meltMultipleThenCreateSpore, transferMultipleSpore } from '../api'; | ||
import { predefinedSporeConfigs } from '../config'; | ||
import { bytifyRawString } from '../helpers'; | ||
import { signAndOrSendTransaction } from './helpers'; | ||
import { MultipleTestSPORE_OUTPOINT_RECORDS, TEST_ACCOUNTS, TEST_ENV } from './shared'; | ||
|
||
const options = { | ||
timeout: 10000000, | ||
}; | ||
describe('Multiple', options, () => { | ||
const { rpc, config } = TEST_ENV; | ||
const { ALICE, BOB, CHARLIE } = TEST_ACCOUNTS; | ||
|
||
it('Create Multiple First', async () => { | ||
const createAmount = 2; | ||
const { txSkeleton, outputIndices } = await createMultipleSpores({ | ||
sporeInfos: Array(createAmount).fill({ | ||
data: { | ||
contentType: 'text/plain', | ||
content: bytifyRawString('content'), | ||
}, | ||
toLock: CHARLIE.lock, | ||
}), | ||
fromInfos: [CHARLIE.address], | ||
config, | ||
}); | ||
const { hash } = await signAndOrSendTransaction({ | ||
account: CHARLIE, | ||
txSkeleton, | ||
config, | ||
rpc, | ||
send: true, | ||
}); | ||
|
||
if (hash) { | ||
for (const index of outputIndices) { | ||
MultipleTestSPORE_OUTPOINT_RECORDS.push({ | ||
outPoint: { | ||
txHash: hash, | ||
index: BI.from(index).toHexString(), | ||
}, | ||
account: CHARLIE, | ||
sporeId: txSkeleton.get('outputs').get(index)!.cellOutput.type!.args, | ||
}); | ||
} | ||
} | ||
}); | ||
it('Multiple Transfer', async () => { | ||
// wait for transaction success | ||
// dirty but works | ||
await new Promise((f) => setTimeout(f, 20000)); | ||
const spore_cells = MultipleTestSPORE_OUTPOINT_RECORDS.map((spore) => spore.outPoint); | ||
const txSkeleton = await transferMultipleSpore({ | ||
outPoints: spore_cells, | ||
fromInfos: [CHARLIE.address], | ||
toLock: ALICE.lock, | ||
config: predefinedSporeConfigs.Testnet, | ||
}); | ||
|
||
const hash = await signAndOrSendTransaction({ account: CHARLIE, txSkeleton, config, rpc, send: true }); | ||
console.log(`Spore Multiple Transfer at: https://pudge.explorer.nervos.org/transaction/${hash.hash}`); | ||
//console.log(`Spore ID: ${txSkeleton.get('outputs').get(outputIndex)!.cellOutput.type!.args}`); | ||
}), | ||
it('Multiple Melt Then Create One', async () => { | ||
// wait for transaction success | ||
// dirty but works | ||
await new Promise((f) => setTimeout(f, 20000)); | ||
const sporeIds = MultipleTestSPORE_OUTPOINT_RECORDS.map((spore) => spore.sporeId); | ||
const sporeCells = ( | ||
await Promise.all( | ||
sporeIds.map(async (spore_id) => { | ||
const sporeData = await getSporeById(spore_id, predefinedSporeConfigs.Testnet); | ||
return sporeData?.outPoint; | ||
}), | ||
) | ||
).filter((outPoint) => outPoint !== undefined); | ||
|
||
const { txSkeleton } = await meltMultipleThenCreateSpore({ | ||
outPoints: sporeCells, | ||
fromInfos: [ALICE.address], | ||
toLock: BOB.lock, | ||
config: predefinedSporeConfigs.Testnet, | ||
data: { | ||
contentType: 'text/plain', | ||
content: bytifyRawString('content'), | ||
}, | ||
}); | ||
const hash = await signAndOrSendTransaction({ account: ALICE, txSkeleton, config, rpc, send: true }); | ||
console.log(`Spore created at: https://pudge.explorer.nervos.org/transaction/${hash.hash}`); | ||
//console.log(`Spore ID: ${txSkeleton.get('outputs').get(outputIndex)!.cellOutput.type!.args}`); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { defaultEmptyWitnessArgs, updateWitnessArgs, isScriptValueEquals, getSporeConfig } from '../..'; | ||
import { hd, helpers, RPC, Address, Hash, Script, HexString } from '@ckb-lumos/lumos'; | ||
import { secp256k1Blake160 } from '@ckb-lumos/lumos/common-scripts'; | ||
|
||
export interface Wallet { | ||
lock: Script; | ||
address: Address; | ||
signMessage(message: HexString): Hash; | ||
signTransaction(txSkeleton: helpers.TransactionSkeletonType): helpers.TransactionSkeletonType; | ||
signAndSendTransaction(txSkeleton: helpers.TransactionSkeletonType): Promise<Hash>; | ||
} | ||
|
||
/** | ||
* Create a CKB Default Lock (Secp256k1Blake160 Sign-all) Wallet by a private-key and a SporeConfig, | ||
* providing lock/address, and functions to sign message/transaction and send the transaction on-chain. | ||
*/ | ||
export function createDefaultLockWallet(privateKey: HexString): Wallet { | ||
const config = getSporeConfig(); | ||
|
||
// Generate a lock script from the private key | ||
const defaultLock = config.lumos.SCRIPTS.SECP256K1_BLAKE160!; | ||
const lock: Script = { | ||
codeHash: defaultLock.CODE_HASH, | ||
hashType: defaultLock.HASH_TYPE, | ||
args: hd.key.privateKeyToBlake160(privateKey), | ||
}; | ||
|
||
// Generate address from the lock script | ||
const address = helpers.encodeToAddress(lock, { | ||
config: config.lumos, | ||
}); | ||
|
||
// Sign for a message | ||
function signMessage(message: HexString): Hash { | ||
return hd.key.signRecoverable(message, privateKey); | ||
} | ||
|
||
// Sign prepared signing entries, | ||
// and then fill signatures into Transaction.witnesses | ||
function signTransaction(txSkeleton: helpers.TransactionSkeletonType): helpers.TransactionSkeletonType { | ||
const signingEntries = txSkeleton.get('signingEntries'); | ||
const signatures = new Map<HexString, Hash>(); | ||
const inputs = txSkeleton.get('inputs'); | ||
|
||
let witnesses = txSkeleton.get('witnesses'); | ||
for (let i = 0; i < signingEntries.size; i++) { | ||
const entry = signingEntries.get(i)!; | ||
if (entry.type === 'witness_args_lock') { | ||
// Skip if the input's lock does not match to the wallet's lock | ||
const input = inputs.get(entry.index); | ||
if (!input || !isScriptValueEquals(input.cellOutput.lock, lock)) { | ||
continue; | ||
} | ||
|
||
// Sign message | ||
if (!signatures.has(entry.message)) { | ||
const sig = signMessage(entry.message); | ||
signatures.set(entry.message, sig); | ||
} | ||
|
||
// Update signature to Transaction.witnesses | ||
const signature = signatures.get(entry.message)!; | ||
const witness = witnesses.get(entry.index, defaultEmptyWitnessArgs); | ||
witnesses = witnesses.set(entry.index, updateWitnessArgs(witness, 'lock', signature)); | ||
} | ||
} | ||
|
||
return txSkeleton.set('witnesses', witnesses); | ||
} | ||
|
||
// Sign the transaction and send it via RPC | ||
async function signAndSendTransaction(txSkeleton: helpers.TransactionSkeletonType): Promise<Hash> { | ||
// 1. Sign transaction | ||
txSkeleton = secp256k1Blake160.prepareSigningEntries(txSkeleton, { config: config.lumos }); | ||
txSkeleton = signTransaction(txSkeleton); | ||
|
||
// 2. Convert TransactionSkeleton to Transaction | ||
const tx = helpers.createTransactionFromSkeleton(txSkeleton); | ||
|
||
// 3. Send transaction | ||
const rpc = new RPC(config.ckbNodeUrl); | ||
return await rpc.sendTransaction(tx, 'passthrough'); | ||
} | ||
|
||
return { | ||
lock, | ||
address, | ||
signMessage, | ||
signTransaction, | ||
signAndSendTransaction, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.