Skip to content

Commit

Permalink
Feat/milestone 2 (#24)
Browse files Browse the repository at this point in the history
* Fix tests

* Add milestone 3

* Add image id tutorial
  • Loading branch information
dastansam authored May 17, 2024
1 parent 815ec1d commit bd7447b
Show file tree
Hide file tree
Showing 31 changed files with 1,017 additions and 260 deletions.
360 changes: 360 additions & 0 deletions Cargo.lock

Large diffs are not rendered by default.

63 changes: 42 additions & 21 deletions INSTRUCTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ FiatRamps is a chain that aims to connect EBICS banking interface to Polkadot ec

Below is the workflow for easily ramping on and off to our chain:

- First and foremost, every user that wants to connect their bank account to FiatRamps, needs to call `map_iban_account` extrinsic
- First and foremost, every user that wants to connect their bank account to FiatRamps, needs to call `createAccount` extrinsic
- Once on-chain account is mapped to off-chain bank account, user can perform following actions:
- Burn funds, i.e withdraw from bank account
- Transfer funds to IBAN, i.e transfer funds to another IBAN account
Expand All @@ -35,6 +35,22 @@ Below is a tutorial that demonstrates how our Substrate solo chain works.

To get started, obviously make sure you have the necessary setup for Substrate development.

## Ebics Java Service

First, we need to run the EBICS Java service. This service is responsible for connecting to the bank account and providing an API for our offchain worker to interact with. You can find the service [here](https://github.com/element36-io/ebics-java-service). Follow the instructions in the README to run the service.

### Image ID

And you also need to get the image ID of the `hyperfridge` `riscv0` module. You can get it by running the following command:

```bash
docker compose run hyperfridge cat /app/IMAGE_ID.hex

dcaba464d4909890d6638dd14e7a25853a8dd2cad14639d0d310987b32a43957
```

Copy that image ID and pass it to the sudo extrinsic when the chain is running.

## Demo

Compile and run the node with:
Expand All @@ -51,7 +67,13 @@ suri: cup swing hill dinner pioneer mom stick steel sad raven oak practice
public_key: 5C555czPfaHgYhKhsRg2KNCLGCJ82jVsvweTHAnfvT83uy5T
```

Once you have submitted the call, head over to `Extrinsics -> fiatRamps -> createAccount` call. Here we need to map Alice's IBAN number to his on-chain account address. Simply choose Alice as a signer and the copy and paste value of the IBAN number from the following JSON file and submit the extrinsic.
Insert image ID with `fiatRamps::setRisc0ImageId` extrinsic call. This is necessary for offchain worker to know which image to use when running the `riscv0` module. Choose `fiatRamps -> setRisc0ImageId` extrinsic and paste the image ID from the previous step. Click `Submit transaction`.

![Image ID](/assets/image-id.png)

Then, choose `FiatRamps.setApiUrl` extrinsic and paste the new url for the API and click `Submit transaction`. This is only necessary if you have a different URL than the default one with Ebics Java service.

Once you have submitted the call, head over to `Extrinsics -> fiatRamps -> createAccount` call. Here we need to map Alice's IBAN number to his on-chain account address. Simply choose Alice as a signer, copy and paste value of the IBAN number from the following JSON file and submit the extrinsic.

```json
{
Expand All @@ -78,54 +100,53 @@ Once you have submitted the call, head over to `Extrinsics -> fiatRamps -> creat

#### Stablecoins are minted to Alice

Stablecoins are minted only when offchain worker detects an incoming transaction from an unknown IBAN address, i.e from an IBAN address is not mapped to any on-chain account address. In order to see how it works in action, head over to the EBICS service [API](http://w.e36.io:8093/ebics/swagger-ui/?url=/ebics/v2/api-docs/#/). Open `/ebics/api-v1/createOrder` tab and fill out Charlie's details. Namely, we will `purpose` field with Alice's on-chain account and `receipientIban` field with her IBAN number. Fill out Charlie's IBAN from above JSON file as the `sourceIban`. Finally, execute the call. It should look something like this:
Stablecoins are minted only when offchain worker detects an incoming transaction from an unknown IBAN address, i.e from an IBAN address is not mapped to any on-chain account address. In order to see how it works in action, head over to the EBICS service [API](http://w.e36.io:8093/ebics/swagger-ui/?url=/ebics/v2/api-docs/#/). Open `/ebics/api-v1/createOrder` tab and fill out Bob's details. Namely, we will fill `purpose` field with Alice's on-chain account and `receipientIban` field with her IBAN number. Fill out Bob's IBAN from above JSON file as the `sourceIban`. Finally, execute the call. It should look something like this:

![Alice mints](/assets/ebics-minting.png)
![Alice mints](/assets/ebics-minting-zk.png)

Then wait a little bit until offchain worker picks up the statement. After some time (3-5 blocktimes) you should see that new tokens were minted:

![Mint event happens](/assets/ocw-minting.png)
![Mint event happens](/assets/ocw-minting-zk.png)

This is how new stablecoins are minted in our chain.

### Stablecoins are burned from Alice

Now, in order to see how burning works, we can either go to EBICS service again and call `/ebics/api-v1/unpeg` request or submit `fiatRamps.transfer` extrinsic. Let's use EBICS service again, as extrinsic calls are covered in the next demos. After filling up the `recipientIban` field with Charlie's IBAN, our call should look like this:
Now, in order to see how burning works, we can either go to EBICS service again and call `/ebics/api-v1/unpeg` request or submit `fiatRamps.transfer` extrinsic. Let's use EBICS service again, as extrinsic calls are covered in the next demos. After filling up the `recipientIban` field with Bob's IBAN, our call should look like this:

![Alice burns](/assets/ebics-burning.png)
![Alice burns](/assets/ebics-burning-zk.png)

Again, we wait for offchain worker to process the statement and shortly after we should see that it emits a Burn event:

![Burn event happens](/assets/ocw-burning.png)
![Burn event happens](/assets/ocw-burning-zk.png)

#### Alice transfers to Charlie via EBICS API
#### Alice transfers to Jack via EBICS API

For this part of the tutorial we will need to map Charlie and Bob's IBAN numbers to their on-chain account addresses. We submit `createAccount` extrinsic with IBAN addresses of Charlie and Bob, respectively, making sure that they are signing the extrinsic call. For example, Bob mapping his account would look like this:
For this part of the tutorial we will need to map Jack and Bob's IBAN numbers to their on-chain account addresses. We submit `createAccount` extrinsic with IBAN addresses of Jack and Bob, respectively, making sure that they are signing the extrinsic call. For example, Bob mapping his account would look like this:

![Bob connects his account](/assets/bob-map-iban.png)

It is also very important to know that we can not use PolkadotJS transfer button to move funds in our chain. This would break synchronization between the bank account balance and on-chain balance. In the future it should be disabled and the only way to transfer should be via burn requests.

To make a transfer from Alice to Charlie, we head over to our EBICS service [API](http://w.e36.io:8093/ebics/swagger-ui/?url=/ebics/v2/api-docs/#/). We open `/ebics/api-v1/createOrder` tab and fill out Charlie's details. Namely, we will `purpose` field with Charlie's on-chain account and `receipientIban` field with his IBAN number. And `sourceIban` field with Alice's IBAN number. We can then specify the amount and other fields. It should look similar to this:
To make a transfer from Alice to Jack, we head over to our EBICS service [API](http://w.e36.io:8093/ebics/swagger-ui/?url=/ebics/v2/api-docs/#/). We open `/ebics/api-v1/createOrder` tab and fill out Jack's details. Namely, we will `purpose` field with Jack's on-chain account and `receipientIban` field with his IBAN number. And `sourceIban` field with Alice's IBAN number. We can then specify the amount and other fields. It should look similar to this:

![Alice transfer to Charlie](/assets/alice-transfer-charlie.png)
![Alice transfer to Jack](/assets/alice-transfers-jack-zk.png)

This will create a new order and will end up in Alice's bank statement as an outgoing transaction. And when our offchain worker queries bank statements, it will parse Charlie's on-chain account from `reference` field or query it from storage using his IBAN number. Note that transfer on-chain won't happen instantly, since offchain worker performs activities within a minimum of 5 block times interval (~30 seconds) and there are 3 types of actions. So, there is around 90 seconds of time between each new bank statements processing.
This will create a new order and will end up in Alice's bank statement as an outgoing transaction. And when our offchain worker queries bank statements, it will parse Jack's on-chain account from `reference` field or query it from storage using his IBAN number. Note that transfer on-chain won't happen instantly, since offchain worker performs activities within a minimum of 5 block times interval (~30 seconds) and there are 3 types of actions. So, there is around 90 seconds of time between each new bank statements processing.

Once offchain worker has processed new statements, two `Transfer` events occur:

![Transfer from Alice to Charlie](/assets/alice-bob-events.png)
![Transfer from Alice to Jack](/assets/jack-to-alice-zk.png)

#### Alice transfers to Charlie via Extrinsic
#### Alice transfers to Jack via Extrinsic

We go to `Extrinsic` tab, choose `fiatRamps.transfer` extrinsic call and choose `destination` as `Address`. Fill out the necessary fields and make sure that the amount is a positive number, otherwise extrinsic will fail.
We go to `Extrinsic` tab, choose `fiatRamps.transfer` extrinsic call and choose `destination` as `Address`. Fill out the necessary fields and make sure that the amount is a positive number and more than 1 UNIT (10 decimals), otherwise extrinsic will fail.

![Extrinsic from Alice to Charlie](/assets/alice-charlie-ext.png)
![Extrinsic from Alice to Jack](/assets/alice-jack-ext-zk.png)

After we submit extrinsic, we can see that the burn request event is created.

![Extrinsic from Alice to Charlie](/assets/alice-charlie-event-request.png)

Shortly after (approximately 3-4 blocks), we can notice that the burn request has been processed and transfer between Alice and Charlie occurs. Notice that transfer occurs from an unknown wallet to Charlie, not directly from Alice to Charlie. This is offchain worker's account that stores the funds until transaction is finalized by LibEUfin backend.
Shortly after (approximately 3-4 blocks), we can notice that the burn request has been processed and transfer between Alice and Jack occurs. Notice that transfer occurs from an unknown wallet to Jack, not directly from Alice to Jack. This is offchain worker's account that stores the funds until transaction is finalized by LibEUfin backend.

![Extrinsic from Alice to Charlie](/assets/alice-charlie-transfer.png)
![Extrinsic from Alice to Jack](/assets/alice-jack-event-zk.png)

10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ The repository contains a substrate solo chain that is connected to
an arbitrary bank account which supports
the EBICS banking interface [(ISO20022)](https://www.iso20022.org/).
It synchronizes balances and transaction of a bank account with the solo chain
using an off-chain-worker. This worker also contains extrinsincs to
using an off-chain-worker and zero-knowledge proofs. This worker also contains extrinsincs to
trigger wire-transfers on the connected bank account.

Later we want to use Zero-Knowledge proofs to enable trustless atomic swap between
FIAT and any ledger technology. The system will be called Hyperfridge - the whitepaper
Zero-Knowledge proofs are used to enable trustless atomic swap between
FIAT and any ledger technology. The system is called Hyperfridge - the whitepaper
is [here](docs/hyperfridge-draft.pdf).

### Getting Started
Expand All @@ -19,7 +19,9 @@ Our runtime includes a pallet called `fiat-ramps` that is responsible for synchr

`Fiat-ramps` is located inside `/pallets` folder. It is an offchain-worker pallet that primarily does two activities:

- Poll EBICS service to get the latest bank statements and process every transaction
- Poll EBICS service to get the latest bank statements, puts them in the queue
- Polls EBICS service for the ZK receipt of the queued statements until it is available
- Reads the receipt and verifies it
- Process *burn requests* registered in the local pallet storage and send `unpeg` request to EBICS API.

*Burn request* is a single request to *burn*, *transfer* funds from EBICS supporting bank account using *extrinsics*. Account submits a *request* to a chain and it is registered in the local storage. Offchain worker picks up the burn request and sends it to the EBICS service. If everything goes well, EBICS service confirms the transaction and includes it in the statement, thus *finalizing* the burn request. This is done because transactions in traditional banks are not instant and sometimes it takes days to finalize them.
Expand Down
Binary file removed assets/alice-bob-events.png
Binary file not shown.
Binary file removed assets/alice-charlie-event-request.png
Binary file not shown.
Binary file removed assets/alice-charlie-ext.png
Binary file not shown.
Binary file removed assets/alice-charlie-transfer.png
Binary file not shown.
Binary file added assets/alice-jack-event-zk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/alice-jack-ext-zk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/alice-transfer-charlie.png
Binary file not shown.
Binary file added assets/alice-transfers-jack-zk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/ebics-burning-zk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/ebics-burning.png
Binary file not shown.
Binary file added assets/ebics-minting-zk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/ebics-minting.png
Binary file not shown.
Binary file added assets/image-id.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/jack-to-alice-zk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/ocw-burning-zk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/ocw-burning.png
Binary file not shown.
Binary file added assets/ocw-minting-zk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/ocw-minting.png
Binary file not shown.
9 changes: 6 additions & 3 deletions pallets/fiat-ramps/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@ frame-support = { default-features = false, git = "https://github.com/paritytech
sp-keystore = { git = "https://github.com/paritytech/substrate.git", optional = true, branch = "polkadot-v1.0.0" }
sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }
sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }
sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }
sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0", features = ["serde"] }
sp-io = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }

pallet-sudo = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }
risc0-zkvm = { version = "0.20.1", default-features = false }
serde-json-core = { version = "0.5.1", default-features = false }

[dev-dependencies]
pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" }
Expand All @@ -46,5 +47,7 @@ std = [
'sp-keystore',
'pallet-balances/std',
'pallet-timestamp/std',
'pallet-sudo/std'
'pallet-sudo/std',
'serde-json-core/std',
]
risc0-dev-mode = []
21 changes: 12 additions & 9 deletions pallets/fiat-ramps/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,28 @@
/// them with the pallet-specific identifier.
use crate::KEY_TYPE;
use sp_core::sr25519::Signature as Sr25519Signature;
use sp_runtime::{
app_crypto::{app_crypto, sr25519},
traits::Verify,
MultiSignature, MultiSigner,
};
use sp_std::convert::TryFrom;
use sp_runtime::app_crypto::{app_crypto, sr25519};
use sp_runtime::{traits::Verify, MultiSignature, MultiSigner};

app_crypto!(sr25519, KEY_TYPE);

pub struct OcwAuthId;

impl frame_system::offchain::AppCrypto<MultiSigner, MultiSignature> for OcwAuthId {
type RuntimeAppPublic = Public;
type GenericSignature = sp_core::sr25519::Signature;
type GenericPublic = sp_core::sr25519::Public;
type RuntimeAppPublic = Public;
type GenericSignature = sp_core::sr25519::Signature;
type GenericPublic = sp_core::sr25519::Public;
}

// implemented for mock runtime in test
impl frame_system::offchain::AppCrypto<<Sr25519Signature as Verify>::Signer, Sr25519Signature>
for OcwAuthId
for OcwAuthId
{
type RuntimeAppPublic = Public;
type GenericSignature = sp_core::sr25519::Signature;
type GenericPublic = sp_core::sr25519::Public;
type RuntimeAppPublic = Public;
type GenericSignature = sp_core::sr25519::Signature;
type GenericPublic = sp_core::sr25519::Public;
}
Loading

0 comments on commit bd7447b

Please sign in to comment.