Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added cardano connector plugin #1636

Open
wants to merge 44 commits into
base: main
Choose a base branch
from

Conversation

SupernaviX
Copy link
Contributor

@SupernaviX SupernaviX commented Feb 8, 2025

Proposed changes

Adds a cardano connector plugin to firefly core. This connector interacts with the firefly-cardanoconnect service from firefly-cardano.


Types of changes

  • Bug fix
  • New feature added
  • Documentation Update

Please make sure to follow these points

  • I have read the contributing guidelines.
  • I have performed a self-review of my own code or work.
  • I have commented my code, particularly in hard-to-understand areas.
  • My changes generates no new warnings. (uh
  • My Pull Request title is in format < issue name > eg Added links in the documentation.

firefly


Other Information

The Cardano connector and implementation is still a work in progress; in particular we need documentation and tests. However, it works well enough to demo, and I'd like to merge this relatively soon so that people can run it without building firefly-core from a branch, or downloading an image from a private repo.

@SupernaviX SupernaviX requested a review from a team as a code owner February 8, 2025 00:13
Signed-off-by: Simon Gellis <[email protected]>
Signed-off-by: Simon Gellis <[email protected]>
Copy link

codecov bot commented Feb 8, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 99.96%. Comparing base (71f04f3) to head (6ef9ba7).

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #1636    +/-   ##
========================================
  Coverage   99.95%   99.96%            
========================================
  Files         339      342     +3     
  Lines       29780    30455   +675     
========================================
+ Hits        29768    30443   +675     
  Misses          8        8            
  Partials        4        4            

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@EnriqueL8 EnriqueL8 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! It's fantastic to see 👏🏼

Some initial thoughts and will follow up with more thorough review after playing around with it 😃

Address string `json:"address"`
}

var addressVerify = regexp.MustCompile("^addr1|^addr_test1|^stake1|^stake_test1")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is just used for (extremely simple) address validation. Cardano uses bech32 addresses with a conventional set of HRPs, but I wanted to avoid pulling in a bech32 decoder, so I allowlisted the conventional HRPs instead.

