Symbol[XYM] <-> ETH, Polygon[MATIC] などのスワップ備忘録

# 準備
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インストール

https://trufflesuite.com/ganache/

# HTLC作成

https://github.com/chatch/hashed-timelock-contract-ethereum

これを使う、ちょっと加工するだけなのでSolidity知識は不要。ただし、最低限の知識はあったほうがいいかも

クリプトゾンビ一周で良いと思われ

https://cryptozombies.io/jp/course

## 適当な作業フォルダを作成しておく
```
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


```diff_solidity 
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

```diff_solidity
const newSecretHashPair = () => {
  const secret = random32()
-  const hash = sha256(secret)
+  const hash = sha256(sha256(secret))
  return {
    secret: bufToStr(secret),
    hash: bufToStr(hash),
  }
}
```
変えるのは、これだけｗ

# ネットワーク立ち上げ
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
```

```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]);
})();

```

# ここまでの使い方

## コントラクトアドレスを書き込む
```js
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は公開情報

## せっかくなので情報取得
 
```diff_javascript
(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を記入する
```diff_javascript
(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が公開情報となる

# テストネットに展開する

以下参考

https://blog.logrocket.com/migrate-smart-contracts-between-different-chains/

結論、簡単だった。

# イーサリアムのテストネット（Sepolia）へのスマートコントラクトの展開

記事内ではRinkebyを使っているが、おそらく終了したテストネットぽいのでSepoliaを使う。

## MetaMaskの設定
- MetaMask 設定 -> 高度な設定 -> テストネットワークを表示 -> ON
- Sepoliaテストネットワークを選択
- 受け渡しテスト用にアカウントを追加しておく

### Faucetより両アカウントにETHを取得

ロックトランザクションはアカウント１から、引き出し（いわゆるプルーフ）はアカウント２から行うので両アカウントに手数料が必要。
なお、アカウント１でmigrateをするのでその手数料も必要。一度のFaucetからの取得で足りる。

https://faucet.sepolia.dev/

## デプロイ

### infuraアカウント作成

https://infura.io/dashboard

- Project作成
- EndPoints -> Ethereum -> Sepolia を選択しエンドポイントURLをコピー

### truffle-config.js 編集

`hashed-timelock-contract-ethereum`に`@truffle/hdwallet-provider`を追加する
```
npm install @truffle/hdwallet-provider
```

```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,
    },
  }
}
```
`truffle-config.js`を編集。
アカウント１の秘密鍵（MetaMaskから取得）と先程コピーしたエンドポイントを編集する
なおSepoliaの`network_id`は`11155111`

### Migrate

```
truffle migrate -network development --reset
```

以上。
３つのコントラクトがデプロイされるので、コントラクトアドレスを控えておく。

### test
ローカルでテストしたのと同じことをやる。
変更点は
```htlc.js
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を取得

https://faucet.polygon.technology/

## truffle-config.js 編集

```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テストネットでも確認。

https://github.com/0x070696E65/swap-test