Skip to content

Commit 0903c24

Browse files
feat: add chaincode-to-fsc asset transfer POC example
Signed-off-by: Aaradhy Chinche <aaradhychinche@gmail.com>
1 parent d167aa3 commit 0903c24

25 files changed

Lines changed: 3127 additions & 0 deletions

File tree

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# Chaincode -> FSC Example (Asset Transfer)
2+
3+
## Topology
4+
5+
This example uses a single process to simulate the tutorial roles below:
6+
7+
* Client (CLI)
8+
* FSC Node (initiator flow)
9+
* Endorser (responder flow)
10+
* Fabric-X (ordering + finality)
11+
12+
### Flow diagram
13+
14+
```text
15+
Client (CLI)
16+
|
17+
v
18+
FSC Node (initiator flow)
19+
|-- ReadAsset(id)
20+
|-- validate transfer
21+
|-- prepare transaction
22+
|-- collect endorsement
23+
|-- submit transaction
24+
|-- verify state
25+
v
26+
Endorser (responder flow)
27+
|
28+
v
29+
Fabric-X (ordering + finality)
30+
```
31+
32+
## API
33+
34+
The example follows the same asset-transfer shape as Fabric chaincode:
35+
36+
* `ReadAsset(id string)`
37+
* `TransferAsset(id, newOwner string)`
38+
39+
`ReadAsset` reads the current asset state. `TransferAsset` drives the FSC flow, submits the transaction, and verifies the updated state after finality.
40+
41+
These functions mirror the Fabric asset-transfer chaincode, but are implemented using FSC views instead of on-ledger chaincode execution.
42+
43+
## Execution
44+
45+
Run the example with:
46+
47+
```bash
48+
go run ./cmd/token-transfer --asset-id a1 --new-owner bob
49+
```
50+
51+
## Example output
52+
53+
```text
54+
reading asset
55+
validating transfer
56+
preparing transaction (phase: pre-submission)
57+
endorsing transaction
58+
submitting transaction (phase: post-submission)
59+
verifying state
60+
success: asset a1 now owned by bob
61+
```
62+
63+
Each line corresponds to a stage in the FSC transaction lifecycle.
64+
65+
## Notes
66+
67+
* The roles are simulated in a single process.
68+
* In this example, client and endorser roles are simulated within a single process for simplicity, but the flow structure matches how FSC nodes would interact in a real network.
69+
* The example stays tutorial-style and intentionally small.
70+
* The code mirrors the chaincode asset-transfer API without extra abstractions.
71+
72+
## Future Work
73+
74+
This example currently simulates all roles in a single process for simplicity.
75+
76+
The next step is to extend this into a real multi-node FSC + Fabric-X setup where:
77+
78+
* separate FSC nodes act as clients and endorsers
79+
* communication happens over networked views
80+
* ordering and finality are handled by a real Fabric-X deployment
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
8+
"github.com/hyperledger-labs/fabric-smart-client/examples/chaincode-to-fsc/internal/flow"
9+
)
10+
11+
func main() {
12+
assetID := flag.String("asset-id", "", "asset identifier")
13+
newOwner := flag.String("new-owner", "", "new owner")
14+
flag.Parse()
15+
16+
if *assetID == "" || *newOwner == "" {
17+
fmt.Fprintln(os.Stderr, "usage: go run ./cmd/token-transfer --asset-id <id> --new-owner <owner>")
18+
os.Exit(2)
19+
}
20+
21+
result, err := flow.TransferAsset(*assetID, *newOwner)
22+
if err != nil {
23+
fmt.Fprintf(os.Stderr, "error: %v\n", err)
24+
os.Exit(1)
25+
}
26+
27+
fmt.Println(result)
28+
}

examples/chaincode-to-fsc/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/hyperledger-labs/fabric-smart-client/examples/chaincode-to-fsc
2+
3+
go 1.25.7
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package flow
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/hyperledger-labs/fabric-smart-client/examples/chaincode-to-fsc/internal/model"
8+
"github.com/hyperledger-labs/fabric-smart-client/examples/chaincode-to-fsc/internal/protocol"
9+
)
10+
11+
func ReadAsset(assetID string) (model.Asset, error) {
12+
if strings.TrimSpace(assetID) == "" {
13+
return model.Asset{}, fmt.Errorf("asset id cannot be empty")
14+
}
15+
16+
asset, err := protocol.GetAsset(assetID)
17+
if err != nil {
18+
return model.Asset{}, fmt.Errorf("read asset %s: %w", assetID, err)
19+
}
20+
21+
return asset, nil
22+
}
23+
24+
func TransferAsset(assetID, newOwner string) (string, error) {
25+
if strings.TrimSpace(newOwner) == "" {
26+
return "", fmt.Errorf("new owner cannot be empty")
27+
}
28+
29+
fmt.Println("reading asset")
30+
asset, err := ReadAsset(assetID)
31+
if err != nil {
32+
return "", fmt.Errorf("read asset failed: %w", err)
33+
}
34+
35+
fmt.Println("validating transfer")
36+
if asset.Owner == newOwner {
37+
return "", fmt.Errorf("asset %s is already owned by %s", assetID, newOwner)
38+
}
39+
40+
fmt.Println("preparing transaction (phase: pre-submission)")
41+
fmt.Println("endorsing transaction")
42+
if err := protocol.SubmitTransfer(assetID, newOwner); err != nil {
43+
return "", fmt.Errorf("submit transfer failed: %w", err)
44+
}
45+
46+
fmt.Println("submitting transaction (phase: post-submission)")
47+
fmt.Println("verifying state")
48+
updated, err := ReadAsset(assetID)
49+
if err != nil {
50+
return "", fmt.Errorf("verify asset failed: %w", err)
51+
}
52+
if updated.Owner != newOwner {
53+
return "", fmt.Errorf("transfer verification failed: expected owner %s, got %s", newOwner, updated.Owner)
54+
}
55+
56+
result := fmt.Sprintf("success: asset %s now owned by %s", updated.ID, updated.Owner)
57+
return result, nil
58+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package model
2+
3+
type Asset struct {
4+
ID string
5+
Owner string
6+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package protocol
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/hyperledger-labs/fabric-smart-client/examples/chaincode-to-fsc/internal/model"
7+
)
8+
9+
var assets = map[string]model.Asset{
10+
"a1": {
11+
ID: "a1",
12+
Owner: "alice",
13+
},
14+
}
15+
16+
func GetAsset(id string) (model.Asset, error) {
17+
asset, ok := assets[id]
18+
if !ok {
19+
return model.Asset{}, fmt.Errorf("asset %s not found", id)
20+
}
21+
return asset, nil
22+
}
23+
24+
func SubmitTransfer(id, newOwner string) error {
25+
asset, ok := assets[id]
26+
if !ok {
27+
return fmt.Errorf("asset %s not found", id)
28+
}
29+
asset.Owner = newOwner
30+
assets[id] = asset
31+
return nil
32+
}

0 commit comments

Comments
 (0)