Comment on lines +205 to +222
func (c *Cardano) DeployContract(ctx context.Context, nsOpID, signingKey string, definition, contract *fftypes.JSONAny, input []interface{}, options map[string]interface{}) (submissionRejected bool, err error) {
body := map[string]interface{}{
"id": nsOpID,
"contract": contract,
"definition": definition,
}
var resErr common.BlockchainRESTError
res, err := c.client.R().
SetContext(ctx).
SetBody(body).
SetError(&resErr).
Post("/contracts/deploy")
if err != nil || !res.IsSuccess() {
return resErr.SubmissionRejected, common.WrapRESTError(ctx, &resErr, res, err, coremsgs.MsgCardanoconnectRESTErr)
}
return false, nil
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the signing key being used

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. The "smart contracts" we're running in the cardano connector are offchain wasm components, nothing is actually published to the chain when you deploy a contract.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay need to understand this a bit better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was the offline chat enough to explain why we don't need the signing key here?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it was! Going to test this e2e myself as well

Comment on lines 390 to 393
func (c *Cardano) GenerateErrorSignature(ctx context.Context, event *fftypes.FFIErrorDefinition) string {
// TODO: impl
return ""
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be interested to understand more how errors are reported in Cardano from smart contract

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cardano smart contract doesn't report errors right now, only events. I don't understand the difference between errors and events, and I couldn't find any code in other plugins which streamed errors from the connector.

I've copied the "event signature" code into this function, but that's just a guess at what it's supposed to do. I'd want to see how consumers actually use events to be more confident about that.

Comment on lines 542 to 550
if !isBatch {
var receipt common.BlockchainReceiptNotification
_ = json.Unmarshal(msgBytes, &receipt)

err := common.HandleReceipt(ctx, "", c, &receipt, c.callbacks)
if err != nil {
l.Errorf("Failed to process receipt: %+v", msgTyped)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that this is heavily inspired from FFTM and the evm plugin - I would recommend in the future moving to a durable event stream here where the receipts have to be ack'd back as part of a batch.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was using EVM, Tezos, and FFTM as references for how the eventstream is supposed to work (trying my best to make Cardano match the semantics of other connectors). It looked like other connectors assume that every message in a batch is an event, not a receipt.

I think it makes sense to use resilient ack-ing for everything, but I'm a little wary about inventing semantics for it in this plugin that nothing else is using.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - probably as a follow up. I'm building a framework at moment to allow for resilient ack-ing and get confirmation from FireFly core it has processed things correctly

Copy link
Contributor

@EnriqueL8 EnriqueL8 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks really good! A few comments 😃

Going to give it a go locally

Comment on lines 45 to 48
const (
ReceiptTransactionSuccess string = "TransactionSuccess"
ReceiptTransactionFailed string = "TransactionFailed"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be able to re-use these?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They're not defined anywhere shared. You want I should move them to common?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I though they were, if you can move them to common would be great only if you have the bandwidth

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines +205 to +222
func (c *Cardano) DeployContract(ctx context.Context, nsOpID, signingKey string, definition, contract *fftypes.JSONAny, input []interface{}, options map[string]interface{}) (submissionRejected bool, err error) {
body := map[string]interface{}{
"id": nsOpID,
"contract": contract,
"definition": definition,
}
var resErr common.BlockchainRESTError
res, err := c.client.R().
SetContext(ctx).
SetBody(body).
SetError(&resErr).
Post("/contracts/deploy")
if err != nil || !res.IsSuccess() {
return resErr.SubmissionRejected, common.WrapRESTError(ctx, &resErr, res, err, coremsgs.MsgCardanoconnectRESTErr)
}
return false, nil
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it was! Going to test this e2e myself as well

Comment on lines 489 to 502
if (operation.Status == core.OpStatusPending || operation.Status == core.OpStatusInitialized) && txStatus != cardanoTxStatusPending {
receipt := &common.BlockchainReceiptNotification{
Headers: common.BlockchainReceiptHeaders{
ReceiptID: statusResponse.GetString("id"),
ReplyType: replyType,
},
TxHash: statusResponse.GetString("transactionHash"),
Message: statusResponse.GetString("errorMessage"),
ProtocolID: receiptInfo.GetString("protocolId")}
err := common.HandleReceipt(ctx, operation.Namespace, c, receipt, c.callbacks)
if err != nil {
log.L(ctx).Warnf("Failed to handle receipt")
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@EnriqueL8 by "this hack" do you mean updating the status inside of GetTransactionStatus? I can get rid of that, I just thought that it was a "better safe than sorry" redundancy which other connectors were using.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Comment on lines 580 to 586
var receipt common.BlockchainReceiptNotification
_ = json.Unmarshal(msgBytes, &receipt)

err := common.HandleReceipt(ctx, namespace, c, &receipt, c.callbacks)
if err != nil {
l.Errorf("Failed to process receipt: %+v", msgTyped)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there no ack here? I couldn't find where you differentiate in the firefly-cardano between a batch and this receipt. Would recommend this being durable please

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@EnriqueL8 this is the same logic (literally copypasted :) ) as the ethereum and fabric and tezos connectors. None of them are using acks.

We discussed this before: #1636 (comment)

I'm open to making receipts durable, but I don't want to invent the semantics for it in this single connector if it's something that all connectors should be doing.

Copy link
Contributor

@EnriqueL8 EnriqueL8 Mar 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I've recently added a new function to the operation callback handler for durable receipts but I understand the complexity of making acking work in the cardano connector itself with receipts.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The thing is we are actively trying to remove this in the other plugins, so it's debt that would need be tackled for the Cardano to be truly robust for receipt listening

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there's a tool available, I'll try to use it

@@ -0,0 +1,47 @@
// Copyright © 2025 Kaleido, Inc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update all copyrights to be the company you work for in the new files added

func (nm *networkMap) generateCardanoAddressVerifier(identity *core.Identity, verifier *core.Verifier) *VerificationMethod {
return &VerificationMethod{
ID: verifier.Hash.String(),
Type: "PaymentVerificationKeyShelley_ed25519", // hope that it's safe to assume we always use Shelley
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happenes when it's a script witness?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can that happen? I thought DID documents were for identities attached to firefly nodes, i.e. the wallets they could spend from.

Comment on lines 564 to 582
switch msgJSON.GetString("type") {
case "ContractEvent":
signature := msgJSON.GetString("signature")

logger := log.L(ctx)
logger.Infof("[%d:%d/%d]: '%s'", batchID, i+1, count, signature)
logger.Tracef("Message: %+v", msgJSON)
c.processContractEvent(ctx, namespace, events, msgJSON)
case "Receipt":
var receipt common.BlockchainReceiptNotification
msgBytes, _ := json.Marshal(msgMap)
_ = json.Unmarshal(msgBytes, &receipt)

err := common.HandleReceipt(ctx, namespace, c, &receipt, c.callbacks)
if err != nil {
log.L(ctx).Errorf("Failed to process receipt: %+v", msgMap)
}
default:
log.L(ctx).Errorf("Unexpected message in batch: %+v", msgMap)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome, thanks!

@EnriqueL8
Copy link
Contributor

EnriqueL8 commented Mar 12, 2025

Error from build - FYI @onelapahead I believe you added recently this DX callback. Feels like a race condition as sometimes fails and others passes

2025-03-11T23:25:22.9354359Z === RUN   TestEventsWithManifest
2025-03-11T23:25:22.9354697Z time="2025-03-11T23:25:13Z" level=info msg="WS ws://127.0.0.1:37865/ws connected"
2025-03-11T23:25:22.9355572Z time="2025-03-11T23:25:13Z" level=error msg="Message cannot be parsed as JSON: invalid character '!' looking for beginning of value\n!}" dx=https role=event-loop
2025-03-11T23:25:22.9356408Z time="2025-03-11T23:25:13Z" level=warning msg="Failed to dispatch DX event: FF10380: Unexpected websocket event type from DX plugin: " dx=https
2025-03-11T23:25:22.9356819Z     ffdx_test.go:817: PASS:	OperationUpdate(mock.argumentMatcher)
2025-03-11T23:25:22.9357594Z     ffdx_test.go:817: PASS:	OperationUpdate(mock.argumentMatcher)
2025-03-11T23:25:22.9358206Z time="2025-03-11T23:25:13Z" level=info msg="WS ws://127.0.0.1:37865/ws closed: websocket: close 1006 (abnormal closure): unexpected EOF"
2025-03-11T23:25:22.9358329Z panic: 
2025-03-11T23:25:22.9358671Z assert: mock: I don't know what to return because the method call was unexpected.
2025-03-11T23:25:22.9359055Z 	Either do Mock.On("DXConnect").Return(...) first, or remove the DXConnect() call.
2025-03-11T23:25:22.9359216Z 	This method was unexpected:
2025-03-11T23:25:22.9359367Z 		DXConnect(*ffdx.FFDX)
2025-03-11T23:25:22.9364836Z 		0: &ffdx.FFDX{ctx:(*context.valueCtx)(0xc000446ba0), cancelCtx:(context.CancelFunc)(0x552540), capabilities:(*dataexchange.Capabilities)(0xc0004beb1f), callbacks:ffdx.callbacks{plugin:(*ffdx.FFDX)(0xc00012adc0), writeLock:sync.Mutex{state:0, sema:0x0}, handlers:map[string]dataexchange.Callbacks{"ns1:node1":(*dataexchangemocks.Callbacks)(0xc000288cd0)}, opHandlers:map[string]core.OperationCallbacks{"ns1":(*coremocks.OperationCallbacks)(0xc000288d20)}}, client:(*resty.Client)(0xc0004fac88), wsconn:(*wsclient.wsClient)(0xc0005610a0), needsInit:false, initialized:true, initMutex:sync.Mutex{state:1, sema:0x0}, nodes:map[string]*ffdx.dxNode{}, ackChannel:(chan *ffdx.ack)(0xc0004ce600), retry:(*retry.Retry)(0xc000308180), backgroundStart:false, backgroundRetry:(*retry.Retry)(nil), metrics:(*metricsmocks.Manager)(0xc00008a6e0)}
2025-03-11T23:25:22.9369728Z 	at: [/home/runner/work/firefly/firefly/mocks/dataexchangemocks/callbacks.go:17 /home/runner/work/firefly/firefly/internal/dataexchange/ffdx/ffdx.go:311 /home/runner/go/pkg/mod/github.com/hyperledger/[email protected]/pkg/wsclient/wsclient.go:301 /home/runner/go/pkg/mod/github.com/hyperledger/[email protected]/pkg/retry/retry.go:57 /home/runner/go/pkg/mod/github.com/hyperledger/[email protected]/pkg/retry/retry.go:42 /home/runner/go/pkg/mod/github.com/hyperledger/[email protected]/pkg/wsclient/wsclient.go:294 /home/runner/go/pkg/mod/github.com/hyperledger/[email protected]/pkg/wsclient/wsclient.go:497 /opt/hostedtoolcache/go/1.22.12/x64/src/runtime/asm_amd64.s:1695]
2025-03-11T23:25:22.9369747Z 
2025-03-11T23:25:22.9369898Z goroutine 251 [running]:
2025-03-11T23:25:22.9370413Z github.com/stretchr/testify/mock.(*Mock).fail(0xc000288cd0, {0xa50de6?, 0x8?}, {0xc0003cb500?, 0x1?, 0x1?})
2025-03-11T23:25:22.9370855Z 	/home/runner/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:332 +0x134
2025-03-11T23:25:22.9371388Z github.com/stretchr/testify/mock.(*Mock).MethodCalled(0xc000288cd0, {0xbbdc9e, 0x9}, {0xc0002eaf80, 0x1, 0x1})
2025-03-11T23:25:22.9371843Z 	/home/runner/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:499 +0x705
2025-03-11T23:25:22.9372335Z github.com/stretchr/testify/mock.(*Mock).Called(0xc000288cd0, {0xc0002eaf80, 0x1, 0x1})
2025-03-11T23:25:22.9372776Z 	/home/runner/go/pkg/mod/github.com/stretchr/[email protected]/mock/mock.go:464 +0x137
2025-03-11T23:25:22.9373431Z github.com/hyperledger/firefly/mocks/dataexchangemocks.(*Callbacks).DXConnect(0xc000288cd0, {0xb18e98, 0xc00012adc0})
2025-03-11T23:25:22.9373913Z 	/home/runner/work/firefly/firefly/mocks/dataexchangemocks/callbacks.go:17 +0x7a
2025-03-11T23:25:22.9374704Z github.com/hyperledger/firefly/internal/dataexchange/ffdx.(*FFDX).beforeConnect(0xc00012adc0, {0xb134d0, 0xc00008a730}, {0x751820?, 0xc0001de9a0?})
2025-03-11T23:25:22.9375146Z 	/home/runner/work/firefly/firefly/internal/dataexchange/ffdx/ffdx.go:311 +0x779
2025-03-11T23:25:22.9375554Z github.com/hyperledger/firefly-common/pkg/wsclient.(*wsClient).connect.func1(0x1)
2025-03-11T23:25:22.9376147Z 	/home/runner/go/pkg/mod/github.com/hyperledger/[email protected]/pkg/wsclient/wsclient.go:301 +0x82
2025-03-11T23:25:22.9376850Z github.com/hyperledger/firefly-common/pkg/retry.(*Retry).Do(0xc0005610e0, {0xb134d0, 0xc00008a730}, {0x0, 0x0}, 0xc00042fee0)
2025-03-11T23:25:22.9401500Z 	/home/runner/go/pkg/mod/github.com/hyperledger/[email protected]/pkg/retry/retry.go:57 +0xd6
2025-03-11T23:25:22.9401929Z github.com/hyperledger/firefly-common/pkg/retry.(*Retry).DoCustomLog(...)
2025-03-11T23:25:22.9402488Z 	/home/runner/go/pkg/mod/github.com/hyperledger/[email protected]/pkg/retry/retry.go:42
2025-03-11T23:25:22.9402962Z github.com/hyperledger/firefly-common/pkg/wsclient.(*wsClient).connect(0xc0005610a0, 0x0)
2025-03-11T23:25:22.9433341Z 	/home/runner/go/pkg/mod/github.com/hyperledger/[email protected]/pkg/wsclient/wsclient.go:294 +0x8b
2025-03-11T23:25:22.9433964Z github.com/hyperledger/firefly-common/pkg/wsclient.(*wsClient).receiveReconnectLoop(0xc0005610a0)
2025-03-11T23:25:22.9434649Z 	/home/runner/go/pkg/mod/github.com/hyperledger/[email protected]/pkg/wsclient/wsclient.go:497 +0x325
2025-03-11T23:25:22.9435224Z created by github.com/hyperledger/firefly-common/pkg/wsclient.(*wsClient).Connect in goroutine 245
2025-03-11T23:25:22.9435875Z 	/home/runner/go/pkg/mod/github.com/hyperledger/[email protected]/pkg/wsclient/wsclient.go:191 +0x65
2025-03-11T23:25:22.9436519Z FAIL	github.com/hyperledger/firefly/internal/dataexchange/ffdx	0.054s
2025-03-11T23:25:22.9436717Z === RUN   TestHandleFFIBroadcastOk

@EnriqueL8
Copy link
Contributor

Alright fixed above #1658

Copy link
Contributor

@EnriqueL8 EnriqueL8 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was reminded yesterday of the key being mandatory today as part of deploying a contract and it doesn't make sense for Cardano as the connector has used that API to run a off-chain dApp that is responsible of building Cardano TX and indexing state. Raised a discussion on Discord https://discord.com/channels/905194001349627914/928377875827154984/1349704667524763689


## Create the stack

A Cardano stack can be run in two different ways; with a firefly
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A Cardano stack can be run in two different ways; with a firefly
A Cardano stack can be run in two different ways with Hyperledger FireFly


Start a local cardano node. The fastest way to do this is to [use mithril](https://mithril.network/doc/manual/getting-started/bootstrap-cardano-node/) to bootstrap the node.

For an example of how to bootstrap and run the cardano node in docker, see [the firefly-cardano repo](https://github.com/hyperledger/firefly-cardano/blob/1be3b08d301d6d6eeb5b79e40cf3dbf66181c3de/infra/docker-compose.node.yaml#L4).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For an example of how to bootstrap and run the cardano node in docker, see [the firefly-cardano repo](https://github.com/hyperledger/firefly-cardano/blob/1be3b08d301d6d6eeb5b79e40cf3dbf66181c3de/infra/docker-compose.node.yaml#L4).
For an example of how to bootstrap and run the Cardano node in Docker, see [the firefly-cardano repo](https://github.com/hyperledger/firefly-cardano/blob/1be3b08d301d6d6eeb5b79e40cf3dbf66181c3de/infra/docker-compose.node.yaml#L4).


> **NOTE**: The cardano-node communicates over a Unix socket, so this will not work on Windows.

Start a local cardano node. The fastest way to do this is to [use mithril](https://mithril.network/doc/manual/getting-started/bootstrap-cardano-node/) to bootstrap the node.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Start a local cardano node. The fastest way to do this is to [use mithril](https://mithril.network/doc/manual/getting-started/bootstrap-cardano-node/) to bootstrap the node.
Start a local Cardano node. The fastest way to do this is to [use mithril](https://mithril.network/doc/manual/getting-started/bootstrap-cardano-node/) to bootstrap the node.

Comment on lines +73 to +90
## Get some ADA

Now that you have a stack, you need some seed funds to get started. Your stack was created with a wallet already (these are free to create in Cardano). To get the address, you can run
```sh
ff accounts list dev
```

The response will look like
```json
[
{
"address": "addr_test1...",
"privateKey": "..."
}
]
```

If you're developing against a testnet such as preview, you can receive funds from the [testnet faucet](https://docs.cardano.org/cardano-testnets/tools/faucet). Pass the `address` from that response to the faucet.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would recommend following this a Transaction either in this section or working with smart contracts section. I do see a gap of how do you create a simple transfer of ADA through the API

manifest.json Outdated
Comment on lines 3 to 10
"image": "ghcr.io/hyperledger/firefly-cardanoconnect",
"tag": "v0.2.1",
"sha": "de880330e10faee733a30306add75139ccdf248329724f0b1292cf62eb65619f"
},
"cardanosigner": {
"image": "hyperledger/firefly-cardanosigner",
"tag": "main"
"image": "ghcr.io/hyperledger/firefly-cardanosigner",
"tag": "v0.2.1",
"sha": "d277bb99de78ed0fafd8b6a83f78a70efaa493a2b2c8259a4c46508f6987b0d5"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome thank you

@EnriqueL8
Copy link
Contributor

EnriqueL8 commented Mar 13, 2025

Sorry I tagged a comment in a linked PR as Fixes and it automatically closed this PR when the fix was merged

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants