準備
node, npm install済み前提
※自分の環境
node v16.1.1
npm 8.0.0
Mac Os 12.2.1
Apple M1 Pro
truffleインストール
npm install -g truffle
Ganacheインストール
HTLC作成
これを使う、ちょっと加工するだけなのでSolidity知識は不要。ただし、最低限の知識はあったほうがいいかも
クリプトゾンビ一周で良いと思われ
適当な作業フォルダを作成しておく
mkdir swap-test
cd swap-test
clone
git clone https://github.com/chatch/hashed-timelock-contract-ethereum.git
cd hashed-timelock-contract-ethereum
npm install
npm install
Contractの書き換え
ERC20やERC721も今後使うかもしれないのでひとまず全部変えておく。
ただし内容は同じでこれだけ
- contracts/HashedTimelock.sol
- contracts/HashedTimelockERC20.sol
- contracts/HashedTimelockERC721.sol
modifier hashlockMatches(bytes32 _contractId, bytes32 _x) {
- require(
- contracts[_contractId].hashlock == sha256(abi.encodePacked(_x)),
- "hashlock hash does not match"
- );
+ bytes32 pre = sha256(abi.encodePacked(_x));
+ require(
+ contracts[_contractId].hashlock == sha256(abi.encodePacked(pre)),
+ "hashlock hash does not match"
+ );
_;
}
以下は別に触らなくてもいいけどTest(npm run test
)する時にエラー出さないために変えておいたほうが無難
- test/helper/utils.js
const newSecretHashPair = () => {
const secret = random32()
- const hash = sha256(secret)
+ const hash = sha256(sha256(secret))
return {
secret: bufToStr(secret),
hash: bufToStr(hash),
}
}
変えるのは、これだけw
ネットワーク立ち上げ
Ganacheを起動して、NewWorkSpaceをクリック
- ワークスペースネームは適当
- [ADD PROJECT]をクリックして先程Cloneしたルートフォルダにある
truffle-config.js
を選択する。 - [SERVER]を選択し[NETWORK ID]を4447に変更
- 右上の[SAVE WORKSPACE]をクリックする。
ブロックチェーンへの書き込み
truffle migrate
HashedTimelockだけ抜粋、ここのcontract addressをあとで使う
Deploying 'HashedTimelock'
--------------------------
> transaction hash: 0x6d1aed16ac35e464b6fe40237f4fd8279a7b55a481e946abdbc5401929490cce
> Blocks: 0 Seconds: 0
> contract address: 0xA0fA8F128FC5D438C5CbD1b4Ec0839ce797d7337
> block number: 3
> block timestamp: 1670671838
> account: 0xd9F8E9bC584083eE86c9E04056ffdF7f448012f9
> balance: 99.97453284
> gas used: 1005758 (0xf58be)
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.02011516 ETH
一応テスト
さきほど、ちゃんと書き換えていれば全部通るはず
npm run test
これでSolidiy側は終わりです
実行ファイルの作成
cd ../
mkdir test
cd test
we3jsインストール
npm init
npm install web3
abi のコピー
mkdir abi
cp ../hashed-timelock-contract-ethereum/build/contracts/HashedTimelock.json abi/HashedTimelock.json
js書く
touch htlc.js
const Web3 = require('web3');
const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545"));
const abi = require('./abi/HashedTimelock.json').abi;
const crypto = require('crypto')
const bufToStr = b => '0x' + b.toString('hex')
const sha256 = x =>
crypto
.createHash('sha256')
.update(x)
.digest()
const random32 = () => crypto.randomBytes(32)
// randomSecretからHASH256()でハッシュ作成
const newSecretHashPair = () => {
const secret = random32()
const hash = sha256(sha256(secret))
return {
// Symbolで言うところのProof
secret: bufToStr(secret),
// Symbolで言うところのSecret、名前ややこしいから変えてもいい
hash: bufToStr(hash),
}
}
const nowSeconds = () => Math.floor(Date.now() / 1000)
const hourSeconds = 3600
const timeLock1Hour = nowSeconds() + hourSeconds
// migrateした時のコントラクトアドレスを使う
const contractAddress = "CONTRACT_ADDRESS";
const htlc = new web3.eth.Contract(abi, contractAddress);
// ロックコントラクトの情報取得(Ganacheからでも分かる)
const contractInfo = (contractId) => {
return htlc.methods.getContract(contractId).call()
}
// 新しいロックコントラクトの作成(Symbolで言うシークレットロック)
const newContract = (sender, receiver, amount) => {
const hashPair = newSecretHashPair()
const oneFinney = web3.utils.toWei(web3.utils.toBN(amount), 'finney')
htlc.methods.newContract(
receiver,
hashPair.hash,
timeLock1Hour,
)
.send(
{
from: sender,
value: oneFinney,
gas: '1000000'
}
)
.on("receipt", function(receipt) {
console.log(receipt.events.LogHTLCNew.returnValues);
})
.on("error", function(error) {
console.error(error);
});
console.log(hashPair);
}
// Secretを使って引き出し(Symbolで言うシークレットプルーフ)
const withDraw = (contractId, secret, receiver)=> {
htlc.methods.withdraw(
contractId,
secret,
)
.send(
{
from: receiver,
gas: '1000000'
}
)
.on("receipt", function(receipt) {
console.log(receipt.events.LogHTLCWithdraw);
})
.on("error", function(error) {
console.error(error);
});
}
(async()=>{
const accounts = await web3.eth.getAccounts();
console.log('accounts: ', accounts);
newContract(accounts[0], accounts[1], 1);
//console.log(await contractInfo("CONTRACT_ID"));
//withDraw("CONTRACT_ID", "SECRET", accounts[1]);
})();
ここまでの使い方
コントラクトアドレスを書き込む
const contractAddress = "0xA0fA8F128FC5D438C5CbD1b4Ec0839ce797d7337";
- 新しいコントラクト(Symbolで言うところのシークレットロック)の作成
node htlc.js
accounts: [
'0x9548d0aF212baA56B66c652A548F00de3C7E3927',
'0xB74f347B42a4A998f154608A7F74dD526E4edc44',
'0x1569cE8620F3bBFe8d647952b972F2fA76787417',
'0xCFd150D41842804b04fa6F81e8BE6Bd11c97d24b',
'0x0F56108C77bA0Bc3193452892D379C89dfA75613',
'0xF54242dAF4aa5b222087c9711477c40c499581Fe',
'0x3BAfa87546444319298fACa5Af0Ef253740A81f5',
'0x7Cea903cfc65Fa662C909f2D60e3fBdc86198750',
'0x9025ce74a70C6B9ac26F89065c38AfF562eB8D1B',
'0x013342aB590070A56b020d4bf2CdCaFb59B6E6b4'
]
{
secret: '0x93c45674bbc165f3b755b8931f3fb5b5df9655507409cdb35795ffe4e2aae52f',
hash: '0x5eb0d90785a2dd629f0b6a8dafda0d8fb0eaf76983d63591df7c6d644c65b13a'
}
Result {
'0': '0x18d80f36686d946f6357fbba0b77696048eeb12aae341058f76a6486c54b2873',
'1': '0x9548d0aF212baA56B66c652A548F00de3C7E3927',
'2': '0xB74f347B42a4A998f154608A7F74dD526E4edc44',
'3': '1000000000000000',
'4': '0x5eb0d90785a2dd629f0b6a8dafda0d8fb0eaf76983d63591df7c6d644c65b13a',
'5': '1670678842',
contractId: '0x18d80f36686d946f6357fbba0b77696048eeb12aae341058f76a6486c54b2873',
sender: '0x9548d0aF212baA56B66c652A548F00de3C7E3927',
receiver: '0xB74f347B42a4A998f154608A7F74dD526E4edc44',
amount: '1000000000000000',
hashlock: '0x5eb0d90785a2dd629f0b6a8dafda0d8fb0eaf76983d63591df7c6d644c65b13a',
timelock: '1670678842'
}
これでロック完了。
この時のSecretの値がのちほど必要。Hashは公開情報
せっかくなので情報取得
(async()=>{
const accounts = await web3.eth.getAccounts();
console.log('accounts: ', accounts);
- newContract(accounts[0], accounts[1], 1);
- //console.log(await contractInfo("CONTRACT_ID"));
+ console.log(await contractInfo("0x18d80f36686d946f6357fbba0b77696048eeb12aae341058f76a6486c54b2873"));
//withDraw("CONTRACT_ID", "SECRET", accounts[1]);
})();
実行
node htlc.js
Result {
'0': '0x9548d0aF212baA56B66c652A548F00de3C7E3927',
'1': '0xB74f347B42a4A998f154608A7F74dD526E4edc44',
'2': '1000000000000000',
'3': '0x5eb0d90785a2dd629f0b6a8dafda0d8fb0eaf76983d63591df7c6d644c65b13a',
'4': '1670678842',
'5': false,
'6': false,
'7': '0x0000000000000000000000000000000000000000000000000000000000000000',
sender: '0x9548d0aF212baA56B66c652A548F00de3C7E3927',
receiver: '0xB74f347B42a4A998f154608A7F74dD526E4edc44',
amount: '1000000000000000',
hashlock: '0x5eb0d90785a2dd629f0b6a8dafda0d8fb0eaf76983d63591df7c6d644c65b13a',
timelock: '1670678842',
withdrawn: false,
refunded: false,
preimage: '0x0000000000000000000000000000000000000000000000000000000000000000'
}
hashは公開されるけどsecretは非公開情報(preimage: '0x0000000000000000000000000000000000000000000000000000000000000000'
)。なお、このへんはGaracheのトランザクション情報を掘ると見れる。
WithDraw(Symbolで言うところのシークレットプルーフ)
第一引数をコントラクトID、第二引数にSecretを記入する
(async()=>{
const accounts = await web3.eth.getAccounts();
console.log('accounts: ', accounts);
//newContract(accounts[0], accounts[1], 1);
- //console.log(await contractInfo("0x18d80f36686d946f6357fbba0b77696048eeb12aae341058f76a6486c54b2873"));
- //withDraw("CONTRACT_ID", "SECRET", accounts[1]);
+ withDraw("0x18d80f36686d946f6357fbba0b77696048eeb12aae341058f76a6486c54b2873", "0x93c45674bbc165f3b755b8931f3fb5b5df9655507409cdb35795ffe4e2aae52f", accounts[1]);
})();
実行
node htlc.js
エラーが無ければOK。
最後にもう一度、contractInfo()
を実行してみると
Result {
'0': '0x9548d0aF212baA56B66c652A548F00de3C7E3927',
'1': '0xB74f347B42a4A998f154608A7F74dD526E4edc44',
'2': '1000000000000000',
'3': '0x5eb0d90785a2dd629f0b6a8dafda0d8fb0eaf76983d63591df7c6d644c65b13a',
'4': '1670678842',
'5': true,
'6': false,
'7': '0x93c45674bbc165f3b755b8931f3fb5b5df9655507409cdb35795ffe4e2aae52f',
sender: '0x9548d0aF212baA56B66c652A548F00de3C7E3927',
receiver: '0xB74f347B42a4A998f154608A7F74dD526E4edc44',
amount: '1000000000000000',
hashlock: '0x5eb0d90785a2dd629f0b6a8dafda0d8fb0eaf76983d63591df7c6d644c65b13a',
timelock: '1670678842',
withdrawn: true,
refunded: false,
preimage: '0x93c45674bbc165f3b755b8931f3fb5b5df9655507409cdb35795ffe4e2aae52f'
}
preimage: '0x93c45674bbc165f3b755b8931f3fb5b5df9655507409cdb35795ffe4e2aae52f'
となりSecretが公開情報となる
テストネットに展開する
以下参考
結論、簡単だった。
イーサリアムのテストネット(Sepolia)へのスマートコントラクトの展開
記事内ではRinkebyを使っているが、おそらく終了したテストネットぽいのでSepoliaを使う。
MetaMaskの設定
- MetaMask 設定 -> 高度な設定 -> テストネットワークを表示 -> ON
- Sepoliaテストネットワークを選択
- 受け渡しテスト用にアカウントを追加しておく
Faucetより両アカウントにETHを取得
ロックトランザクションはアカウント1から、引き出し(いわゆるプルーフ)はアカウント2から行うので両アカウントに手数料が必要。
なお、アカウント1でmigrateをするのでその手数料も必要。一度のFaucetからの取得で足りる。
デプロイ
infuraアカウント作成
- Project作成
- EndPoints -> Ethereum -> Sepolia を選択しエンドポイントURLをコピー
truffle-config.js 編集
hashed-timelock-contract-ethereum
に@truffle/hdwallet-provider
を追加する
npm install @truffle/hdwallet-provider
const HDWalletProvider = require('@truffle/hdwallet-provider')
const privateKeys = ['PRIVATE_KEY']
module.exports = {
networks: {
development: {
provider: () => new
HDWalletProvider(privateKeys, 'SEPOLIA_ENDPOINT'),
network_id: 11155111,
},
}
}
truffle-config.js
を編集。
アカウント1の秘密鍵(MetaMaskから取得)と先程コピーしたエンドポイントを編集する
なおSepoliaのnetwork_id
は11155111
Migrate
truffle migrate -network development --reset
以上。
3つのコントラクトがデプロイされるので、コントラクトアドレスを控えておく。
test
ローカルでテストしたのと同じことをやる。
変更点は
const web3 = new Web3(new Web3.providers.HttpProvider("ENDPOINT"));
-----
const contractAddress = "CONTRACT_ADDRESS";
ここだけ。
スマートコントラクトをPolygonのテストネット(ムンバイ)に展開する
MetaMask
Polygonはもっと簡単。
- MetaMask -> ネットワーク -> ネットワーク追加 -> PolygonテストネットMumbaiを追加
Network name: PolygonTestnet
RPC URL: https://rpc-mumbai.maticvigil.com
Chain ID: 80001
Currency symbol: MATIC
Block Explorer URL: https://mumbai.polygonscan.com/
- faucetから両アカウントにMaticを取得
truffle-config.js 編集
const HDWalletProvider = require('@truffle/hdwallet-provider')
const privateKeys = ['PRIVATE_KEY']
module.exports = {
networks: {
development: {
provider: () => new
HDWalletProvider(privateKeys, 'SEPOLIA_ENDPOINT'),
network_id: 11155111,
},
polygonTestnet: {
provider: () => new
HDWalletProvider(privateKeys, `https://rpc-mumbai.maticvigil.com`),
network_id: 80001,
}
}
}
Migrate
truffle migrate -network polygonTestnet --reset
以上。Ethテストネットと同じ用にPolygonテストネットでも確認。