[entropy] Typescript code for coin flip example + docs (#1128)
* adding stuff * add coin flip example * it works * docs * hm * pr comments
This commit is contained in:
parent
f0a077a2cf
commit
f36e868ef6
|
@ -20,6 +20,7 @@
|
|||
"target_chains/ethereum/entropy_sdk/solidity",
|
||||
"target_chains/ethereum/sdk/js",
|
||||
"target_chains/ethereum/sdk/solidity",
|
||||
"target_chains/ethereum/examples/coin_flip/app",
|
||||
"target_chains/ethereum/examples/oracle_swap/app",
|
||||
"target_chains/sui/sdk/js",
|
||||
"target_chains/sui/cli",
|
||||
|
@ -11760,6 +11761,10 @@
|
|||
"resolved": "target_chains/ethereum/entropy_sdk/solidity",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@pythnetwork/eth-coin-flip-example": {
|
||||
"resolved": "target_chains/ethereum/examples/coin_flip/app",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@pythnetwork/eth-oracle-swap-example-frontend": {
|
||||
"resolved": "target_chains/ethereum/examples/oracle_swap/app",
|
||||
"link": true
|
||||
|
@ -57995,6 +58000,7 @@
|
|||
}
|
||||
},
|
||||
"target_chains/ethereum/entropy_sdk/solidity": {
|
||||
"name": "@pythnetwork/entropy-sdk-solidity",
|
||||
"version": "0.1.0",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
|
@ -58042,6 +58048,133 @@
|
|||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"target_chains/ethereum/examples/coin_flip/app": {
|
||||
"name": "@pythnetwork/eth-coin-flip-example",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@pythnetwork/pyth-evm-js": "*",
|
||||
"@pythnetwork/pyth-sdk-solidity": "*",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.11.64",
|
||||
"buffer": "^6.0.3",
|
||||
"ethers": "^5.7.2",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ts-node": "^10.9.1"
|
||||
}
|
||||
},
|
||||
"target_chains/ethereum/examples/coin_flip/app/node_modules/@types/jest": {
|
||||
"version": "27.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz",
|
||||
"integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==",
|
||||
"dependencies": {
|
||||
"jest-matcher-utils": "^27.0.0",
|
||||
"pretty-format": "^27.0.0"
|
||||
}
|
||||
},
|
||||
"target_chains/ethereum/examples/coin_flip/app/node_modules/@types/node": {
|
||||
"version": "16.18.60",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.60.tgz",
|
||||
"integrity": "sha512-ZUGPWx5vKfN+G2/yN7pcSNLkIkXEvlwNaJEd4e0ppX7W2S8XAkdc/37hM4OUNJB9sa0p12AOvGvxL4JCPiz9DA=="
|
||||
},
|
||||
"target_chains/ethereum/examples/coin_flip/app/node_modules/ansi-styles": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"target_chains/ethereum/examples/coin_flip/app/node_modules/buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/feross"
|
||||
},
|
||||
{
|
||||
"type": "patreon",
|
||||
"url": "https://www.patreon.com/feross"
|
||||
},
|
||||
{
|
||||
"type": "consulting",
|
||||
"url": "https://feross.org/support"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"target_chains/ethereum/examples/coin_flip/app/node_modules/diff-sequences": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz",
|
||||
"integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==",
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"target_chains/ethereum/examples/coin_flip/app/node_modules/jest-diff": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz",
|
||||
"integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==",
|
||||
"dependencies": {
|
||||
"chalk": "^4.0.0",
|
||||
"diff-sequences": "^27.5.1",
|
||||
"jest-get-type": "^27.5.1",
|
||||
"pretty-format": "^27.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"target_chains/ethereum/examples/coin_flip/app/node_modules/jest-get-type": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz",
|
||||
"integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==",
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"target_chains/ethereum/examples/coin_flip/app/node_modules/jest-matcher-utils": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz",
|
||||
"integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==",
|
||||
"dependencies": {
|
||||
"chalk": "^4.0.0",
|
||||
"jest-diff": "^27.5.1",
|
||||
"jest-get-type": "^27.5.1",
|
||||
"pretty-format": "^27.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"target_chains/ethereum/examples/coin_flip/app/node_modules/pretty-format": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"target_chains/ethereum/examples/coin_flip/app/node_modules/react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
},
|
||||
"target_chains/ethereum/examples/oracle_swap/app": {
|
||||
"name": "@pythnetwork/eth-oracle-swap-example-frontend",
|
||||
"version": "0.1.0",
|
||||
|
@ -66441,6 +66574,97 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"@pythnetwork/eth-coin-flip-example": {
|
||||
"version": "file:target_chains/ethereum/examples/coin_flip/app",
|
||||
"requires": {
|
||||
"@pythnetwork/pyth-evm-js": "*",
|
||||
"@pythnetwork/pyth-sdk-solidity": "*",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.11.64",
|
||||
"buffer": "^6.0.3",
|
||||
"ethers": "^5.7.2",
|
||||
"prettier": "^2.7.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/jest": {
|
||||
"version": "27.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz",
|
||||
"integrity": "sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==",
|
||||
"requires": {
|
||||
"jest-matcher-utils": "^27.0.0",
|
||||
"pretty-format": "^27.0.0"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.18.60",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.60.tgz",
|
||||
"integrity": "sha512-ZUGPWx5vKfN+G2/yN7pcSNLkIkXEvlwNaJEd4e0ppX7W2S8XAkdc/37hM4OUNJB9sa0p12AOvGvxL4JCPiz9DA=="
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
|
||||
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="
|
||||
},
|
||||
"buffer": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
|
||||
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
|
||||
"requires": {
|
||||
"base64-js": "^1.3.1",
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"diff-sequences": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz",
|
||||
"integrity": "sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ=="
|
||||
},
|
||||
"jest-diff": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.5.1.tgz",
|
||||
"integrity": "sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==",
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"diff-sequences": "^27.5.1",
|
||||
"jest-get-type": "^27.5.1",
|
||||
"pretty-format": "^27.5.1"
|
||||
}
|
||||
},
|
||||
"jest-get-type": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz",
|
||||
"integrity": "sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw=="
|
||||
},
|
||||
"jest-matcher-utils": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz",
|
||||
"integrity": "sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==",
|
||||
"requires": {
|
||||
"chalk": "^4.0.0",
|
||||
"jest-diff": "^27.5.1",
|
||||
"jest-get-type": "^27.5.1",
|
||||
"pretty-format": "^27.5.1"
|
||||
}
|
||||
},
|
||||
"pretty-format": {
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
|
||||
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
|
||||
"requires": {
|
||||
"ansi-regex": "^5.0.1",
|
||||
"ansi-styles": "^5.0.0",
|
||||
"react-is": "^17.0.1"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@pythnetwork/eth-oracle-swap-example-frontend": {
|
||||
"version": "file:target_chains/ethereum/examples/oracle_swap/app",
|
||||
"requires": {
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"target_chains/ethereum/entropy_sdk/solidity",
|
||||
"target_chains/ethereum/sdk/js",
|
||||
"target_chains/ethereum/sdk/solidity",
|
||||
"target_chains/ethereum/examples/coin_flip/app",
|
||||
"target_chains/ethereum/examples/oracle_swap/app",
|
||||
"target_chains/sui/sdk/js",
|
||||
"target_chains/sui/cli",
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
# Pyth Entropy Solidity SDK
|
||||
|
||||
The Pyth Entropy Solidity SDK allows you to generate secure random numbers on the blockchain by
|
||||
interacting with the Pyth Entropy protocol.
|
||||
This SDK can be used for any application that requires random numbers, such as NFT mints, gaming, and more.
|
||||
|
||||
**WARNING**: The Entropy protocol is currently in testnet. It is **NOT INTENDED** for use in production applications.
|
||||
Use this protocol at your own risk.
|
||||
|
||||
## Install
|
||||
|
||||
TODO
|
||||
|
||||
## Setup
|
||||
|
||||
To use the SDK, you need the address of an Entropy contract on your blockchain and a randomness provider.
|
||||
The following table lists the current deployments of entropy.
|
||||
|
||||
| Chain | Entropy Address | Provider |
|
||||
| avalanche-fuji | 0xD42c7a708E74AD19401D907a14146F006c851Ee3 | 0x368397bDc956b4F23847bE244f350Bde4615F25E
|
||||
| optimism-goerli | 0x28F16Af4D87523910b843a801454AEde5F9B0459 | 0x368397bDc956b4F23847bE244f350Bde4615F25E
|
||||
| eos-evm-testnet | 0xD42c7a708E74AD19401D907a14146F006c851Ee3 | 0x368397bDc956b4F23847bE244f350Bde4615F25E
|
||||
|
||||
Choose one of these networks and instantiate an `IEntropy` contract in your solidity contract:
|
||||
|
||||
```solidity
|
||||
IEntropy entropy = IEntropy(<address>);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
To generate a random number, follow these steps.
|
||||
|
||||
### 1. Commit to a random number
|
||||
|
||||
Generate a 32-byte random number on the client side, then hash it with keccak256 to create a commitment.
|
||||
You can do this with typescript and web3.js as follows:
|
||||
|
||||
```typescript
|
||||
const randomNumber = web3.utils.randomHex(32);
|
||||
const commitment = web3.utils.keccak256(randomNumber);
|
||||
```
|
||||
|
||||
### 2. Request a number from Entropy
|
||||
|
||||
Invoke the `request` method of the `IEntropy` contract:
|
||||
|
||||
```solidity
|
||||
uint64 sequenceNumber = entropy.request(provider, commitment, true)
|
||||
```
|
||||
|
||||
This method returns a sequence number. Store this sequence number for use in later steps.
|
||||
If you are invoking this off-chain, the method also emits a `PythRandomEvents.Requested` event that contains the sequence number in it.
|
||||
|
||||
### 3. Fetch the provider's number
|
||||
|
||||
Fetch the provider's random number from them.
|
||||
For the provider `0x368397bDc956b4F23847bE244f350Bde4615F25E` you can query the webservice at https://fortuna-staging.pyth.network :
|
||||
|
||||
```typescript
|
||||
await axios.get(
|
||||
`https://fortuna-staging.pyth.network/v1/chains/${chainName}/revelations/${sequenceNumber}`
|
||||
);
|
||||
```
|
||||
|
||||
This method returns a JSON object containing the provider's random number.
|
||||
|
||||
### 4. Reveal the number
|
||||
|
||||
Invoke the `reveal` method on the `IEntropy` contract:
|
||||
|
||||
```solidity
|
||||
bytes32 randomNumber = entropy.reveal(
|
||||
provider,
|
||||
sequenceNumber,
|
||||
randomNumber,
|
||||
providerRandomNumber
|
||||
)
|
||||
```
|
||||
|
||||
This method will combine the user and provider's random numbers, along with the blockhash, to construct the final secure random number.
|
|
@ -0,0 +1,57 @@
|
|||
# Coin Flip Example application
|
||||
|
||||
The coin flip example demonstrates how to use Pyth Entropy to flip a fair coin.
|
||||
|
||||
## Try it out
|
||||
|
||||
To try the example, first run the following commands from the root of the `pyth-crosschain` repository:
|
||||
|
||||
```shell
|
||||
npm install
|
||||
npx lerna run build
|
||||
```
|
||||
|
||||
These commands will build dependencies for the typescript project.
|
||||
|
||||
Next, choose a network to run the example on.
|
||||
The example has been deployed on the following networks:
|
||||
|
||||
| Chain Name | Address | RPC |
|
||||
| optimism-goerli | 0x075A5160FF6462924B4124595F6f987187496476 | https://goerli.optimism.io |
|
||||
|
||||
You will also need the private key of a wallet with some gas tokens for your chosen network.
|
||||
Then, from the `coin_flip/app` directory, run the following command:
|
||||
|
||||
```
|
||||
npm run flip-coin -- \
|
||||
--private-key <hexadecimal evm private key> \
|
||||
--chain-name <chain name> \
|
||||
--address <address> \
|
||||
--rpc-url <rpc url>
|
||||
```
|
||||
|
||||
You can populate the arguments to this command from the table above.
|
||||
The command should print output like this:
|
||||
|
||||
```text
|
||||
Running coin flip prototcol.
|
||||
1. Generating user's random number...
|
||||
number : 0x79b029406af43b11937bca98c49633f9382ed7d3fc0d60e110258c5c8f0d1a05
|
||||
commitment: 0xd4bca63083f9fb9e83e68348cb48f45babd820fc3559c60ba9a67b0ab3845cea
|
||||
2. Requesting coin flip...
|
||||
fee : 87 wei
|
||||
tx : 0x3a59bb8c1aaa8c6ff97147bb3197e9b89c0d87174b0b6c32374fc62de6d8db94
|
||||
sequence : 50
|
||||
3. Retrieving provider's random number...
|
||||
fetch url : https://fortuna-staging.pyth.network/v1/chains/optimism-goerli/revelations/50
|
||||
number : 0x760e53a19a4677ef671fde63db59462dfb3e09e94418e9962e2fa764026b8400
|
||||
4. Revealing the result of the coin flip...
|
||||
tx : 0x0549b93b12684187f73ddcaf8351ca4049867882c1b138989e15363a4d103220
|
||||
result : tails
|
||||
```
|
||||
|
||||
## Understanding the Example
|
||||
|
||||
The example consists of a Solidity contract and a Typescript script.
|
||||
See the extensive code comments in the contract at `contract/src/CoinFlip.sol` to learn how the example works.
|
||||
The typescript script is available at `app/src/flip_coin.ts` and demonstrates how to interact with the contract.
|
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
lib
|
||||
.dccache
|
||||
*mnemonic*
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "@pythnetwork/eth-coin-flip-example",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@pythnetwork/pyth-evm-js": "*",
|
||||
"@pythnetwork/pyth-sdk-solidity": "*",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.11.64",
|
||||
"buffer": "^6.0.3",
|
||||
"ethers": "^5.7.2",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"flip-coin": "npm run build && node lib/flip_coin.js",
|
||||
"format": "prettier --write src/"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_entropy",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "_entropyProvider",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "IncorrectSender",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "InsufficientFee",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint64",
|
||||
"name": "sequenceNumber",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"name": "FlipRequest",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bool",
|
||||
"name": "isHeads",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"name": "FlipResult",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "getFlipFee",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "fee",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "userCommitment",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "requestFlip",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "sequenceNumber",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "userRandom",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "providerRandom",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "revealFlip",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"stateMutability": "payable",
|
||||
"type": "receive"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,123 @@
|
|||
import Web3 from "web3";
|
||||
import yargs from "yargs";
|
||||
import { hideBin } from "yargs/helpers";
|
||||
import HDWalletProvider from "@truffle/hdwallet-provider";
|
||||
import CoinFlipAbi from "./CoinFlipAbi.json";
|
||||
import axios from "axios";
|
||||
|
||||
const argv = yargs(hideBin(process.argv))
|
||||
.option("private-key", {
|
||||
description: "Private key (as a hexadecimal string) of the sender",
|
||||
type: "string",
|
||||
required: true,
|
||||
})
|
||||
.option("fortuna-url", {
|
||||
description: "URL of the fortuna server for your chosen provider",
|
||||
type: "string",
|
||||
default: "https://fortuna-staging.pyth.network",
|
||||
})
|
||||
.option("chain-name", {
|
||||
description:
|
||||
"The name of your blockchain (for accessing data from fortuna)",
|
||||
type: "string",
|
||||
required: true,
|
||||
})
|
||||
.option("address", {
|
||||
description: "The address of the CoinFlip contract",
|
||||
type: "string",
|
||||
required: true,
|
||||
})
|
||||
.option("rpc-url", {
|
||||
description:
|
||||
"The URL of an ETH RPC service for reading/writing to the blockchain",
|
||||
type: "string",
|
||||
required: true,
|
||||
})
|
||||
.help()
|
||||
.alias("help", "h")
|
||||
.parserConfiguration({
|
||||
"parse-numbers": false,
|
||||
})
|
||||
.parseSync();
|
||||
|
||||
const fortunaUrl = argv.fortunaUrl;
|
||||
const chainName = argv.chainName;
|
||||
const coinFlipContractAddress = argv.address;
|
||||
const rpc = argv.rpcUrl;
|
||||
const privateKey = argv.privateKey;
|
||||
|
||||
async function fetchWithRetry(url: string, maxRetries: number): Promise<any> {
|
||||
let retryCount = 0;
|
||||
|
||||
async function doRequest() {
|
||||
try {
|
||||
const response = await axios.get(url);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (retryCount < maxRetries) {
|
||||
retryCount++;
|
||||
setTimeout(doRequest, 1000);
|
||||
} else {
|
||||
console.error("Max retry attempts reached. Exiting.");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await doRequest(); // Start the initial request
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const provider = new HDWalletProvider({
|
||||
privateKeys: [privateKey],
|
||||
providerOrUrl: rpc,
|
||||
});
|
||||
|
||||
const web3 = new Web3(provider as any);
|
||||
|
||||
const coinFlipContract = new web3.eth.Contract(
|
||||
CoinFlipAbi as any,
|
||||
coinFlipContractAddress
|
||||
);
|
||||
|
||||
console.log(`Running coin flip prototcol.`);
|
||||
|
||||
console.log("1. Generating user's random number...");
|
||||
const randomNumber = web3.utils.randomHex(32);
|
||||
const commitment = web3.utils.keccak256(randomNumber);
|
||||
console.log(` number : ${randomNumber}`);
|
||||
console.log(` commitment: ${commitment}`);
|
||||
|
||||
console.log("2. Requesting coin flip...");
|
||||
const flipFee = await coinFlipContract.methods.getFlipFee().call();
|
||||
console.log(` fee : ${flipFee} wei`);
|
||||
|
||||
const receipt = await coinFlipContract.methods
|
||||
.requestFlip(commitment)
|
||||
.send({ value: flipFee, from: provider.getAddress(0) });
|
||||
|
||||
console.log(` tx : ${receipt.transactionHash}`);
|
||||
const sequenceNumber = receipt.events.FlipRequest.returnValues.sequenceNumber;
|
||||
console.log(` sequence : ${sequenceNumber}`);
|
||||
|
||||
console.log("3. Retrieving provider's random number...");
|
||||
const url = `${fortunaUrl}/v1/chains/${chainName}/revelations/${sequenceNumber}`;
|
||||
console.log(` fetch url : ${url}`);
|
||||
// Note that there is a potential race condition here: the server may not have observed the request ^
|
||||
// before this HTTP response. Hence, we retry fetching the url a couple of times.
|
||||
const response = await fetchWithRetry(url, 3);
|
||||
const providerRandom = web3.utils.bytesToHex(response.value);
|
||||
console.log(` number : ${providerRandom}`);
|
||||
|
||||
console.log("4. Revealing the result of the coin flip...");
|
||||
const receipt2 = await coinFlipContract.methods
|
||||
.revealFlip(sequenceNumber, randomNumber, providerRandom)
|
||||
.send({ from: provider.getAddress(0) });
|
||||
console.log(` tx : ${receipt2.transactionHash}`);
|
||||
const isHeads = receipt2.events.FlipResult.returnValues.isHeads;
|
||||
console.log(` result : ${isHeads ? "heads" : "tails"}`);
|
||||
|
||||
provider.engine.stop();
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"extends": "../../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"rootDir": "src/",
|
||||
"outDir": "./lib",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src", "src/*.json"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
#!/bin/bash -e
|
||||
|
||||
# URL of the ethereum RPC node to use. Choose this based on your target network
|
||||
RPC_URL=https://api.avax-test.network/ext/bc/C/rpc
|
||||
RPC_URL=https://goerli.optimism.io
|
||||
|
||||
# The address of the Pyth contract on your network. See the list of contract addresses here https://docs.pyth.network/documentation/pythnet-price-feeds/evm
|
||||
ENTROPY_CONTRACT_ADDRESS="0xD42c7a708E74AD19401D907a14146F006c851Ee3"
|
||||
ENTROPY_CONTRACT_ADDRESS="0x28F16Af4D87523910b843a801454AEde5F9B0459"
|
||||
PROVIDER="0x368397bDc956b4F23847bE244f350Bde4615F25E"
|
||||
|
||||
# Avalanche fuji address:
|
||||
# 0x544c5ab499C38dff495724451783F63a3eeA40F2
|
||||
# Deployed contracts
|
||||
# Optimism goerli 0x075A5160FF6462924B4124595F6f987187496476
|
||||
|
||||
# Note the -l here uses a ledger wallet to deploy your contract. You may need to change this
|
||||
# option if you are using a different wallet.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-License-Identifier: Apache 2
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
// Import the entropy SDK in order to interact with the entropy contracts
|
||||
import "entropy-sdk-solidity/IEntropy.sol";
|
||||
|
||||
library CoinFlipErrors {
|
||||
|
@ -73,6 +74,11 @@ contract CoinFlip {
|
|||
emit FlipRequest(sequenceNumber);
|
||||
}
|
||||
|
||||
// Get the fee to flip a coin. See the comment above about fees.
|
||||
function getFlipFee() public returns (uint256 fee) {
|
||||
fee = entropy.getFee(entropyProvider);
|
||||
}
|
||||
|
||||
// Reveal the result of the coin flip. The caller must have an in-flight request for a coin flip, which is
|
||||
// identified by `sequenceNumber`. The caller must additionally provide the random number that they previously
|
||||
// committed to, as well as the entropy provider's random number. The provider's random number can be retrieved
|
||||
|
|
Loading…
Reference in New Issue