From e6fd3024eb1668f6713bd7a7c54c851d5fb81e53 Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Mon, 8 Jan 2024 13:03:19 -0600 Subject: [PATCH 01/17] consistently format addresses to be used when generation dependencies --- flixkit/generator.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/flixkit/generator.go b/flixkit/generator.go index cc0a609..3e5b335 100644 --- a/flixkit/generator.go +++ b/flixkit/generator.go @@ -78,9 +78,10 @@ func NewGenerator(contractInfos ContractInfos, logger output.Logger) (*Generator Networks: make([]v1_1.Network, 0), } for network, address := range networks { + addr := flow.HexToAddress(address) contract.Networks = append(contract.Networks, v1_1.Network{ Network: network, - Address: address, + Address: "0x" + addr.Hex(), }) } deployedContracts = append(deployedContracts, contract) @@ -248,23 +249,21 @@ func (g *Generator) GenerateDepPinDepthFirst(ctx context.Context, flowkit *flowk } func generateDependencyNetworks(ctx context.Context, flowkit *flowkit.Flowkit, address string, name string, cache map[string]v1_1.PinDetail, height uint64) (*v1_1.PinDetail, error) { - identifier := fmt.Sprintf("A.%s.%s", strings.ReplaceAll(address, "0x", ""), name) + addr := flow.HexToAddress(address) + identifier := fmt.Sprintf("A.%s.%s", addr.Hex(), name) pinDetail, ok := cache[identifier] if ok { return &pinDetail, nil } - account, err := flowkit.GetAccount(ctx, flow.HexToAddress(address)) + account, err := flowkit.GetAccount(ctx, addr) if err != nil { return nil, err } code := account.Contracts[name] - if !strings.HasPrefix(address, "0x") { - address = "0x" + address - } depend := v1_1.PinDetail{ PinContractName: name, - PinContractAddress: address, + PinContractAddress: "0x" + addr.Hex(), PinSelf: v1_1.ShaHex(code, ""), } depend.CalculatePin(height) From 16366b5c7429bc79ef65b4b644bd7108685a54da Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Tue, 9 Jan 2024 15:45:36 -0600 Subject: [PATCH 02/17] add default output if one does not exist, add output when generating v1.1 flix --- flixkit/fcl_bindings.go | 30 +++- flixkit/fcl_bindings_test.go | 137 ++++++++++++++++++ .../TestBindingReadTokenBalance.golden | 33 +++++ flixkit/testdata/TestHelloScript.golden | 8 +- .../TestGenerateParametersScripts.golden | 8 +- flixkit/v1_1/types.go | 6 + 6 files changed, 214 insertions(+), 8 deletions(-) create mode 100644 flixkit/testdata/TestBindingReadTokenBalance.golden diff --git a/flixkit/fcl_bindings.go b/flixkit/fcl_bindings.go index 535faa1..0863dad 100644 --- a/flixkit/fcl_bindings.go +++ b/flixkit/fcl_bindings.go @@ -128,18 +128,28 @@ func getTemplateDataV1_1(flix *v1_1.InteractionTemplate, templateLocation string title := msgs.GetTitle("Request") methodName := strcase.LowerCamelCase(title) description := msgs.GetDescription("") - result := simpleParameter{} - if flix.Data.Output != nil { - o := transformParameters([]v1_1.Parameter{*flix.Data.Output}) + var sp simpleParameter + + if flix.Data.Type == "script" { + oTemp := flix.Data.Output + if flix.Data.Output == nil { + // if output is not defined, add default output + oTemp = &v1_1.Parameter{ + Label: "result", + Type: "String", + Messages: v1_1.InteractionTemplateMessages{}, + } + } + o := transformParameters([]v1_1.Parameter{*oTemp}) if len(o) > 0 { - result = o[0] + sp = o[0] } } data := templateData{ Version: flix.FVersion, Parameters: transformParameters(flix.Data.Parameters), ParametersPrefixName: strcase.UpperCamelCase(title), - Output: result, + Output: sp, Title: methodName, Description: description, Location: templateLocation, @@ -153,7 +163,14 @@ func getTemplateDataV1_0(flix *v1.FlowInteractionTemplate, templateLocation stri title := flix.Data.Messages.GetTitleValue("Request") methodName := strcase.LowerCamelCase(title) description := flix.GetDescription() - + var sp simpleParameter + // version 1.0 does not support output parameters, add default output + if flix.Data.Type == "script" { + sp = simpleParameter{ + Name: "result", + JsType: "string", + } + } data := templateData{ Version: flix.FVersion, Parameters: transformArguments(flix.Data.Arguments), @@ -163,6 +180,7 @@ func getTemplateDataV1_0(flix *v1.FlowInteractionTemplate, templateLocation stri Location: templateLocation, IsScript: flix.IsScript(), IsLocalTemplate: isLocal, + Output: sp, } return data } diff --git a/flixkit/fcl_bindings_test.go b/flixkit/fcl_bindings_test.go index 5e5fbed..307529c 100644 --- a/flixkit/fcl_bindings_test.go +++ b/flixkit/fcl_bindings_test.go @@ -458,3 +458,140 @@ func TestTSGenParamsTx(t *testing.T) { assert.NoError(err, "ParseTemplate should not return an error") autogold.ExpectFile(t, out) } + +const ReadTokenScript = ` +{ + "f_type": "InteractionTemplate", + "f_version": "1.1.0", + "id": "29d03aafbbb5a02e0d5f4ffee685c12494915410812305c2858008d3e2902b72", + "data": { + "type": "script", + "interface": "", + "messages": null, + "cadence": { + "body": "import \"FungibleToken\"\nimport \"FlowToken\"\n\npub fun main(address: Address): UFix64 {\n let account = getAccount(address)\n\n let vaultRef = account\n .getCapability(/public/flowTokenBalance)\n .borrow\u003c\u0026FlowToken.Vault{FungibleToken.Balance}\u003e()\n ?? panic(\"Could not borrow balance reference to the Vault\")\n\n return vaultRef.balance\n}\n", + "network_pins": [ + { + "network": "mainnet", + "pin_self": "e0a1c0443b724d1238410c4a05c48441ee974160cad8cf1103c63b6999f81dd5" + }, + { + "network": "testnet", + "pin_self": "6fee459b35d7013a83070c9ac42ea43ee04a3925deca445c34614c1bd6dc4cb8" + } + ] + }, + "dependencies": [ + { + "contracts": [ + { + "contract": "FungibleToken", + "networks": [ + { + "network": "mainnet", + "address": "0xf233dcee88fe0abe", + "dependency_pin_block_height": 69539302, + "dependency_pin": { + "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", + "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0xf233dcee88fe0abe", + "imports": [] + } + }, + { + "network": "testnet", + "address": "0x9a0766d93b6608b7", + "dependency_pin_block_height": 146201102, + "dependency_pin": { + "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", + "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0x9a0766d93b6608b7", + "imports": [] + } + }, + { + "network": "emulator", + "address": "0xee82856bf20e2aa6", + "dependency_pin_block_height": 0 + } + ] + } + ] + }, + { + "contracts": [ + { + "contract": "FlowToken", + "networks": [ + { + "network": "mainnet", + "address": "0x1654653399040a61", + "dependency_pin_block_height": 69539302, + "dependency_pin": { + "pin": "a341e772da413bfbcf43b0fc167bd50a20c9f40baf10e12d3dbc2f5181526de9", + "pin_self": "0e932728b73bff3c09dd58922f2529fc7b7fe7477f1dcc61169bc8f46948ad91", + "pin_contract_name": "FlowToken", + "pin_contract_address": "0x1654653399040a61", + "imports": [ + { + "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", + "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0xf233dcee88fe0abe", + "imports": [] + } + ] + } + }, + { + "network": "testnet", + "address": "0x7e60df042a9c0868", + "dependency_pin_block_height": 146201102, + "dependency_pin": { + "pin": "9cc21a34a01486ebd6f044e99dbcdd58671850f81fcc345d071181c19f61aaa4", + "pin_self": "6f01c7001e2d6635b667a170d3ccbc13659c40d01bb35e56979fcc7fa2d18646", + "pin_contract_name": "FlowToken", + "pin_contract_address": "0x7e60df042a9c0868", + "imports": [ + { + "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", + "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0x9a0766d93b6608b7", + "imports": [] + } + ] + } + }, + { + "network": "emulator", + "address": "0x0ae53cb6e3f42a79", + "dependency_pin_block_height": 0 + } + ] + } + ] + } + ], + "parameters": [ + { + "label": "address", + "index": 0, + "type": "Address", + "messages": [] + } + ] + } +} +` + +func TestBindingReadTokenBalance(t *testing.T) { + generator := NewFclTSGenerator() + assert := assert.New(t) + + out, err := generator.Generate(ReadTokenScript, "./read-token-balance.template.json") + assert.NoError(err, "ParseTemplate should not return an error") + autogold.ExpectFile(t, out) +} diff --git a/flixkit/testdata/TestBindingReadTokenBalance.golden b/flixkit/testdata/TestBindingReadTokenBalance.golden new file mode 100644 index 0000000..68df772 --- /dev/null +++ b/flixkit/testdata/TestBindingReadTokenBalance.golden @@ -0,0 +1,33 @@ +`/** + This binding file was auto generated based on FLIX template v1.1.0. + Changes to this file might get overwritten. + Note fcl version 1.9.0 or higher is required to use templates. +**/ + +import * as fcl from "@onflow/fcl" +import flixTemplate from "./read-token-balance.template.json" + +interface RequestParams { + address: string; +} + +/** +* request: +* @param string address - +* @returns {Promise} - +*/ +export async function request({address}: RequestParams): Promise { + const info = await fcl.query({ + cadence: "", + template: flixTemplate, + args: (arg, t) => [arg(address, t.Address)] + }); + + return info +} + + + + + +` diff --git a/flixkit/testdata/TestHelloScript.golden b/flixkit/testdata/TestHelloScript.golden index 3bd53c0..478a19f 100644 --- a/flixkit/testdata/TestHelloScript.golden +++ b/flixkit/testdata/TestHelloScript.golden @@ -64,6 +64,12 @@ ] } ], - "parameters": null + "parameters": null, + "output": { + "label": "result", + "index": 0, + "type": "String", + "messages": [] + } } }` diff --git a/flixkit/v1_1/testdata/TestGenerateParametersScripts.golden b/flixkit/v1_1/testdata/TestGenerateParametersScripts.golden index a61e0ca..059ae36 100644 --- a/flixkit/v1_1/testdata/TestGenerateParametersScripts.golden +++ b/flixkit/v1_1/testdata/TestGenerateParametersScripts.golden @@ -56,6 +56,12 @@ } ] } - ] + ], + "output": { + "label": "result", + "index": 0, + "type": "UFix64", + "messages": [] + } } }` diff --git a/flixkit/v1_1/types.go b/flixkit/v1_1/types.go index 0df025d..9a593cc 100644 --- a/flixkit/v1_1/types.go +++ b/flixkit/v1_1/types.go @@ -354,6 +354,12 @@ func (template *InteractionTemplate) ProcessParameters(program *ast.Program) err for _, d := range functionDeclaration { if d.Identifier.String() == "main" { parameterList = d.ParameterList.Parameters + r := d.ReturnTypeAnnotation.Type.String() + template.Data.Output = &Parameter{ + Label: "result", + Type: r, + Messages: make([]Message, 0), + } } } From 6d5b6074127739062a6877da224169f0b4a6b0bc Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Wed, 10 Jan 2024 15:11:03 -0600 Subject: [PATCH 03/17] impl binding generator interface, needed for mocking and better code structure --- flixkit/fcl_bindings.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/flixkit/fcl_bindings.go b/flixkit/fcl_bindings.go index 0863dad..567641c 100644 --- a/flixkit/fcl_bindings.go +++ b/flixkit/fcl_bindings.go @@ -12,7 +12,7 @@ import ( "github.com/stoewer/go-strcase" ) -func NewFclTSGenerator() *FclGenerator { +func NewFclTSGenerator() GenerateBinding { t := []string{ templates.GetTsFclMainTemplate(), templates.GetTsFclScriptTemplate(), @@ -26,7 +26,7 @@ func NewFclTSGenerator() *FclGenerator { } } -func NewFclJSGenerator() *FclGenerator { +func NewFclJSGenerator() GenerateBinding { t := []string{ templates.GetJsFclMainTemplate(), templates.GetJsFclScriptTemplate(), @@ -104,6 +104,8 @@ type FclGenerator struct { Templates []string } +var _ GenerateBinding = (*FclGenerator)(nil) + type FlixParameter struct { Name string Type string From 9cfc115ca648a3d2327474cdb333d656fbb5608f Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Wed, 10 Jan 2024 21:58:49 -0600 Subject: [PATCH 04/17] update to not having breaking changes --- flixkit/fcl_bindings.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/flixkit/fcl_bindings.go b/flixkit/fcl_bindings.go index 567641c..b3d3246 100644 --- a/flixkit/fcl_bindings.go +++ b/flixkit/fcl_bindings.go @@ -12,7 +12,12 @@ import ( "github.com/stoewer/go-strcase" ) -func NewFclTSGenerator() GenerateBinding { +// Don't use this one deprecated +func NewFclTSGenerator() *FclGenerator { + return NewFclGeneratorTS().(*FclGenerator) +} + +func NewFclGeneratorTS() GenerateBinding { t := []string{ templates.GetTsFclMainTemplate(), templates.GetTsFclScriptTemplate(), @@ -26,7 +31,12 @@ func NewFclTSGenerator() GenerateBinding { } } -func NewFclJSGenerator() GenerateBinding { +// Don't use this one deprecated +func NewFclJSGenerator() *FclGenerator { + return NewFclGeneratorJS().(*FclGenerator) +} + +func NewFclGeneratorJS() GenerateBinding { t := []string{ templates.GetJsFclMainTemplate(), templates.GetJsFclScriptTemplate(), From f3fb8ad5266e7bd3f6234d313e50ec8148034a26 Mon Sep 17 00:00:00 2001 From: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Date: Thu, 11 Jan 2024 10:35:49 -0800 Subject: [PATCH 05/17] Change codeowners --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 8dab471..57ec5a6 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @chasefleming @bjartek @bthaile \ No newline at end of file +* @bjartek @bthaile @ianthpun \ No newline at end of file From 70ef28ce58c8bfef363302d505d62326acedd926 Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Thu, 11 Jan 2024 12:59:51 -0600 Subject: [PATCH 06/17] update naming of interfaces to be more consistent --- flixkit/fcl_bindings.go | 16 +++------------- flixkit/flixkit.go | 4 ++-- flixkit/generator.go | 2 +- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/flixkit/fcl_bindings.go b/flixkit/fcl_bindings.go index b3d3246..e066921 100644 --- a/flixkit/fcl_bindings.go +++ b/flixkit/fcl_bindings.go @@ -12,12 +12,7 @@ import ( "github.com/stoewer/go-strcase" ) -// Don't use this one deprecated -func NewFclTSGenerator() *FclGenerator { - return NewFclGeneratorTS().(*FclGenerator) -} - -func NewFclGeneratorTS() GenerateBinding { +func NewFclTSGenerator() Binder { t := []string{ templates.GetTsFclMainTemplate(), templates.GetTsFclScriptTemplate(), @@ -31,12 +26,7 @@ func NewFclGeneratorTS() GenerateBinding { } } -// Don't use this one deprecated -func NewFclJSGenerator() *FclGenerator { - return NewFclGeneratorJS().(*FclGenerator) -} - -func NewFclGeneratorJS() GenerateBinding { +func NewFclJSGenerator() Binder { t := []string{ templates.GetJsFclMainTemplate(), templates.GetJsFclScriptTemplate(), @@ -114,7 +104,7 @@ type FclGenerator struct { Templates []string } -var _ GenerateBinding = (*FclGenerator)(nil) +var _ Binder = (*FclGenerator)(nil) type FlixParameter struct { Name string diff --git a/flixkit/flixkit.go b/flixkit/flixkit.go index 13d2d36..69c09a9 100644 --- a/flixkit/flixkit.go +++ b/flixkit/flixkit.go @@ -26,11 +26,11 @@ type FlowInteractionTemplateVersion struct { FVersion string `json:"f_version"` } -type GenerateTemplate interface { +type FlixTemplater interface { Generate(ctx context.Context, code string, preFill string) (string, error) } -type GenerateBinding interface { +type Binder interface { Generate(flixString string, templateLocation string) (string, error) } diff --git a/flixkit/generator.go b/flixkit/generator.go index 3e5b335..d54a671 100644 --- a/flixkit/generator.go +++ b/flixkit/generator.go @@ -38,7 +38,7 @@ type Generator struct { template *v1_1.InteractionTemplate } -func NewGenerator(contractInfos ContractInfos, logger output.Logger) (*Generator, error) { +func NewGenerator(contractInfos ContractInfos, logger output.Logger) (FlixTemplater, error) { loader := afero.Afero{Fs: afero.NewOsFs()} gwt, err := gateway.NewGrpcGateway(config.TestnetNetwork) From ae405f93c309821d44f8b0ba3ca54b1f3e1464fd Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Thu, 11 Jan 2024 13:09:20 -0600 Subject: [PATCH 07/17] add conv method --- flixkit/generator.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flixkit/generator.go b/flixkit/generator.go index d54a671..b9c2e8d 100644 --- a/flixkit/generator.go +++ b/flixkit/generator.go @@ -38,6 +38,8 @@ type Generator struct { template *v1_1.InteractionTemplate } +var _ FlixTemplater = (*Generator)(nil) + func NewGenerator(contractInfos ContractInfos, logger output.Logger) (FlixTemplater, error) { loader := afero.Afero{Fs: afero.NewOsFs()} From 6cc3d9d22e632c014ec9a256fc3a0ed8b58c6149 Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Fri, 12 Jan 2024 09:40:17 -0600 Subject: [PATCH 08/17] set methods private that are no longer public --- flixkit/fcl_bindings.go | 2 +- flixkit/flixkit.go | 202 ++++++++++++++++++++-------------------- flixkit/flixkit_test.go | 4 +- 3 files changed, 104 insertions(+), 104 deletions(-) diff --git a/flixkit/fcl_bindings.go b/flixkit/fcl_bindings.go index e066921..6dcc587 100644 --- a/flixkit/fcl_bindings.go +++ b/flixkit/fcl_bindings.go @@ -48,7 +48,7 @@ func (g *FclGenerator) Generate(flixString string, templateLocation string) (str return "", fmt.Errorf("no flix template provided") } - ver, err := GetTemplateVersion(flixString) + ver, err := getTemplateVersion(flixString) if err != nil { return "", fmt.Errorf("invalid flix template version, %w", err) } diff --git a/flixkit/flixkit.go b/flixkit/flixkit.go index 69c09a9..3c82f23 100644 --- a/flixkit/flixkit.go +++ b/flixkit/flixkit.go @@ -70,13 +70,104 @@ func NewFlixService(config *Config) FlixService { } } -func (s *flixServiceImpl) GetFlixRaw(ctx context.Context, templateName string) (string, error) { +func (s *flixServiceImpl) GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) { + template, err := s.GetTemplate(ctx, templateName) + if err != nil { + return nil, err + } + var execution FlowInteractionTemplateExecution + var cadenceCode string + ver, err := getTemplateVersion(template) + if err != nil { + return nil, fmt.Errorf("invalid flix template version, %w", err) + } + var replaceableCadence FlowInteractionTemplateCadence + switch ver { + case "1.1.0": + replaceableCadence, err = v1_1.ParseFlix(template) + if err != nil { + return nil, err + } + cadenceCode, err = replaceableCadence.GetAndReplaceCadenceImports(network) + if err != nil { + return nil, err + } + execution.Cadence = cadenceCode + execution.IsScript = replaceableCadence.IsScript() + execution.IsTransaciton = replaceableCadence.IsTransaction() + case "1.0.0": + replaceableCadence, err = v1.ParseFlix(template) + if err != nil { + return nil, err + } + cadenceCode, err = replaceableCadence.GetAndReplaceCadenceImports(network) + if err != nil { + return nil, err + } + execution.Cadence = cadenceCode + execution.IsScript = replaceableCadence.IsScript() + execution.IsTransaciton = replaceableCadence.IsTransaction() + default: + return nil, fmt.Errorf("flix template version: %s not supported", ver) + } + + if execution.Cadence == "" { + return nil, fmt.Errorf("could not parse template, invalid flix template") + } + + return &execution, nil +} + +func (s *flixServiceImpl) GetTemplate(ctx context.Context, flixQuery string) (string, error) { + var template string + var err error + + switch getType(flixQuery) { + case flixId: + template, err = s.getFlixByID(ctx, flixQuery) + if err != nil { + return "", fmt.Errorf("could not find flix with id %s: %w", flixQuery, err) + } + + case flixName: + template, err = s.getFlix(ctx, flixQuery) + if err != nil { + return "", fmt.Errorf("could not find flix with name %s: %w", flixQuery, err) + } + + case flixPath: + if s.config.FileReader == nil { + return "", fmt.Errorf("file reader not provided") + } + file, err := s.config.FileReader.ReadFile(flixQuery) + if err != nil { + return "", fmt.Errorf("could not read flix file %s: %w", flixQuery, err) + } + template = string(file) + if err != nil { + return "", fmt.Errorf("could not parse flix from file %s: %w", flixQuery, err) + } + + case flixUrl: + template, err = fetchFlixWithContext(ctx, flixQuery) + if err != nil { + return "", fmt.Errorf("could not parse flix from url %s: %w", flixQuery, err) + } + + default: + return "", fmt.Errorf("invalid flix query type: %s", flixQuery) + } + + return template, nil +} + +func (s *flixServiceImpl) getFlixRaw(ctx context.Context, templateName string) (string, error) { url := fmt.Sprintf("%s?name=%s", s.config.FlixServerURL, templateName) - return FetchFlixWithContext(ctx, url) + return fetchFlixWithContext(ctx, url) } -func (s *flixServiceImpl) GetFlix(ctx context.Context, templateName string) (string, error) { - template, err := s.GetFlixRaw(ctx, templateName) +func (s *flixServiceImpl) getFlix(ctx context.Context, templateName string) (string, error) { + template, err := s.getFlixRaw(ctx, templateName) if err != nil { return "", err } @@ -84,20 +175,20 @@ func (s *flixServiceImpl) GetFlix(ctx context.Context, templateName string) (str return template, nil } -func (s *flixServiceImpl) GetFlixByIDRaw(ctx context.Context, templateID string) (string, error) { +func (s *flixServiceImpl) getFlixByIDRaw(ctx context.Context, templateID string) (string, error) { url := fmt.Sprintf("%s/%s", s.config.FlixServerURL, templateID) - return FetchFlixWithContext(ctx, url) + return fetchFlixWithContext(ctx, url) } -func (s *flixServiceImpl) GetFlixByID(ctx context.Context, templateID string) (string, error) { - template, err := s.GetFlixByIDRaw(ctx, templateID) +func (s *flixServiceImpl) getFlixByID(ctx context.Context, templateID string) (string, error) { + template, err := s.getFlixByIDRaw(ctx, templateID) if err != nil { return "", err } return template, nil } -func GetTemplateVersion(template string) (string, error) { +func getTemplateVersion(template string) (string, error) { var flowTemplate FlowInteractionTemplateVersion err := json.Unmarshal([]byte(template), &flowTemplate) @@ -112,7 +203,7 @@ func GetTemplateVersion(template string) (string, error) { return flowTemplate.FVersion, nil } -func FetchFlixWithContext(ctx context.Context, url string) (string, error) { +func fetchFlixWithContext(ctx context.Context, url string) (string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { return "", err @@ -136,54 +227,6 @@ func FetchFlixWithContext(ctx context.Context, url string) (string, error) { return string(body), nil } -func (s *flixServiceImpl) GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) { - template, err := s.GetTemplate(ctx, templateName) - if err != nil { - return nil, err - } - var execution FlowInteractionTemplateExecution - var cadenceCode string - ver, err := GetTemplateVersion(template) - if err != nil { - return nil, fmt.Errorf("invalid flix template version, %w", err) - } - var replaceableCadence FlowInteractionTemplateCadence - switch ver { - case "1.1.0": - replaceableCadence, err = v1_1.ParseFlix(template) - if err != nil { - return nil, err - } - cadenceCode, err = replaceableCadence.GetAndReplaceCadenceImports(network) - if err != nil { - return nil, err - } - execution.Cadence = cadenceCode - execution.IsScript = replaceableCadence.IsScript() - execution.IsTransaciton = replaceableCadence.IsTransaction() - case "1.0.0": - replaceableCadence, err = v1.ParseFlix(template) - if err != nil { - return nil, err - } - cadenceCode, err = replaceableCadence.GetAndReplaceCadenceImports(network) - if err != nil { - return nil, err - } - execution.Cadence = cadenceCode - execution.IsScript = replaceableCadence.IsScript() - execution.IsTransaciton = replaceableCadence.IsTransaction() - default: - return nil, fmt.Errorf("flix template version: %s not supported", ver) - } - - if execution.Cadence == "" { - return nil, fmt.Errorf("could not parse template, invalid flix template") - } - - return &execution, nil -} - type flixQueryTypes string const ( @@ -231,46 +274,3 @@ func getType(s string) flixQueryTypes { return flixName } } - -func (s *flixServiceImpl) GetTemplate(ctx context.Context, flixQuery string) (string, error) { - var template string - var err error - - switch getType(flixQuery) { - case flixId: - template, err = s.GetFlixByID(ctx, flixQuery) - if err != nil { - return "", fmt.Errorf("could not find flix with id %s: %w", flixQuery, err) - } - - case flixName: - template, err = s.GetFlix(ctx, flixQuery) - if err != nil { - return "", fmt.Errorf("could not find flix with name %s: %w", flixQuery, err) - } - - case flixPath: - if s.config.FileReader == nil { - return "", fmt.Errorf("file reader not provided") - } - file, err := s.config.FileReader.ReadFile(flixQuery) - if err != nil { - return "", fmt.Errorf("could not read flix file %s: %w", flixQuery, err) - } - template = string(file) - if err != nil { - return "", fmt.Errorf("could not parse flix from file %s: %w", flixQuery, err) - } - - case flixUrl: - template, err = FetchFlixWithContext(ctx, flixQuery) - if err != nil { - return "", fmt.Errorf("could not parse flix from url %s: %w", flixQuery, err) - } - - default: - return "", fmt.Errorf("invalid flix query type: %s", flixQuery) - } - - return template, nil -} diff --git a/flixkit/flixkit_test.go b/flixkit/flixkit_test.go index 6831818..17f9c81 100644 --- a/flixkit/flixkit_test.go +++ b/flixkit/flixkit_test.go @@ -247,7 +247,7 @@ func TestFetchFlix(t *testing.T) { defer server.Close() ctx := context.Background() - body, err := FetchFlixWithContext(ctx, server.URL) + body, err := fetchFlixWithContext(ctx, server.URL) assert.NoError(err, "GetFlix should not return an error") assert.Equal("Hello World", body, "GetFlix should return the correct body") } @@ -355,7 +355,7 @@ func TestTemplateVersion(t *testing.T) { for _, tt := range tests { t.Run(tt.templateStr, func(t *testing.T) { - ver, err := GetTemplateVersion(tt.templateStr) + ver, err := getTemplateVersion(tt.templateStr) if tt.wantErr { assert.Error(err, "TemplateVersion should return an error") } else { From 0ecb51f00018b91ae8ed2e672736361744bf5dc8 Mon Sep 17 00:00:00 2001 From: Ian Pun Date: Fri, 12 Jan 2024 09:00:47 -0800 Subject: [PATCH 09/17] update --- flixkit/flixkit.go | 169 -------------------------------- flixkit/flixkit_service.go | 186 ++++++++++++++++++++++++++++++++++++ flixkitv2/flixkit.go | 25 +++++ internal/flixkit_service.go | 29 ++++++ 4 files changed, 240 insertions(+), 169 deletions(-) create mode 100644 flixkit/flixkit_service.go create mode 100644 flixkitv2/flixkit.go create mode 100644 internal/flixkit_service.go diff --git a/flixkit/flixkit.go b/flixkit/flixkit.go index 13d2d36..e990b47 100644 --- a/flixkit/flixkit.go +++ b/flixkit/flixkit.go @@ -45,11 +45,6 @@ type FlixService interface { GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) } -type flixServiceImpl struct { - config *Config -} - -var _ FlixService = (*flixServiceImpl)(nil) type FileReader interface { ReadFile(path string) ([]byte, error) @@ -70,32 +65,6 @@ func NewFlixService(config *Config) FlixService { } } -func (s *flixServiceImpl) GetFlixRaw(ctx context.Context, templateName string) (string, error) { - url := fmt.Sprintf("%s?name=%s", s.config.FlixServerURL, templateName) - return FetchFlixWithContext(ctx, url) -} - -func (s *flixServiceImpl) GetFlix(ctx context.Context, templateName string) (string, error) { - template, err := s.GetFlixRaw(ctx, templateName) - if err != nil { - return "", err - } - - return template, nil -} - -func (s *flixServiceImpl) GetFlixByIDRaw(ctx context.Context, templateID string) (string, error) { - url := fmt.Sprintf("%s/%s", s.config.FlixServerURL, templateID) - return FetchFlixWithContext(ctx, url) -} - -func (s *flixServiceImpl) GetFlixByID(ctx context.Context, templateID string) (string, error) { - template, err := s.GetFlixByIDRaw(ctx, templateID) - if err != nil { - return "", err - } - return template, nil -} func GetTemplateVersion(template string) (string, error) { var flowTemplate FlowInteractionTemplateVersion @@ -136,141 +105,3 @@ func FetchFlixWithContext(ctx context.Context, url string) (string, error) { return string(body), nil } -func (s *flixServiceImpl) GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) { - template, err := s.GetTemplate(ctx, templateName) - if err != nil { - return nil, err - } - var execution FlowInteractionTemplateExecution - var cadenceCode string - ver, err := GetTemplateVersion(template) - if err != nil { - return nil, fmt.Errorf("invalid flix template version, %w", err) - } - var replaceableCadence FlowInteractionTemplateCadence - switch ver { - case "1.1.0": - replaceableCadence, err = v1_1.ParseFlix(template) - if err != nil { - return nil, err - } - cadenceCode, err = replaceableCadence.GetAndReplaceCadenceImports(network) - if err != nil { - return nil, err - } - execution.Cadence = cadenceCode - execution.IsScript = replaceableCadence.IsScript() - execution.IsTransaciton = replaceableCadence.IsTransaction() - case "1.0.0": - replaceableCadence, err = v1.ParseFlix(template) - if err != nil { - return nil, err - } - cadenceCode, err = replaceableCadence.GetAndReplaceCadenceImports(network) - if err != nil { - return nil, err - } - execution.Cadence = cadenceCode - execution.IsScript = replaceableCadence.IsScript() - execution.IsTransaciton = replaceableCadence.IsTransaction() - default: - return nil, fmt.Errorf("flix template version: %s not supported", ver) - } - - if execution.Cadence == "" { - return nil, fmt.Errorf("could not parse template, invalid flix template") - } - - return &execution, nil -} - -type flixQueryTypes string - -const ( - flixName flixQueryTypes = "name" - flixPath flixQueryTypes = "path" - flixId flixQueryTypes = "id" - flixUrl flixQueryTypes = "url" - flixJson flixQueryTypes = "json" -) - -func isHex(str string) bool { - if len(str) != 64 { - return false - } - _, err := hex.DecodeString(str) - return err == nil -} - -func isPath(path string) bool { - _, err := os.Stat(path) - return err == nil -} - -func isUrl(str string) bool { - u, err := url.Parse(str) - return err == nil && u.Scheme != "" && u.Host != "" -} - -func isJson(str string) bool { - var js json.RawMessage - return json.Unmarshal([]byte(str), &js) == nil -} - -func getType(s string) flixQueryTypes { - switch { - case isPath(s): - return flixPath - case isHex(s): - return flixId - case isUrl(s): - return flixUrl - case isJson(s): - return flixJson - default: - return flixName - } -} - -func (s *flixServiceImpl) GetTemplate(ctx context.Context, flixQuery string) (string, error) { - var template string - var err error - - switch getType(flixQuery) { - case flixId: - template, err = s.GetFlixByID(ctx, flixQuery) - if err != nil { - return "", fmt.Errorf("could not find flix with id %s: %w", flixQuery, err) - } - - case flixName: - template, err = s.GetFlix(ctx, flixQuery) - if err != nil { - return "", fmt.Errorf("could not find flix with name %s: %w", flixQuery, err) - } - - case flixPath: - if s.config.FileReader == nil { - return "", fmt.Errorf("file reader not provided") - } - file, err := s.config.FileReader.ReadFile(flixQuery) - if err != nil { - return "", fmt.Errorf("could not read flix file %s: %w", flixQuery, err) - } - template = string(file) - if err != nil { - return "", fmt.Errorf("could not parse flix from file %s: %w", flixQuery, err) - } - - case flixUrl: - template, err = FetchFlixWithContext(ctx, flixQuery) - if err != nil { - return "", fmt.Errorf("could not parse flix from url %s: %w", flixQuery, err) - } - - default: - return "", fmt.Errorf("invalid flix query type: %s", flixQuery) - } - - return template, nil -} diff --git a/flixkit/flixkit_service.go b/flixkit/flixkit_service.go new file mode 100644 index 0000000..b8eb7a1 --- /dev/null +++ b/flixkit/flixkit_service.go @@ -0,0 +1,186 @@ +package flixkit + +import ( + "context" + "encoding/hex" + "encoding/json" + "fmt" + "net/url" + "os" + + v1 "github.com/onflow/flixkit-go/flixkit/v1" + v1_1 "github.com/onflow/flixkit-go/flixkit/v1_1" +) + + +type flixServiceImpl struct { + config *Config +} + +var _ FlixService = (*flixServiceImpl)(nil) + +func (s *flixServiceImpl) GetFlixRaw(ctx context.Context, templateName string) (string, error) { + url := fmt.Sprintf("%s?name=%s", s.config.FlixServerURL, templateName) + return FetchFlixWithContext(ctx, url) +} + +func (s *flixServiceImpl) GetFlix(ctx context.Context, templateName string) (string, error) { + template, err := s.GetFlixRaw(ctx, templateName) + if err != nil { + return "", err + } + + return template, nil +} + +func (s *flixServiceImpl) GetFlixByIDRaw(ctx context.Context, templateID string) (string, error) { + url := fmt.Sprintf("%s/%s", s.config.FlixServerURL, templateID) + return FetchFlixWithContext(ctx, url) +} + +func (s *flixServiceImpl) GetFlixByID(ctx context.Context, templateID string) (string, error) { + template, err := s.GetFlixByIDRaw(ctx, templateID) + if err != nil { + return "", err + } + return template, nil +} + +func (s *flixServiceImpl) GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) { + template, err := s.GetTemplate(ctx, templateName) + if err != nil { + return nil, err + } + var execution FlowInteractionTemplateExecution + var cadenceCode string + ver, err := GetTemplateVersion(template) + if err != nil { + return nil, fmt.Errorf("invalid flix template version, %w", err) + } + var replaceableCadence FlowInteractionTemplateCadence + switch ver { + case "1.1.0": + replaceableCadence, err = v1_1.ParseFlix(template) + if err != nil { + return nil, err + } + cadenceCode, err = replaceableCadence.GetAndReplaceCadenceImports(network) + if err != nil { + return nil, err + } + execution.Cadence = cadenceCode + execution.IsScript = replaceableCadence.IsScript() + execution.IsTransaciton = replaceableCadence.IsTransaction() + case "1.0.0": + replaceableCadence, err = v1.ParseFlix(template) + if err != nil { + return nil, err + } + cadenceCode, err = replaceableCadence.GetAndReplaceCadenceImports(network) + if err != nil { + return nil, err + } + execution.Cadence = cadenceCode + execution.IsScript = replaceableCadence.IsScript() + execution.IsTransaciton = replaceableCadence.IsTransaction() + default: + return nil, fmt.Errorf("flix template version: %s not supported", ver) + } + + if execution.Cadence == "" { + return nil, fmt.Errorf("could not parse template, invalid flix template") + } + + return &execution, nil +} + +type flixQueryTypes string + +const ( + flixName flixQueryTypes = "name" + flixPath flixQueryTypes = "path" + flixId flixQueryTypes = "id" + flixUrl flixQueryTypes = "url" + flixJson flixQueryTypes = "json" +) + +func isHex(str string) bool { + if len(str) != 64 { + return false + } + _, err := hex.DecodeString(str) + return err == nil +} + +func isPath(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +func isUrl(str string) bool { + u, err := url.Parse(str) + return err == nil && u.Scheme != "" && u.Host != "" +} + +func isJson(str string) bool { + var js json.RawMessage + return json.Unmarshal([]byte(str), &js) == nil +} + +func getType(s string) flixQueryTypes { + switch { + case isPath(s): + return flixPath + case isHex(s): + return flixId + case isUrl(s): + return flixUrl + case isJson(s): + return flixJson + default: + return flixName + } +} + +func (s *flixServiceImpl) GetTemplate(ctx context.Context, flixQuery string) (string, error) { + var template string + var err error + + switch getType(flixQuery) { + case flixId: + template, err = s.GetFlixByID(ctx, flixQuery) + if err != nil { + return "", fmt.Errorf("could not find flix with id %s: %w", flixQuery, err) + } + + case flixName: + template, err = s.GetFlix(ctx, flixQuery) + if err != nil { + return "", fmt.Errorf("could not find flix with name %s: %w", flixQuery, err) + } + + case flixPath: + if s.config.FileReader == nil { + return "", fmt.Errorf("file reader not provided") + } + file, err := s.config.FileReader.ReadFile(flixQuery) + if err != nil { + return "", fmt.Errorf("could not read flix file %s: %w", flixQuery, err) + } + template = string(file) + if err != nil { + return "", fmt.Errorf("could not parse flix from file %s: %w", flixQuery, err) + } + + case flixUrl: + template, err = FetchFlixWithContext(ctx, flixQuery) + if err != nil { + return "", fmt.Errorf("could not parse flix from url %s: %w", flixQuery, err) + } + + default: + return "", fmt.Errorf("invalid flix query type: %s", flixQuery) + } + + return template, nil +} \ No newline at end of file diff --git a/flixkitv2/flixkit.go b/flixkitv2/flixkit.go new file mode 100644 index 0000000..b3a96db --- /dev/null +++ b/flixkitv2/flixkit.go @@ -0,0 +1,25 @@ +package flixkitv2 + +import ( + "context" + + "github.com/onflow/flixkit-go/internal" +) + +// FlixService is the interface for the flix service +type FlixService interface { + // GetTemplate returns the raw flix template + GetTemplate(ctx context.Context, templateName string) (string, error) + // GetAndReplaceCadenceImports returns the raw flix template with cadence imports replaced + GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (string, error) + // GenerateTemplate returns the generated template + GenerateTemplate(ctx context.Context, code string, preFill string) (string, error) + // GenerateBinding returns the generated binding + GenerateBinding(ctx context.Context, flixString string, templateLocation string) (string, error) +} + +type Config = internal.FlixServiceConfig + +func NewFlixService(config *Config) FlixService { + return internal.NewFlixService(config) +} \ No newline at end of file diff --git a/internal/flixkit_service.go b/internal/flixkit_service.go new file mode 100644 index 0000000..109b505 --- /dev/null +++ b/internal/flixkit_service.go @@ -0,0 +1,29 @@ +package internal + +import ( + "context" +) + +type FileReader interface { + ReadFile(path string) ([]byte, error) +} + +type FlixServiceConfig struct { + FlixServerURL string + FileReader FileReader +} + +func NewFlixService(config *FlixServiceConfig) flixService { + return flixService{} +} + +type flixService struct { +} + +func (s flixService) GetTemplate(ctx context.Context, templateName string) (string, error) { + return "", nil +} + +func (s flixService) GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (string, error) { + return "", nil +} \ No newline at end of file From fb7d76a656f18528792150a17405a9d07832a635 Mon Sep 17 00:00:00 2001 From: Ian Pun Date: Fri, 12 Jan 2024 12:06:02 -0800 Subject: [PATCH 10/17] update --- flixkitv2/flixkit.go | 24 +- internal/flixkit_service.go | 29 - internal/flixkitv2/common.go | 40 + internal/flixkitv2/fcl_binder_generator.go | 268 ++++++ internal/flixkitv2/flixkit_service.go | 52 ++ internal/flixkitv2/v1/types.go | 134 +++ internal/flixkitv2/v1_1/generator.go | 304 +++++++ .../TestEmptyPragmaWithParameter.golden | 23 + .../TestGenerateParametersScripts.golden | 67 ++ .../testdata/TestParsePragma/Empty.golden | 16 + .../testdata/TestParsePragma/Minimum.golden | 16 + .../TestParsePragma/WithParameters.golden | 86 ++ .../TestParsePragma/WithoutParameters.golden | 35 + internal/flixkitv2/v1_1/types.go | 600 ++++++++++++ internal/flixkitv2/v1_1/types_test.go | 853 ++++++++++++++++++ 15 files changed, 2512 insertions(+), 35 deletions(-) delete mode 100644 internal/flixkit_service.go create mode 100644 internal/flixkitv2/common.go create mode 100644 internal/flixkitv2/fcl_binder_generator.go create mode 100644 internal/flixkitv2/flixkit_service.go create mode 100644 internal/flixkitv2/v1/types.go create mode 100644 internal/flixkitv2/v1_1/generator.go create mode 100644 internal/flixkitv2/v1_1/testdata/TestEmptyPragmaWithParameter.golden create mode 100644 internal/flixkitv2/v1_1/testdata/TestGenerateParametersScripts.golden create mode 100644 internal/flixkitv2/v1_1/testdata/TestParsePragma/Empty.golden create mode 100644 internal/flixkitv2/v1_1/testdata/TestParsePragma/Minimum.golden create mode 100644 internal/flixkitv2/v1_1/testdata/TestParsePragma/WithParameters.golden create mode 100644 internal/flixkitv2/v1_1/testdata/TestParsePragma/WithoutParameters.golden create mode 100644 internal/flixkitv2/v1_1/types.go create mode 100644 internal/flixkitv2/v1_1/types_test.go diff --git a/flixkitv2/flixkit.go b/flixkitv2/flixkit.go index b3a96db..6dbd0d9 100644 --- a/flixkitv2/flixkit.go +++ b/flixkitv2/flixkit.go @@ -3,7 +3,7 @@ package flixkitv2 import ( "context" - "github.com/onflow/flixkit-go/internal" + internal "github.com/onflow/flixkit-go/internal/flixkitv2" ) // FlixService is the interface for the flix service @@ -11,15 +11,27 @@ type FlixService interface { // GetTemplate returns the raw flix template GetTemplate(ctx context.Context, templateName string) (string, error) // GetAndReplaceCadenceImports returns the raw flix template with cadence imports replaced - GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (string, error) + GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) +} + +type FlowInteractionTemplateExecution = internal.FlowInteractionTemplateExecution + +type FlixGenerator interface { // GenerateTemplate returns the generated template GenerateTemplate(ctx context.Context, code string, preFill string) (string, error) - // GenerateBinding returns the generated binding - GenerateBinding(ctx context.Context, flixString string, templateLocation string) (string, error) + // GenerateBinding returns the generated binding given the language + GenerateBinding(ctx context.Context, flixString string, templateLocation string, lang string) (string, error) + } -type Config = internal.FlixServiceConfig +type FlixServiceConfig = internal.FlixServiceConfig -func NewFlixService(config *Config) FlixService { +type FlixGeneratorConfig = internal.FlixServiceConfig + +func NewFlixService(config *FlixServiceConfig) FlixService { return internal.NewFlixService(config) +} + +func NewFlixGenaarator(config *FlixGeneratorConfig) FlixGenerator { + return internal.NewFlixGenerator(config) } \ No newline at end of file diff --git a/internal/flixkit_service.go b/internal/flixkit_service.go deleted file mode 100644 index 109b505..0000000 --- a/internal/flixkit_service.go +++ /dev/null @@ -1,29 +0,0 @@ -package internal - -import ( - "context" -) - -type FileReader interface { - ReadFile(path string) ([]byte, error) -} - -type FlixServiceConfig struct { - FlixServerURL string - FileReader FileReader -} - -func NewFlixService(config *FlixServiceConfig) flixService { - return flixService{} -} - -type flixService struct { -} - -func (s flixService) GetTemplate(ctx context.Context, templateName string) (string, error) { - return "", nil -} - -func (s flixService) GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (string, error) { - return "", nil -} \ No newline at end of file diff --git a/internal/flixkitv2/common.go b/internal/flixkitv2/common.go new file mode 100644 index 0000000..ef35f23 --- /dev/null +++ b/internal/flixkitv2/common.go @@ -0,0 +1,40 @@ +package flixkitv2 + +import ( + "encoding/json" + "fmt" + "net/url" +) + + +func getTemplateVersion(template string) (string, error) { +type FlowInteractionTemplateVersion struct { + FVersion string `json:"f_version"` +} + var flowTemplate FlowInteractionTemplateVersion + + err := json.Unmarshal([]byte(template), &flowTemplate) + if err != nil { + return "", err + } + + if flowTemplate.FVersion == "" { + return "", fmt.Errorf("version not found") + } + + return flowTemplate.FVersion, nil +} + +func isArrayParameter(arg FlixParameter) (isArray bool, cType string, jsType string) { + if arg.Type == "" || arg.Type[0] != '[' { + return false, "", "" + } + cadenceType := arg.Type[1 : len(arg.Type)-1] + javascriptType := "Array<" + convertCadenceTypeToJS(cadenceType) + ">" + return true, cadenceType, javascriptType +} + +func isUrl(str string) bool { + u, err := url.Parse(str) + return err == nil && u.Scheme != "" && u.Host != "" +} \ No newline at end of file diff --git a/internal/flixkitv2/fcl_binder_generator.go b/internal/flixkitv2/fcl_binder_generator.go new file mode 100644 index 0000000..31c7252 --- /dev/null +++ b/internal/flixkitv2/fcl_binder_generator.go @@ -0,0 +1,268 @@ +package flixkitv2 + +import ( + "bytes" + "fmt" + "sort" + "text/template" + + v1 "github.com/onflow/flixkit-go/flixkit/v1" + v1_1 "github.com/onflow/flixkit-go/internal/flixkitv2/v1_1" + "github.com/onflow/flixkit-go/internal/templates" + "github.com/stoewer/go-strcase" +) + +func NewFclTSGenerator() *FclGenerator{ + t := []string{ + templates.GetTsFclMainTemplate(), + templates.GetTsFclScriptTemplate(), + templates.GetTsFclTxTemplate(), + templates.GetTsFclParamsTemplate(), + templates.GetTsFclInterfaceTemplate(), + } + + return &FclGenerator{ + Templates: t, + } +} + +func NewFclJSGenerator() *FclGenerator{ + t := []string{ + templates.GetJsFclMainTemplate(), + templates.GetJsFclScriptTemplate(), + templates.GetJsFclTxTemplate(), + templates.GetJsFclParamsTemplate(), + } + + return &FclGenerator{ + Templates: t, + } +} + +func (g *FclGenerator) Generate(flixString string, templateLocation string) (string, error) { + tmpl, err := parseTemplates(g.Templates) + if err != nil { + return "", err + } + if flixString == "" { + return "", fmt.Errorf("no flix template provided") + } + + ver, err := getTemplateVersion(flixString) + if err != nil { + return "", fmt.Errorf("invalid flix template version, %w", err) + } + + isLocal := !isUrl(templateLocation) + var data templateData + switch ver { + case "1.0.0": + + flix, err := v1.ParseFlix(flixString) + if err != nil { + return "", err + } + data = getTemplateDataV1_0(flix, templateLocation, isLocal) + case "1.1.0": + flix, err := v1_1.ParseFlix(flixString) + if err != nil { + return "", err + } + data = getTemplateDataV1_1(flix, templateLocation, isLocal) + default: + return "", fmt.Errorf("invalid flix template version: %s", ver) + } + + data.FclVersion = GetFlixFclCompatibility(ver) + var buf bytes.Buffer + err = tmpl.Execute(&buf, data) + return buf.String(), err +} + +type simpleParameter struct { + Name string + JsType string + Description string + FclType string + CadType string +} + +type templateData struct { + FclVersion string + Version string + Parameters []simpleParameter + ParametersPrefixName string + Output simpleParameter + Title string + Description string + Location string + IsScript bool + IsLocalTemplate bool +} + +type FclGenerator struct { + Templates []string +} + + +type FlixParameter struct { + Name string + Type string +} + +func GetFlixFclCompatibility(flixVersion string) string { + compatibleVersions := map[string]string{ + "1.0.0": "1.3.0", + "1.1.0": "1.9.0", + // add more versions here + } + v, ok := compatibleVersions[flixVersion] + if !ok { + // default to latest if flix version not configured + return "1.9.0" + } + return v +} + +func getTemplateDataV1_1(flix *v1_1.InteractionTemplate, templateLocation string, isLocal bool) templateData { + var msgs v1_1.InteractionTemplateMessages = flix.Data.Messages + title := msgs.GetTitle("Request") + methodName := strcase.LowerCamelCase(title) + description := msgs.GetDescription("") + var sp simpleParameter + + if flix.Data.Type == "script" { + oTemp := flix.Data.Output + if flix.Data.Output == nil { + // if output is not defined, add default output + oTemp = &v1_1.Parameter{ + Label: "result", + Type: "String", + Messages: v1_1.InteractionTemplateMessages{}, + } + } + o := transformParameters([]v1_1.Parameter{*oTemp}) + if len(o) > 0 { + sp = o[0] + } + } + data := templateData{ + Version: flix.FVersion, + Parameters: transformParameters(flix.Data.Parameters), + ParametersPrefixName: strcase.UpperCamelCase(title), + Output: sp, + Title: methodName, + Description: description, + Location: templateLocation, + IsScript: flix.IsScript(), + IsLocalTemplate: isLocal, + } + return data +} + +func getTemplateDataV1_0(flix *v1.FlowInteractionTemplate, templateLocation string, isLocal bool) templateData { + title := flix.Data.Messages.GetTitleValue("Request") + methodName := strcase.LowerCamelCase(title) + description := flix.GetDescription() + var sp simpleParameter + // version 1.0 does not support output parameters, add default output + if flix.Data.Type == "script" { + sp = simpleParameter{ + Name: "result", + JsType: "string", + } + } + data := templateData{ + Version: flix.FVersion, + Parameters: transformArguments(flix.Data.Arguments), + ParametersPrefixName: strcase.UpperCamelCase(title), + Title: methodName, + Description: description, + Location: templateLocation, + IsScript: flix.IsScript(), + IsLocalTemplate: isLocal, + Output: sp, + } + return data +} + +func convertCadenceTypeToJS(cadenceType string) string { + // need to determine js type based on fcl supported types + // looking at fcl types and how arguments work as parameters + // https://github.com/onflow/fcl-js/blob/master/packages/types/src/types.js + switch cadenceType { + case "Bool": + return "boolean" + case "Void": + return "void" + case "Dictionary": + return "object" + case "Struct": + return "object" + case "Enum": + return "object" + default: + return "string" + } +} + +func parseTemplates(templates []string) (*template.Template, error) { + baseTemplate := template.New("base") + + for _, tmplStr := range templates { + _, err := baseTemplate.Parse(tmplStr) + if err != nil { + return nil, err + } + } + + return baseTemplate, nil +} + +func transformParameters(args []v1_1.Parameter) []simpleParameter { + simpleArgs := []simpleParameter{} + if len(args) == 0 { + return simpleArgs + } + sort.Slice(args, func(i, j int) bool { + return args[i].Index < args[j].Index + }) + + for _, arg := range args { + isArray, cType, jsType := isArrayParameter(FlixParameter{Name: arg.Label, Type: arg.Type}) + var msgs v1_1.InteractionTemplateMessages = arg.Messages + desciption := msgs.GetDescription("") + if isArray { + simpleArgs = append(simpleArgs, simpleParameter{Name: arg.Label, CadType: cType, JsType: jsType, FclType: "Array(t." + cType + ")", Description: desciption}) + } else { + jsType := convertCadenceTypeToJS(arg.Type) + simpleArgs = append(simpleArgs, simpleParameter{Name: arg.Label, CadType: arg.Type, JsType: jsType, FclType: arg.Type, Description: desciption}) + } + } + return simpleArgs +} + +func transformArguments(args v1.Arguments) []simpleParameter { + simpleArgs := []simpleParameter{} + var keys []string + // get keys for sorting + for k := range args { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return args[keys[i]].Index < args[keys[j]].Index + }) + for _, key := range keys { + arg := args[key] + isArray, cType, jsType := isArrayParameter(FlixParameter{Name: key, Type: arg.Type}) + desciption := arg.Messages.GetTitleValue("") + if isArray { + simpleArgs = append(simpleArgs, simpleParameter{Name: key, CadType: cType, JsType: jsType, FclType: "Array(t." + cType + ")", Description: desciption}) + } else { + jsType := convertCadenceTypeToJS(arg.Type) + simpleArgs = append(simpleArgs, simpleParameter{Name: key, CadType: arg.Type, JsType: jsType, FclType: arg.Type, Description: desciption}) + } + } + return simpleArgs +} \ No newline at end of file diff --git a/internal/flixkitv2/flixkit_service.go b/internal/flixkitv2/flixkit_service.go new file mode 100644 index 0000000..9f8fc16 --- /dev/null +++ b/internal/flixkitv2/flixkit_service.go @@ -0,0 +1,52 @@ +package flixkitv2 + +import ( + "context" + + v1_1 "github.com/onflow/flixkit-go/internal/flixkitv2/v1_1" +) + +type FileReader interface { + ReadFile(path string) ([]byte, error) +} + +type FlixServiceConfig struct { + FlixServerURL string + FileReader FileReader +} + +func NewFlixService(config *FlixServiceConfig) flixService { + return flixService{} +} + +type flixService struct { + generator v1_1.Generator +} + +// GetTemplateAndReplaceImports implements flixkitv2.FlixService. +func (flixService) GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) { + panic("unimplemented") +} + +type FlowInteractionTemplateExecution struct { + Network string + Cadence string + IsTransaciton bool + IsScript bool +} + +func (s flixService) GetTemplate(ctx context.Context, templateName string) (string, error) { + panic("unimplemented") +} + +func (s flixService) GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) { + panic("unimplemented") +} + +func (s flixService) GenerateBinding(ctx context.Context, flixString string, templateLocation string, lang string) (string, error) { + panic("unimplemented") +} + +func (s flixService) GenerateTemplate(ctx context.Context, code string, preFill string) (string, error) { + return s.generator.GenerateTemplate(ctx, code, preFill) +} diff --git a/internal/flixkitv2/v1/types.go b/internal/flixkitv2/v1/types.go new file mode 100644 index 0000000..ef373c6 --- /dev/null +++ b/internal/flixkitv2/v1/types.go @@ -0,0 +1,134 @@ +package flixkit + +import ( + "encoding/json" + "fmt" + "regexp" + + "github.com/onflow/flixkit-go/internal/contracts" +) + +type Network struct { + Address string `json:"address"` + FqAddress string `json:"fq_address"` + Contract string `json:"contract"` + Pin string `json:"pin"` + PinBlockHeight uint64 `json:"pin_block_height"` +} + +type Argument struct { + Index int `json:"index"` + Type string `json:"type"` + Messages Messages `json:"messages"` + Balance string `json:"balance"` +} + +type Title struct { + I18N map[string]string `json:"i18n"` +} + +type Description struct { + I18N map[string]string `json:"i18n"` +} + +type Messages struct { + Title *Title `json:"title,omitempty"` + Description *Description `json:"description,omitempty"` +} + +type Dependencies map[string]Contracts +type Contracts map[string]Networks +type Networks map[string]Network +type Arguments map[string]Argument + +type Data struct { + Type string `json:"type"` + Interface string `json:"interface"` + Messages Messages `json:"messages"` + Cadence string `json:"cadence"` + Dependencies Dependencies `json:"dependencies"` + Arguments Arguments `json:"arguments"` +} + +type FlowInteractionTemplate struct { + FType string `json:"f_type"` + FVersion string `json:"f_version"` + ID string `json:"id"` + Data Data `json:"data"` +} + +func (t *FlowInteractionTemplate) IsScript() bool { + return t.Data.Type == "script" +} + +func (t *FlowInteractionTemplate) IsTransaction() bool { + return t.Data.Type == "transaction" +} + +func ParseFlix(template string) (*FlowInteractionTemplate, error) { + var flowTemplate FlowInteractionTemplate + + err := json.Unmarshal([]byte(template), &flowTemplate) + if err != nil { + return nil, err + } + + return &flowTemplate, nil +} + +func (t *FlowInteractionTemplate) GetAndReplaceCadenceImports(networkName string) (string, error) { + var cadence string + + for dependencyAddress, c := range t.Data.Dependencies { + for contractName, networks := range c { + var networkAddress string + network, ok := networks[networkName] + networkAddress = network.Address + if !ok { + coreContractAddress := contracts.GetCoreContractForNetwork(contractName, networkName) + if coreContractAddress == "" { + return "", fmt.Errorf("network %s not found for contract %s in dependencies", networkName, contractName) + } + networkAddress = coreContractAddress + } + + pattern := fmt.Sprintf(`import\s*%s\s*from\s*%s`, contractName, dependencyAddress) + re, err := regexp.Compile(pattern) + if err != nil { + return "", fmt.Errorf("invalid regex pattern: %v", err) + } + + replacement := fmt.Sprintf("import %s from %s", contractName, networkAddress) + cadence = re.ReplaceAllString(t.Data.Cadence, replacement) + } + } + + return cadence, nil +} + +func (t *FlowInteractionTemplate) GetDescription() string { + s := "" + if t.Data.Messages.Description != nil && + t.Data.Messages.Description.I18N != nil { + + // relying on en-US for now, future we need to know what language to use + value, exists := t.Data.Messages.Description.I18N["en-US"] + if exists { + s = value + } + } + return s +} + +func (msgs *Messages) GetTitleValue(placeholder string) string { + s := placeholder + if msgs.Title != nil && + msgs.Title.I18N != nil { + // relying on en-US for now, future we need to know what language to use + value, exists := msgs.Title.I18N["en-US"] + if exists { + s = value + } + } + return s +} diff --git a/internal/flixkitv2/v1_1/generator.go b/internal/flixkitv2/v1_1/generator.go new file mode 100644 index 0000000..99ab0f2 --- /dev/null +++ b/internal/flixkitv2/v1_1/generator.go @@ -0,0 +1,304 @@ +package flixkit + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/cadence/runtime/cmd" + "github.com/onflow/cadence/runtime/common" + "github.com/onflow/cadence/runtime/parser" + "github.com/onflow/flixkit-go/internal/contracts" + "github.com/onflow/flow-cli/flowkit" + "github.com/onflow/flow-cli/flowkit/config" + "github.com/onflow/flow-cli/flowkit/gateway" + "github.com/onflow/flow-cli/flowkit/output" + "github.com/onflow/flow-go-sdk" + "github.com/onflow/flow-go-sdk/crypto" + "github.com/spf13/afero" +) + +/* +Same structure as core contracts, using config network names +*/ +type NetworkAddressMap map[string]string + +/* +Same structure as core contracts, keyed by contract name +*/ +type ContractInfos map[string]NetworkAddressMap + +type Generator struct { + deployedContracts []Contract + testnetClient *flowkit.Flowkit + mainnetClient *flowkit.Flowkit + template *InteractionTemplate +} + +func NewGenerator(contractInfos ContractInfos, logger output.Logger) (*Generator, error) { + loader := afero.Afero{Fs: afero.NewOsFs()} + + gwt, err := gateway.NewGrpcGateway(config.TestnetNetwork) + if err != nil { + return nil, fmt.Errorf("could not create grpc gateway for testnet %w", err) + } + + gwm, err := gateway.NewGrpcGateway(config.MainnetNetwork) + if err != nil { + return nil, fmt.Errorf("could not create grpc gateway for mainnet %w", err) + } + + state, err := flowkit.Init(loader, crypto.ECDSA_P256, crypto.SHA3_256) + if err != nil { + return nil, fmt.Errorf("could not initialize flowkit state %w", err) + } + testnetClient := flowkit.NewFlowkit(state, config.TestnetNetwork, gwt, logger) + mainnetClient := flowkit.NewFlowkit(state, config.MainnetNetwork, gwm, logger) + // add core contracts to deployed contracts + cc := contracts.GetCoreContracts() + deployedContracts := make([]Contract, 0) + for contractName, c := range cc { + contract := Contract{ + Contract: contractName, + Networks: []Network{ + {Network: config.MainnetNetwork.Name, Address: c[config.MainnetNetwork.Name]}, + {Network: config.TestnetNetwork.Name, Address: c[config.TestnetNetwork.Name]}, + {Network: config.EmulatorNetwork.Name, Address: c[config.EmulatorNetwork.Name]}, + }, + } + deployedContracts = append(deployedContracts, contract) + } + // allow user contracts to override core contracts + for contractInfo, networks := range contractInfos { + contract := Contract{ + Contract: contractInfo, + Networks: make([]Network, 0), + } + for network, address := range networks { + addr := flow.HexToAddress(address) + contract.Networks = append(contract.Networks, Network{ + Network: network, + Address: "0x" + addr.Hex(), + }) + } + deployedContracts = append(deployedContracts, contract) + } + + return &Generator{ + deployedContracts: deployedContracts, + testnetClient: testnetClient, + mainnetClient: mainnetClient, + template: &InteractionTemplate{}, + }, nil +} + +func (g Generator) GenerateTemplate(ctx context.Context, code string, preFill string) (string, error) { + g.template = &InteractionTemplate{} + g.template.Init() + if preFill != "" { + t, err := ParseFlix(preFill) + if err != nil { + return "", err + } + g.template = t + } + + // make sure imports use new import syntax "string import" + g.template.ProcessImports(code) + program, err := parser.ParseProgram(nil, []byte(g.template.Data.Cadence.Body), parser.Config{}) + if err != nil { + return "", err + } + + err = g.template.DetermineCadenceType(program) + if err != nil { + return "", err + } + + err = g.template.ParsePragma(program) + if err != nil { + return "", err + } + + err = g.template.ProcessParameters(program) + if err != nil { + return "", err + } + + err = g.processDependencies(ctx, program) + if err != nil { + return "", err + } + + // need to process dependencies before calculating network pins + _ = g.calculateNetworkPins(program) + id, _ := GenerateFlixID(g.template) + g.template.ID = id + templateJson, err := json.MarshalIndent(g.template, "", " ") + + return string(templateJson), err + +} + +func (g Generator) calculateNetworkPins(program *ast.Program) error { + networksOfInterest := []string{ + config.MainnetNetwork.Name, + config.TestnetNetwork.Name, + } + networkPins := make([]NetworkPin, 0) + for _, netName := range networksOfInterest { + cad, err := g.template.GetAndReplaceCadenceImports(netName) + if err != nil { + continue + } + networkPins = append(networkPins, NetworkPin{ + Network: netName, + PinSelf: ShaHex(cad, ""), + }) + } + g.template.Data.Cadence.NetworkPins = networkPins + return nil +} + +func (g Generator) processDependencies(ctx context.Context, program *ast.Program) error { + imports := program.ImportDeclarations() + + if len(imports) == 0 { + return nil + } + + // fill in dependence information + g.template.Data.Dependencies = make([]Dependency, 0) + for _, imp := range imports { + contractName, err := ExtractContractName(imp.String()) + if err != nil { + return err + } + networks, err := g.generateDependenceInfo(ctx, contractName) + if err != nil { + return err + } + c := Contract{ + Contract: contractName, + Networks: networks, + } + dep := Dependency{ + Contracts: []Contract{c}, + } + g.template.Data.Dependencies = append(g.template.Data.Dependencies, dep) + } + + return nil +} + +func (g *Generator) generateDependenceInfo(ctx context.Context, contractName string) ([]Network, error) { + // only support string import syntax + contractNetworks := g.LookupImportContractInfo(contractName) + if len(contractNetworks) == 0 { + return nil, fmt.Errorf("could not find contract dependency %s", contractName) + } + var networks []Network + for _, n := range contractNetworks { + network := Network{ + Network: n.Network, + Address: n.Address, + } + var flowkit *flowkit.Flowkit + if n.Network == config.MainnetNetwork.Name && g.mainnetClient != nil { + flowkit = g.mainnetClient + } else if n.Network == config.TestnetNetwork.Name && g.testnetClient != nil { + flowkit = g.testnetClient + } + if n.DependencyPinBlockHeight == 0 && flowkit != nil { + block, _ := flowkit.Gateway().GetLatestBlock() + height := block.Height + + details, err := g.GenerateDepPinDepthFirst(ctx, flowkit, n.Address, contractName, height) + if err != nil { + return nil, err + } + network.DependencyPinBlockHeight = height + network.DependencyPin = details + } + networks = append(networks, network) + } + + return networks, nil +} + +func (g *Generator) LookupImportContractInfo(contractName string) []Network { + for _, contract := range g.deployedContracts { + if contractName == contract.Contract { + return contract.Networks + } + } + return nil +} + +func (g *Generator) GenerateDepPinDepthFirst(ctx context.Context, flowkit *flowkit.Flowkit, address string, name string, height uint64) (details *PinDetail, err error) { + memoize := make(map[string]PinDetail) + networkPinDetail, err := generateDependencyNetworks(ctx, flowkit, address, name, memoize, height) + if err != nil { + return nil, err + } + + return networkPinDetail, nil +} + +func generateDependencyNetworks(ctx context.Context, flowkit *flowkit.Flowkit, address string, name string, cache map[string]PinDetail, height uint64) (*PinDetail, error) { + addr := flow.HexToAddress(address) + identifier := fmt.Sprintf("A.%s.%s", addr.Hex(), name) + pinDetail, ok := cache[identifier] + if ok { + return &pinDetail, nil + } + + account, err := flowkit.GetAccount(ctx, addr) + if err != nil { + return nil, err + } + code := account.Contracts[name] + depend := PinDetail{ + PinContractName: name, + PinContractAddress: "0x" + addr.Hex(), + PinSelf: ShaHex(code, ""), + } + depend.CalculatePin(height) + pins := []string{depend.PinSelf} + imports := getAddressImports(code, name) + detailImports := make([]PinDetail, 0) + for _, imp := range imports { + split := strings.Split(imp, ".") + address, name := split[0], split[1] + dep, err := generateDependencyNetworks(ctx, flowkit, address, name, cache, height) + if err != nil { + return nil, err + } + if dep != nil { + detailImports = append(detailImports, *dep) + cache[identifier] = *dep + } + pins = append(pins, dep.PinSelf) + } + depend.Imports = detailImports + depend.Pin = ShaHex(strings.Join(pins, ""), "") + return &depend, nil +} + +func getAddressImports(code []byte, name string) []string { + deps := []string{} + codes := map[common.Location][]byte{} + location := common.StringLocation(name) + program, _ := cmd.PrepareProgram(code, location, codes) + for _, imp := range program.ImportDeclarations() { + address, isAddressImport := imp.Location.(common.AddressLocation) + if isAddressImport { + adr := address.Address.Hex() + impName := imp.Identifiers[0].Identifier + deps = append(deps, fmt.Sprintf("%s.%s", adr, impName)) + } + } + return deps +} diff --git a/internal/flixkitv2/v1_1/testdata/TestEmptyPragmaWithParameter.golden b/internal/flixkitv2/v1_1/testdata/TestEmptyPragmaWithParameter.golden new file mode 100644 index 0000000..ce02d09 --- /dev/null +++ b/internal/flixkitv2/v1_1/testdata/TestEmptyPragmaWithParameter.golden @@ -0,0 +1,23 @@ +`{ + "f_type": "", + "f_version": "", + "id": "", + "data": { + "type": "", + "interface": "", + "messages": null, + "cadence": { + "body": "", + "network_pins": null + }, + "dependencies": null, + "parameters": [ + { + "label": "greeting", + "index": 0, + "type": "String", + "messages": [] + } + ] + } +}` diff --git a/internal/flixkitv2/v1_1/testdata/TestGenerateParametersScripts.golden b/internal/flixkitv2/v1_1/testdata/TestGenerateParametersScripts.golden new file mode 100644 index 0000000..059ae36 --- /dev/null +++ b/internal/flixkitv2/v1_1/testdata/TestGenerateParametersScripts.golden @@ -0,0 +1,67 @@ +`{ + "f_type": "InteractionTemplate", + "f_version": "1.1.0", + "id": "", + "data": { + "type": "script", + "interface": "", + "messages": [ + { + "key": "title", + "i18n": [ + { + "tag": "en-US", + "translation": "User Balance" + } + ] + }, + { + "key": "description", + "i18n": [ + { + "tag": "en-US", + "translation": "Get User Balance" + } + ] + } + ], + "cadence": { + "body": "", + "network_pins": null + }, + "dependencies": [], + "parameters": [ + { + "label": "address", + "index": 0, + "type": "Address", + "messages": [ + { + "key": "title", + "i18n": [ + { + "tag": "en-US", + "translation": "User Address" + } + ] + }, + { + "key": "description", + "i18n": [ + { + "tag": "en-US", + "translation": "Get user balance" + } + ] + } + ] + } + ], + "output": { + "label": "result", + "index": 0, + "type": "UFix64", + "messages": [] + } + } +}` diff --git a/internal/flixkitv2/v1_1/testdata/TestParsePragma/Empty.golden b/internal/flixkitv2/v1_1/testdata/TestParsePragma/Empty.golden new file mode 100644 index 0000000..9277e16 --- /dev/null +++ b/internal/flixkitv2/v1_1/testdata/TestParsePragma/Empty.golden @@ -0,0 +1,16 @@ +`{ + "f_type": "", + "f_version": "", + "id": "", + "data": { + "type": "", + "interface": "", + "messages": null, + "cadence": { + "body": "", + "network_pins": null + }, + "dependencies": null, + "parameters": null + } +}` diff --git a/internal/flixkitv2/v1_1/testdata/TestParsePragma/Minimum.golden b/internal/flixkitv2/v1_1/testdata/TestParsePragma/Minimum.golden new file mode 100644 index 0000000..129680c --- /dev/null +++ b/internal/flixkitv2/v1_1/testdata/TestParsePragma/Minimum.golden @@ -0,0 +1,16 @@ +`{ + "f_type": "", + "f_version": "1.1.0", + "id": "", + "data": { + "type": "", + "interface": "", + "messages": null, + "cadence": { + "body": "", + "network_pins": null + }, + "dependencies": null, + "parameters": null + } +}` diff --git a/internal/flixkitv2/v1_1/testdata/TestParsePragma/WithParameters.golden b/internal/flixkitv2/v1_1/testdata/TestParsePragma/WithParameters.golden new file mode 100644 index 0000000..c9bfaee --- /dev/null +++ b/internal/flixkitv2/v1_1/testdata/TestParsePragma/WithParameters.golden @@ -0,0 +1,86 @@ +`{ + "f_type": "", + "f_version": "1.1.0", + "id": "", + "data": { + "type": "", + "interface": "", + "messages": [ + { + "key": "title", + "i18n": [ + { + "tag": "en-US", + "translation": "Update Greeting" + } + ] + }, + { + "key": "description", + "i18n": [ + { + "tag": "en-US", + "translation": "Update the greeting on the HelloWorld contract" + } + ] + } + ], + "cadence": { + "body": "", + "network_pins": null + }, + "dependencies": null, + "parameters": [ + { + "label": "greeting", + "index": 0, + "type": "", + "messages": [ + { + "key": "title", + "i18n": [ + { + "tag": "en-US", + "translation": "Greeting" + } + ] + }, + { + "key": "description", + "i18n": [ + { + "tag": "en-US", + "translation": "The greeting to set on the HelloWorld contract" + } + ] + } + ] + }, + { + "label": "amount", + "index": 1, + "type": "", + "messages": [ + { + "key": "title", + "i18n": [ + { + "tag": "en-US", + "translation": "Amount" + } + ] + }, + { + "key": "description", + "i18n": [ + { + "tag": "en-US", + "translation": "The amount parameter to Test" + } + ] + } + ] + } + ] + } +}` diff --git a/internal/flixkitv2/v1_1/testdata/TestParsePragma/WithoutParameters.golden b/internal/flixkitv2/v1_1/testdata/TestParsePragma/WithoutParameters.golden new file mode 100644 index 0000000..eceba88 --- /dev/null +++ b/internal/flixkitv2/v1_1/testdata/TestParsePragma/WithoutParameters.golden @@ -0,0 +1,35 @@ +`{ + "f_type": "", + "f_version": "1.1.0", + "id": "", + "data": { + "type": "", + "interface": "", + "messages": [ + { + "key": "title", + "i18n": [ + { + "tag": "en-US", + "translation": "Update Greeting" + } + ] + }, + { + "key": "description", + "i18n": [ + { + "tag": "en-US", + "translation": "Update the greeting on the HelloWorld contract" + } + ] + } + ], + "cadence": { + "body": "", + "network_pins": null + }, + "dependencies": null, + "parameters": null + } +}` diff --git a/internal/flixkitv2/v1_1/types.go b/internal/flixkitv2/v1_1/types.go new file mode 100644 index 0000000..9a593cc --- /dev/null +++ b/internal/flixkitv2/v1_1/types.go @@ -0,0 +1,600 @@ +package flixkit + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "encoding/json" + "fmt" + "regexp" + "sort" + "strings" + + "github.com/ethereum/go-ethereum/rlp" + "github.com/onflow/cadence/runtime/ast" + "github.com/onflow/flixkit-go/internal/contracts" + "golang.org/x/crypto/sha3" +) + +type InteractionTemplate struct { + FType string `json:"f_type"` + FVersion string `json:"f_version"` + ID string `json:"id"` + Data Data `json:"data"` +} + +type Data struct { + Type string `json:"type"` + Interface string `json:"interface"` + Messages []Message `json:"messages"` + Cadence Cadence `json:"cadence"` + Dependencies []Dependency `json:"dependencies"` + Parameters []Parameter `json:"parameters"` + Output *Parameter `json:"output,omitempty"` +} + +type Message struct { + Key string `json:"key"` + I18n []I18n `json:"i18n"` +} + +type InteractionTemplateMessages []Message + +func (msgs InteractionTemplateMessages) GetTitle(placeholder string) string { + return msgs.getMessageValue("title", placeholder) +} + +func (msgs InteractionTemplateMessages) GetDescription(placeholder string) string { + return msgs.getMessageValue("description", placeholder) +} + +func (msgs InteractionTemplateMessages) getMessageValue(key string, placeholder string) string { + s := placeholder + for _, msg := range msgs { + if msg.Key == key { + for _, i18n := range msg.I18n { + // set default if en-US not present + s = i18n.Translation + if i18n.Tag == "en-US" { + s = i18n.Translation + break + } + } + } + } + return strings.TrimSpace(s) +} + +type I18n struct { + Tag string `json:"tag"` + Translation string `json:"translation"` +} + +type Cadence struct { + Body string `json:"body"` + NetworkPins []NetworkPin `json:"network_pins"` +} + +type NetworkPin struct { + Network string `json:"network"` + PinSelf string `json:"pin_self"` +} + +type Dependency struct { + Contracts []Contract `json:"contracts"` +} + +type Contract struct { + Contract string `json:"contract"` + Networks []Network `json:"networks"` +} + +type Network struct { + Network string `json:"network"` + Address string `json:"address"` + DependencyPinBlockHeight uint64 `json:"dependency_pin_block_height"` + DependencyPin *PinDetail `json:"dependency_pin,omitempty"` +} + +type PinDetail struct { + Pin string `json:"pin"` + PinSelf string `json:"pin_self"` + PinContractName string `json:"pin_contract_name"` + PinContractAddress string `json:"pin_contract_address"` + Imports []PinDetail `json:"imports"` +} + +func (p *PinDetail) CalculatePin(blockHeight uint64) { + var a []string + a = append(a, ShaHex(p.PinContractAddress, "address")) + a = append(a, ShaHex(p.PinContractName, "address")) + a = append(a, ShaHex(p.PinSelf, "pin_self")) + a = append(a, ShaHex(fmt.Sprint(blockHeight), "pin_block_height")) + hash := ShaHex(strings.Join(a, ""), "calculate_pin") + p.Pin = hash +} + +type Import struct { + Pin string `json:"pin"` + PinSelf string `json:"pin_self"` + PinContractName string `json:"pin_contract_name"` + PinContractAddress string `json:"pin_contract_address"` + Imports []Import `json:"imports"` // Recursive imports, if any +} + +type Parameter struct { + Label string `json:"label"` + Index int `json:"index"` + Type string `json:"type"` + Messages []Message `json:"messages"` +} + +func (t *InteractionTemplate) Init() { + t.FType = "InteractionTemplate" + if t.FVersion == "" { + t.FVersion = "1.1.0" + } +} + +func (t *InteractionTemplate) IsScript() bool { + return t.Data.Type == "script" +} + +func (t *InteractionTemplate) IsTransaction() bool { + return t.Data.Type == "transaction" +} + +func replaceImport(code string, from string, to string) string { + pathRegex := regexp.MustCompile(fmt.Sprintf(`import\s+(\w+)\s+from\s+"%s"`, from)) + identifierRegex := regexp.MustCompile(fmt.Sprintf(`import\s+"(%s)"`, from)) + + replacement := fmt.Sprintf(`import $1 from %s`, to) + code = pathRegex.ReplaceAllString(code, replacement) + code = identifierRegex.ReplaceAllString(code, replacement) + return code +} + +func (t *InteractionTemplate) GetAndReplaceCadenceImports(networkName string) (string, error) { + cadence := t.Data.Cadence.Body + // Compile regular expression to match and capture contract names + re := regexp.MustCompile(`import\s*"([^"]+)"`) + + // Find all matches and their captured groups + matches := re.FindAllStringSubmatch(cadence, -1) + if len(matches) == 0 { + return cadence, nil + } + for _, match := range matches { + contractName := match[1] + var dependencyAddress string + for _, Dependence := range t.Data.Dependencies { + for _, contract := range Dependence.Contracts { + if contract.Contract == contractName { + for _, network := range contract.Networks { + if network.Network == networkName { + dependencyAddress = network.Address + break + } + } + break + } + } + } + + if dependencyAddress == "" { + dependencyAddress = contracts.GetCoreContractForNetwork(contractName, networkName) + if dependencyAddress == "" { + return "", fmt.Errorf("network %s not found for contract %s in dependencies", networkName, contractName) + } + } + cadence = replaceImport(cadence, contractName, dependencyAddress) + } + + return cadence, nil + +} + +func ParseFlix(template string) (*InteractionTemplate, error) { + var flowTemplate InteractionTemplate + err := json.Unmarshal([]byte(template), &flowTemplate) + if err != nil { + return nil, err + } + + return &flowTemplate, nil +} + +type PragmaDeclaration struct { + Expression InteractionExpression `json:"Expression"` +} + +type InteractionExpression struct { + InvokedExpression IdentifierExpression `json:"InvokedExpression"` + Arguments []Argument `json:"Arguments"` + Value string `json:"Value"` // Used for string expressions + Type string `json:"Type"` // Used for string expressions + Values []InteractionExpression `json:"Values"` // Used for array expressions +} + +type IdentifierExpression struct { + Identifier Identifier `json:"Identifier"` +} + +type Identifier struct { + Identifier string `json:"Identifier"` +} + +type Argument struct { + Expression InteractionExpression `json:"Expression"` + Label string `json:"Label"` +} + +func (template *InteractionTemplate) ParsePragma(program *ast.Program) error { + pragmas := program.PragmaDeclarations() + if len(pragmas) == 0 { + return nil + } + + for _, prag := range pragmas { + var pragmaDeclaration PragmaDeclaration + jsonData, err := prag.MarshalJSON() + if err != nil { + return err + } + err = json.Unmarshal([]byte(jsonData), &pragmaDeclaration) + if err != nil { + return err + } + if pragmaDeclaration.Expression.InvokedExpression.Identifier.Identifier == "interaction" { + pragmaInfo := flatten(pragmaDeclaration) + if template.FVersion == "" { + template.FVersion = pragmaInfo.meta["version"] + } + if pragmaInfo.meta["title"] != "" { + template.Data.Messages = append(template.Data.Messages, Message{ + Key: "title", + I18n: []I18n{ + { + Tag: pragmaInfo.meta["language"], + Translation: pragmaInfo.meta["title"], + }, + }, + }) + } + if pragmaInfo.meta["description"] != "" { + template.Data.Messages = append(template.Data.Messages, Message{ + Key: "description", + I18n: []I18n{ + { + Tag: pragmaInfo.meta["language"], + Translation: pragmaInfo.meta["description"], + }, + }, + }) + } + if pragmaInfo.parameters != nil { + for i, paramInfo := range pragmaInfo.parameters { + param := Parameter{ + Label: paramInfo.params["name"], + Index: i, + } + if paramInfo.params["title"] != "" { + param.Messages = append(param.Messages, Message{ + Key: "title", + I18n: []I18n{ + { + Tag: pragmaInfo.meta["language"], + Translation: paramInfo.params["title"], + }, + }, + }) + } + if paramInfo.params["description"] != "" { + param.Messages = append(param.Messages, Message{ + Key: "description", + I18n: []I18n{ + { + Tag: pragmaInfo.meta["language"], + Translation: paramInfo.params["description"], + }, + }, + }) + } + template.Data.Parameters = append(template.Data.Parameters, param) + } + } + } + } + return nil +} + +type parametermetadata struct { + params map[string]string +} +type metadata struct { + meta map[string]string + parameters []parametermetadata +} + +func flatten(pragma PragmaDeclaration) metadata { + var nameValuePairs map[string]string + var parameterPairs []parametermetadata + nameValuePairs = make(map[string]string) + parameterPairs = make([]parametermetadata, 0) + + for _, arg := range pragma.Expression.Arguments { + // For regular arguments + if arg.Expression.Value != "" { + nameValuePairs[arg.Label] = arg.Expression.Value + } + + // For arguments that contain arrays (like parameters) + if len(arg.Expression.Values) > 0 { + for _, param := range arg.Expression.Values { + p := parametermetadata{ + params: make(map[string]string), + } + for _, paramArg := range param.Arguments { + p.params[paramArg.Label] = paramArg.Expression.Value + } + parameterPairs = append(parameterPairs, p) + } + } + } + return metadata{nameValuePairs, parameterPairs} +} + +func (template *InteractionTemplate) ProcessParameters(program *ast.Program) error { + if program == nil { + return fmt.Errorf("no cadence program provided") + } + var parameterList []*ast.Parameter + functionDeclaration := program.FunctionDeclarations() + // only interested in main function of script + for _, d := range functionDeclaration { + if d.Identifier.String() == "main" { + parameterList = d.ParameterList.Parameters + r := d.ReturnTypeAnnotation.Type.String() + template.Data.Output = &Parameter{ + Label: "result", + Type: r, + Messages: make([]Message, 0), + } + } + } + + if program.SoleTransactionDeclaration() != nil && program.SoleTransactionDeclaration().ParameterList != nil { + parameterList = program.SoleTransactionDeclaration().ParameterList.Parameters + } + + if parameterList != nil && len(template.Data.Parameters) == 0 { + template.Data.Parameters = make([]Parameter, 0) + } + + // use existing parameter of template or create new one + for i, param := range parameterList { + var tempParam *Parameter + if hasValueAtIndex(template.Data.Parameters, i) { + tempParam = &template.Data.Parameters[i] + // verify that the parameter name matches, + // could happen if dev inputted param data incorrectly + if tempParam.Label != param.Identifier.String() { + return fmt.Errorf("parameter name mismatch, expected %s, got %s", tempParam.Label, param.Identifier.String()) + } + tempParam.Type = param.TypeAnnotation.Type.String() + tempParam.Index = i + } else { + tempParam = &Parameter{ + Label: param.Identifier.String(), + Index: i, + Type: param.TypeAnnotation.Type.String(), + Messages: make([]Message, 0), + } + template.Data.Parameters = append(template.Data.Parameters, *tempParam) + } + } + + return nil +} + +func hasValueAtIndex(arr []Parameter, index int) bool { + if len(arr) == 0 { + return false + } + if index >= 0 && index < len(arr) { + return true + } + return false +} + +func (template *InteractionTemplate) DetermineCadenceType(program *ast.Program) error { + funcs := program.FunctionDeclarations() + trans := program.TransactionDeclarations() + var t string + if len(funcs) > 0 { + t = "script" + } else if len(trans) > 0 { + t = "transaction" + template.Data.Output = nil + } else { + return fmt.Errorf("no function or transaction declarations found") + } + template.Data.Type = t + return nil +} + +func (template *InteractionTemplate) ProcessImports(cadenceCode string) { + // Define a regular expression to match the "import ContractName from 0xContractName" pattern + pattern := regexp.MustCompile(`import\s+(\w+)\s+from\s+0x\w+`) + // Replace the matched pattern with "import \"ContractName\"" + replaced := pattern.ReplaceAllString(cadenceCode, `import "$1"`) + template.Data.Cadence.Body = replaced +} + +func messagesToRlp(messages []Message) []interface{} { + values := make([]interface{}, 0) + for _, message := range messages { + var mv []interface{} + mv = append(mv, ShaHex(message.Key, message.Key)) + var templateMessageTranslations []interface{} + for _, v := range message.I18n { + var tagTranslation []interface{} + tagTranslation = append(tagTranslation, ShaHex(v.Tag, v.Tag)) + tagTranslation = append(tagTranslation, ShaHex(v.Translation, v.Translation)) + templateMessageTranslations = append(templateMessageTranslations, tagTranslation) + } + mv = append(mv, templateMessageTranslations) + values = append(values, mv) + } + return values +} + +func parameterToRLP(p Parameter) []interface{} { + var values []interface{} + values = append(values, ShaHex(p.Label, "label")) + + var param []interface{} + param = append(param, ShaHex(fmt.Sprint(p.Index), "index")) + param = append(param, ShaHex(p.Type, "type")) + param = append(param, messagesToRlp(p.Messages)) + values = append(values, param) + + return values +} + +func parametersToRlp(params []Parameter) []interface{} { + values := make([]interface{}, 0) + sort.Slice(params, func(i, j int) bool { + return params[i].Index < params[j].Index + }) + + for _, p := range params { + values = append(values, parameterToRLP(p)) + } + return values +} + +func networksToRlp(Networks []Network) []interface{} { + values := make([]interface{}, 0) + for _, network := range Networks { + var networks []interface{} + networks = append(networks, ShaHex(network.Network, "key")) + if network.DependencyPin != nil { + networks = append(networks, ShaHex(network.DependencyPin.Pin, "networkPin")) + } + values = append(values, networks) + } + return values +} + +func contractsToRlp(Contracts []Contract) []interface{} { + values := make([]interface{}, 0) + for _, contract := range Contracts { + var contracts []interface{} + contracts = append(contracts, ShaHex(contract.Contract, "key")) + contracts = append(contracts, networksToRlp(contract.Networks)) + values = append(values, contracts) + } + return values +} + +func dependenciesToRlp(Dependencies []Dependency) []interface{} { + values := make([]interface{}, 0) + for _, dependency := range Dependencies { + var deps []interface{} + deps = append(deps, contractsToRlp(dependency.Contracts)) + values = append(values, deps) + } + return values +} + +func (flix InteractionTemplate) EncodeRLP() (result string, err error) { + var buffer bytes.Buffer // Create a new buffer + + input := []interface{}{ + ShaHex(flix.FType, ""), + ShaHex(flix.FVersion, ""), + ShaHex(flix.Data.Type, ""), + ShaHex(flix.Data.Interface, ""), + messagesToRlp(flix.Data.Messages), + ShaHex(flix.Data.Cadence, ""), + dependenciesToRlp(flix.Data.Dependencies), + parametersToRlp(flix.Data.Parameters), + } + + // msg := dependenciesToRlp(flix.Data.Dependencies) + //prettyJSON, _ := json.MarshalIndent(input, "", " ") + //fmt.Println(string(prettyJSON)) + + err = rlp.Encode(&buffer, input) + if err != nil { + return "", err + } + hexString := hex.EncodeToString(buffer.Bytes()) + + //fmt.Println("call to hash hex string") + fullyHashed := ShaHex(hexString, "input") + + //fmt.Println("hexString", fullyHashed) + return fullyHashed, nil + +} + +func GenerateFlixID(flix *InteractionTemplate) (string, error) { + rlpOutput, err := flix.EncodeRLP() + if err != nil { + return "", err + } + return string(rlpOutput), nil +} + +func ShaHex(value interface{}, debugKey string) string { + + // Convert the value to a byte array + data, err := convertToBytes(value) + if err != nil { + if debugKey != "" { + fmt.Printf("%30s value=%v hex=%x\n", debugKey, value, err.Error()) + } + return "" + } + + // Calculate the SHA-3 hash + hash := sha3.Sum256(data) + + // Convert the hash to a hexadecimal string + hashHex := hex.EncodeToString(hash[:]) + + return hashHex +} + +func convertToBytes(value interface{}) ([]byte, error) { + switch v := value.(type) { + case []byte: + return v, nil + case string: + return []byte(v), nil + case int: + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, uint32(v)) + return buf, nil + case uint64: + buf := make([]byte, 8) + binary.BigEndian.PutUint64(buf, v) + return buf, nil + default: + return nil, fmt.Errorf("unsupported type %T", v) + } +} + +func ExtractContractName(importStr string) (string, error) { + // Create a regex pattern to find the contract name inside the quotes + pattern := regexp.MustCompile(`import "([^"]+)"`) + matches := pattern.FindStringSubmatch(importStr) + + if len(matches) >= 2 { + return matches[1], nil + } + + return "", fmt.Errorf("no contract name found in string") +} diff --git a/internal/flixkitv2/v1_1/types_test.go b/internal/flixkitv2/v1_1/types_test.go new file mode 100644 index 0000000..1bcf41a --- /dev/null +++ b/internal/flixkitv2/v1_1/types_test.go @@ -0,0 +1,853 @@ +package flixkit + +import ( + "encoding/json" + "testing" + + "github.com/hexops/autogold/v2" + "github.com/onflow/cadence/runtime/parser" + "github.com/stretchr/testify/assert" +) + +var pragmaWithParameters = ` +#interaction( + version: "1.1.0", + title: "Update Greeting", + description: "Update the greeting on the HelloWorld contract", + language: "en-US", + parameters: [ + Parameter( + name: "greeting", + title: "Greeting", + description: "The greeting to set on the HelloWorld contract" + ), + Parameter( + name: "amount", + title: "Amount", + description: "The amount parameter to Test" + ) + ], +) + +import "HelloWorld" +transaction(greeting: String, amount: UFix64) { + + prepare(acct: AuthAccount) { + log(acct.address) + } + + execute { + HelloWorld.updateGreeting(newGreeting: greeting) + } +} +` + +var pragmaWithoutParameters = ` +#interaction( + version: "1.1.0", + title: "Update Greeting", + description: "Update the greeting on the HelloWorld contract", + language: "en-US", +) + +import "HelloWorld" +transaction(greeting: String) { + + prepare(acct: AuthAccount) { + log(acct.address) + } + + execute { + HelloWorld.updateGreeting(newGreeting: greeting) + } +} +` + +var pragmaMinimum = ` +#interaction( + version: "1.1.0", +) + +import "HelloWorld" +transaction(greeting: String) { + + prepare(acct: AuthAccount) { + log(acct.address) + } + + execute { + HelloWorld.updateGreeting(newGreeting: greeting) + } +} +` + +var PragmaEmpty = ` +import "HelloWorld" +transaction(greeting: String) { + + prepare(acct: AuthAccount) { + log(acct.address) + } + + execute { + HelloWorld.updateGreeting(newGreeting: greeting) + } +} +` + +func TestParsePragma(t *testing.T) { + assert := assert.New(t) + + tests := []struct { + name string + wantErr bool + code string + }{ + { + name: "WithParameters", + wantErr: false, + code: pragmaWithParameters, + }, + { + name: "WithoutParameters", + wantErr: false, + code: pragmaWithoutParameters, + }, + { + name: "Minimum", + wantErr: false, + code: pragmaMinimum, + }, + { + name: "Empty", + wantErr: false, + code: PragmaEmpty, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + codeBytes := []byte(tt.code) + program, err := parser.ParseProgram(nil, codeBytes, parser.Config{}) + if err != nil { + t.Fatal(err) + } + var template InteractionTemplate + if err != nil { + t.Fatal(err) + } + + err = template.ParsePragma(program) + if err != nil && !tt.wantErr { + t.Fatal(err) + } + if err != nil { + t.Fatal(err) + } + prettyJSON, err := json.MarshalIndent(template, "", " ") + assert.NoError(err, "marshal template to json should not return an error") + autogold.ExpectFile(t, string(prettyJSON)) + + }) + } +} + +func TestGenerateParametersScripts(t *testing.T) { + templateString := ` + { + "f_type": "InteractionTemplate", + "f_version": "1.1.0", + "id": "", + "data": + { + "type": "script", + "interface": "", + "messages": [ + { + "key": "title", + "i18n": [ + { + "tag": "en-US", + "translation": "User Balance" + } + ] + }, + { + "key": "description", + "i18n": [ + { + "tag": "en-US", + "translation": "Get User Balance" + } + ] + }], + "cadence": {}, + "dependencies": [], + "parameters": [ + { + "label": "address", + "index": 0, + "type": "Address", + "messages": [ + { + "key": "title", + "i18n": [ + { + "tag": "en-US", + "translation": "User Address" + } + ] + }, + { + "key": "description", + "i18n": [ + { + "tag": "en-US", + "translation": "Get user balance" + } + ] + } + ] + } + ] + } + }` + + cadence := ` + import "FungibleToken" + import "FlowToken" + + pub fun main(address: Address): UFix64 { + let account = getAccount(address) + let vaultRef = account.getCapability(/public/flowTokenBalance) + .borrow<&FlowToken.Vault{FungibleToken.Balance}>() + ?? panic("Could not borrow balance reference to the Vault") + + return vaultRef.balance + } + + ` + + codeBytes := []byte(cadence) + program, err := parser.ParseProgram(nil, codeBytes, parser.Config{}) + if err != nil { + t.Errorf("ParseProgram() err %v", err) + } + + template, err := ParseFlix(templateString) + if err != nil { + t.Errorf("ParseFlix() err %v", err) + } + err = template.ProcessParameters(program) + if err != nil { + t.Errorf("process parameters err %v", err) + } + prettyJSON, err := json.MarshalIndent(template, "", " ") + if err != nil { + t.Errorf("process parameters err %v", err) + } + + autogold.ExpectFile(t, string(prettyJSON)) +} + +func TestGenerateTemplateIdWithDeps(t *testing.T) { + templateId := "fcada4d7a654a0386a4bb048ac4c851ad7de3945e6e835dc4593581b8c8113da" + code := ` + { + "f_type": "InteractionTemplate", + "f_version": "1.1.0", + "id": "", + "data": { + "type": "transaction", + "interface": "", + "messages": [ + { + "key": "title", + "i18n": [ + { + "tag": "en-US", + "translation": "Update Greeting" + } + ] + }, + { + "key": "description", + "i18n": [ + { + "tag": "en-US", + "translation": "Update the greeting on the HelloWorld contract" + } + ] + } + ], + "cadence": { + "body": "import \"HelloWorld\"\n\n#interaction (\n version: \"1.1.0\",\n\ttitle: \"Update Greeting\",\n\tdescription: \"Update the greeting on the HelloWorld contract\",\n\tlanguage: \"en-US\",\n\tparameters: [\n\t\tParameter(\n\t\t\tname: \"greeting\", \n\t\t\ttitle: \"Greeting\", \n\t\t\tdescription: \"The greeting to set on the HelloWorld contract\"\n\t\t)\n\t],\n)\ntransaction(greeting: String) {\n\n prepare(acct: AuthAccount) {\n log(acct.address)\n }\n\n execute {\n HelloWorld.updateGreeting(newGreeting: greeting)\n }\n}\n", + "network_pins": [ + { + "network": "testnet", + "pin_self": "f61e68b5ba6987aaee393401889d5410b01ffa603a66952307319ea09fd505e7" + } + ] + }, + "dependencies": [ + { + "contracts": [ + { + "contract": "HelloWorld", + "networks": [ + { + "network": "testnet", + "address": "0xe15193734357cf5c", + "dependency_pin_block_height": 139331034, + "dependency_pin": { + "pin": "38b038a23c5975f90a797d6a821f9a8c4e4325a661f92513aedd73fda0e3300c", + "pin_self": "a06b3cd29330a3c22df3ac2383653e89c249c5e773fd4bbee73c45ea10294b97", + "pin_contract_name": "HelloWorld", + "pin_contract_address": "0xe15193734357cf5c", + "imports": [ + { + "pin": "3efc62adadbb1dedab0716ac031066a431cd7d627bc1b9260dd08a5a67b26b55", + "pin_self": "403cd82df774d247bc1fd7471e5ef1fdb7e2e0cb8ec44dce3af5473627179f9a", + "pin_contract_name": "GiveNumber", + "pin_contract_address": "0xe15193734357cf5c", + "imports": [] + } + ] + } + } + ] + } + ] + } + ], + "parameters": [ + { + "label": "greeting", + "index": 0, + "type": "String", + "messages": [ + { + "key": "title", + "i18n": [ + { + "tag": "en-US", + "translation": "Greeting" + } + ] + }, + { + "key": "description", + "i18n": [ + { + "tag": "en-US", + "translation": "The greeting to set on the HelloWorld contract" + } + ] + } + ] + } + ] + } + }` + + template, err := ParseFlix(code) + if err != nil { + t.Errorf("ParseFlix() err %v", err) + } + id, err := GenerateFlixID(template) + if err != nil { + t.Errorf("GenerateFlixID err %v", err) + } + + if id != templateId { + t.Errorf("GenerateFlixID got = %v, want %v", id, templateId) + } + +} + +func TestEmptyPragmaWithParameter(t *testing.T) { + codeBytes := []byte(PragmaEmpty) + program, err := parser.ParseProgram(nil, codeBytes, parser.Config{}) + if err != nil { + t.Fatal(err) + } + var template InteractionTemplate + err = template.ProcessParameters(program) + if err != nil { + t.Errorf("process parameters err %v", err) + } + prettyJSON, err := json.MarshalIndent(template, "", " ") + if err != nil { + t.Errorf("process parameters err %v", err) + } + + autogold.ExpectFile(t, string(prettyJSON)) + +} + +const template = ` +{ + "f_type": "InteractionTemplate", + "f_version": "1.1.0", + "id": "a2b2d73def...aabc5472d2", + "data": { + "type": "transaction", + "interface": "asadf23234...fas234234", + "messages": [ + { + "key": "title", + "i18n": [ + { + "tag": "en-US", + "translation": "Transfer FLOW" + }, + { + "tag": "fr-FR", + "translation": "FLOW de transfert" + }, + { + "tag": "zh-CN", + "translation": "转移流程" + } + ] + }, + { + "key": "description", + "i18n": [ + { + "tag": "en-US", + "translation": "Transfer {amount} FLOW to {to}" + }, + { + "tag": "fr-FR", + "translation": "Transférez {amount} FLOW à {to}" + }, + { + "tag": "zh-CN", + "translation": "将 {amount} FLOW 转移到 {to}" + } + ] + }, + { + "key": "signer", + "i18n": [ + { + "tag": "en-US", + "translation": "Sign this message to transfer FLOW" + }, + { + "tag": "fr-FR", + "translation": "Signez ce message pour transférer FLOW." + }, + { + "tag": "zh-CN", + "translation": "签署此消息以转移FLOW。" + } + ] + } + ], + "cadence": { + "body": "import \"FlowToken\"\n transaction(amount: UFix64, to: Address) {\n let vault: @FungibleToken.Vault\n prepare(signer: AuthAccount) {\n %%self.vault <- signer\n .borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)!\n .withdraw(amount: amount)\n self.vault <- FungibleToken.getVault(signer)\n }\n execute {\n getAccount(to)\n .getCapability(/public/flowTokenReceiver)!\n .borrow<&{FungibleToken.Receiver}>()!\n .deposit(from: <-self.vault)\n }\n }", + "network_pins": [ + { + "network": "mainnet", + "pin_self": "186e262ce6fe06b5075ec6569a0e5482a79c471881182612d8e4a665c2977f3e" + }, + { + "network": "testnet", + "pin_self": "f93977d7a297f559e97259cb2a95fed0f87cfeec46c5257a26adc26a260d6c4c" + } + ] + }, + "dependencies": [ + { + "contracts": [ + { + "contract": "FlowToken", + "networks": [ + { + "network": "mainnet", + "address": "0x1654653399040a61", + "dependency_pin_block_height": 10123123123, + "dependency_pin": { + "pin": "c8cb7cc7a1c2a329de65d83455016bc3a9b53f9668c74ef555032804bac0b25b", + "pin_self": "38d0cca4b74c4e88213df636b4cfc2eb6e86fd8b2b84579d3b9bffab3e0b1fcb", + "pin_contract_name": "FlowToken", + "imports": [ + { + "pin": "b8a3ed26c222ed67016a28021d8fee5603b948533cbc992b3c90f71a61b2b312", + "pin_self": "7bc3056ba5d39d130f45411c2c05bb549db8ce727c11a1cb821136a621be27fb", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0xf233dcee88fe0abe", + "imports": [] + } + ] + } + }, + { + "network": "testnet", + "address": "0x7e60df042a9c0868", + "dependency_pin_block_height": 10123123123, + "dependency_pin": { + "pin": "c8cb7cc7a1c2a329de65d83455016bc3a9b53f9668c74ef555032804bac0b25b", + "pin_self": "38d0cca4b74c4e88213df636b4cfc2eb6e86fd8b2b84579d3b9bffab3e0b1fcb", + "pin_contract_name": "FlowToken", + "pin_contract_address": "0x7e60df042a9c0868", + "imports": [ + { + "pin": "b8a3ed26c222ed67016a28021d8fee5603b948533cbc992b3c90f71a61b2b312", + "pin_self": "7bc3056ba5d39d130f45411c2c05bb549db8ce727c11a1cb821136a621be27fb", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0x9a0766d93b6608b7", + "imports": [] + } + ] + } + } + ] + } + ] + } + ], + "parameters": [ + { + "label": "amount", + "index": 0, + "type": "UFix64", + "messages": [ + { + "key": "title", + "i18n": [ + { + "tag": "en-US", + "translation": "Amount" + }, + { + "tag": "fr-FR", + "translation": "Montant" + }, + { + "tag": "zh-CN", + "translation": "数量" + } + ] + }, + { + "key": "description", + "i18n": [ + { + "tag": "en-US", + "translation": "Amount of FLOW token to transfer" + }, + { + "tag": "fr-FR", + "translation": "Quantité de token FLOW à transférer" + }, + { + "tag": "zh-CN", + "translation": "要转移的 FLOW 代币数量" + } + ] + } + ], + "balance": "FlowToken" + }, + { + "label": "to", + "index": 1, + "type": "Address", + "messages": [ + { + "key": "title", + "i18n": [ + { + "tag": "en-US", + "translation": "To" + }, + { + "tag": "fr-FR", + "translation": "Pour" + }, + { + "tag": "zh-CN", + "translation": "到" + } + ] + }, + { + "key": "description", + "i18n": [ + { + "tag": "en-US", + "translation": "Amount of FLOW token to transfer" + }, + { + "tag": "fr-FR", + "translation": "Le compte vers lequel transférer les jetons FLOW" + }, + { + "tag": "zh-CN", + "translation": "将 FLOW 代币转移到的帐户" + } + ] + } + ] + } + ] + } + } +` +const templateMultipleImports = ` +{ + "f_type": "InteractionTemplate", + "f_version": "1.1.0", + "id": "29d03aafbbb5a02e0d5f4ffee685c12494915410812305c2858008d3e2902b72", + "data": { + "type": "script", + "interface": "", + "messages": null, + "cadence": { + "body": "import \"FungibleToken\"\nimport \"FlowToken\"\n\npub fun main(address: Address): UFix64 {\n let account = getAccount(address)\n\n let vaultRef = account\n .getCapability(/public/flowTokenBalance)\n .borrow\u003c\u0026FlowToken.Vault{FungibleToken.Balance}\u003e()\n ?? panic(\"Could not borrow balance reference to the Vault\")\n\n return vaultRef.balance\n}\n", + "network_pins": [ + { + "network": "mainnet", + "pin_self": "c9aef2c441b2ff0e1a724fcd72f7a48ae7fbbba3c6e72c530607a90ea0fdf93a" + }, + { + "network": "testnet", + "pin_self": "74331585cf3df9cd60e6570566d079f97b3e28b0e2156a06731e73e492fe120e" + } + ] + }, + "dependencies": [ + { + "contracts": [ + { + "contract": "FungibleToken", + "networks": [ + { + "network": "mainnet", + "address": "0xf233dcee88fe0abe", + "dependency_pin_block_height": 67669170, + "dependency_pin": { + "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", + "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0xf233dcee88fe0abe", + "imports": [] + } + }, + { + "network": "testnet", + "address": "0x9a0766d93b6608b7", + "dependency_pin_block_height": 139547221, + "dependency_pin": { + "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", + "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0x9a0766d93b6608b7", + "imports": [] + } + }, + { + "network": "emulator", + "address": "0xee82856bf20e2aa6", + "dependency_pin_block_height": 0 + } + ] + } + ] + }, + { + "contracts": [ + { + "contract": "FlowToken", + "networks": [ + { + "network": "mainnet", + "address": "0x1654653399040a61", + "dependency_pin_block_height": 67669170, + "dependency_pin": { + "pin": "a341e772da413bfbcf43b0fc167bd50a20c9f40baf10e12d3dbc2f5181526de9", + "pin_self": "0e932728b73bff3c09dd58922f2529fc7b7fe7477f1dcc61169bc8f46948ad91", + "pin_contract_name": "FlowToken", + "pin_contract_address": "0x1654653399040a61", + "imports": [ + { + "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", + "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0xf233dcee88fe0abe", + "imports": [] + } + ] + } + }, + { + "network": "testnet", + "address": "0x7e60df042a9c0868", + "dependency_pin_block_height": 139547221, + "dependency_pin": { + "pin": "9cc21a34a01486ebd6f044e99dbcdd58671850f81fcc345d071181c19f61aaa4", + "pin_self": "6f01c7001e2d6635b667a170d3ccbc13659c40d01bb35e56979fcc7fa2d18646", + "pin_contract_name": "FlowToken", + "pin_contract_address": "0x7e60df042a9c0868", + "imports": [ + { + "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", + "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0x9a0766d93b6608b7", + "imports": [] + } + ] + } + }, + { + "network": "emulator", + "address": "0x0ae53cb6e3f42a79", + "dependency_pin_block_height": 0 + } + ] + } + ] + } + ], + "parameters": [ + { + "label": "address", + "index": 0, + "type": "Address", + "messages": [] + } + ] + } +} +` +const templateMultipleCoreImports = ` +{ + "f_type": "InteractionTemplate", + "f_version": "1.1.0", + "id": "29d03aafbbb5a02e0d5f4ffee685c12494915410812305c2858008d3e2902b72", + "data": { + "type": "script", + "interface": "", + "messages": null, + "cadence": { + "body": "import \"FungibleToken\"\nimport \"FlowToken\"\n\npub fun main(address: Address): UFix64 {\n let account = getAccount(address)\n\n let vaultRef = account\n .getCapability(/public/flowTokenBalance)\n .borrow\u003c\u0026FlowToken.Vault{FungibleToken.Balance}\u003e()\n ?? panic(\"Could not borrow balance reference to the Vault\")\n\n return vaultRef.balance\n}\n", + "network_pins": [] + }, + "dependencies": [], + "parameters": [] + } +} +` + +const templateMissing = ` +{ + "f_type": "InteractionTemplate", + "f_version": "1.1.0", + "id": "a2b2d73def...aabc5472d2", + "data": { + "type": "transaction", + "interface": "asadf23234...fas234234", + "messages": [], + "cadence": { + "body": "import \"FlowTokenAA\"\n transaction(amount: UFix64, to: Address) {\n let vault: @FungibleToken.Vault\n prepare(signer: AuthAccount) {\n %%self.vault <- signer\n .borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)!\n .withdraw(amount: amount)\n self.vault <- FungibleToken.getVault(signer)\n }\n execute {\n getAccount(to)\n .getCapability(/public/flowTokenReceiver)!\n .borrow<&{FungibleToken.Receiver}>()!\n .deposit(from: <-self.vault)\n }\n }", + "network_pins": [] + }, + "dependencies": [ + { + "contracts": [], + "parameters": [] + } + ] + } +} +` + +func TestGetAndReplaceCadenceImports(t *testing.T) { + assert := assert.New(t) + + tests := []struct { + name string + network string + wantErr bool + wantImport string + template string + }{ + { + name: "Mainnet", + network: "mainnet", + wantErr: false, + wantImport: "import FlowToken from 0x1654653399040a61", + template: template, + }, + { + name: "Testnet", + network: "testnet", + wantErr: false, + wantImport: "import FlowToken from 0x7e60df042a9c0868", + template: template, + }, + { + name: "MissingNetwork", + network: "missing", + wantErr: true, + template: template, + }, + { + name: "MissingCadence", + network: "mainnet", + wantErr: true, + template: templateMissing, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + parsedTemplate, err := ParseFlix(tt.template) + if err != nil { + t.Fatal(err) + } + + cadenceCode, err := parsedTemplate.GetAndReplaceCadenceImports(tt.network) + if tt.wantErr { + assert.Error(err, tt.name, "GetCadenceWithReplacedImports should return an error") + } else { + assert.NoError(err, "GetCadenceWithReplacedImports should not return an error") + assert.NotEmpty(cadenceCode, tt.name, "Cadence should not be empty") + + assert.Contains(cadenceCode, tt.wantImport, "Cadence should contain the expected import") + } + }) + } +} + +func TestGetAndReplaceCadenceImportsMultipleImports(t *testing.T) { + template, err := ParseFlix(templateMultipleImports) + if err != nil { + t.Fatal(err) + } + cadenceCode, err := template.GetAndReplaceCadenceImports("mainnet") + if err != nil { + t.Fatal(err) + } + assert.Contains(t, cadenceCode, "import FungibleToken from 0xf233dcee88fe0abe", "Cadence should contain the expected FungibleToken import") + assert.Contains(t, cadenceCode, "import FlowToken from 0x1654653399040a61", "Cadence should contain the expected FlowTokenimport") + +} + +func TestGetAndReplaceCadenceImportsMultipleCoreImports(t *testing.T) { + template, err := ParseFlix(templateMultipleCoreImports) + if err != nil { + t.Fatal(err) + } + cadenceCode, err := template.GetAndReplaceCadenceImports("mainnet") + if err != nil { + t.Fatal(err) + } + assert.Contains(t, cadenceCode, "import FungibleToken from 0xf233dcee88fe0abe", "Cadence should contain the expected FungibleToken import") + assert.Contains(t, cadenceCode, "import FlowToken from 0x1654653399040a61", "Cadence should contain the expected FlowTokenimport") + +} From 3810160dd3ab0cc2567ccfcf0dcb3e8b1b73ebfe Mon Sep 17 00:00:00 2001 From: Ian Pun Date: Fri, 12 Jan 2024 12:28:10 -0800 Subject: [PATCH 11/17] update --- flixkitv2/flix_generator.go | 21 +++++++++++ flixkitv2/flix_service.go | 22 +++++++++++ flixkitv2/flixkit.go | 37 ------------------- ...er_generator.go => fcl_binding_creator.go} | 20 +++++----- internal/flixkitv2/flix_generator.go | 31 ++++++++++++++++ .../{flixkit_service.go => flix_service.go} | 11 +++--- 6 files changed, 89 insertions(+), 53 deletions(-) create mode 100644 flixkitv2/flix_generator.go create mode 100644 flixkitv2/flix_service.go delete mode 100644 flixkitv2/flixkit.go rename internal/flixkitv2/{fcl_binder_generator.go => fcl_binding_creator.go} (95%) create mode 100644 internal/flixkitv2/flix_generator.go rename internal/flixkitv2/{flixkit_service.go => flix_service.go} (80%) diff --git a/flixkitv2/flix_generator.go b/flixkitv2/flix_generator.go new file mode 100644 index 0000000..60018d7 --- /dev/null +++ b/flixkitv2/flix_generator.go @@ -0,0 +1,21 @@ +package flixkitv2 + +import ( + "context" + + internal "github.com/onflow/flixkit-go/internal/flixkitv2" +) + +type FlixGenerator interface { + // GenerateTemplate returns the generated raw template + GenerateTemplate(ctx context.Context, code string, preFill string) (string, error) + // GenerateBinding returns the generated binding given the language + GenerateBinding(ctx context.Context, flixString string, templateLocation string, lang string) (string, error) +} + +type FlixGeneratorConfig = internal.FlixGeneratorConfig + +// NewFlixGenerator returns a new FlixGenerator +func NewFlixGenerator(conf FlixGeneratorConfig) FlixGenerator { + return internal.NewFlixGenerator(conf) +} \ No newline at end of file diff --git a/flixkitv2/flix_service.go b/flixkitv2/flix_service.go new file mode 100644 index 0000000..8e895ab --- /dev/null +++ b/flixkitv2/flix_service.go @@ -0,0 +1,22 @@ +package flixkitv2 + +import ( + "context" + + internal "github.com/onflow/flixkit-go/internal/flixkitv2" +) + +type FlixService interface { + // GetTemplate returns the raw flix template + GetTemplate(ctx context.Context, templateName string) (string, error) + // GetAndReplaceImports returns the raw flix template with cadence imports replaced + GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) +} + +type FlowInteractionTemplateExecution = internal.FlowInteractionTemplateExecution + +type FlixServiceConfig = internal.FlixServiceConfig + +func NewFlixService(config *FlixServiceConfig) FlixService { + return internal.NewFlixService(config) +} diff --git a/flixkitv2/flixkit.go b/flixkitv2/flixkit.go deleted file mode 100644 index 6dbd0d9..0000000 --- a/flixkitv2/flixkit.go +++ /dev/null @@ -1,37 +0,0 @@ -package flixkitv2 - -import ( - "context" - - internal "github.com/onflow/flixkit-go/internal/flixkitv2" -) - -// FlixService is the interface for the flix service -type FlixService interface { - // GetTemplate returns the raw flix template - GetTemplate(ctx context.Context, templateName string) (string, error) - // GetAndReplaceCadenceImports returns the raw flix template with cadence imports replaced - GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) -} - -type FlowInteractionTemplateExecution = internal.FlowInteractionTemplateExecution - -type FlixGenerator interface { - // GenerateTemplate returns the generated template - GenerateTemplate(ctx context.Context, code string, preFill string) (string, error) - // GenerateBinding returns the generated binding given the language - GenerateBinding(ctx context.Context, flixString string, templateLocation string, lang string) (string, error) - -} - -type FlixServiceConfig = internal.FlixServiceConfig - -type FlixGeneratorConfig = internal.FlixServiceConfig - -func NewFlixService(config *FlixServiceConfig) FlixService { - return internal.NewFlixService(config) -} - -func NewFlixGenaarator(config *FlixGeneratorConfig) FlixGenerator { - return internal.NewFlixGenerator(config) -} \ No newline at end of file diff --git a/internal/flixkitv2/fcl_binder_generator.go b/internal/flixkitv2/fcl_binding_creator.go similarity index 95% rename from internal/flixkitv2/fcl_binder_generator.go rename to internal/flixkitv2/fcl_binding_creator.go index 31c7252..e4e452f 100644 --- a/internal/flixkitv2/fcl_binder_generator.go +++ b/internal/flixkitv2/fcl_binding_creator.go @@ -12,7 +12,7 @@ import ( "github.com/stoewer/go-strcase" ) -func NewFclTSGenerator() *FclGenerator{ +func NewFclTSCreator() *FclCreator{ t := []string{ templates.GetTsFclMainTemplate(), templates.GetTsFclScriptTemplate(), @@ -21,12 +21,12 @@ func NewFclTSGenerator() *FclGenerator{ templates.GetTsFclInterfaceTemplate(), } - return &FclGenerator{ - Templates: t, + return &FclCreator{ + templates: t, } } -func NewFclJSGenerator() *FclGenerator{ +func NewFclJSCreator() *FclCreator{ t := []string{ templates.GetJsFclMainTemplate(), templates.GetJsFclScriptTemplate(), @@ -34,13 +34,13 @@ func NewFclJSGenerator() *FclGenerator{ templates.GetJsFclParamsTemplate(), } - return &FclGenerator{ - Templates: t, + return &FclCreator{ + templates: t, } } -func (g *FclGenerator) Generate(flixString string, templateLocation string) (string, error) { - tmpl, err := parseTemplates(g.Templates) +func (g *FclCreator) Generate(flixString string, templateLocation string) (string, error) { + tmpl, err := parseTemplates(g.templates) if err != nil { return "", err } @@ -100,8 +100,8 @@ type templateData struct { IsLocalTemplate bool } -type FclGenerator struct { - Templates []string +type FclCreator struct { + templates []string } diff --git a/internal/flixkitv2/flix_generator.go b/internal/flixkitv2/flix_generator.go new file mode 100644 index 0000000..2193528 --- /dev/null +++ b/internal/flixkitv2/flix_generator.go @@ -0,0 +1,31 @@ +package flixkitv2 + +import ( + "context" + + v1_1 "github.com/onflow/flixkit-go/internal/flixkitv2/v1_1" +) + +type flixGenerator struct { + generator v1_1.Generator + bindingCreator FclCreator + fileReader FileReader +} + +type FlixGeneratorConfig struct { + FileReader FileReader +} + +func NewFlixGenerator(conf FlixGeneratorConfig) flixGenerator{ + return flixGenerator{ + fileReader: conf.FileReader, + } +} + +func (s flixGenerator) GenerateBinding(ctx context.Context, flixString string, templateLocation string, lang string) (string, error) { + return s.bindingCreator.Generate(flixString, templateLocation) +} + +func (s flixGenerator) GenerateTemplate(ctx context.Context, code string, preFill string) (string, error) { + return s.generator.GenerateTemplate(ctx, code, preFill) +} diff --git a/internal/flixkitv2/flixkit_service.go b/internal/flixkitv2/flix_service.go similarity index 80% rename from internal/flixkitv2/flixkit_service.go rename to internal/flixkitv2/flix_service.go index 9f8fc16..bce049c 100644 --- a/internal/flixkitv2/flixkit_service.go +++ b/internal/flixkitv2/flix_service.go @@ -21,13 +21,11 @@ func NewFlixService(config *FlixServiceConfig) flixService { type flixService struct { generator v1_1.Generator -} + bindingCreator FclCreator -// GetTemplateAndReplaceImports implements flixkitv2.FlixService. -func (flixService) GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) { - panic("unimplemented") } + type FlowInteractionTemplateExecution struct { Network string Cadence string @@ -39,12 +37,13 @@ func (s flixService) GetTemplate(ctx context.Context, templateName string) (stri panic("unimplemented") } -func (s flixService) GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) { +func (flixService) GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) { panic("unimplemented") } + func (s flixService) GenerateBinding(ctx context.Context, flixString string, templateLocation string, lang string) (string, error) { - panic("unimplemented") + return s.bindingCreator.Generate(flixString, templateLocation) } func (s flixService) GenerateTemplate(ctx context.Context, code string, preFill string) (string, error) { From 1eee45983abbf90cfca8f3a0877f0796c8935c34 Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Mon, 15 Jan 2024 15:41:37 -0600 Subject: [PATCH 12/17] restructuring for v2.0 --- flixkit/fcl_bindings.go | 278 ------ flixkit/fcl_bindings_test.go | 597 ------------ flixkit/flix_generator.go | 19 + {flixkitv2 => flixkit}/flix_service.go | 8 +- flixkit/generator.go | 307 ------- .../TestBindingReadTokenBalance.golden | 33 - flixkit/testdata/TestJSGenArrayScript.golden | 28 - flixkit/testdata/TestJSGenMinScript.golden | 28 - .../testdata/TestJSGenNoParamsScript.golden | 27 - flixkit/testdata/TestJSGenScript.golden | 29 - flixkit/testdata/TestJSGenTransaction.golden | 29 - .../testdata/TestTSGenNoParamsScript.golden | 29 - flixkit/testdata/TestTSGenNoParamsTx.golden | 27 - flixkit/testdata/TestTSGenParamsScript.golden | 33 - flixkit/testdata/TestTSGenParamsTx.golden | 31 - flixkit/v1_1/types_test.go | 853 ------------------ flixkitv2/flix_generator.go | 21 - internal/common.go | 84 ++ .../{flixkitv2 => }/fcl_binding_creator.go | 13 +- internal/flix_generator.go | 35 + .../flixkit.go => internal/flix_service.go | 226 ++--- .../flix_service_test.go | 14 +- internal/flixkitv2/common.go | 40 - internal/flixkitv2/flix_generator.go | 31 - internal/flixkitv2/flix_service.go | 51 -- internal/flixkitv2/v1/types.go | 134 --- .../TestEmptyPragmaWithParameter.golden | 23 - .../TestGenerateParametersScripts.golden | 67 -- .../testdata/TestParsePragma/Empty.golden | 16 - .../testdata/TestParsePragma/Minimum.golden | 16 - .../TestParsePragma/WithParameters.golden | 86 -- .../TestParsePragma/WithoutParameters.golden | 35 - internal/flixkitv2/v1_1/types.go | 600 ------------ {flixkit => internal}/v1/types.go | 4 +- internal/{flixkitv2 => }/v1_1/generator.go | 8 +- {flixkit => internal/v1_1}/generator_test.go | 24 +- .../TestEmptyPragmaWithParameter.golden | 0 .../TestGenerateParametersScripts.golden | 0 .../v1_1}/testdata/TestHelloScript.golden | 0 .../testdata/TestParsePragma/Empty.golden | 0 .../testdata/TestParsePragma/Minimum.golden | 0 .../TestParsePragma/WithParameters.golden | 0 .../TestParsePragma/WithoutParameters.golden | 0 .../testdata/TestTransactionValue.golden | 0 .../TestTransferFlowTransaction.golden | 0 {flixkit => internal}/v1_1/types.go | 4 +- internal/{flixkitv2 => }/v1_1/types_test.go | 8 +- 47 files changed, 257 insertions(+), 3639 deletions(-) delete mode 100644 flixkit/fcl_bindings.go delete mode 100644 flixkit/fcl_bindings_test.go create mode 100644 flixkit/flix_generator.go rename {flixkitv2 => flixkit}/flix_service.go (67%) delete mode 100644 flixkit/generator.go delete mode 100644 flixkit/testdata/TestBindingReadTokenBalance.golden delete mode 100644 flixkit/testdata/TestJSGenArrayScript.golden delete mode 100644 flixkit/testdata/TestJSGenMinScript.golden delete mode 100644 flixkit/testdata/TestJSGenNoParamsScript.golden delete mode 100644 flixkit/testdata/TestJSGenScript.golden delete mode 100644 flixkit/testdata/TestJSGenTransaction.golden delete mode 100644 flixkit/testdata/TestTSGenNoParamsScript.golden delete mode 100644 flixkit/testdata/TestTSGenNoParamsTx.golden delete mode 100644 flixkit/testdata/TestTSGenParamsScript.golden delete mode 100644 flixkit/testdata/TestTSGenParamsTx.golden delete mode 100644 flixkit/v1_1/types_test.go delete mode 100644 flixkitv2/flix_generator.go create mode 100644 internal/common.go rename internal/{flixkitv2 => }/fcl_binding_creator.go (97%) create mode 100644 internal/flix_generator.go rename flixkit/flixkit.go => internal/flix_service.go (56%) rename flixkit/flixkit_test.go => internal/flix_service_test.go (95%) delete mode 100644 internal/flixkitv2/common.go delete mode 100644 internal/flixkitv2/flix_generator.go delete mode 100644 internal/flixkitv2/flix_service.go delete mode 100644 internal/flixkitv2/v1/types.go delete mode 100644 internal/flixkitv2/v1_1/testdata/TestEmptyPragmaWithParameter.golden delete mode 100644 internal/flixkitv2/v1_1/testdata/TestGenerateParametersScripts.golden delete mode 100644 internal/flixkitv2/v1_1/testdata/TestParsePragma/Empty.golden delete mode 100644 internal/flixkitv2/v1_1/testdata/TestParsePragma/Minimum.golden delete mode 100644 internal/flixkitv2/v1_1/testdata/TestParsePragma/WithParameters.golden delete mode 100644 internal/flixkitv2/v1_1/testdata/TestParsePragma/WithoutParameters.golden delete mode 100644 internal/flixkitv2/v1_1/types.go rename {flixkit => internal}/v1/types.go (96%) rename internal/{flixkitv2 => }/v1_1/generator.go (96%) rename {flixkit => internal/v1_1}/generator_test.go (88%) rename {flixkit => internal}/v1_1/testdata/TestEmptyPragmaWithParameter.golden (100%) rename {flixkit => internal}/v1_1/testdata/TestGenerateParametersScripts.golden (100%) rename {flixkit => internal/v1_1}/testdata/TestHelloScript.golden (100%) rename {flixkit => internal}/v1_1/testdata/TestParsePragma/Empty.golden (100%) rename {flixkit => internal}/v1_1/testdata/TestParsePragma/Minimum.golden (100%) rename {flixkit => internal}/v1_1/testdata/TestParsePragma/WithParameters.golden (100%) rename {flixkit => internal}/v1_1/testdata/TestParsePragma/WithoutParameters.golden (100%) rename {flixkit => internal/v1_1}/testdata/TestTransactionValue.golden (100%) rename {flixkit => internal/v1_1}/testdata/TestTransferFlowTransaction.golden (100%) rename {flixkit => internal}/v1_1/types.go (99%) rename internal/{flixkitv2 => }/v1_1/types_test.go (99%) diff --git a/flixkit/fcl_bindings.go b/flixkit/fcl_bindings.go deleted file mode 100644 index 6dcc587..0000000 --- a/flixkit/fcl_bindings.go +++ /dev/null @@ -1,278 +0,0 @@ -package flixkit - -import ( - "bytes" - "fmt" - "sort" - "text/template" - - v1 "github.com/onflow/flixkit-go/flixkit/v1" - v1_1 "github.com/onflow/flixkit-go/flixkit/v1_1" - "github.com/onflow/flixkit-go/internal/templates" - "github.com/stoewer/go-strcase" -) - -func NewFclTSGenerator() Binder { - t := []string{ - templates.GetTsFclMainTemplate(), - templates.GetTsFclScriptTemplate(), - templates.GetTsFclTxTemplate(), - templates.GetTsFclParamsTemplate(), - templates.GetTsFclInterfaceTemplate(), - } - - return &FclGenerator{ - Templates: t, - } -} - -func NewFclJSGenerator() Binder { - t := []string{ - templates.GetJsFclMainTemplate(), - templates.GetJsFclScriptTemplate(), - templates.GetJsFclTxTemplate(), - templates.GetJsFclParamsTemplate(), - } - - return &FclGenerator{ - Templates: t, - } -} - -func (g *FclGenerator) Generate(flixString string, templateLocation string) (string, error) { - tmpl, err := parseTemplates(g.Templates) - if err != nil { - return "", err - } - if flixString == "" { - return "", fmt.Errorf("no flix template provided") - } - - ver, err := getTemplateVersion(flixString) - if err != nil { - return "", fmt.Errorf("invalid flix template version, %w", err) - } - - isLocal := !isUrl(templateLocation) - var data templateData - switch ver { - case "1.0.0": - - flix, err := v1.ParseFlix(flixString) - if err != nil { - return "", err - } - data = getTemplateDataV1_0(flix, templateLocation, isLocal) - case "1.1.0": - flix, err := v1_1.ParseFlix(flixString) - if err != nil { - return "", err - } - data = getTemplateDataV1_1(flix, templateLocation, isLocal) - default: - return "", fmt.Errorf("invalid flix template version: %s", ver) - } - - data.FclVersion = GetFlixFclCompatibility(ver) - var buf bytes.Buffer - err = tmpl.Execute(&buf, data) - return buf.String(), err -} - -type simpleParameter struct { - Name string - JsType string - Description string - FclType string - CadType string -} - -type templateData struct { - FclVersion string - Version string - Parameters []simpleParameter - ParametersPrefixName string - Output simpleParameter - Title string - Description string - Location string - IsScript bool - IsLocalTemplate bool -} - -type FclGenerator struct { - Templates []string -} - -var _ Binder = (*FclGenerator)(nil) - -type FlixParameter struct { - Name string - Type string -} - -func GetFlixFclCompatibility(flixVersion string) string { - compatibleVersions := map[string]string{ - "1.0.0": "1.3.0", - "1.1.0": "1.9.0", - // add more versions here - } - v, ok := compatibleVersions[flixVersion] - if !ok { - // default to latest if flix version not configured - return "1.9.0" - } - return v -} - -func getTemplateDataV1_1(flix *v1_1.InteractionTemplate, templateLocation string, isLocal bool) templateData { - var msgs v1_1.InteractionTemplateMessages = flix.Data.Messages - title := msgs.GetTitle("Request") - methodName := strcase.LowerCamelCase(title) - description := msgs.GetDescription("") - var sp simpleParameter - - if flix.Data.Type == "script" { - oTemp := flix.Data.Output - if flix.Data.Output == nil { - // if output is not defined, add default output - oTemp = &v1_1.Parameter{ - Label: "result", - Type: "String", - Messages: v1_1.InteractionTemplateMessages{}, - } - } - o := transformParameters([]v1_1.Parameter{*oTemp}) - if len(o) > 0 { - sp = o[0] - } - } - data := templateData{ - Version: flix.FVersion, - Parameters: transformParameters(flix.Data.Parameters), - ParametersPrefixName: strcase.UpperCamelCase(title), - Output: sp, - Title: methodName, - Description: description, - Location: templateLocation, - IsScript: flix.IsScript(), - IsLocalTemplate: isLocal, - } - return data -} - -func getTemplateDataV1_0(flix *v1.FlowInteractionTemplate, templateLocation string, isLocal bool) templateData { - title := flix.Data.Messages.GetTitleValue("Request") - methodName := strcase.LowerCamelCase(title) - description := flix.GetDescription() - var sp simpleParameter - // version 1.0 does not support output parameters, add default output - if flix.Data.Type == "script" { - sp = simpleParameter{ - Name: "result", - JsType: "string", - } - } - data := templateData{ - Version: flix.FVersion, - Parameters: transformArguments(flix.Data.Arguments), - ParametersPrefixName: strcase.UpperCamelCase(title), - Title: methodName, - Description: description, - Location: templateLocation, - IsScript: flix.IsScript(), - IsLocalTemplate: isLocal, - Output: sp, - } - return data -} - -func convertCadenceTypeToJS(cadenceType string) string { - // need to determine js type based on fcl supported types - // looking at fcl types and how arguments work as parameters - // https://github.com/onflow/fcl-js/blob/master/packages/types/src/types.js - switch cadenceType { - case "Bool": - return "boolean" - case "Void": - return "void" - case "Dictionary": - return "object" - case "Struct": - return "object" - case "Enum": - return "object" - default: - return "string" - } -} - -func parseTemplates(templates []string) (*template.Template, error) { - baseTemplate := template.New("base") - - for _, tmplStr := range templates { - _, err := baseTemplate.Parse(tmplStr) - if err != nil { - return nil, err - } - } - - return baseTemplate, nil -} - -func transformParameters(args []v1_1.Parameter) []simpleParameter { - simpleArgs := []simpleParameter{} - if len(args) == 0 { - return simpleArgs - } - sort.Slice(args, func(i, j int) bool { - return args[i].Index < args[j].Index - }) - - for _, arg := range args { - isArray, cType, jsType := isArrayParameter(FlixParameter{Name: arg.Label, Type: arg.Type}) - var msgs v1_1.InteractionTemplateMessages = arg.Messages - desciption := msgs.GetDescription("") - if isArray { - simpleArgs = append(simpleArgs, simpleParameter{Name: arg.Label, CadType: cType, JsType: jsType, FclType: "Array(t." + cType + ")", Description: desciption}) - } else { - jsType := convertCadenceTypeToJS(arg.Type) - simpleArgs = append(simpleArgs, simpleParameter{Name: arg.Label, CadType: arg.Type, JsType: jsType, FclType: arg.Type, Description: desciption}) - } - } - return simpleArgs -} - -func transformArguments(args v1.Arguments) []simpleParameter { - simpleArgs := []simpleParameter{} - var keys []string - // get keys for sorting - for k := range args { - keys = append(keys, k) - } - - sort.SliceStable(keys, func(i, j int) bool { - return args[keys[i]].Index < args[keys[j]].Index - }) - for _, key := range keys { - arg := args[key] - isArray, cType, jsType := isArrayParameter(FlixParameter{Name: key, Type: arg.Type}) - desciption := arg.Messages.GetTitleValue("") - if isArray { - simpleArgs = append(simpleArgs, simpleParameter{Name: key, CadType: cType, JsType: jsType, FclType: "Array(t." + cType + ")", Description: desciption}) - } else { - jsType := convertCadenceTypeToJS(arg.Type) - simpleArgs = append(simpleArgs, simpleParameter{Name: key, CadType: arg.Type, JsType: jsType, FclType: arg.Type, Description: desciption}) - } - } - return simpleArgs -} - -func isArrayParameter(arg FlixParameter) (isArray bool, cType string, jsType string) { - if arg.Type == "" || arg.Type[0] != '[' { - return false, "", "" - } - cadenceType := arg.Type[1 : len(arg.Type)-1] - javascriptType := "Array<" + convertCadenceTypeToJS(cadenceType) + ">" - return true, cadenceType, javascriptType -} diff --git a/flixkit/fcl_bindings_test.go b/flixkit/fcl_bindings_test.go deleted file mode 100644 index 307529c..0000000 --- a/flixkit/fcl_bindings_test.go +++ /dev/null @@ -1,597 +0,0 @@ -package flixkit - -import ( - "encoding/json" - "testing" - - "github.com/hexops/autogold/v2" - v1 "github.com/onflow/flixkit-go/flixkit/v1" - v1_1 "github.com/onflow/flixkit-go/flixkit/v1_1" - "github.com/onflow/flixkit-go/internal/templates" - "github.com/stretchr/testify/assert" -) - -var parsedTemplateTX = &v1.FlowInteractionTemplate{ - FType: "InteractionTemplate", - FVersion: "1.0.0", - ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", - Data: v1.Data{ - Type: "transaction", - Interface: "", - Messages: v1.Messages{ - Title: &v1.Title{ - I18N: map[string]string{ - "en-US": "Transfer Tokens", - }, - }, - Description: &v1.Description{ - I18N: map[string]string{ - "en-US": "Transfer tokens from one account to another", - }, - }, - }, - Cadence: "import FungibleToken from 0xFUNGIBLETOKENADDRESS\ntransaction(amount: UFix64, to: Address) {\nlet vault: @FungibleToken.Vault\nprepare(signer: AuthAccount) {\nself.vault <- signer\n.borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)!\n.withdraw(amount: amount)\n}\nexecute {\ngetAccount(to)\n.getCapability(/public/flowTokenReceiver)!\n.borrow<&{FungibleToken.Receiver}>()!\n.deposit(from: <-self.vault)\n}\n}", - Dependencies: v1.Dependencies{ - "0xFUNGIBLETOKENADDRESS": v1.Contracts{ - "FungibleToken": v1.Networks{ - "mainnet": v1.Network{ - Address: "0xf233dcee88fe0abe", - FqAddress: "A.0xf233dcee88fe0abe.FungibleToken", - Contract: "FungibleToken", - Pin: "83c9e3d61d3b5ebf24356a9f17b5b57b12d6d56547abc73e05f820a0ae7d9cf5", - PinBlockHeight: 34166296, - }, - "testnet": v1.Network{ - Address: "0x9a0766d93b6608b7", - FqAddress: "A.0x9a0766d93b6608b7.FungibleToken", - Contract: "FungibleToken", - Pin: "83c9e3d61d3b5ebf24356a9f17b5b57b12d6d56547abc73e05f820a0ae7d9cf5", - PinBlockHeight: 74776482, - }, - }, - }, - }, - Arguments: v1.Arguments{ - "amount": v1.Argument{ - Index: 0, - Type: "UFix64", - Messages: v1.Messages{ - Title: &v1.Title{ - I18N: map[string]string{ - "en-US": "The amount of FLOW tokens to send", - }, - }, - }, - Balance: "", - }, - "to": v1.Argument{ - Index: 1, - Type: "Address", - Messages: v1.Messages{ - Title: &v1.Title{ - I18N: map[string]string{ - "en-US": "The Flow account the tokens will go to", - }, - }, - }, - Balance: "", - }, - }, - }, -} - -var parsedTemplateScript = &v1.FlowInteractionTemplate{ - FType: "InteractionTemplate", - FVersion: "1.0.0", - ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", - Data: v1.Data{ - Type: "script", - Interface: "", - Messages: v1.Messages{ - Title: &v1.Title{ - I18N: map[string]string{ - "en-US": "Multiply Two Integers", - }, - }, - Description: &v1.Description{ - I18N: map[string]string{ - "en-US": "Multiply two numbers to another", - }, - }, - }, - Cadence: "pub fun main(x: Int, y: Int): Int { return x * y }", - Arguments: v1.Arguments{ - "x": v1.Argument{ - Index: 0, - Type: "Int", - Messages: v1.Messages{ - Title: &v1.Title{ - I18N: map[string]string{ - "en-US": "number to be multiplied", - }, - }, - }, - Balance: "", - }, - "y": v1.Argument{ - Index: 1, - Type: "Int", - Messages: v1.Messages{ - Title: &v1.Title{ - I18N: map[string]string{ - "en-US": "second number to be multiplied", - }, - }, - }, - Balance: "", - }, - }, - }, -} - -var ArrayTypeScript = &v1.FlowInteractionTemplate{ - FType: "InteractionTemplate", - FVersion: "1.0.0", - ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", - Data: v1.Data{ - Type: "script", - Interface: "", - Messages: v1.Messages{ - Title: &v1.Title{ - I18N: map[string]string{ - "en-US": "Multiply Numbers", - }, - }, - Description: &v1.Description{ - I18N: map[string]string{ - "en-US": "Multiply numbers in an array", - }, - }, - }, - Cadence: "pub fun main(numbers: [Int]): Int { var total = 1; for x in numbers { total = total * x }; return total }", - Arguments: v1.Arguments{ - "numbers": v1.Argument{ - Index: 0, - Type: "[Int]", - Messages: v1.Messages{ - Title: &v1.Title{ - I18N: map[string]string{ - "en-US": "Array of numbers to be multiplied", - }, - }, - }, - Balance: "", - }, - }, - }, -} - -var minimumTemplate = &v1.FlowInteractionTemplate{ - FType: "InteractionTemplate", - FVersion: "1.0.0", - ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", - Data: v1.Data{ - Type: "script", - Interface: "", - Cadence: "pub fun main(numbers: [Int]): Int { var total = 1; for x in numbers { total = total * x }; return total }", - Arguments: v1.Arguments{ - "numbers": v1.Argument{ - Index: 0, - Type: "[Int]", - }, - }, - }, -} - -var minimumNoParamTemplate = &v1.FlowInteractionTemplate{ - FType: "InteractionTemplate", - FVersion: "1.0.0", - ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", - Data: v1.Data{ - Type: "script", - Interface: "", - Cadence: "pub fun main(): Int { return 1 }", - }, -} - -func TestJSGenTransaction(t *testing.T) { - ttemp, err := json.Marshal(parsedTemplateTX) - assert.NoError(t, err, "marshal template to json should not return an error") - generator := FclGenerator{ - Templates: []string{ - templates.GetJsFclMainTemplate(), - templates.GetJsFclScriptTemplate(), - templates.GetJsFclTxTemplate(), - templates.GetJsFclParamsTemplate(), - }, - } - got, _ := generator.Generate(string(ttemp), "./transfer_token.json") - autogold.ExpectFile(t, got) -} - -func TestJSGenScript(t *testing.T) { - ttemp, err := json.Marshal(parsedTemplateScript) - assert.NoError(t, err, "marshal template to json should not return an error") - - generator := FclGenerator{ - Templates: []string{ - templates.GetJsFclMainTemplate(), - templates.GetJsFclScriptTemplate(), - templates.GetJsFclTxTemplate(), - templates.GetJsFclParamsTemplate(), - }, - } - assert := assert.New(t) - got, err := generator.Generate(string(ttemp), "./multiply_two_integers.template.json") - assert.NoError(err, "ParseTemplate should not return an error") - autogold.ExpectFile(t, got) -} - -func TestJSGenArrayScript(t *testing.T) { - ttemp, err := json.Marshal(ArrayTypeScript) - assert.NoError(t, err, "marshal template to json should not return an error") - - generator := FclGenerator{ - Templates: []string{ - templates.GetJsFclMainTemplate(), - templates.GetJsFclScriptTemplate(), - templates.GetJsFclTxTemplate(), - templates.GetJsFclParamsTemplate(), - }, - } - assert := assert.New(t) - - out, err := generator.Generate(string(ttemp), "./multiply-numbers.template.json") - assert.NoError(err, "ParseTemplate should not return an error") - autogold.ExpectFile(t, out) -} - -func TestJSGenMinScript(t *testing.T) { - ttemp, err := json.Marshal(minimumTemplate) - assert.NoError(t, err, "marshal template to json should not return an error") - - generator := NewFclJSGenerator() - assert := assert.New(t) - - out, err := generator.Generate(string(ttemp), "./min.template.json") - assert.NoError(err, "ParseTemplate should not return an error") - autogold.ExpectFile(t, out) -} -func TestJSGenNoParamsScript(t *testing.T) { - ttemp, err := json.Marshal(minimumNoParamTemplate) - assert.NoError(t, err, "marshal template to json should not return an error") - - generator := NewFclJSGenerator() - assert := assert.New(t) - - out, err := generator.Generate(string(ttemp), "./min.template.json") - assert.NoError(err, "ParseTemplate should not return an error") - autogold.ExpectFile(t, out) -} - -var minimumNoParamTemplateTS_SCRIPT = &v1_1.InteractionTemplate{ - FType: "InteractionTemplate", - FVersion: "1.1.0", - ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", - Data: v1_1.Data{ - Type: "script", - Interface: "", - Cadence: v1_1.Cadence{ - Body: "pub fun main(): Int { return 1 }", - NetworkPins: []v1_1.NetworkPin{}, - }, - Output: &v1_1.Parameter{ - Label: "result", - Type: "Int", - Messages: []v1_1.Message{ - { - Key: "description", - I18n: []v1_1.I18n{ - { - Tag: "en-US", - Translation: "Result of some number plus one", - }, - }, - }, - }, - }, - }, -} - -var minimumNoParamTemplateTS_TX = &v1_1.InteractionTemplate{ - FType: "InteractionTemplate", - FVersion: "1.1.0", - ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", - Data: v1_1.Data{ - Type: "transaction", - Interface: "", - Cadence: v1_1.Cadence{ - Body: "import \"HelloWorld\"\n\n#interaction (\n version: \"1.1.0\",\n\ttitle: \"Update Greeting\",\n\tdescription: \"Update the greeting on the HelloWorld contract\",\n\tlanguage: \"en-US\",\n)\ntransaction() {\n\n prepare(acct: AuthAccount) {\n }\n\n execute {\n \n }\n}\n", - NetworkPins: []v1_1.NetworkPin{}, - }, - }, -} - -var minimumParamTemplateTS_SCRIPT = &v1_1.InteractionTemplate{ - FType: "InteractionTemplate", - FVersion: "1.1.0", - ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", - Data: v1_1.Data{ - Type: "script", - Interface: "", - Cadence: v1_1.Cadence{ - Body: "pub fun main(someNumber Int): Int { return 1 + someNumber }", - NetworkPins: []v1_1.NetworkPin{}, - }, - Parameters: []v1_1.Parameter{ - { - Label: "someNumber", - Index: 0, - Type: "Int", - Messages: []v1_1.Message{ - { - Key: "title", - I18n: []v1_1.I18n{ - { - Tag: "en-US", - Translation: "Some Number", - }, - }, - }, - }, - }, - }, - Output: &v1_1.Parameter{ - Label: "result", - Type: "Int", - Messages: []v1_1.Message{ - { - Key: "description", - I18n: []v1_1.I18n{ - { - Tag: "en-US", - Translation: "Result of some number plus one", - }, - }, - }, - }, - }, - }, -} - -var minimumParamTemplateTS_TX = &v1_1.InteractionTemplate{ - FType: "InteractionTemplate", - FVersion: "1.1.0", - ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", - Data: v1_1.Data{ - Type: "transaction", - Interface: "", - Cadence: v1_1.Cadence{ - Body: "import \"HelloWorld\"\n\n#interaction (\n version: \"1.1.0\",\n\ttitle: \"Update Greeting\",\n\tdescription: \"Update the greeting on the HelloWorld contract\",\n\tlanguage: \"en-US\",\n\tparameters: [\n\t\tParameter(\n\t\t\tname: \"greeting\", \n\t\t\ttitle: \"Greeting\", \n\t\t\tdescription: \"The greeting to set on the HelloWorld contract\"\n\t\t)\n\t],\n)\ntransaction(greeting: String) {\n\n prepare(acct: AuthAccount) {\n log(acct.address)\n }\n\n execute {\n HelloWorld.updateGreeting(newGreeting: greeting)\n }\n}\n", - NetworkPins: []v1_1.NetworkPin{}, - }, - Messages: []v1_1.Message{ - { - Key: "title", - I18n: []v1_1.I18n{ - { - Tag: "en-US", - Translation: "Update Greeting", - }, - }, - }, - { - Key: "description", - I18n: []v1_1.I18n{ - { - Tag: "en-US", - Translation: "Update HelloWorld Greeting", - }, - }, - }, - }, - Parameters: []v1_1.Parameter{ - { - Label: "greeting", - Index: 0, - Type: "String", - Messages: []v1_1.Message{ - { - Key: "title", - I18n: []v1_1.I18n{ - { - Tag: "en-US", - Translation: "Greeting", - }, - }, - }, - }, - }, - }, - }, -} - -func TestTSGenNoParamsScript(t *testing.T) { - ttemp, err := json.Marshal(minimumNoParamTemplateTS_SCRIPT) - assert.NoError(t, err, "marshal template to json should not return an error") - - generator := NewFclTSGenerator() - assert := assert.New(t) - - out, err := generator.Generate(string(ttemp), "./min.template.json") - assert.NoError(err, "ParseTemplate should not return an error") - autogold.ExpectFile(t, out) -} - -func TestTSGenNoParamsTx(t *testing.T) { - ttemp, err := json.Marshal(minimumNoParamTemplateTS_TX) - assert.NoError(t, err, "marshal template to json should not return an error") - - generator := NewFclTSGenerator() - assert := assert.New(t) - - out, err := generator.Generate(string(ttemp), "./min.template.json") - assert.NoError(err, "ParseTemplate should not return an error") - autogold.ExpectFile(t, out) -} - -func TestTSGenParamsScript(t *testing.T) { - ttemp, err := json.Marshal(minimumParamTemplateTS_SCRIPT) - assert.NoError(t, err, "marshal template to json should not return an error") - - generator := NewFclTSGenerator() - assert := assert.New(t) - - out, err := generator.Generate(string(ttemp), "./min.template.json") - assert.NoError(err, "ParseTemplate should not return an error") - autogold.ExpectFile(t, out) -} - -func TestTSGenParamsTx(t *testing.T) { - ttemp, err := json.Marshal(minimumParamTemplateTS_TX) - assert.NoError(t, err, "marshal template to json should not return an error") - - generator := NewFclTSGenerator() - assert := assert.New(t) - - out, err := generator.Generate(string(ttemp), "./min.template.json") - assert.NoError(err, "ParseTemplate should not return an error") - autogold.ExpectFile(t, out) -} - -const ReadTokenScript = ` -{ - "f_type": "InteractionTemplate", - "f_version": "1.1.0", - "id": "29d03aafbbb5a02e0d5f4ffee685c12494915410812305c2858008d3e2902b72", - "data": { - "type": "script", - "interface": "", - "messages": null, - "cadence": { - "body": "import \"FungibleToken\"\nimport \"FlowToken\"\n\npub fun main(address: Address): UFix64 {\n let account = getAccount(address)\n\n let vaultRef = account\n .getCapability(/public/flowTokenBalance)\n .borrow\u003c\u0026FlowToken.Vault{FungibleToken.Balance}\u003e()\n ?? panic(\"Could not borrow balance reference to the Vault\")\n\n return vaultRef.balance\n}\n", - "network_pins": [ - { - "network": "mainnet", - "pin_self": "e0a1c0443b724d1238410c4a05c48441ee974160cad8cf1103c63b6999f81dd5" - }, - { - "network": "testnet", - "pin_self": "6fee459b35d7013a83070c9ac42ea43ee04a3925deca445c34614c1bd6dc4cb8" - } - ] - }, - "dependencies": [ - { - "contracts": [ - { - "contract": "FungibleToken", - "networks": [ - { - "network": "mainnet", - "address": "0xf233dcee88fe0abe", - "dependency_pin_block_height": 69539302, - "dependency_pin": { - "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", - "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", - "pin_contract_name": "FungibleToken", - "pin_contract_address": "0xf233dcee88fe0abe", - "imports": [] - } - }, - { - "network": "testnet", - "address": "0x9a0766d93b6608b7", - "dependency_pin_block_height": 146201102, - "dependency_pin": { - "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", - "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", - "pin_contract_name": "FungibleToken", - "pin_contract_address": "0x9a0766d93b6608b7", - "imports": [] - } - }, - { - "network": "emulator", - "address": "0xee82856bf20e2aa6", - "dependency_pin_block_height": 0 - } - ] - } - ] - }, - { - "contracts": [ - { - "contract": "FlowToken", - "networks": [ - { - "network": "mainnet", - "address": "0x1654653399040a61", - "dependency_pin_block_height": 69539302, - "dependency_pin": { - "pin": "a341e772da413bfbcf43b0fc167bd50a20c9f40baf10e12d3dbc2f5181526de9", - "pin_self": "0e932728b73bff3c09dd58922f2529fc7b7fe7477f1dcc61169bc8f46948ad91", - "pin_contract_name": "FlowToken", - "pin_contract_address": "0x1654653399040a61", - "imports": [ - { - "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", - "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", - "pin_contract_name": "FungibleToken", - "pin_contract_address": "0xf233dcee88fe0abe", - "imports": [] - } - ] - } - }, - { - "network": "testnet", - "address": "0x7e60df042a9c0868", - "dependency_pin_block_height": 146201102, - "dependency_pin": { - "pin": "9cc21a34a01486ebd6f044e99dbcdd58671850f81fcc345d071181c19f61aaa4", - "pin_self": "6f01c7001e2d6635b667a170d3ccbc13659c40d01bb35e56979fcc7fa2d18646", - "pin_contract_name": "FlowToken", - "pin_contract_address": "0x7e60df042a9c0868", - "imports": [ - { - "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", - "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", - "pin_contract_name": "FungibleToken", - "pin_contract_address": "0x9a0766d93b6608b7", - "imports": [] - } - ] - } - }, - { - "network": "emulator", - "address": "0x0ae53cb6e3f42a79", - "dependency_pin_block_height": 0 - } - ] - } - ] - } - ], - "parameters": [ - { - "label": "address", - "index": 0, - "type": "Address", - "messages": [] - } - ] - } -} -` - -func TestBindingReadTokenBalance(t *testing.T) { - generator := NewFclTSGenerator() - assert := assert.New(t) - - out, err := generator.Generate(ReadTokenScript, "./read-token-balance.template.json") - assert.NoError(err, "ParseTemplate should not return an error") - autogold.ExpectFile(t, out) -} diff --git a/flixkit/flix_generator.go b/flixkit/flix_generator.go new file mode 100644 index 0000000..7b4b48d --- /dev/null +++ b/flixkit/flix_generator.go @@ -0,0 +1,19 @@ +package flixkit + +import ( + "context" + + "github.com/onflow/flixkit-go/internal" +) + +type FlixGenerator interface { + // GenerateTemplate returns the generated raw template + CreateTemplate(ctx context.Context, code string, preFill string) (string, error) +} + +type FlixTemplateGeneratorConfig = internal.FlixTemplateGeneratorConfig + +// NewFlixGenerator returns a new FlixGenerator +func NewFlixTemplateGenerator(conf FlixTemplateGeneratorConfig) FlixGenerator { + return internal.NewFlixTemplateGenerator(conf) +} diff --git a/flixkitv2/flix_service.go b/flixkit/flix_service.go similarity index 67% rename from flixkitv2/flix_service.go rename to flixkit/flix_service.go index 8e895ab..2519de1 100644 --- a/flixkitv2/flix_service.go +++ b/flixkit/flix_service.go @@ -1,9 +1,9 @@ -package flixkitv2 +package flixkit import ( "context" - internal "github.com/onflow/flixkit-go/internal/flixkitv2" + "github.com/onflow/flixkit-go/internal" ) type FlixService interface { @@ -11,11 +11,13 @@ type FlixService interface { GetTemplate(ctx context.Context, templateName string) (string, error) // GetAndReplaceImports returns the raw flix template with cadence imports replaced GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) + // GenerateBinding returns the generated binding given the language + GetTemplateAndCreateBinding(ctx context.Context, templateName string, lang string) (string, error) } type FlowInteractionTemplateExecution = internal.FlowInteractionTemplateExecution -type FlixServiceConfig = internal.FlixServiceConfig +type FlixServiceConfig = internal.FlixServiceConfig func NewFlixService(config *FlixServiceConfig) FlixService { return internal.NewFlixService(config) diff --git a/flixkit/generator.go b/flixkit/generator.go deleted file mode 100644 index b9c2e8d..0000000 --- a/flixkit/generator.go +++ /dev/null @@ -1,307 +0,0 @@ -package flixkit - -import ( - "context" - "encoding/json" - "fmt" - "strings" - - "github.com/onflow/cadence/runtime/ast" - "github.com/onflow/cadence/runtime/cmd" - "github.com/onflow/cadence/runtime/common" - "github.com/onflow/cadence/runtime/parser" - v1_1 "github.com/onflow/flixkit-go/flixkit/v1_1" - "github.com/onflow/flixkit-go/internal/contracts" - "github.com/onflow/flow-cli/flowkit" - "github.com/onflow/flow-cli/flowkit/config" - "github.com/onflow/flow-cli/flowkit/gateway" - "github.com/onflow/flow-cli/flowkit/output" - "github.com/onflow/flow-go-sdk" - "github.com/onflow/flow-go-sdk/crypto" - "github.com/spf13/afero" -) - -/* -Same structure as core contracts, using config network names -*/ -type NetworkAddressMap map[string]string - -/* -Same structure as core contracts, keyed by contract name -*/ -type ContractInfos map[string]NetworkAddressMap - -type Generator struct { - deployedContracts []v1_1.Contract - testnetClient *flowkit.Flowkit - mainnetClient *flowkit.Flowkit - template *v1_1.InteractionTemplate -} - -var _ FlixTemplater = (*Generator)(nil) - -func NewGenerator(contractInfos ContractInfos, logger output.Logger) (FlixTemplater, error) { - loader := afero.Afero{Fs: afero.NewOsFs()} - - gwt, err := gateway.NewGrpcGateway(config.TestnetNetwork) - if err != nil { - return nil, fmt.Errorf("could not create grpc gateway for testnet %w", err) - } - - gwm, err := gateway.NewGrpcGateway(config.MainnetNetwork) - if err != nil { - return nil, fmt.Errorf("could not create grpc gateway for mainnet %w", err) - } - - state, err := flowkit.Init(loader, crypto.ECDSA_P256, crypto.SHA3_256) - if err != nil { - return nil, fmt.Errorf("could not initialize flowkit state %w", err) - } - testnetClient := flowkit.NewFlowkit(state, config.TestnetNetwork, gwt, logger) - mainnetClient := flowkit.NewFlowkit(state, config.MainnetNetwork, gwm, logger) - // add core contracts to deployed contracts - cc := contracts.GetCoreContracts() - deployedContracts := make([]v1_1.Contract, 0) - for contractName, c := range cc { - contract := v1_1.Contract{ - Contract: contractName, - Networks: []v1_1.Network{ - {Network: config.MainnetNetwork.Name, Address: c[config.MainnetNetwork.Name]}, - {Network: config.TestnetNetwork.Name, Address: c[config.TestnetNetwork.Name]}, - {Network: config.EmulatorNetwork.Name, Address: c[config.EmulatorNetwork.Name]}, - }, - } - deployedContracts = append(deployedContracts, contract) - } - // allow user contracts to override core contracts - for contractInfo, networks := range contractInfos { - contract := v1_1.Contract{ - Contract: contractInfo, - Networks: make([]v1_1.Network, 0), - } - for network, address := range networks { - addr := flow.HexToAddress(address) - contract.Networks = append(contract.Networks, v1_1.Network{ - Network: network, - Address: "0x" + addr.Hex(), - }) - } - deployedContracts = append(deployedContracts, contract) - } - - return &Generator{ - deployedContracts: deployedContracts, - testnetClient: testnetClient, - mainnetClient: mainnetClient, - template: &v1_1.InteractionTemplate{}, - }, nil -} - -func (g Generator) Generate(ctx context.Context, code string, preFill string) (string, error) { - g.template = &v1_1.InteractionTemplate{} - g.template.Init() - if preFill != "" { - t, err := v1_1.ParseFlix(preFill) - if err != nil { - return "", err - } - g.template = t - } - - // make sure imports use new import syntax "string import" - g.template.ProcessImports(code) - program, err := parser.ParseProgram(nil, []byte(g.template.Data.Cadence.Body), parser.Config{}) - if err != nil { - return "", err - } - - err = g.template.DetermineCadenceType(program) - if err != nil { - return "", err - } - - err = g.template.ParsePragma(program) - if err != nil { - return "", err - } - - err = g.template.ProcessParameters(program) - if err != nil { - return "", err - } - - err = g.processDependencies(ctx, program) - if err != nil { - return "", err - } - - // need to process dependencies before calculating network pins - _ = g.calculateNetworkPins(program) - id, _ := v1_1.GenerateFlixID(g.template) - g.template.ID = id - templateJson, err := json.MarshalIndent(g.template, "", " ") - - return string(templateJson), err - -} - -func (g Generator) calculateNetworkPins(program *ast.Program) error { - networksOfInterest := []string{ - config.MainnetNetwork.Name, - config.TestnetNetwork.Name, - } - networkPins := make([]v1_1.NetworkPin, 0) - for _, netName := range networksOfInterest { - cad, err := g.template.GetAndReplaceCadenceImports(netName) - if err != nil { - continue - } - networkPins = append(networkPins, v1_1.NetworkPin{ - Network: netName, - PinSelf: v1_1.ShaHex(cad, ""), - }) - } - g.template.Data.Cadence.NetworkPins = networkPins - return nil -} - -func (g Generator) processDependencies(ctx context.Context, program *ast.Program) error { - imports := program.ImportDeclarations() - - if len(imports) == 0 { - return nil - } - - // fill in dependence information - g.template.Data.Dependencies = make([]v1_1.Dependency, 0) - for _, imp := range imports { - contractName, err := v1_1.ExtractContractName(imp.String()) - if err != nil { - return err - } - networks, err := g.generateDependenceInfo(ctx, contractName) - if err != nil { - return err - } - c := v1_1.Contract{ - Contract: contractName, - Networks: networks, - } - dep := v1_1.Dependency{ - Contracts: []v1_1.Contract{c}, - } - g.template.Data.Dependencies = append(g.template.Data.Dependencies, dep) - } - - return nil -} - -func (g *Generator) generateDependenceInfo(ctx context.Context, contractName string) ([]v1_1.Network, error) { - // only support string import syntax - contractNetworks := g.LookupImportContractInfo(contractName) - if len(contractNetworks) == 0 { - return nil, fmt.Errorf("could not find contract dependency %s", contractName) - } - var networks []v1_1.Network - for _, n := range contractNetworks { - network := v1_1.Network{ - Network: n.Network, - Address: n.Address, - } - var flowkit *flowkit.Flowkit - if n.Network == config.MainnetNetwork.Name && g.mainnetClient != nil { - flowkit = g.mainnetClient - } else if n.Network == config.TestnetNetwork.Name && g.testnetClient != nil { - flowkit = g.testnetClient - } - if n.DependencyPinBlockHeight == 0 && flowkit != nil { - block, _ := flowkit.Gateway().GetLatestBlock() - height := block.Height - - details, err := g.GenerateDepPinDepthFirst(ctx, flowkit, n.Address, contractName, height) - if err != nil { - return nil, err - } - network.DependencyPinBlockHeight = height - network.DependencyPin = details - } - networks = append(networks, network) - } - - return networks, nil -} - -func (g *Generator) LookupImportContractInfo(contractName string) []v1_1.Network { - for _, contract := range g.deployedContracts { - if contractName == contract.Contract { - return contract.Networks - } - } - return nil -} - -func (g *Generator) GenerateDepPinDepthFirst(ctx context.Context, flowkit *flowkit.Flowkit, address string, name string, height uint64) (details *v1_1.PinDetail, err error) { - memoize := make(map[string]v1_1.PinDetail) - networkPinDetail, err := generateDependencyNetworks(ctx, flowkit, address, name, memoize, height) - if err != nil { - return nil, err - } - - return networkPinDetail, nil -} - -func generateDependencyNetworks(ctx context.Context, flowkit *flowkit.Flowkit, address string, name string, cache map[string]v1_1.PinDetail, height uint64) (*v1_1.PinDetail, error) { - addr := flow.HexToAddress(address) - identifier := fmt.Sprintf("A.%s.%s", addr.Hex(), name) - pinDetail, ok := cache[identifier] - if ok { - return &pinDetail, nil - } - - account, err := flowkit.GetAccount(ctx, addr) - if err != nil { - return nil, err - } - code := account.Contracts[name] - depend := v1_1.PinDetail{ - PinContractName: name, - PinContractAddress: "0x" + addr.Hex(), - PinSelf: v1_1.ShaHex(code, ""), - } - depend.CalculatePin(height) - pins := []string{depend.PinSelf} - imports := getAddressImports(code, name) - detailImports := make([]v1_1.PinDetail, 0) - for _, imp := range imports { - split := strings.Split(imp, ".") - address, name := split[0], split[1] - dep, err := generateDependencyNetworks(ctx, flowkit, address, name, cache, height) - if err != nil { - return nil, err - } - if dep != nil { - detailImports = append(detailImports, *dep) - cache[identifier] = *dep - } - pins = append(pins, dep.PinSelf) - } - depend.Imports = detailImports - depend.Pin = v1_1.ShaHex(strings.Join(pins, ""), "") - return &depend, nil -} - -func getAddressImports(code []byte, name string) []string { - deps := []string{} - codes := map[common.Location][]byte{} - location := common.StringLocation(name) - program, _ := cmd.PrepareProgram(code, location, codes) - for _, imp := range program.ImportDeclarations() { - address, isAddressImport := imp.Location.(common.AddressLocation) - if isAddressImport { - adr := address.Address.Hex() - impName := imp.Identifiers[0].Identifier - deps = append(deps, fmt.Sprintf("%s.%s", adr, impName)) - } - } - return deps -} diff --git a/flixkit/testdata/TestBindingReadTokenBalance.golden b/flixkit/testdata/TestBindingReadTokenBalance.golden deleted file mode 100644 index 68df772..0000000 --- a/flixkit/testdata/TestBindingReadTokenBalance.golden +++ /dev/null @@ -1,33 +0,0 @@ -`/** - This binding file was auto generated based on FLIX template v1.1.0. - Changes to this file might get overwritten. - Note fcl version 1.9.0 or higher is required to use templates. -**/ - -import * as fcl from "@onflow/fcl" -import flixTemplate from "./read-token-balance.template.json" - -interface RequestParams { - address: string; -} - -/** -* request: -* @param string address - -* @returns {Promise} - -*/ -export async function request({address}: RequestParams): Promise { - const info = await fcl.query({ - cadence: "", - template: flixTemplate, - args: (arg, t) => [arg(address, t.Address)] - }); - - return info -} - - - - - -` diff --git a/flixkit/testdata/TestJSGenArrayScript.golden b/flixkit/testdata/TestJSGenArrayScript.golden deleted file mode 100644 index 38f5441..0000000 --- a/flixkit/testdata/TestJSGenArrayScript.golden +++ /dev/null @@ -1,28 +0,0 @@ -`/** - This binding file was auto generated based on FLIX template v1.0.0. - Changes to this file might get overwritten. - Note fcl version 1.3.0 or higher is required to use templates. -**/ - -import * as fcl from "@onflow/fcl" -import flixTemplate from "./multiply-numbers.template.json" - -/** -* Multiply numbers in an array -* @param {Object} Parameters - parameters for the cadence -* @param {Array} Parameters.numbers - Array of numbers to be multiplied: Int -*/ -export async function multiplyNumbers({numbers}) { - const info = await fcl.query({ - template: flixTemplate, - args: (arg, t) => [arg(numbers, t.Array(t.Int))] - }); - - return info -} - - - - - -` diff --git a/flixkit/testdata/TestJSGenMinScript.golden b/flixkit/testdata/TestJSGenMinScript.golden deleted file mode 100644 index 9a70473..0000000 --- a/flixkit/testdata/TestJSGenMinScript.golden +++ /dev/null @@ -1,28 +0,0 @@ -`/** - This binding file was auto generated based on FLIX template v1.0.0. - Changes to this file might get overwritten. - Note fcl version 1.3.0 or higher is required to use templates. -**/ - -import * as fcl from "@onflow/fcl" -import flixTemplate from "./min.template.json" - -/** -* -* @param {Object} Parameters - parameters for the cadence -* @param {Array} Parameters.numbers - : Int -*/ -export async function request({numbers}) { - const info = await fcl.query({ - template: flixTemplate, - args: (arg, t) => [arg(numbers, t.Array(t.Int))] - }); - - return info -} - - - - - -` diff --git a/flixkit/testdata/TestJSGenNoParamsScript.golden b/flixkit/testdata/TestJSGenNoParamsScript.golden deleted file mode 100644 index 3ba6c64..0000000 --- a/flixkit/testdata/TestJSGenNoParamsScript.golden +++ /dev/null @@ -1,27 +0,0 @@ -`/** - This binding file was auto generated based on FLIX template v1.0.0. - Changes to this file might get overwritten. - Note fcl version 1.3.0 or higher is required to use templates. -**/ - -import * as fcl from "@onflow/fcl" -import flixTemplate from "./min.template.json" - -/** -* -* No parameters needed. -*/ -export async function request() { - const info = await fcl.query({ - template: flixTemplate, - - }); - - return info -} - - - - - -` diff --git a/flixkit/testdata/TestJSGenScript.golden b/flixkit/testdata/TestJSGenScript.golden deleted file mode 100644 index df9e616..0000000 --- a/flixkit/testdata/TestJSGenScript.golden +++ /dev/null @@ -1,29 +0,0 @@ -`/** - This binding file was auto generated based on FLIX template v1.0.0. - Changes to this file might get overwritten. - Note fcl version 1.3.0 or higher is required to use templates. -**/ - -import * as fcl from "@onflow/fcl" -import flixTemplate from "./multiply_two_integers.template.json" - -/** -* Multiply two numbers to another -* @param {Object} Parameters - parameters for the cadence -* @param {string} Parameters.x - number to be multiplied: Int -* @param {string} Parameters.y - second number to be multiplied: Int -*/ -export async function multiplyTwoIntegers({x, y}) { - const info = await fcl.query({ - template: flixTemplate, - args: (arg, t) => [arg(x, t.Int), arg(y, t.Int)] - }); - - return info -} - - - - - -` diff --git a/flixkit/testdata/TestJSGenTransaction.golden b/flixkit/testdata/TestJSGenTransaction.golden deleted file mode 100644 index 0d8aeb5..0000000 --- a/flixkit/testdata/TestJSGenTransaction.golden +++ /dev/null @@ -1,29 +0,0 @@ -`/** - This binding file was auto generated based on FLIX template v1.0.0. - Changes to this file might get overwritten. - Note fcl version 1.3.0 or higher is required to use templates. -**/ - -import * as fcl from "@onflow/fcl" -import flixTemplate from "./transfer_token.json" - -/** -* Transfer tokens from one account to another -* @param {Object} Parameters - parameters for the cadence -* @param {string} Parameters.amount - The amount of FLOW tokens to send: UFix64 -* @param {string} Parameters.to - The Flow account the tokens will go to: Address -* @returns {Promise} - returns a promise which resolves to the transaction id -*/ -export async function transferTokens({amount, to}) { - const transactionId = await fcl.mutate({ - template: flixTemplate, - args: (arg, t) => [arg(amount, t.UFix64), arg(to, t.Address)] - }); - - return transactionId -} - - - - -` diff --git a/flixkit/testdata/TestTSGenNoParamsScript.golden b/flixkit/testdata/TestTSGenNoParamsScript.golden deleted file mode 100644 index bddb8a0..0000000 --- a/flixkit/testdata/TestTSGenNoParamsScript.golden +++ /dev/null @@ -1,29 +0,0 @@ -`/** - This binding file was auto generated based on FLIX template v1.1.0. - Changes to this file might get overwritten. - Note fcl version 1.9.0 or higher is required to use templates. -**/ - -import * as fcl from "@onflow/fcl" -import flixTemplate from "./min.template.json" - - -/** -* request: -* @returns {Promise} - Result of some number plus one -*/ -export async function request(): Promise { - const info = await fcl.query({ - cadence: "", - template: flixTemplate, - - }); - - return info -} - - - - - -` diff --git a/flixkit/testdata/TestTSGenNoParamsTx.golden b/flixkit/testdata/TestTSGenNoParamsTx.golden deleted file mode 100644 index 127eda0..0000000 --- a/flixkit/testdata/TestTSGenNoParamsTx.golden +++ /dev/null @@ -1,27 +0,0 @@ -`/** - This binding file was auto generated based on FLIX template v1.1.0. - Changes to this file might get overwritten. - Note fcl version 1.9.0 or higher is required to use templates. -**/ - -import * as fcl from "@onflow/fcl" -import flixTemplate from "./min.template.json" - - -/** -* request: -* @returns {Promise} - Returns a promise that resolves to the transaction ID -*/ -export async function request(): Promise { - const transactionId = await fcl.mutate({ - template: flixTemplate, - - }); - - return transactionId -} - - - - -` diff --git a/flixkit/testdata/TestTSGenParamsScript.golden b/flixkit/testdata/TestTSGenParamsScript.golden deleted file mode 100644 index b66c036..0000000 --- a/flixkit/testdata/TestTSGenParamsScript.golden +++ /dev/null @@ -1,33 +0,0 @@ -`/** - This binding file was auto generated based on FLIX template v1.1.0. - Changes to this file might get overwritten. - Note fcl version 1.9.0 or higher is required to use templates. -**/ - -import * as fcl from "@onflow/fcl" -import flixTemplate from "./min.template.json" - -interface RequestParams { - someNumber: string; -} - -/** -* request: -* @param string someNumber - -* @returns {Promise} - Result of some number plus one -*/ -export async function request({someNumber}: RequestParams): Promise { - const info = await fcl.query({ - cadence: "", - template: flixTemplate, - args: (arg, t) => [arg(someNumber, t.Int)] - }); - - return info -} - - - - - -` diff --git a/flixkit/testdata/TestTSGenParamsTx.golden b/flixkit/testdata/TestTSGenParamsTx.golden deleted file mode 100644 index eff4484..0000000 --- a/flixkit/testdata/TestTSGenParamsTx.golden +++ /dev/null @@ -1,31 +0,0 @@ -`/** - This binding file was auto generated based on FLIX template v1.1.0. - Changes to this file might get overwritten. - Note fcl version 1.9.0 or higher is required to use templates. -**/ - -import * as fcl from "@onflow/fcl" -import flixTemplate from "./min.template.json" - -interface UpdateGreetingParams { - greeting: string; -} - -/** -* updateGreeting: Update HelloWorld Greeting -* @param string greeting - -* @returns {Promise} - Returns a promise that resolves to the transaction ID -*/ -export async function updateGreeting({greeting}: UpdateGreetingParams): Promise { - const transactionId = await fcl.mutate({ - template: flixTemplate, - args: (arg, t) => [arg(greeting, t.String)] - }); - - return transactionId -} - - - - -` diff --git a/flixkit/v1_1/types_test.go b/flixkit/v1_1/types_test.go deleted file mode 100644 index 1bcf41a..0000000 --- a/flixkit/v1_1/types_test.go +++ /dev/null @@ -1,853 +0,0 @@ -package flixkit - -import ( - "encoding/json" - "testing" - - "github.com/hexops/autogold/v2" - "github.com/onflow/cadence/runtime/parser" - "github.com/stretchr/testify/assert" -) - -var pragmaWithParameters = ` -#interaction( - version: "1.1.0", - title: "Update Greeting", - description: "Update the greeting on the HelloWorld contract", - language: "en-US", - parameters: [ - Parameter( - name: "greeting", - title: "Greeting", - description: "The greeting to set on the HelloWorld contract" - ), - Parameter( - name: "amount", - title: "Amount", - description: "The amount parameter to Test" - ) - ], -) - -import "HelloWorld" -transaction(greeting: String, amount: UFix64) { - - prepare(acct: AuthAccount) { - log(acct.address) - } - - execute { - HelloWorld.updateGreeting(newGreeting: greeting) - } -} -` - -var pragmaWithoutParameters = ` -#interaction( - version: "1.1.0", - title: "Update Greeting", - description: "Update the greeting on the HelloWorld contract", - language: "en-US", -) - -import "HelloWorld" -transaction(greeting: String) { - - prepare(acct: AuthAccount) { - log(acct.address) - } - - execute { - HelloWorld.updateGreeting(newGreeting: greeting) - } -} -` - -var pragmaMinimum = ` -#interaction( - version: "1.1.0", -) - -import "HelloWorld" -transaction(greeting: String) { - - prepare(acct: AuthAccount) { - log(acct.address) - } - - execute { - HelloWorld.updateGreeting(newGreeting: greeting) - } -} -` - -var PragmaEmpty = ` -import "HelloWorld" -transaction(greeting: String) { - - prepare(acct: AuthAccount) { - log(acct.address) - } - - execute { - HelloWorld.updateGreeting(newGreeting: greeting) - } -} -` - -func TestParsePragma(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - name string - wantErr bool - code string - }{ - { - name: "WithParameters", - wantErr: false, - code: pragmaWithParameters, - }, - { - name: "WithoutParameters", - wantErr: false, - code: pragmaWithoutParameters, - }, - { - name: "Minimum", - wantErr: false, - code: pragmaMinimum, - }, - { - name: "Empty", - wantErr: false, - code: PragmaEmpty, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - codeBytes := []byte(tt.code) - program, err := parser.ParseProgram(nil, codeBytes, parser.Config{}) - if err != nil { - t.Fatal(err) - } - var template InteractionTemplate - if err != nil { - t.Fatal(err) - } - - err = template.ParsePragma(program) - if err != nil && !tt.wantErr { - t.Fatal(err) - } - if err != nil { - t.Fatal(err) - } - prettyJSON, err := json.MarshalIndent(template, "", " ") - assert.NoError(err, "marshal template to json should not return an error") - autogold.ExpectFile(t, string(prettyJSON)) - - }) - } -} - -func TestGenerateParametersScripts(t *testing.T) { - templateString := ` - { - "f_type": "InteractionTemplate", - "f_version": "1.1.0", - "id": "", - "data": - { - "type": "script", - "interface": "", - "messages": [ - { - "key": "title", - "i18n": [ - { - "tag": "en-US", - "translation": "User Balance" - } - ] - }, - { - "key": "description", - "i18n": [ - { - "tag": "en-US", - "translation": "Get User Balance" - } - ] - }], - "cadence": {}, - "dependencies": [], - "parameters": [ - { - "label": "address", - "index": 0, - "type": "Address", - "messages": [ - { - "key": "title", - "i18n": [ - { - "tag": "en-US", - "translation": "User Address" - } - ] - }, - { - "key": "description", - "i18n": [ - { - "tag": "en-US", - "translation": "Get user balance" - } - ] - } - ] - } - ] - } - }` - - cadence := ` - import "FungibleToken" - import "FlowToken" - - pub fun main(address: Address): UFix64 { - let account = getAccount(address) - let vaultRef = account.getCapability(/public/flowTokenBalance) - .borrow<&FlowToken.Vault{FungibleToken.Balance}>() - ?? panic("Could not borrow balance reference to the Vault") - - return vaultRef.balance - } - - ` - - codeBytes := []byte(cadence) - program, err := parser.ParseProgram(nil, codeBytes, parser.Config{}) - if err != nil { - t.Errorf("ParseProgram() err %v", err) - } - - template, err := ParseFlix(templateString) - if err != nil { - t.Errorf("ParseFlix() err %v", err) - } - err = template.ProcessParameters(program) - if err != nil { - t.Errorf("process parameters err %v", err) - } - prettyJSON, err := json.MarshalIndent(template, "", " ") - if err != nil { - t.Errorf("process parameters err %v", err) - } - - autogold.ExpectFile(t, string(prettyJSON)) -} - -func TestGenerateTemplateIdWithDeps(t *testing.T) { - templateId := "fcada4d7a654a0386a4bb048ac4c851ad7de3945e6e835dc4593581b8c8113da" - code := ` - { - "f_type": "InteractionTemplate", - "f_version": "1.1.0", - "id": "", - "data": { - "type": "transaction", - "interface": "", - "messages": [ - { - "key": "title", - "i18n": [ - { - "tag": "en-US", - "translation": "Update Greeting" - } - ] - }, - { - "key": "description", - "i18n": [ - { - "tag": "en-US", - "translation": "Update the greeting on the HelloWorld contract" - } - ] - } - ], - "cadence": { - "body": "import \"HelloWorld\"\n\n#interaction (\n version: \"1.1.0\",\n\ttitle: \"Update Greeting\",\n\tdescription: \"Update the greeting on the HelloWorld contract\",\n\tlanguage: \"en-US\",\n\tparameters: [\n\t\tParameter(\n\t\t\tname: \"greeting\", \n\t\t\ttitle: \"Greeting\", \n\t\t\tdescription: \"The greeting to set on the HelloWorld contract\"\n\t\t)\n\t],\n)\ntransaction(greeting: String) {\n\n prepare(acct: AuthAccount) {\n log(acct.address)\n }\n\n execute {\n HelloWorld.updateGreeting(newGreeting: greeting)\n }\n}\n", - "network_pins": [ - { - "network": "testnet", - "pin_self": "f61e68b5ba6987aaee393401889d5410b01ffa603a66952307319ea09fd505e7" - } - ] - }, - "dependencies": [ - { - "contracts": [ - { - "contract": "HelloWorld", - "networks": [ - { - "network": "testnet", - "address": "0xe15193734357cf5c", - "dependency_pin_block_height": 139331034, - "dependency_pin": { - "pin": "38b038a23c5975f90a797d6a821f9a8c4e4325a661f92513aedd73fda0e3300c", - "pin_self": "a06b3cd29330a3c22df3ac2383653e89c249c5e773fd4bbee73c45ea10294b97", - "pin_contract_name": "HelloWorld", - "pin_contract_address": "0xe15193734357cf5c", - "imports": [ - { - "pin": "3efc62adadbb1dedab0716ac031066a431cd7d627bc1b9260dd08a5a67b26b55", - "pin_self": "403cd82df774d247bc1fd7471e5ef1fdb7e2e0cb8ec44dce3af5473627179f9a", - "pin_contract_name": "GiveNumber", - "pin_contract_address": "0xe15193734357cf5c", - "imports": [] - } - ] - } - } - ] - } - ] - } - ], - "parameters": [ - { - "label": "greeting", - "index": 0, - "type": "String", - "messages": [ - { - "key": "title", - "i18n": [ - { - "tag": "en-US", - "translation": "Greeting" - } - ] - }, - { - "key": "description", - "i18n": [ - { - "tag": "en-US", - "translation": "The greeting to set on the HelloWorld contract" - } - ] - } - ] - } - ] - } - }` - - template, err := ParseFlix(code) - if err != nil { - t.Errorf("ParseFlix() err %v", err) - } - id, err := GenerateFlixID(template) - if err != nil { - t.Errorf("GenerateFlixID err %v", err) - } - - if id != templateId { - t.Errorf("GenerateFlixID got = %v, want %v", id, templateId) - } - -} - -func TestEmptyPragmaWithParameter(t *testing.T) { - codeBytes := []byte(PragmaEmpty) - program, err := parser.ParseProgram(nil, codeBytes, parser.Config{}) - if err != nil { - t.Fatal(err) - } - var template InteractionTemplate - err = template.ProcessParameters(program) - if err != nil { - t.Errorf("process parameters err %v", err) - } - prettyJSON, err := json.MarshalIndent(template, "", " ") - if err != nil { - t.Errorf("process parameters err %v", err) - } - - autogold.ExpectFile(t, string(prettyJSON)) - -} - -const template = ` -{ - "f_type": "InteractionTemplate", - "f_version": "1.1.0", - "id": "a2b2d73def...aabc5472d2", - "data": { - "type": "transaction", - "interface": "asadf23234...fas234234", - "messages": [ - { - "key": "title", - "i18n": [ - { - "tag": "en-US", - "translation": "Transfer FLOW" - }, - { - "tag": "fr-FR", - "translation": "FLOW de transfert" - }, - { - "tag": "zh-CN", - "translation": "转移流程" - } - ] - }, - { - "key": "description", - "i18n": [ - { - "tag": "en-US", - "translation": "Transfer {amount} FLOW to {to}" - }, - { - "tag": "fr-FR", - "translation": "Transférez {amount} FLOW à {to}" - }, - { - "tag": "zh-CN", - "translation": "将 {amount} FLOW 转移到 {to}" - } - ] - }, - { - "key": "signer", - "i18n": [ - { - "tag": "en-US", - "translation": "Sign this message to transfer FLOW" - }, - { - "tag": "fr-FR", - "translation": "Signez ce message pour transférer FLOW." - }, - { - "tag": "zh-CN", - "translation": "签署此消息以转移FLOW。" - } - ] - } - ], - "cadence": { - "body": "import \"FlowToken\"\n transaction(amount: UFix64, to: Address) {\n let vault: @FungibleToken.Vault\n prepare(signer: AuthAccount) {\n %%self.vault <- signer\n .borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)!\n .withdraw(amount: amount)\n self.vault <- FungibleToken.getVault(signer)\n }\n execute {\n getAccount(to)\n .getCapability(/public/flowTokenReceiver)!\n .borrow<&{FungibleToken.Receiver}>()!\n .deposit(from: <-self.vault)\n }\n }", - "network_pins": [ - { - "network": "mainnet", - "pin_self": "186e262ce6fe06b5075ec6569a0e5482a79c471881182612d8e4a665c2977f3e" - }, - { - "network": "testnet", - "pin_self": "f93977d7a297f559e97259cb2a95fed0f87cfeec46c5257a26adc26a260d6c4c" - } - ] - }, - "dependencies": [ - { - "contracts": [ - { - "contract": "FlowToken", - "networks": [ - { - "network": "mainnet", - "address": "0x1654653399040a61", - "dependency_pin_block_height": 10123123123, - "dependency_pin": { - "pin": "c8cb7cc7a1c2a329de65d83455016bc3a9b53f9668c74ef555032804bac0b25b", - "pin_self": "38d0cca4b74c4e88213df636b4cfc2eb6e86fd8b2b84579d3b9bffab3e0b1fcb", - "pin_contract_name": "FlowToken", - "imports": [ - { - "pin": "b8a3ed26c222ed67016a28021d8fee5603b948533cbc992b3c90f71a61b2b312", - "pin_self": "7bc3056ba5d39d130f45411c2c05bb549db8ce727c11a1cb821136a621be27fb", - "pin_contract_name": "FungibleToken", - "pin_contract_address": "0xf233dcee88fe0abe", - "imports": [] - } - ] - } - }, - { - "network": "testnet", - "address": "0x7e60df042a9c0868", - "dependency_pin_block_height": 10123123123, - "dependency_pin": { - "pin": "c8cb7cc7a1c2a329de65d83455016bc3a9b53f9668c74ef555032804bac0b25b", - "pin_self": "38d0cca4b74c4e88213df636b4cfc2eb6e86fd8b2b84579d3b9bffab3e0b1fcb", - "pin_contract_name": "FlowToken", - "pin_contract_address": "0x7e60df042a9c0868", - "imports": [ - { - "pin": "b8a3ed26c222ed67016a28021d8fee5603b948533cbc992b3c90f71a61b2b312", - "pin_self": "7bc3056ba5d39d130f45411c2c05bb549db8ce727c11a1cb821136a621be27fb", - "pin_contract_name": "FungibleToken", - "pin_contract_address": "0x9a0766d93b6608b7", - "imports": [] - } - ] - } - } - ] - } - ] - } - ], - "parameters": [ - { - "label": "amount", - "index": 0, - "type": "UFix64", - "messages": [ - { - "key": "title", - "i18n": [ - { - "tag": "en-US", - "translation": "Amount" - }, - { - "tag": "fr-FR", - "translation": "Montant" - }, - { - "tag": "zh-CN", - "translation": "数量" - } - ] - }, - { - "key": "description", - "i18n": [ - { - "tag": "en-US", - "translation": "Amount of FLOW token to transfer" - }, - { - "tag": "fr-FR", - "translation": "Quantité de token FLOW à transférer" - }, - { - "tag": "zh-CN", - "translation": "要转移的 FLOW 代币数量" - } - ] - } - ], - "balance": "FlowToken" - }, - { - "label": "to", - "index": 1, - "type": "Address", - "messages": [ - { - "key": "title", - "i18n": [ - { - "tag": "en-US", - "translation": "To" - }, - { - "tag": "fr-FR", - "translation": "Pour" - }, - { - "tag": "zh-CN", - "translation": "到" - } - ] - }, - { - "key": "description", - "i18n": [ - { - "tag": "en-US", - "translation": "Amount of FLOW token to transfer" - }, - { - "tag": "fr-FR", - "translation": "Le compte vers lequel transférer les jetons FLOW" - }, - { - "tag": "zh-CN", - "translation": "将 FLOW 代币转移到的帐户" - } - ] - } - ] - } - ] - } - } -` -const templateMultipleImports = ` -{ - "f_type": "InteractionTemplate", - "f_version": "1.1.0", - "id": "29d03aafbbb5a02e0d5f4ffee685c12494915410812305c2858008d3e2902b72", - "data": { - "type": "script", - "interface": "", - "messages": null, - "cadence": { - "body": "import \"FungibleToken\"\nimport \"FlowToken\"\n\npub fun main(address: Address): UFix64 {\n let account = getAccount(address)\n\n let vaultRef = account\n .getCapability(/public/flowTokenBalance)\n .borrow\u003c\u0026FlowToken.Vault{FungibleToken.Balance}\u003e()\n ?? panic(\"Could not borrow balance reference to the Vault\")\n\n return vaultRef.balance\n}\n", - "network_pins": [ - { - "network": "mainnet", - "pin_self": "c9aef2c441b2ff0e1a724fcd72f7a48ae7fbbba3c6e72c530607a90ea0fdf93a" - }, - { - "network": "testnet", - "pin_self": "74331585cf3df9cd60e6570566d079f97b3e28b0e2156a06731e73e492fe120e" - } - ] - }, - "dependencies": [ - { - "contracts": [ - { - "contract": "FungibleToken", - "networks": [ - { - "network": "mainnet", - "address": "0xf233dcee88fe0abe", - "dependency_pin_block_height": 67669170, - "dependency_pin": { - "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", - "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", - "pin_contract_name": "FungibleToken", - "pin_contract_address": "0xf233dcee88fe0abe", - "imports": [] - } - }, - { - "network": "testnet", - "address": "0x9a0766d93b6608b7", - "dependency_pin_block_height": 139547221, - "dependency_pin": { - "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", - "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", - "pin_contract_name": "FungibleToken", - "pin_contract_address": "0x9a0766d93b6608b7", - "imports": [] - } - }, - { - "network": "emulator", - "address": "0xee82856bf20e2aa6", - "dependency_pin_block_height": 0 - } - ] - } - ] - }, - { - "contracts": [ - { - "contract": "FlowToken", - "networks": [ - { - "network": "mainnet", - "address": "0x1654653399040a61", - "dependency_pin_block_height": 67669170, - "dependency_pin": { - "pin": "a341e772da413bfbcf43b0fc167bd50a20c9f40baf10e12d3dbc2f5181526de9", - "pin_self": "0e932728b73bff3c09dd58922f2529fc7b7fe7477f1dcc61169bc8f46948ad91", - "pin_contract_name": "FlowToken", - "pin_contract_address": "0x1654653399040a61", - "imports": [ - { - "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", - "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", - "pin_contract_name": "FungibleToken", - "pin_contract_address": "0xf233dcee88fe0abe", - "imports": [] - } - ] - } - }, - { - "network": "testnet", - "address": "0x7e60df042a9c0868", - "dependency_pin_block_height": 139547221, - "dependency_pin": { - "pin": "9cc21a34a01486ebd6f044e99dbcdd58671850f81fcc345d071181c19f61aaa4", - "pin_self": "6f01c7001e2d6635b667a170d3ccbc13659c40d01bb35e56979fcc7fa2d18646", - "pin_contract_name": "FlowToken", - "pin_contract_address": "0x7e60df042a9c0868", - "imports": [ - { - "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", - "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", - "pin_contract_name": "FungibleToken", - "pin_contract_address": "0x9a0766d93b6608b7", - "imports": [] - } - ] - } - }, - { - "network": "emulator", - "address": "0x0ae53cb6e3f42a79", - "dependency_pin_block_height": 0 - } - ] - } - ] - } - ], - "parameters": [ - { - "label": "address", - "index": 0, - "type": "Address", - "messages": [] - } - ] - } -} -` -const templateMultipleCoreImports = ` -{ - "f_type": "InteractionTemplate", - "f_version": "1.1.0", - "id": "29d03aafbbb5a02e0d5f4ffee685c12494915410812305c2858008d3e2902b72", - "data": { - "type": "script", - "interface": "", - "messages": null, - "cadence": { - "body": "import \"FungibleToken\"\nimport \"FlowToken\"\n\npub fun main(address: Address): UFix64 {\n let account = getAccount(address)\n\n let vaultRef = account\n .getCapability(/public/flowTokenBalance)\n .borrow\u003c\u0026FlowToken.Vault{FungibleToken.Balance}\u003e()\n ?? panic(\"Could not borrow balance reference to the Vault\")\n\n return vaultRef.balance\n}\n", - "network_pins": [] - }, - "dependencies": [], - "parameters": [] - } -} -` - -const templateMissing = ` -{ - "f_type": "InteractionTemplate", - "f_version": "1.1.0", - "id": "a2b2d73def...aabc5472d2", - "data": { - "type": "transaction", - "interface": "asadf23234...fas234234", - "messages": [], - "cadence": { - "body": "import \"FlowTokenAA\"\n transaction(amount: UFix64, to: Address) {\n let vault: @FungibleToken.Vault\n prepare(signer: AuthAccount) {\n %%self.vault <- signer\n .borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)!\n .withdraw(amount: amount)\n self.vault <- FungibleToken.getVault(signer)\n }\n execute {\n getAccount(to)\n .getCapability(/public/flowTokenReceiver)!\n .borrow<&{FungibleToken.Receiver}>()!\n .deposit(from: <-self.vault)\n }\n }", - "network_pins": [] - }, - "dependencies": [ - { - "contracts": [], - "parameters": [] - } - ] - } -} -` - -func TestGetAndReplaceCadenceImports(t *testing.T) { - assert := assert.New(t) - - tests := []struct { - name string - network string - wantErr bool - wantImport string - template string - }{ - { - name: "Mainnet", - network: "mainnet", - wantErr: false, - wantImport: "import FlowToken from 0x1654653399040a61", - template: template, - }, - { - name: "Testnet", - network: "testnet", - wantErr: false, - wantImport: "import FlowToken from 0x7e60df042a9c0868", - template: template, - }, - { - name: "MissingNetwork", - network: "missing", - wantErr: true, - template: template, - }, - { - name: "MissingCadence", - network: "mainnet", - wantErr: true, - template: templateMissing, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - parsedTemplate, err := ParseFlix(tt.template) - if err != nil { - t.Fatal(err) - } - - cadenceCode, err := parsedTemplate.GetAndReplaceCadenceImports(tt.network) - if tt.wantErr { - assert.Error(err, tt.name, "GetCadenceWithReplacedImports should return an error") - } else { - assert.NoError(err, "GetCadenceWithReplacedImports should not return an error") - assert.NotEmpty(cadenceCode, tt.name, "Cadence should not be empty") - - assert.Contains(cadenceCode, tt.wantImport, "Cadence should contain the expected import") - } - }) - } -} - -func TestGetAndReplaceCadenceImportsMultipleImports(t *testing.T) { - template, err := ParseFlix(templateMultipleImports) - if err != nil { - t.Fatal(err) - } - cadenceCode, err := template.GetAndReplaceCadenceImports("mainnet") - if err != nil { - t.Fatal(err) - } - assert.Contains(t, cadenceCode, "import FungibleToken from 0xf233dcee88fe0abe", "Cadence should contain the expected FungibleToken import") - assert.Contains(t, cadenceCode, "import FlowToken from 0x1654653399040a61", "Cadence should contain the expected FlowTokenimport") - -} - -func TestGetAndReplaceCadenceImportsMultipleCoreImports(t *testing.T) { - template, err := ParseFlix(templateMultipleCoreImports) - if err != nil { - t.Fatal(err) - } - cadenceCode, err := template.GetAndReplaceCadenceImports("mainnet") - if err != nil { - t.Fatal(err) - } - assert.Contains(t, cadenceCode, "import FungibleToken from 0xf233dcee88fe0abe", "Cadence should contain the expected FungibleToken import") - assert.Contains(t, cadenceCode, "import FlowToken from 0x1654653399040a61", "Cadence should contain the expected FlowTokenimport") - -} diff --git a/flixkitv2/flix_generator.go b/flixkitv2/flix_generator.go deleted file mode 100644 index 60018d7..0000000 --- a/flixkitv2/flix_generator.go +++ /dev/null @@ -1,21 +0,0 @@ -package flixkitv2 - -import ( - "context" - - internal "github.com/onflow/flixkit-go/internal/flixkitv2" -) - -type FlixGenerator interface { - // GenerateTemplate returns the generated raw template - GenerateTemplate(ctx context.Context, code string, preFill string) (string, error) - // GenerateBinding returns the generated binding given the language - GenerateBinding(ctx context.Context, flixString string, templateLocation string, lang string) (string, error) -} - -type FlixGeneratorConfig = internal.FlixGeneratorConfig - -// NewFlixGenerator returns a new FlixGenerator -func NewFlixGenerator(conf FlixGeneratorConfig) FlixGenerator { - return internal.NewFlixGenerator(conf) -} \ No newline at end of file diff --git a/internal/common.go b/internal/common.go new file mode 100644 index 0000000..f495bdf --- /dev/null +++ b/internal/common.go @@ -0,0 +1,84 @@ +package internal + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "net/url" + "os" +) + +func getTemplateVersion(template string) (string, error) { + type FlowInteractionTemplateVersion struct { + FVersion string `json:"f_version"` + } + var flowTemplate FlowInteractionTemplateVersion + + err := json.Unmarshal([]byte(template), &flowTemplate) + if err != nil { + return "", err + } + + if flowTemplate.FVersion == "" { + return "", fmt.Errorf("version not found") + } + + return flowTemplate.FVersion, nil +} + +func isArrayParameter(arg FlixParameter) (isArray bool, cType string, jsType string) { + if arg.Type == "" || arg.Type[0] != '[' { + return false, "", "" + } + cadenceType := arg.Type[1 : len(arg.Type)-1] + javascriptType := "Array<" + convertCadenceTypeToJS(cadenceType) + ">" + return true, cadenceType, javascriptType +} + +func isUrl(str string) bool { + u, err := url.Parse(str) + return err == nil && u.Scheme != "" && u.Host != "" +} + +type flixQueryTypes string + +const ( + flixName flixQueryTypes = "name" + flixPath flixQueryTypes = "path" + flixId flixQueryTypes = "id" + flixUrl flixQueryTypes = "url" + flixJson flixQueryTypes = "json" +) + +func isHex(str string) bool { + if len(str) != 64 { + return false + } + _, err := hex.DecodeString(str) + return err == nil +} + +func isPath(path string) bool { + _, err := os.Stat(path) + return err == nil +} + +func isJson(str string) bool { + var js json.RawMessage + return json.Unmarshal([]byte(str), &js) == nil +} + +func getType(s string) flixQueryTypes { + switch { + case isPath(s): + return flixPath + case isHex(s): + return flixId + case isUrl(s): + return flixUrl + case isJson(s): + return flixJson + default: + return flixName + } +} diff --git a/internal/flixkitv2/fcl_binding_creator.go b/internal/fcl_binding_creator.go similarity index 97% rename from internal/flixkitv2/fcl_binding_creator.go rename to internal/fcl_binding_creator.go index e4e452f..80d14d1 100644 --- a/internal/flixkitv2/fcl_binding_creator.go +++ b/internal/fcl_binding_creator.go @@ -1,4 +1,4 @@ -package flixkitv2 +package internal import ( "bytes" @@ -6,13 +6,13 @@ import ( "sort" "text/template" - v1 "github.com/onflow/flixkit-go/flixkit/v1" - v1_1 "github.com/onflow/flixkit-go/internal/flixkitv2/v1_1" "github.com/onflow/flixkit-go/internal/templates" + v1 "github.com/onflow/flixkit-go/internal/v1" + v1_1 "github.com/onflow/flixkit-go/internal/v1_1" "github.com/stoewer/go-strcase" ) -func NewFclTSCreator() *FclCreator{ +func NewFclTSCreator() *FclCreator { t := []string{ templates.GetTsFclMainTemplate(), templates.GetTsFclScriptTemplate(), @@ -26,7 +26,7 @@ func NewFclTSCreator() *FclCreator{ } } -func NewFclJSCreator() *FclCreator{ +func NewFclJSCreator() *FclCreator { t := []string{ templates.GetJsFclMainTemplate(), templates.GetJsFclScriptTemplate(), @@ -104,7 +104,6 @@ type FclCreator struct { templates []string } - type FlixParameter struct { Name string Type string @@ -265,4 +264,4 @@ func transformArguments(args v1.Arguments) []simpleParameter { } } return simpleArgs -} \ No newline at end of file +} diff --git a/internal/flix_generator.go b/internal/flix_generator.go new file mode 100644 index 0000000..5259036 --- /dev/null +++ b/internal/flix_generator.go @@ -0,0 +1,35 @@ +package internal + +import ( + "context" + + v1_1 "github.com/onflow/flixkit-go/internal/v1_1" + "github.com/onflow/flow-cli/flowkit/output" +) + +type flixGenerator struct { + generator v1_1.Generator + fileReader FileReader +} + +type FlixTemplateGeneratorConfig struct { + FileReader FileReader + deployedContracts v1_1.ContractInfos + logger output.Logger +} + +func NewFlixTemplateGenerator(conf FlixTemplateGeneratorConfig) flixGenerator { + var gen, err = v1_1.NewTemplateGenerator(conf.deployedContracts, conf.logger) + if err != nil { + panic(err) + } + + return flixGenerator{ + generator: *gen, + fileReader: conf.FileReader, + } +} + +func (s flixGenerator) CreateTemplate(ctx context.Context, code string, preFill string) (string, error) { + return s.generator.CreateTemplate(ctx, code, preFill) +} diff --git a/flixkit/flixkit.go b/internal/flix_service.go similarity index 56% rename from flixkit/flixkit.go rename to internal/flix_service.go index 3c82f23..33bc87a 100644 --- a/flixkit/flixkit.go +++ b/internal/flix_service.go @@ -1,76 +1,97 @@ -package flixkit +package internal import ( "context" - "encoding/hex" - "encoding/json" "fmt" "io" "log" "net/http" - "net/url" - "os" - v1 "github.com/onflow/flixkit-go/flixkit/v1" - v1_1 "github.com/onflow/flixkit-go/flixkit/v1_1" + v1 "github.com/onflow/flixkit-go/internal/v1" + v1_1 "github.com/onflow/flixkit-go/internal/v1_1" ) -type FlowInteractionTemplateExecution struct { - Network string - Cadence string - IsTransaciton bool - IsScript bool +type FileReader interface { + ReadFile(path string) ([]byte, error) } -type FlowInteractionTemplateVersion struct { - FVersion string `json:"f_version"` +type FlixServiceConfig struct { + FlixServerURL string + FileReader FileReader +} + +func NewFlixService(config *FlixServiceConfig) flixService { + if config.FlixServerURL == "" { + config.FlixServerURL = "https://flix.flow.com/v1/templates" + } + + return flixService{ + config: config, + } } -type FlixTemplater interface { - Generate(ctx context.Context, code string, preFill string) (string, error) +type flixService struct { + config *FlixServiceConfig + bindingCreator FclCreator } -type Binder interface { - Generate(flixString string, templateLocation string) (string, error) +type FlowInteractionTemplateExecution struct { + Network string + Cadence string + IsTransaciton bool + IsScript bool } type FlowInteractionTemplateCadence interface { - GetAndReplaceCadenceImports(templateName string) (string, error) + ReplaceCadenceImports(templateName string) (string, error) IsTransaction() bool IsScript() bool } -type FlixService interface { - GetTemplate(ctx context.Context, templateName string) (string, error) - GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) -} +func (s flixService) GetTemplate(ctx context.Context, flixQuery string) (string, error) { + var template string + var err error -type flixServiceImpl struct { - config *Config -} + switch getType(flixQuery) { + case flixId: + template, err = s.getFlixByID(ctx, flixQuery) + if err != nil { + return "", fmt.Errorf("could not find flix with id %s: %w", flixQuery, err) + } -var _ FlixService = (*flixServiceImpl)(nil) + case flixName: + template, err = s.getFlix(ctx, flixQuery) + if err != nil { + return "", fmt.Errorf("could not find flix with name %s: %w", flixQuery, err) + } -type FileReader interface { - ReadFile(path string) ([]byte, error) -} + case flixPath: + if s.config.FileReader == nil { + return "", fmt.Errorf("file reader not provided") + } + file, err := s.config.FileReader.ReadFile(flixQuery) + if err != nil { + return "", fmt.Errorf("could not read flix file %s: %w", flixQuery, err) + } + template = string(file) + if err != nil { + return "", fmt.Errorf("could not parse flix from file %s: %w", flixQuery, err) + } -type Config struct { - FlixServerURL string - FileReader FileReader -} + case flixUrl: + template, err = fetchFlixWithContext(ctx, flixQuery) + if err != nil { + return "", fmt.Errorf("could not parse flix from url %s: %w", flixQuery, err) + } -func NewFlixService(config *Config) FlixService { - if config.FlixServerURL == "" { - config.FlixServerURL = "https://flix.flow.com/v1/templates" + default: + return "", fmt.Errorf("invalid flix query type: %s", flixQuery) } - return &flixServiceImpl{ - config: config, - } + return template, nil } -func (s *flixServiceImpl) GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) { +func (s flixService) GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) { template, err := s.GetTemplate(ctx, templateName) if err != nil { return nil, err @@ -88,7 +109,7 @@ func (s *flixServiceImpl) GetAndReplaceCadenceImports(ctx context.Context, templ if err != nil { return nil, err } - cadenceCode, err = replaceableCadence.GetAndReplaceCadenceImports(network) + cadenceCode, err = replaceableCadence.ReplaceCadenceImports(network) if err != nil { return nil, err } @@ -100,7 +121,7 @@ func (s *flixServiceImpl) GetAndReplaceCadenceImports(ctx context.Context, templ if err != nil { return nil, err } - cadenceCode, err = replaceableCadence.GetAndReplaceCadenceImports(network) + cadenceCode, err = replaceableCadence.ReplaceCadenceImports(network) if err != nil { return nil, err } @@ -118,55 +139,25 @@ func (s *flixServiceImpl) GetAndReplaceCadenceImports(ctx context.Context, templ return &execution, nil } -func (s *flixServiceImpl) GetTemplate(ctx context.Context, flixQuery string) (string, error) { - var template string - var err error - - switch getType(flixQuery) { - case flixId: - template, err = s.getFlixByID(ctx, flixQuery) - if err != nil { - return "", fmt.Errorf("could not find flix with id %s: %w", flixQuery, err) - } - - case flixName: - template, err = s.getFlix(ctx, flixQuery) - if err != nil { - return "", fmt.Errorf("could not find flix with name %s: %w", flixQuery, err) - } - - case flixPath: - if s.config.FileReader == nil { - return "", fmt.Errorf("file reader not provided") - } - file, err := s.config.FileReader.ReadFile(flixQuery) - if err != nil { - return "", fmt.Errorf("could not read flix file %s: %w", flixQuery, err) - } - template = string(file) - if err != nil { - return "", fmt.Errorf("could not parse flix from file %s: %w", flixQuery, err) - } - - case flixUrl: - template, err = fetchFlixWithContext(ctx, flixQuery) - if err != nil { - return "", fmt.Errorf("could not parse flix from url %s: %w", flixQuery, err) - } - - default: - return "", fmt.Errorf("invalid flix query type: %s", flixQuery) +func (s flixService) GetTemplateAndCreateBinding(ctx context.Context, templateName string, lang string) (string, error) { + template, err := s.GetTemplate(ctx, templateName) + if err != nil { + return "", err } - return template, nil + return s.bindingCreator.Generate(template, templateName) +} + +func (s flixService) GenerateBinding(ctx context.Context, flixString string, templateLocation string, lang string) (string, error) { + return s.bindingCreator.Generate(flixString, templateLocation) } -func (s *flixServiceImpl) getFlixRaw(ctx context.Context, templateName string) (string, error) { +func (s flixService) getFlixRaw(ctx context.Context, templateName string) (string, error) { url := fmt.Sprintf("%s?name=%s", s.config.FlixServerURL, templateName) return fetchFlixWithContext(ctx, url) } -func (s *flixServiceImpl) getFlix(ctx context.Context, templateName string) (string, error) { +func (s flixService) getFlix(ctx context.Context, templateName string) (string, error) { template, err := s.getFlixRaw(ctx, templateName) if err != nil { return "", err @@ -175,12 +166,12 @@ func (s *flixServiceImpl) getFlix(ctx context.Context, templateName string) (str return template, nil } -func (s *flixServiceImpl) getFlixByIDRaw(ctx context.Context, templateID string) (string, error) { +func (s flixService) getFlixByIDRaw(ctx context.Context, templateID string) (string, error) { url := fmt.Sprintf("%s/%s", s.config.FlixServerURL, templateID) return fetchFlixWithContext(ctx, url) } -func (s *flixServiceImpl) getFlixByID(ctx context.Context, templateID string) (string, error) { +func (s flixService) getFlixByID(ctx context.Context, templateID string) (string, error) { template, err := s.getFlixByIDRaw(ctx, templateID) if err != nil { return "", err @@ -188,21 +179,6 @@ func (s *flixServiceImpl) getFlixByID(ctx context.Context, templateID string) (s return template, nil } -func getTemplateVersion(template string) (string, error) { - var flowTemplate FlowInteractionTemplateVersion - - err := json.Unmarshal([]byte(template), &flowTemplate) - if err != nil { - return "", err - } - - if flowTemplate.FVersion == "" { - return "", fmt.Errorf("version not found") - } - - return flowTemplate.FVersion, nil -} - func fetchFlixWithContext(ctx context.Context, url string) (string, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { @@ -226,51 +202,3 @@ func fetchFlixWithContext(ctx context.Context, url string) (string, error) { return string(body), nil } - -type flixQueryTypes string - -const ( - flixName flixQueryTypes = "name" - flixPath flixQueryTypes = "path" - flixId flixQueryTypes = "id" - flixUrl flixQueryTypes = "url" - flixJson flixQueryTypes = "json" -) - -func isHex(str string) bool { - if len(str) != 64 { - return false - } - _, err := hex.DecodeString(str) - return err == nil -} - -func isPath(path string) bool { - _, err := os.Stat(path) - return err == nil -} - -func isUrl(str string) bool { - u, err := url.Parse(str) - return err == nil && u.Scheme != "" && u.Host != "" -} - -func isJson(str string) bool { - var js json.RawMessage - return json.Unmarshal([]byte(str), &js) == nil -} - -func getType(s string) flixQueryTypes { - switch { - case isPath(s): - return flixPath - case isHex(s): - return flixId - case isUrl(s): - return flixUrl - case isJson(s): - return flixJson - default: - return flixName - } -} diff --git a/flixkit/flixkit_test.go b/internal/flix_service_test.go similarity index 95% rename from flixkit/flixkit_test.go rename to internal/flix_service_test.go index 17f9c81..581dac1 100644 --- a/flixkit/flixkit_test.go +++ b/internal/flix_service_test.go @@ -1,4 +1,4 @@ -package flixkit +package internal import ( "context" @@ -6,7 +6,7 @@ import ( "net/http/httptest" "testing" - v1 "github.com/onflow/flixkit-go/flixkit/v1" + v1 "github.com/onflow/flixkit-go/internal/v1" "github.com/stretchr/testify/assert" ) @@ -189,7 +189,7 @@ func TestGetAndReplaceCadenceImports(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - cadence, err := parsedTemplate.GetAndReplaceCadenceImports(tt.network) + cadence, err := parsedTemplate.ReplaceCadenceImports(tt.network) if tt.wantErr { assert.Error(err, "GetCadenceWithReplacedImports should return an error") } else { @@ -267,7 +267,7 @@ func TestGetFlixRaw(t *testing.T) { })) defer server.Close() - flixService := NewFlixService(&Config{FlixServerURL: server.URL, FileReader: DefaultReader{}}) + flixService := NewFlixService(&FlixServiceConfig{FlixServerURL: server.URL, FileReader: DefaultReader{}}) ctx := context.Background() body, err := flixService.GetTemplate(ctx, "templateName") assert.NoError(err, "GetFlixByName should not return an error") @@ -282,7 +282,7 @@ func TestGetFlixFilename(t *testing.T) { })) defer server.Close() - flixService := NewFlixService(&Config{FlixServerURL: server.URL, FileReader: DefaultReader{}}) + flixService := NewFlixService(&FlixServiceConfig{FlixServerURL: server.URL, FileReader: DefaultReader{}}) ctx := context.Background() flix, err := flixService.GetTemplate(ctx, "./templateFileName") assert.NoError(err, "GetParsedFlixByName should not return an error") @@ -299,7 +299,7 @@ func TestGetFlixByIDRaw(t *testing.T) { })) defer server.Close() - flixService := NewFlixService(&Config{FlixServerURL: server.URL}) + flixService := NewFlixService(&FlixServiceConfig{FlixServerURL: server.URL}) ctx := context.Background() body, err := flixService.GetTemplate(ctx, "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF") assert.NoError(err, "GetFlixByID should not return an error") @@ -314,7 +314,7 @@ func TestGetFlixByID(t *testing.T) { })) defer server.Close() - flixService := NewFlixService(&Config{FlixServerURL: server.URL}) + flixService := NewFlixService(&FlixServiceConfig{FlixServerURL: server.URL}) ctx := context.Background() flix, err := flixService.GetTemplate(ctx, "templateID") assert.NoError(err, "GetParsedFlixByID should not return an error") diff --git a/internal/flixkitv2/common.go b/internal/flixkitv2/common.go deleted file mode 100644 index ef35f23..0000000 --- a/internal/flixkitv2/common.go +++ /dev/null @@ -1,40 +0,0 @@ -package flixkitv2 - -import ( - "encoding/json" - "fmt" - "net/url" -) - - -func getTemplateVersion(template string) (string, error) { -type FlowInteractionTemplateVersion struct { - FVersion string `json:"f_version"` -} - var flowTemplate FlowInteractionTemplateVersion - - err := json.Unmarshal([]byte(template), &flowTemplate) - if err != nil { - return "", err - } - - if flowTemplate.FVersion == "" { - return "", fmt.Errorf("version not found") - } - - return flowTemplate.FVersion, nil -} - -func isArrayParameter(arg FlixParameter) (isArray bool, cType string, jsType string) { - if arg.Type == "" || arg.Type[0] != '[' { - return false, "", "" - } - cadenceType := arg.Type[1 : len(arg.Type)-1] - javascriptType := "Array<" + convertCadenceTypeToJS(cadenceType) + ">" - return true, cadenceType, javascriptType -} - -func isUrl(str string) bool { - u, err := url.Parse(str) - return err == nil && u.Scheme != "" && u.Host != "" -} \ No newline at end of file diff --git a/internal/flixkitv2/flix_generator.go b/internal/flixkitv2/flix_generator.go deleted file mode 100644 index 2193528..0000000 --- a/internal/flixkitv2/flix_generator.go +++ /dev/null @@ -1,31 +0,0 @@ -package flixkitv2 - -import ( - "context" - - v1_1 "github.com/onflow/flixkit-go/internal/flixkitv2/v1_1" -) - -type flixGenerator struct { - generator v1_1.Generator - bindingCreator FclCreator - fileReader FileReader -} - -type FlixGeneratorConfig struct { - FileReader FileReader -} - -func NewFlixGenerator(conf FlixGeneratorConfig) flixGenerator{ - return flixGenerator{ - fileReader: conf.FileReader, - } -} - -func (s flixGenerator) GenerateBinding(ctx context.Context, flixString string, templateLocation string, lang string) (string, error) { - return s.bindingCreator.Generate(flixString, templateLocation) -} - -func (s flixGenerator) GenerateTemplate(ctx context.Context, code string, preFill string) (string, error) { - return s.generator.GenerateTemplate(ctx, code, preFill) -} diff --git a/internal/flixkitv2/flix_service.go b/internal/flixkitv2/flix_service.go deleted file mode 100644 index bce049c..0000000 --- a/internal/flixkitv2/flix_service.go +++ /dev/null @@ -1,51 +0,0 @@ -package flixkitv2 - -import ( - "context" - - v1_1 "github.com/onflow/flixkit-go/internal/flixkitv2/v1_1" -) - -type FileReader interface { - ReadFile(path string) ([]byte, error) -} - -type FlixServiceConfig struct { - FlixServerURL string - FileReader FileReader -} - -func NewFlixService(config *FlixServiceConfig) flixService { - return flixService{} -} - -type flixService struct { - generator v1_1.Generator - bindingCreator FclCreator - -} - - -type FlowInteractionTemplateExecution struct { - Network string - Cadence string - IsTransaciton bool - IsScript bool -} - -func (s flixService) GetTemplate(ctx context.Context, templateName string) (string, error) { - panic("unimplemented") -} - -func (flixService) GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) { - panic("unimplemented") -} - - -func (s flixService) GenerateBinding(ctx context.Context, flixString string, templateLocation string, lang string) (string, error) { - return s.bindingCreator.Generate(flixString, templateLocation) -} - -func (s flixService) GenerateTemplate(ctx context.Context, code string, preFill string) (string, error) { - return s.generator.GenerateTemplate(ctx, code, preFill) -} diff --git a/internal/flixkitv2/v1/types.go b/internal/flixkitv2/v1/types.go deleted file mode 100644 index ef373c6..0000000 --- a/internal/flixkitv2/v1/types.go +++ /dev/null @@ -1,134 +0,0 @@ -package flixkit - -import ( - "encoding/json" - "fmt" - "regexp" - - "github.com/onflow/flixkit-go/internal/contracts" -) - -type Network struct { - Address string `json:"address"` - FqAddress string `json:"fq_address"` - Contract string `json:"contract"` - Pin string `json:"pin"` - PinBlockHeight uint64 `json:"pin_block_height"` -} - -type Argument struct { - Index int `json:"index"` - Type string `json:"type"` - Messages Messages `json:"messages"` - Balance string `json:"balance"` -} - -type Title struct { - I18N map[string]string `json:"i18n"` -} - -type Description struct { - I18N map[string]string `json:"i18n"` -} - -type Messages struct { - Title *Title `json:"title,omitempty"` - Description *Description `json:"description,omitempty"` -} - -type Dependencies map[string]Contracts -type Contracts map[string]Networks -type Networks map[string]Network -type Arguments map[string]Argument - -type Data struct { - Type string `json:"type"` - Interface string `json:"interface"` - Messages Messages `json:"messages"` - Cadence string `json:"cadence"` - Dependencies Dependencies `json:"dependencies"` - Arguments Arguments `json:"arguments"` -} - -type FlowInteractionTemplate struct { - FType string `json:"f_type"` - FVersion string `json:"f_version"` - ID string `json:"id"` - Data Data `json:"data"` -} - -func (t *FlowInteractionTemplate) IsScript() bool { - return t.Data.Type == "script" -} - -func (t *FlowInteractionTemplate) IsTransaction() bool { - return t.Data.Type == "transaction" -} - -func ParseFlix(template string) (*FlowInteractionTemplate, error) { - var flowTemplate FlowInteractionTemplate - - err := json.Unmarshal([]byte(template), &flowTemplate) - if err != nil { - return nil, err - } - - return &flowTemplate, nil -} - -func (t *FlowInteractionTemplate) GetAndReplaceCadenceImports(networkName string) (string, error) { - var cadence string - - for dependencyAddress, c := range t.Data.Dependencies { - for contractName, networks := range c { - var networkAddress string - network, ok := networks[networkName] - networkAddress = network.Address - if !ok { - coreContractAddress := contracts.GetCoreContractForNetwork(contractName, networkName) - if coreContractAddress == "" { - return "", fmt.Errorf("network %s not found for contract %s in dependencies", networkName, contractName) - } - networkAddress = coreContractAddress - } - - pattern := fmt.Sprintf(`import\s*%s\s*from\s*%s`, contractName, dependencyAddress) - re, err := regexp.Compile(pattern) - if err != nil { - return "", fmt.Errorf("invalid regex pattern: %v", err) - } - - replacement := fmt.Sprintf("import %s from %s", contractName, networkAddress) - cadence = re.ReplaceAllString(t.Data.Cadence, replacement) - } - } - - return cadence, nil -} - -func (t *FlowInteractionTemplate) GetDescription() string { - s := "" - if t.Data.Messages.Description != nil && - t.Data.Messages.Description.I18N != nil { - - // relying on en-US for now, future we need to know what language to use - value, exists := t.Data.Messages.Description.I18N["en-US"] - if exists { - s = value - } - } - return s -} - -func (msgs *Messages) GetTitleValue(placeholder string) string { - s := placeholder - if msgs.Title != nil && - msgs.Title.I18N != nil { - // relying on en-US for now, future we need to know what language to use - value, exists := msgs.Title.I18N["en-US"] - if exists { - s = value - } - } - return s -} diff --git a/internal/flixkitv2/v1_1/testdata/TestEmptyPragmaWithParameter.golden b/internal/flixkitv2/v1_1/testdata/TestEmptyPragmaWithParameter.golden deleted file mode 100644 index ce02d09..0000000 --- a/internal/flixkitv2/v1_1/testdata/TestEmptyPragmaWithParameter.golden +++ /dev/null @@ -1,23 +0,0 @@ -`{ - "f_type": "", - "f_version": "", - "id": "", - "data": { - "type": "", - "interface": "", - "messages": null, - "cadence": { - "body": "", - "network_pins": null - }, - "dependencies": null, - "parameters": [ - { - "label": "greeting", - "index": 0, - "type": "String", - "messages": [] - } - ] - } -}` diff --git a/internal/flixkitv2/v1_1/testdata/TestGenerateParametersScripts.golden b/internal/flixkitv2/v1_1/testdata/TestGenerateParametersScripts.golden deleted file mode 100644 index 059ae36..0000000 --- a/internal/flixkitv2/v1_1/testdata/TestGenerateParametersScripts.golden +++ /dev/null @@ -1,67 +0,0 @@ -`{ - "f_type": "InteractionTemplate", - "f_version": "1.1.0", - "id": "", - "data": { - "type": "script", - "interface": "", - "messages": [ - { - "key": "title", - "i18n": [ - { - "tag": "en-US", - "translation": "User Balance" - } - ] - }, - { - "key": "description", - "i18n": [ - { - "tag": "en-US", - "translation": "Get User Balance" - } - ] - } - ], - "cadence": { - "body": "", - "network_pins": null - }, - "dependencies": [], - "parameters": [ - { - "label": "address", - "index": 0, - "type": "Address", - "messages": [ - { - "key": "title", - "i18n": [ - { - "tag": "en-US", - "translation": "User Address" - } - ] - }, - { - "key": "description", - "i18n": [ - { - "tag": "en-US", - "translation": "Get user balance" - } - ] - } - ] - } - ], - "output": { - "label": "result", - "index": 0, - "type": "UFix64", - "messages": [] - } - } -}` diff --git a/internal/flixkitv2/v1_1/testdata/TestParsePragma/Empty.golden b/internal/flixkitv2/v1_1/testdata/TestParsePragma/Empty.golden deleted file mode 100644 index 9277e16..0000000 --- a/internal/flixkitv2/v1_1/testdata/TestParsePragma/Empty.golden +++ /dev/null @@ -1,16 +0,0 @@ -`{ - "f_type": "", - "f_version": "", - "id": "", - "data": { - "type": "", - "interface": "", - "messages": null, - "cadence": { - "body": "", - "network_pins": null - }, - "dependencies": null, - "parameters": null - } -}` diff --git a/internal/flixkitv2/v1_1/testdata/TestParsePragma/Minimum.golden b/internal/flixkitv2/v1_1/testdata/TestParsePragma/Minimum.golden deleted file mode 100644 index 129680c..0000000 --- a/internal/flixkitv2/v1_1/testdata/TestParsePragma/Minimum.golden +++ /dev/null @@ -1,16 +0,0 @@ -`{ - "f_type": "", - "f_version": "1.1.0", - "id": "", - "data": { - "type": "", - "interface": "", - "messages": null, - "cadence": { - "body": "", - "network_pins": null - }, - "dependencies": null, - "parameters": null - } -}` diff --git a/internal/flixkitv2/v1_1/testdata/TestParsePragma/WithParameters.golden b/internal/flixkitv2/v1_1/testdata/TestParsePragma/WithParameters.golden deleted file mode 100644 index c9bfaee..0000000 --- a/internal/flixkitv2/v1_1/testdata/TestParsePragma/WithParameters.golden +++ /dev/null @@ -1,86 +0,0 @@ -`{ - "f_type": "", - "f_version": "1.1.0", - "id": "", - "data": { - "type": "", - "interface": "", - "messages": [ - { - "key": "title", - "i18n": [ - { - "tag": "en-US", - "translation": "Update Greeting" - } - ] - }, - { - "key": "description", - "i18n": [ - { - "tag": "en-US", - "translation": "Update the greeting on the HelloWorld contract" - } - ] - } - ], - "cadence": { - "body": "", - "network_pins": null - }, - "dependencies": null, - "parameters": [ - { - "label": "greeting", - "index": 0, - "type": "", - "messages": [ - { - "key": "title", - "i18n": [ - { - "tag": "en-US", - "translation": "Greeting" - } - ] - }, - { - "key": "description", - "i18n": [ - { - "tag": "en-US", - "translation": "The greeting to set on the HelloWorld contract" - } - ] - } - ] - }, - { - "label": "amount", - "index": 1, - "type": "", - "messages": [ - { - "key": "title", - "i18n": [ - { - "tag": "en-US", - "translation": "Amount" - } - ] - }, - { - "key": "description", - "i18n": [ - { - "tag": "en-US", - "translation": "The amount parameter to Test" - } - ] - } - ] - } - ] - } -}` diff --git a/internal/flixkitv2/v1_1/testdata/TestParsePragma/WithoutParameters.golden b/internal/flixkitv2/v1_1/testdata/TestParsePragma/WithoutParameters.golden deleted file mode 100644 index eceba88..0000000 --- a/internal/flixkitv2/v1_1/testdata/TestParsePragma/WithoutParameters.golden +++ /dev/null @@ -1,35 +0,0 @@ -`{ - "f_type": "", - "f_version": "1.1.0", - "id": "", - "data": { - "type": "", - "interface": "", - "messages": [ - { - "key": "title", - "i18n": [ - { - "tag": "en-US", - "translation": "Update Greeting" - } - ] - }, - { - "key": "description", - "i18n": [ - { - "tag": "en-US", - "translation": "Update the greeting on the HelloWorld contract" - } - ] - } - ], - "cadence": { - "body": "", - "network_pins": null - }, - "dependencies": null, - "parameters": null - } -}` diff --git a/internal/flixkitv2/v1_1/types.go b/internal/flixkitv2/v1_1/types.go deleted file mode 100644 index 9a593cc..0000000 --- a/internal/flixkitv2/v1_1/types.go +++ /dev/null @@ -1,600 +0,0 @@ -package flixkit - -import ( - "bytes" - "encoding/binary" - "encoding/hex" - "encoding/json" - "fmt" - "regexp" - "sort" - "strings" - - "github.com/ethereum/go-ethereum/rlp" - "github.com/onflow/cadence/runtime/ast" - "github.com/onflow/flixkit-go/internal/contracts" - "golang.org/x/crypto/sha3" -) - -type InteractionTemplate struct { - FType string `json:"f_type"` - FVersion string `json:"f_version"` - ID string `json:"id"` - Data Data `json:"data"` -} - -type Data struct { - Type string `json:"type"` - Interface string `json:"interface"` - Messages []Message `json:"messages"` - Cadence Cadence `json:"cadence"` - Dependencies []Dependency `json:"dependencies"` - Parameters []Parameter `json:"parameters"` - Output *Parameter `json:"output,omitempty"` -} - -type Message struct { - Key string `json:"key"` - I18n []I18n `json:"i18n"` -} - -type InteractionTemplateMessages []Message - -func (msgs InteractionTemplateMessages) GetTitle(placeholder string) string { - return msgs.getMessageValue("title", placeholder) -} - -func (msgs InteractionTemplateMessages) GetDescription(placeholder string) string { - return msgs.getMessageValue("description", placeholder) -} - -func (msgs InteractionTemplateMessages) getMessageValue(key string, placeholder string) string { - s := placeholder - for _, msg := range msgs { - if msg.Key == key { - for _, i18n := range msg.I18n { - // set default if en-US not present - s = i18n.Translation - if i18n.Tag == "en-US" { - s = i18n.Translation - break - } - } - } - } - return strings.TrimSpace(s) -} - -type I18n struct { - Tag string `json:"tag"` - Translation string `json:"translation"` -} - -type Cadence struct { - Body string `json:"body"` - NetworkPins []NetworkPin `json:"network_pins"` -} - -type NetworkPin struct { - Network string `json:"network"` - PinSelf string `json:"pin_self"` -} - -type Dependency struct { - Contracts []Contract `json:"contracts"` -} - -type Contract struct { - Contract string `json:"contract"` - Networks []Network `json:"networks"` -} - -type Network struct { - Network string `json:"network"` - Address string `json:"address"` - DependencyPinBlockHeight uint64 `json:"dependency_pin_block_height"` - DependencyPin *PinDetail `json:"dependency_pin,omitempty"` -} - -type PinDetail struct { - Pin string `json:"pin"` - PinSelf string `json:"pin_self"` - PinContractName string `json:"pin_contract_name"` - PinContractAddress string `json:"pin_contract_address"` - Imports []PinDetail `json:"imports"` -} - -func (p *PinDetail) CalculatePin(blockHeight uint64) { - var a []string - a = append(a, ShaHex(p.PinContractAddress, "address")) - a = append(a, ShaHex(p.PinContractName, "address")) - a = append(a, ShaHex(p.PinSelf, "pin_self")) - a = append(a, ShaHex(fmt.Sprint(blockHeight), "pin_block_height")) - hash := ShaHex(strings.Join(a, ""), "calculate_pin") - p.Pin = hash -} - -type Import struct { - Pin string `json:"pin"` - PinSelf string `json:"pin_self"` - PinContractName string `json:"pin_contract_name"` - PinContractAddress string `json:"pin_contract_address"` - Imports []Import `json:"imports"` // Recursive imports, if any -} - -type Parameter struct { - Label string `json:"label"` - Index int `json:"index"` - Type string `json:"type"` - Messages []Message `json:"messages"` -} - -func (t *InteractionTemplate) Init() { - t.FType = "InteractionTemplate" - if t.FVersion == "" { - t.FVersion = "1.1.0" - } -} - -func (t *InteractionTemplate) IsScript() bool { - return t.Data.Type == "script" -} - -func (t *InteractionTemplate) IsTransaction() bool { - return t.Data.Type == "transaction" -} - -func replaceImport(code string, from string, to string) string { - pathRegex := regexp.MustCompile(fmt.Sprintf(`import\s+(\w+)\s+from\s+"%s"`, from)) - identifierRegex := regexp.MustCompile(fmt.Sprintf(`import\s+"(%s)"`, from)) - - replacement := fmt.Sprintf(`import $1 from %s`, to) - code = pathRegex.ReplaceAllString(code, replacement) - code = identifierRegex.ReplaceAllString(code, replacement) - return code -} - -func (t *InteractionTemplate) GetAndReplaceCadenceImports(networkName string) (string, error) { - cadence := t.Data.Cadence.Body - // Compile regular expression to match and capture contract names - re := regexp.MustCompile(`import\s*"([^"]+)"`) - - // Find all matches and their captured groups - matches := re.FindAllStringSubmatch(cadence, -1) - if len(matches) == 0 { - return cadence, nil - } - for _, match := range matches { - contractName := match[1] - var dependencyAddress string - for _, Dependence := range t.Data.Dependencies { - for _, contract := range Dependence.Contracts { - if contract.Contract == contractName { - for _, network := range contract.Networks { - if network.Network == networkName { - dependencyAddress = network.Address - break - } - } - break - } - } - } - - if dependencyAddress == "" { - dependencyAddress = contracts.GetCoreContractForNetwork(contractName, networkName) - if dependencyAddress == "" { - return "", fmt.Errorf("network %s not found for contract %s in dependencies", networkName, contractName) - } - } - cadence = replaceImport(cadence, contractName, dependencyAddress) - } - - return cadence, nil - -} - -func ParseFlix(template string) (*InteractionTemplate, error) { - var flowTemplate InteractionTemplate - err := json.Unmarshal([]byte(template), &flowTemplate) - if err != nil { - return nil, err - } - - return &flowTemplate, nil -} - -type PragmaDeclaration struct { - Expression InteractionExpression `json:"Expression"` -} - -type InteractionExpression struct { - InvokedExpression IdentifierExpression `json:"InvokedExpression"` - Arguments []Argument `json:"Arguments"` - Value string `json:"Value"` // Used for string expressions - Type string `json:"Type"` // Used for string expressions - Values []InteractionExpression `json:"Values"` // Used for array expressions -} - -type IdentifierExpression struct { - Identifier Identifier `json:"Identifier"` -} - -type Identifier struct { - Identifier string `json:"Identifier"` -} - -type Argument struct { - Expression InteractionExpression `json:"Expression"` - Label string `json:"Label"` -} - -func (template *InteractionTemplate) ParsePragma(program *ast.Program) error { - pragmas := program.PragmaDeclarations() - if len(pragmas) == 0 { - return nil - } - - for _, prag := range pragmas { - var pragmaDeclaration PragmaDeclaration - jsonData, err := prag.MarshalJSON() - if err != nil { - return err - } - err = json.Unmarshal([]byte(jsonData), &pragmaDeclaration) - if err != nil { - return err - } - if pragmaDeclaration.Expression.InvokedExpression.Identifier.Identifier == "interaction" { - pragmaInfo := flatten(pragmaDeclaration) - if template.FVersion == "" { - template.FVersion = pragmaInfo.meta["version"] - } - if pragmaInfo.meta["title"] != "" { - template.Data.Messages = append(template.Data.Messages, Message{ - Key: "title", - I18n: []I18n{ - { - Tag: pragmaInfo.meta["language"], - Translation: pragmaInfo.meta["title"], - }, - }, - }) - } - if pragmaInfo.meta["description"] != "" { - template.Data.Messages = append(template.Data.Messages, Message{ - Key: "description", - I18n: []I18n{ - { - Tag: pragmaInfo.meta["language"], - Translation: pragmaInfo.meta["description"], - }, - }, - }) - } - if pragmaInfo.parameters != nil { - for i, paramInfo := range pragmaInfo.parameters { - param := Parameter{ - Label: paramInfo.params["name"], - Index: i, - } - if paramInfo.params["title"] != "" { - param.Messages = append(param.Messages, Message{ - Key: "title", - I18n: []I18n{ - { - Tag: pragmaInfo.meta["language"], - Translation: paramInfo.params["title"], - }, - }, - }) - } - if paramInfo.params["description"] != "" { - param.Messages = append(param.Messages, Message{ - Key: "description", - I18n: []I18n{ - { - Tag: pragmaInfo.meta["language"], - Translation: paramInfo.params["description"], - }, - }, - }) - } - template.Data.Parameters = append(template.Data.Parameters, param) - } - } - } - } - return nil -} - -type parametermetadata struct { - params map[string]string -} -type metadata struct { - meta map[string]string - parameters []parametermetadata -} - -func flatten(pragma PragmaDeclaration) metadata { - var nameValuePairs map[string]string - var parameterPairs []parametermetadata - nameValuePairs = make(map[string]string) - parameterPairs = make([]parametermetadata, 0) - - for _, arg := range pragma.Expression.Arguments { - // For regular arguments - if arg.Expression.Value != "" { - nameValuePairs[arg.Label] = arg.Expression.Value - } - - // For arguments that contain arrays (like parameters) - if len(arg.Expression.Values) > 0 { - for _, param := range arg.Expression.Values { - p := parametermetadata{ - params: make(map[string]string), - } - for _, paramArg := range param.Arguments { - p.params[paramArg.Label] = paramArg.Expression.Value - } - parameterPairs = append(parameterPairs, p) - } - } - } - return metadata{nameValuePairs, parameterPairs} -} - -func (template *InteractionTemplate) ProcessParameters(program *ast.Program) error { - if program == nil { - return fmt.Errorf("no cadence program provided") - } - var parameterList []*ast.Parameter - functionDeclaration := program.FunctionDeclarations() - // only interested in main function of script - for _, d := range functionDeclaration { - if d.Identifier.String() == "main" { - parameterList = d.ParameterList.Parameters - r := d.ReturnTypeAnnotation.Type.String() - template.Data.Output = &Parameter{ - Label: "result", - Type: r, - Messages: make([]Message, 0), - } - } - } - - if program.SoleTransactionDeclaration() != nil && program.SoleTransactionDeclaration().ParameterList != nil { - parameterList = program.SoleTransactionDeclaration().ParameterList.Parameters - } - - if parameterList != nil && len(template.Data.Parameters) == 0 { - template.Data.Parameters = make([]Parameter, 0) - } - - // use existing parameter of template or create new one - for i, param := range parameterList { - var tempParam *Parameter - if hasValueAtIndex(template.Data.Parameters, i) { - tempParam = &template.Data.Parameters[i] - // verify that the parameter name matches, - // could happen if dev inputted param data incorrectly - if tempParam.Label != param.Identifier.String() { - return fmt.Errorf("parameter name mismatch, expected %s, got %s", tempParam.Label, param.Identifier.String()) - } - tempParam.Type = param.TypeAnnotation.Type.String() - tempParam.Index = i - } else { - tempParam = &Parameter{ - Label: param.Identifier.String(), - Index: i, - Type: param.TypeAnnotation.Type.String(), - Messages: make([]Message, 0), - } - template.Data.Parameters = append(template.Data.Parameters, *tempParam) - } - } - - return nil -} - -func hasValueAtIndex(arr []Parameter, index int) bool { - if len(arr) == 0 { - return false - } - if index >= 0 && index < len(arr) { - return true - } - return false -} - -func (template *InteractionTemplate) DetermineCadenceType(program *ast.Program) error { - funcs := program.FunctionDeclarations() - trans := program.TransactionDeclarations() - var t string - if len(funcs) > 0 { - t = "script" - } else if len(trans) > 0 { - t = "transaction" - template.Data.Output = nil - } else { - return fmt.Errorf("no function or transaction declarations found") - } - template.Data.Type = t - return nil -} - -func (template *InteractionTemplate) ProcessImports(cadenceCode string) { - // Define a regular expression to match the "import ContractName from 0xContractName" pattern - pattern := regexp.MustCompile(`import\s+(\w+)\s+from\s+0x\w+`) - // Replace the matched pattern with "import \"ContractName\"" - replaced := pattern.ReplaceAllString(cadenceCode, `import "$1"`) - template.Data.Cadence.Body = replaced -} - -func messagesToRlp(messages []Message) []interface{} { - values := make([]interface{}, 0) - for _, message := range messages { - var mv []interface{} - mv = append(mv, ShaHex(message.Key, message.Key)) - var templateMessageTranslations []interface{} - for _, v := range message.I18n { - var tagTranslation []interface{} - tagTranslation = append(tagTranslation, ShaHex(v.Tag, v.Tag)) - tagTranslation = append(tagTranslation, ShaHex(v.Translation, v.Translation)) - templateMessageTranslations = append(templateMessageTranslations, tagTranslation) - } - mv = append(mv, templateMessageTranslations) - values = append(values, mv) - } - return values -} - -func parameterToRLP(p Parameter) []interface{} { - var values []interface{} - values = append(values, ShaHex(p.Label, "label")) - - var param []interface{} - param = append(param, ShaHex(fmt.Sprint(p.Index), "index")) - param = append(param, ShaHex(p.Type, "type")) - param = append(param, messagesToRlp(p.Messages)) - values = append(values, param) - - return values -} - -func parametersToRlp(params []Parameter) []interface{} { - values := make([]interface{}, 0) - sort.Slice(params, func(i, j int) bool { - return params[i].Index < params[j].Index - }) - - for _, p := range params { - values = append(values, parameterToRLP(p)) - } - return values -} - -func networksToRlp(Networks []Network) []interface{} { - values := make([]interface{}, 0) - for _, network := range Networks { - var networks []interface{} - networks = append(networks, ShaHex(network.Network, "key")) - if network.DependencyPin != nil { - networks = append(networks, ShaHex(network.DependencyPin.Pin, "networkPin")) - } - values = append(values, networks) - } - return values -} - -func contractsToRlp(Contracts []Contract) []interface{} { - values := make([]interface{}, 0) - for _, contract := range Contracts { - var contracts []interface{} - contracts = append(contracts, ShaHex(contract.Contract, "key")) - contracts = append(contracts, networksToRlp(contract.Networks)) - values = append(values, contracts) - } - return values -} - -func dependenciesToRlp(Dependencies []Dependency) []interface{} { - values := make([]interface{}, 0) - for _, dependency := range Dependencies { - var deps []interface{} - deps = append(deps, contractsToRlp(dependency.Contracts)) - values = append(values, deps) - } - return values -} - -func (flix InteractionTemplate) EncodeRLP() (result string, err error) { - var buffer bytes.Buffer // Create a new buffer - - input := []interface{}{ - ShaHex(flix.FType, ""), - ShaHex(flix.FVersion, ""), - ShaHex(flix.Data.Type, ""), - ShaHex(flix.Data.Interface, ""), - messagesToRlp(flix.Data.Messages), - ShaHex(flix.Data.Cadence, ""), - dependenciesToRlp(flix.Data.Dependencies), - parametersToRlp(flix.Data.Parameters), - } - - // msg := dependenciesToRlp(flix.Data.Dependencies) - //prettyJSON, _ := json.MarshalIndent(input, "", " ") - //fmt.Println(string(prettyJSON)) - - err = rlp.Encode(&buffer, input) - if err != nil { - return "", err - } - hexString := hex.EncodeToString(buffer.Bytes()) - - //fmt.Println("call to hash hex string") - fullyHashed := ShaHex(hexString, "input") - - //fmt.Println("hexString", fullyHashed) - return fullyHashed, nil - -} - -func GenerateFlixID(flix *InteractionTemplate) (string, error) { - rlpOutput, err := flix.EncodeRLP() - if err != nil { - return "", err - } - return string(rlpOutput), nil -} - -func ShaHex(value interface{}, debugKey string) string { - - // Convert the value to a byte array - data, err := convertToBytes(value) - if err != nil { - if debugKey != "" { - fmt.Printf("%30s value=%v hex=%x\n", debugKey, value, err.Error()) - } - return "" - } - - // Calculate the SHA-3 hash - hash := sha3.Sum256(data) - - // Convert the hash to a hexadecimal string - hashHex := hex.EncodeToString(hash[:]) - - return hashHex -} - -func convertToBytes(value interface{}) ([]byte, error) { - switch v := value.(type) { - case []byte: - return v, nil - case string: - return []byte(v), nil - case int: - buf := make([]byte, 4) - binary.BigEndian.PutUint32(buf, uint32(v)) - return buf, nil - case uint64: - buf := make([]byte, 8) - binary.BigEndian.PutUint64(buf, v) - return buf, nil - default: - return nil, fmt.Errorf("unsupported type %T", v) - } -} - -func ExtractContractName(importStr string) (string, error) { - // Create a regex pattern to find the contract name inside the quotes - pattern := regexp.MustCompile(`import "([^"]+)"`) - matches := pattern.FindStringSubmatch(importStr) - - if len(matches) >= 2 { - return matches[1], nil - } - - return "", fmt.Errorf("no contract name found in string") -} diff --git a/flixkit/v1/types.go b/internal/v1/types.go similarity index 96% rename from flixkit/v1/types.go rename to internal/v1/types.go index ef373c6..fa16f07 100644 --- a/flixkit/v1/types.go +++ b/internal/v1/types.go @@ -1,4 +1,4 @@ -package flixkit +package v1 import ( "encoding/json" @@ -76,7 +76,7 @@ func ParseFlix(template string) (*FlowInteractionTemplate, error) { return &flowTemplate, nil } -func (t *FlowInteractionTemplate) GetAndReplaceCadenceImports(networkName string) (string, error) { +func (t *FlowInteractionTemplate) ReplaceCadenceImports(networkName string) (string, error) { var cadence string for dependencyAddress, c := range t.Data.Dependencies { diff --git a/internal/flixkitv2/v1_1/generator.go b/internal/v1_1/generator.go similarity index 96% rename from internal/flixkitv2/v1_1/generator.go rename to internal/v1_1/generator.go index 99ab0f2..10629ca 100644 --- a/internal/flixkitv2/v1_1/generator.go +++ b/internal/v1_1/generator.go @@ -1,4 +1,4 @@ -package flixkit +package v1_1 import ( "context" @@ -37,7 +37,7 @@ type Generator struct { template *InteractionTemplate } -func NewGenerator(contractInfos ContractInfos, logger output.Logger) (*Generator, error) { +func NewTemplateGenerator(contractInfos ContractInfos, logger output.Logger) (*Generator, error) { loader := afero.Afero{Fs: afero.NewOsFs()} gwt, err := gateway.NewGrpcGateway(config.TestnetNetwork) @@ -94,7 +94,7 @@ func NewGenerator(contractInfos ContractInfos, logger output.Logger) (*Generator }, nil } -func (g Generator) GenerateTemplate(ctx context.Context, code string, preFill string) (string, error) { +func (g Generator) CreateTemplate(ctx context.Context, code string, preFill string) (string, error) { g.template = &InteractionTemplate{} g.template.Init() if preFill != "" { @@ -149,7 +149,7 @@ func (g Generator) calculateNetworkPins(program *ast.Program) error { } networkPins := make([]NetworkPin, 0) for _, netName := range networksOfInterest { - cad, err := g.template.GetAndReplaceCadenceImports(netName) + cad, err := g.template.ReplaceCadenceImports(netName) if err != nil { continue } diff --git a/flixkit/generator_test.go b/internal/v1_1/generator_test.go similarity index 88% rename from flixkit/generator_test.go rename to internal/v1_1/generator_test.go index 2fed309..31ffdce 100644 --- a/flixkit/generator_test.go +++ b/internal/v1_1/generator_test.go @@ -1,21 +1,20 @@ -package flixkit +package v1_1 import ( "context" "testing" "github.com/hexops/autogold/v2" - v1_1 "github.com/onflow/flixkit-go/flixkit/v1_1" "github.com/onflow/flixkit-go/internal/contracts" "github.com/onflow/flow-cli/flowkit/config" "github.com/stretchr/testify/assert" ) func TestHelloScript(t *testing.T) { - contracts := []v1_1.Contract{ + contracts := []Contract{ { Contract: "HelloWorld", - Networks: []v1_1.Network{ + Networks: []Network{ { Network: "testnet", Address: "0xee82856bf20e2aa6", @@ -37,6 +36,7 @@ func TestHelloScript(t *testing.T) { testnetClient: nil, mainnetClient: nil, } + assert := assert.New(t) code := ` #interaction( @@ -54,17 +54,17 @@ func TestHelloScript(t *testing.T) { } ` ctx := context.Background() - template, err := generator.Generate(ctx, code, "") + template, err := generator.CreateTemplate(ctx, code, "") assert.NoError(err, "Generate should not return an error") autogold.ExpectFile(t, template) } func TestTransactionValue(t *testing.T) { - contracts := []v1_1.Contract{ + contracts := []Contract{ { Contract: "HelloWorld", - Networks: []v1_1.Network{ + Networks: []Network{ { Network: "testnet", Address: "0xee82856bf20e2aa6", @@ -115,19 +115,19 @@ func TestTransactionValue(t *testing.T) { } ` ctx := context.Background() - template, err := generator.Generate(ctx, code, "") + template, err := generator.CreateTemplate(ctx, code, "") assert.NoError(err, "Generate should not return an error") autogold.ExpectFile(t, template) } func TestTransferFlowTransaction(t *testing.T) { - cs := []v1_1.Contract{} + cs := []Contract{} cc := contracts.GetCoreContracts() for contractName, c := range cc { - contract := v1_1.Contract{ + contract := Contract{ Contract: contractName, - Networks: []v1_1.Network{ + Networks: []Network{ {Network: config.MainnetNetwork.Name, Address: c[config.MainnetNetwork.Name]}, {Network: config.TestnetNetwork.Name, Address: c[config.TestnetNetwork.Name]}, {Network: config.EmulatorNetwork.Name, Address: c[config.EmulatorNetwork.Name]}, @@ -171,7 +171,7 @@ func TestTransferFlowTransaction(t *testing.T) { } ` ctx := context.Background() - template, err := generator.Generate(ctx, code, "") + template, err := generator.CreateTemplate(ctx, code, "") assert.NoError(err, "Generate should not return an error") autogold.ExpectFile(t, template) diff --git a/flixkit/v1_1/testdata/TestEmptyPragmaWithParameter.golden b/internal/v1_1/testdata/TestEmptyPragmaWithParameter.golden similarity index 100% rename from flixkit/v1_1/testdata/TestEmptyPragmaWithParameter.golden rename to internal/v1_1/testdata/TestEmptyPragmaWithParameter.golden diff --git a/flixkit/v1_1/testdata/TestGenerateParametersScripts.golden b/internal/v1_1/testdata/TestGenerateParametersScripts.golden similarity index 100% rename from flixkit/v1_1/testdata/TestGenerateParametersScripts.golden rename to internal/v1_1/testdata/TestGenerateParametersScripts.golden diff --git a/flixkit/testdata/TestHelloScript.golden b/internal/v1_1/testdata/TestHelloScript.golden similarity index 100% rename from flixkit/testdata/TestHelloScript.golden rename to internal/v1_1/testdata/TestHelloScript.golden diff --git a/flixkit/v1_1/testdata/TestParsePragma/Empty.golden b/internal/v1_1/testdata/TestParsePragma/Empty.golden similarity index 100% rename from flixkit/v1_1/testdata/TestParsePragma/Empty.golden rename to internal/v1_1/testdata/TestParsePragma/Empty.golden diff --git a/flixkit/v1_1/testdata/TestParsePragma/Minimum.golden b/internal/v1_1/testdata/TestParsePragma/Minimum.golden similarity index 100% rename from flixkit/v1_1/testdata/TestParsePragma/Minimum.golden rename to internal/v1_1/testdata/TestParsePragma/Minimum.golden diff --git a/flixkit/v1_1/testdata/TestParsePragma/WithParameters.golden b/internal/v1_1/testdata/TestParsePragma/WithParameters.golden similarity index 100% rename from flixkit/v1_1/testdata/TestParsePragma/WithParameters.golden rename to internal/v1_1/testdata/TestParsePragma/WithParameters.golden diff --git a/flixkit/v1_1/testdata/TestParsePragma/WithoutParameters.golden b/internal/v1_1/testdata/TestParsePragma/WithoutParameters.golden similarity index 100% rename from flixkit/v1_1/testdata/TestParsePragma/WithoutParameters.golden rename to internal/v1_1/testdata/TestParsePragma/WithoutParameters.golden diff --git a/flixkit/testdata/TestTransactionValue.golden b/internal/v1_1/testdata/TestTransactionValue.golden similarity index 100% rename from flixkit/testdata/TestTransactionValue.golden rename to internal/v1_1/testdata/TestTransactionValue.golden diff --git a/flixkit/testdata/TestTransferFlowTransaction.golden b/internal/v1_1/testdata/TestTransferFlowTransaction.golden similarity index 100% rename from flixkit/testdata/TestTransferFlowTransaction.golden rename to internal/v1_1/testdata/TestTransferFlowTransaction.golden diff --git a/flixkit/v1_1/types.go b/internal/v1_1/types.go similarity index 99% rename from flixkit/v1_1/types.go rename to internal/v1_1/types.go index 9a593cc..9cea150 100644 --- a/flixkit/v1_1/types.go +++ b/internal/v1_1/types.go @@ -1,4 +1,4 @@ -package flixkit +package v1_1 import ( "bytes" @@ -154,7 +154,7 @@ func replaceImport(code string, from string, to string) string { return code } -func (t *InteractionTemplate) GetAndReplaceCadenceImports(networkName string) (string, error) { +func (t *InteractionTemplate) ReplaceCadenceImports(networkName string) (string, error) { cadence := t.Data.Cadence.Body // Compile regular expression to match and capture contract names re := regexp.MustCompile(`import\s*"([^"]+)"`) diff --git a/internal/flixkitv2/v1_1/types_test.go b/internal/v1_1/types_test.go similarity index 99% rename from internal/flixkitv2/v1_1/types_test.go rename to internal/v1_1/types_test.go index 1bcf41a..fd8a322 100644 --- a/internal/flixkitv2/v1_1/types_test.go +++ b/internal/v1_1/types_test.go @@ -1,4 +1,4 @@ -package flixkit +package v1_1 import ( "encoding/json" @@ -811,7 +811,7 @@ func TestGetAndReplaceCadenceImports(t *testing.T) { t.Fatal(err) } - cadenceCode, err := parsedTemplate.GetAndReplaceCadenceImports(tt.network) + cadenceCode, err := parsedTemplate.ReplaceCadenceImports(tt.network) if tt.wantErr { assert.Error(err, tt.name, "GetCadenceWithReplacedImports should return an error") } else { @@ -829,7 +829,7 @@ func TestGetAndReplaceCadenceImportsMultipleImports(t *testing.T) { if err != nil { t.Fatal(err) } - cadenceCode, err := template.GetAndReplaceCadenceImports("mainnet") + cadenceCode, err := template.ReplaceCadenceImports("mainnet") if err != nil { t.Fatal(err) } @@ -843,7 +843,7 @@ func TestGetAndReplaceCadenceImportsMultipleCoreImports(t *testing.T) { if err != nil { t.Fatal(err) } - cadenceCode, err := template.GetAndReplaceCadenceImports("mainnet") + cadenceCode, err := template.ReplaceCadenceImports("mainnet") if err != nil { t.Fatal(err) } From 5073f3c7697f47d556bc7c32b83728b133672473 Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Tue, 16 Jan 2024 11:26:09 -0600 Subject: [PATCH 13/17] add unit tests, fix issue with creating binding file from template name or id --- flixkit/flix_service.go | 4 +- internal/common.go | 30 +- internal/fcl_binding_creator.go | 2 +- internal/fcl_binding_creator_test.go | 597 ++++++++++++++++++ internal/flix_service.go | 94 +-- internal/flix_service_test.go | 18 +- .../TestBindingReadTokenBalance.golden | 33 + internal/testdata/TestJSGenArrayScript.golden | 28 + internal/testdata/TestJSGenMinScript.golden | 28 + .../testdata/TestJSGenNoParamsScript.golden | 27 + internal/testdata/TestJSGenScript.golden | 29 + internal/testdata/TestJSGenTransaction.golden | 29 + .../testdata/TestTSGenNoParamsScript.golden | 29 + internal/testdata/TestTSGenNoParamsTx.golden | 27 + .../testdata/TestTSGenParamsScript.golden | 33 + internal/testdata/TestTSGenParamsTx.golden | 31 + 16 files changed, 986 insertions(+), 53 deletions(-) create mode 100644 internal/fcl_binding_creator_test.go create mode 100644 internal/testdata/TestBindingReadTokenBalance.golden create mode 100644 internal/testdata/TestJSGenArrayScript.golden create mode 100644 internal/testdata/TestJSGenMinScript.golden create mode 100644 internal/testdata/TestJSGenNoParamsScript.golden create mode 100644 internal/testdata/TestJSGenScript.golden create mode 100644 internal/testdata/TestJSGenTransaction.golden create mode 100644 internal/testdata/TestTSGenNoParamsScript.golden create mode 100644 internal/testdata/TestTSGenNoParamsTx.golden create mode 100644 internal/testdata/TestTSGenParamsScript.golden create mode 100644 internal/testdata/TestTSGenParamsTx.golden diff --git a/flixkit/flix_service.go b/flixkit/flix_service.go index 2519de1..ad516b9 100644 --- a/flixkit/flix_service.go +++ b/flixkit/flix_service.go @@ -8,11 +8,11 @@ import ( type FlixService interface { // GetTemplate returns the raw flix template - GetTemplate(ctx context.Context, templateName string) (string, error) + GetTemplate(ctx context.Context, templateName string) (string, string, error) // GetAndReplaceImports returns the raw flix template with cadence imports replaced GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) // GenerateBinding returns the generated binding given the language - GetTemplateAndCreateBinding(ctx context.Context, templateName string, lang string) (string, error) + GetTemplateAndCreateBinding(ctx context.Context, templateName string, lang string, destFile string) (string, error) } type FlowInteractionTemplateExecution = internal.FlowInteractionTemplateExecution diff --git a/internal/common.go b/internal/common.go index f495bdf..39c9d52 100644 --- a/internal/common.go +++ b/internal/common.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" "net/url" - "os" + "path/filepath" ) func getTemplateVersion(template string) (string, error) { @@ -58,8 +58,11 @@ func isHex(str string) bool { return err == nil } -func isPath(path string) bool { - _, err := os.Stat(path) +func isPath(path string, f FileReader) bool { + if f == nil { + return false + } + _, err := f.ReadFile(path) return err == nil } @@ -68,9 +71,9 @@ func isJson(str string) bool { return json.Unmarshal([]byte(str), &js) == nil } -func getType(s string) flixQueryTypes { +func getType(s string, f FileReader) flixQueryTypes { switch { - case isPath(s): + case isPath(s, f): return flixPath case isHex(s): return flixId @@ -82,3 +85,20 @@ func getType(s string) flixQueryTypes { return flixName } } + +// GetRelativePath computes the relative path from generated file to flix json file. +// This path is used in the binding file to reference the flix json file. +func GetRelativePath(configFile, bindingFile string) (string, error) { + relPath, err := filepath.Rel(filepath.Dir(bindingFile), configFile) + if err != nil { + return "", err + } + + // If the file is in the same directory and doesn't start with "./", prepend it. + if !filepath.IsAbs(relPath) && relPath[0] != '.' { + relPath = "./" + relPath + } + + // Currently binding files are js, we need to convert the path to unix style + return filepath.ToSlash(relPath), nil +} diff --git a/internal/fcl_binding_creator.go b/internal/fcl_binding_creator.go index 80d14d1..115980e 100644 --- a/internal/fcl_binding_creator.go +++ b/internal/fcl_binding_creator.go @@ -39,7 +39,7 @@ func NewFclJSCreator() *FclCreator { } } -func (g *FclCreator) Generate(flixString string, templateLocation string) (string, error) { +func (g *FclCreator) Create(flixString string, templateLocation string) (string, error) { tmpl, err := parseTemplates(g.templates) if err != nil { return "", err diff --git a/internal/fcl_binding_creator_test.go b/internal/fcl_binding_creator_test.go new file mode 100644 index 0000000..76963e0 --- /dev/null +++ b/internal/fcl_binding_creator_test.go @@ -0,0 +1,597 @@ +package internal + +import ( + "encoding/json" + "testing" + + "github.com/hexops/autogold/v2" + "github.com/onflow/flixkit-go/internal/templates" + v1 "github.com/onflow/flixkit-go/internal/v1" + v1_1 "github.com/onflow/flixkit-go/internal/v1_1" + "github.com/stretchr/testify/assert" +) + +var parsedTemplateTX = &v1.FlowInteractionTemplate{ + FType: "InteractionTemplate", + FVersion: "1.0.0", + ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", + Data: v1.Data{ + Type: "transaction", + Interface: "", + Messages: v1.Messages{ + Title: &v1.Title{ + I18N: map[string]string{ + "en-US": "Transfer Tokens", + }, + }, + Description: &v1.Description{ + I18N: map[string]string{ + "en-US": "Transfer tokens from one account to another", + }, + }, + }, + Cadence: "import FungibleToken from 0xFUNGIBLETOKENADDRESS\ntransaction(amount: UFix64, to: Address) {\nlet vault: @FungibleToken.Vault\nprepare(signer: AuthAccount) {\nself.vault <- signer\n.borrow<&{FungibleToken.Provider}>(from: /storage/flowTokenVault)!\n.withdraw(amount: amount)\n}\nexecute {\ngetAccount(to)\n.getCapability(/public/flowTokenReceiver)!\n.borrow<&{FungibleToken.Receiver}>()!\n.deposit(from: <-self.vault)\n}\n}", + Dependencies: v1.Dependencies{ + "0xFUNGIBLETOKENADDRESS": v1.Contracts{ + "FungibleToken": v1.Networks{ + "mainnet": v1.Network{ + Address: "0xf233dcee88fe0abe", + FqAddress: "A.0xf233dcee88fe0abe.FungibleToken", + Contract: "FungibleToken", + Pin: "83c9e3d61d3b5ebf24356a9f17b5b57b12d6d56547abc73e05f820a0ae7d9cf5", + PinBlockHeight: 34166296, + }, + "testnet": v1.Network{ + Address: "0x9a0766d93b6608b7", + FqAddress: "A.0x9a0766d93b6608b7.FungibleToken", + Contract: "FungibleToken", + Pin: "83c9e3d61d3b5ebf24356a9f17b5b57b12d6d56547abc73e05f820a0ae7d9cf5", + PinBlockHeight: 74776482, + }, + }, + }, + }, + Arguments: v1.Arguments{ + "amount": v1.Argument{ + Index: 0, + Type: "UFix64", + Messages: v1.Messages{ + Title: &v1.Title{ + I18N: map[string]string{ + "en-US": "The amount of FLOW tokens to send", + }, + }, + }, + Balance: "", + }, + "to": v1.Argument{ + Index: 1, + Type: "Address", + Messages: v1.Messages{ + Title: &v1.Title{ + I18N: map[string]string{ + "en-US": "The Flow account the tokens will go to", + }, + }, + }, + Balance: "", + }, + }, + }, +} + +var parsedTemplateScript = &v1.FlowInteractionTemplate{ + FType: "InteractionTemplate", + FVersion: "1.0.0", + ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", + Data: v1.Data{ + Type: "script", + Interface: "", + Messages: v1.Messages{ + Title: &v1.Title{ + I18N: map[string]string{ + "en-US": "Multiply Two Integers", + }, + }, + Description: &v1.Description{ + I18N: map[string]string{ + "en-US": "Multiply two numbers to another", + }, + }, + }, + Cadence: "pub fun main(x: Int, y: Int): Int { return x * y }", + Arguments: v1.Arguments{ + "x": v1.Argument{ + Index: 0, + Type: "Int", + Messages: v1.Messages{ + Title: &v1.Title{ + I18N: map[string]string{ + "en-US": "number to be multiplied", + }, + }, + }, + Balance: "", + }, + "y": v1.Argument{ + Index: 1, + Type: "Int", + Messages: v1.Messages{ + Title: &v1.Title{ + I18N: map[string]string{ + "en-US": "second number to be multiplied", + }, + }, + }, + Balance: "", + }, + }, + }, +} + +var ArrayTypeScript = &v1.FlowInteractionTemplate{ + FType: "InteractionTemplate", + FVersion: "1.0.0", + ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", + Data: v1.Data{ + Type: "script", + Interface: "", + Messages: v1.Messages{ + Title: &v1.Title{ + I18N: map[string]string{ + "en-US": "Multiply Numbers", + }, + }, + Description: &v1.Description{ + I18N: map[string]string{ + "en-US": "Multiply numbers in an array", + }, + }, + }, + Cadence: "pub fun main(numbers: [Int]): Int { var total = 1; for x in numbers { total = total * x }; return total }", + Arguments: v1.Arguments{ + "numbers": v1.Argument{ + Index: 0, + Type: "[Int]", + Messages: v1.Messages{ + Title: &v1.Title{ + I18N: map[string]string{ + "en-US": "Array of numbers to be multiplied", + }, + }, + }, + Balance: "", + }, + }, + }, +} + +var minimumTemplate = &v1.FlowInteractionTemplate{ + FType: "InteractionTemplate", + FVersion: "1.0.0", + ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", + Data: v1.Data{ + Type: "script", + Interface: "", + Cadence: "pub fun main(numbers: [Int]): Int { var total = 1; for x in numbers { total = total * x }; return total }", + Arguments: v1.Arguments{ + "numbers": v1.Argument{ + Index: 0, + Type: "[Int]", + }, + }, + }, +} + +var minimumNoParamTemplate = &v1.FlowInteractionTemplate{ + FType: "InteractionTemplate", + FVersion: "1.0.0", + ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", + Data: v1.Data{ + Type: "script", + Interface: "", + Cadence: "pub fun main(): Int { return 1 }", + }, +} + +func TestJSGenTransaction(t *testing.T) { + ttemp, err := json.Marshal(parsedTemplateTX) + assert.NoError(t, err, "marshal template to json should not return an error") + generator := FclCreator{ + templates: []string{ + templates.GetJsFclMainTemplate(), + templates.GetJsFclScriptTemplate(), + templates.GetJsFclTxTemplate(), + templates.GetJsFclParamsTemplate(), + }, + } + got, _ := generator.Create(string(ttemp), "./transfer_token.json") + autogold.ExpectFile(t, got) +} + +func TestJSGenScript(t *testing.T) { + ttemp, err := json.Marshal(parsedTemplateScript) + assert.NoError(t, err, "marshal template to json should not return an error") + + generator := FclCreator{ + templates: []string{ + templates.GetJsFclMainTemplate(), + templates.GetJsFclScriptTemplate(), + templates.GetJsFclTxTemplate(), + templates.GetJsFclParamsTemplate(), + }, + } + assert := assert.New(t) + got, err := generator.Create(string(ttemp), "./multiply_two_integers.template.json") + assert.NoError(err, "ParseTemplate should not return an error") + autogold.ExpectFile(t, got) +} + +func TestJSGenArrayScript(t *testing.T) { + ttemp, err := json.Marshal(ArrayTypeScript) + assert.NoError(t, err, "marshal template to json should not return an error") + + generator := FclCreator{ + templates: []string{ + templates.GetJsFclMainTemplate(), + templates.GetJsFclScriptTemplate(), + templates.GetJsFclTxTemplate(), + templates.GetJsFclParamsTemplate(), + }, + } + assert := assert.New(t) + + out, err := generator.Create(string(ttemp), "./multiply-numbers.template.json") + assert.NoError(err, "ParseTemplate should not return an error") + autogold.ExpectFile(t, out) +} + +func TestJSGenMinScript(t *testing.T) { + ttemp, err := json.Marshal(minimumTemplate) + assert.NoError(t, err, "marshal template to json should not return an error") + + generator := NewFclJSCreator() + assert := assert.New(t) + + out, err := generator.Create(string(ttemp), "./min.template.json") + assert.NoError(err, "ParseTemplate should not return an error") + autogold.ExpectFile(t, out) +} +func TestJSGenNoParamsScript(t *testing.T) { + ttemp, err := json.Marshal(minimumNoParamTemplate) + assert.NoError(t, err, "marshal template to json should not return an error") + + generator := NewFclJSCreator() + assert := assert.New(t) + + out, err := generator.Create(string(ttemp), "./min.template.json") + assert.NoError(err, "ParseTemplate should not return an error") + autogold.ExpectFile(t, out) +} + +var minimumNoParamTemplateTS_SCRIPT = &v1_1.InteractionTemplate{ + FType: "InteractionTemplate", + FVersion: "1.1.0", + ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", + Data: v1_1.Data{ + Type: "script", + Interface: "", + Cadence: v1_1.Cadence{ + Body: "pub fun main(): Int { return 1 }", + NetworkPins: []v1_1.NetworkPin{}, + }, + Output: &v1_1.Parameter{ + Label: "result", + Type: "Int", + Messages: []v1_1.Message{ + { + Key: "description", + I18n: []v1_1.I18n{ + { + Tag: "en-US", + Translation: "Result of some number plus one", + }, + }, + }, + }, + }, + }, +} + +var minimumNoParamTemplateTS_TX = &v1_1.InteractionTemplate{ + FType: "InteractionTemplate", + FVersion: "1.1.0", + ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", + Data: v1_1.Data{ + Type: "transaction", + Interface: "", + Cadence: v1_1.Cadence{ + Body: "import \"HelloWorld\"\n\n#interaction (\n version: \"1.1.0\",\n\ttitle: \"Update Greeting\",\n\tdescription: \"Update the greeting on the HelloWorld contract\",\n\tlanguage: \"en-US\",\n)\ntransaction() {\n\n prepare(acct: AuthAccount) {\n }\n\n execute {\n \n }\n}\n", + NetworkPins: []v1_1.NetworkPin{}, + }, + }, +} + +var minimumParamTemplateTS_SCRIPT = &v1_1.InteractionTemplate{ + FType: "InteractionTemplate", + FVersion: "1.1.0", + ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", + Data: v1_1.Data{ + Type: "script", + Interface: "", + Cadence: v1_1.Cadence{ + Body: "pub fun main(someNumber Int): Int { return 1 + someNumber }", + NetworkPins: []v1_1.NetworkPin{}, + }, + Parameters: []v1_1.Parameter{ + { + Label: "someNumber", + Index: 0, + Type: "Int", + Messages: []v1_1.Message{ + { + Key: "title", + I18n: []v1_1.I18n{ + { + Tag: "en-US", + Translation: "Some Number", + }, + }, + }, + }, + }, + }, + Output: &v1_1.Parameter{ + Label: "result", + Type: "Int", + Messages: []v1_1.Message{ + { + Key: "description", + I18n: []v1_1.I18n{ + { + Tag: "en-US", + Translation: "Result of some number plus one", + }, + }, + }, + }, + }, + }, +} + +var minimumParamTemplateTS_TX = &v1_1.InteractionTemplate{ + FType: "InteractionTemplate", + FVersion: "1.1.0", + ID: "290b6b6222b2a77b16db896a80ddf29ebd1fa3038c9e6625a933fa213fce51fa", + Data: v1_1.Data{ + Type: "transaction", + Interface: "", + Cadence: v1_1.Cadence{ + Body: "import \"HelloWorld\"\n\n#interaction (\n version: \"1.1.0\",\n\ttitle: \"Update Greeting\",\n\tdescription: \"Update the greeting on the HelloWorld contract\",\n\tlanguage: \"en-US\",\n\tparameters: [\n\t\tParameter(\n\t\t\tname: \"greeting\", \n\t\t\ttitle: \"Greeting\", \n\t\t\tdescription: \"The greeting to set on the HelloWorld contract\"\n\t\t)\n\t],\n)\ntransaction(greeting: String) {\n\n prepare(acct: AuthAccount) {\n log(acct.address)\n }\n\n execute {\n HelloWorld.updateGreeting(newGreeting: greeting)\n }\n}\n", + NetworkPins: []v1_1.NetworkPin{}, + }, + Messages: []v1_1.Message{ + { + Key: "title", + I18n: []v1_1.I18n{ + { + Tag: "en-US", + Translation: "Update Greeting", + }, + }, + }, + { + Key: "description", + I18n: []v1_1.I18n{ + { + Tag: "en-US", + Translation: "Update HelloWorld Greeting", + }, + }, + }, + }, + Parameters: []v1_1.Parameter{ + { + Label: "greeting", + Index: 0, + Type: "String", + Messages: []v1_1.Message{ + { + Key: "title", + I18n: []v1_1.I18n{ + { + Tag: "en-US", + Translation: "Greeting", + }, + }, + }, + }, + }, + }, + }, +} + +func TestTSGenNoParamsScript(t *testing.T) { + ttemp, err := json.Marshal(minimumNoParamTemplateTS_SCRIPT) + assert.NoError(t, err, "marshal template to json should not return an error") + + generator := NewFclTSCreator() + assert := assert.New(t) + + out, err := generator.Create(string(ttemp), "./min.template.json") + assert.NoError(err, "ParseTemplate should not return an error") + autogold.ExpectFile(t, out) +} + +func TestTSGenNoParamsTx(t *testing.T) { + ttemp, err := json.Marshal(minimumNoParamTemplateTS_TX) + assert.NoError(t, err, "marshal template to json should not return an error") + + generator := NewFclTSCreator() + assert := assert.New(t) + + out, err := generator.Create(string(ttemp), "./min.template.json") + assert.NoError(err, "ParseTemplate should not return an error") + autogold.ExpectFile(t, out) +} + +func TestTSGenParamsScript(t *testing.T) { + ttemp, err := json.Marshal(minimumParamTemplateTS_SCRIPT) + assert.NoError(t, err, "marshal template to json should not return an error") + + generator := NewFclTSCreator() + assert := assert.New(t) + + out, err := generator.Create(string(ttemp), "./min.template.json") + assert.NoError(err, "ParseTemplate should not return an error") + autogold.ExpectFile(t, out) +} + +func TestTSGenParamsTx(t *testing.T) { + ttemp, err := json.Marshal(minimumParamTemplateTS_TX) + assert.NoError(t, err, "marshal template to json should not return an error") + + generator := NewFclTSCreator() + assert := assert.New(t) + + out, err := generator.Create(string(ttemp), "./min.template.json") + assert.NoError(err, "ParseTemplate should not return an error") + autogold.ExpectFile(t, out) +} + +const ReadTokenScript = ` +{ + "f_type": "InteractionTemplate", + "f_version": "1.1.0", + "id": "29d03aafbbb5a02e0d5f4ffee685c12494915410812305c2858008d3e2902b72", + "data": { + "type": "script", + "interface": "", + "messages": null, + "cadence": { + "body": "import \"FungibleToken\"\nimport \"FlowToken\"\n\npub fun main(address: Address): UFix64 {\n let account = getAccount(address)\n\n let vaultRef = account\n .getCapability(/public/flowTokenBalance)\n .borrow\u003c\u0026FlowToken.Vault{FungibleToken.Balance}\u003e()\n ?? panic(\"Could not borrow balance reference to the Vault\")\n\n return vaultRef.balance\n}\n", + "network_pins": [ + { + "network": "mainnet", + "pin_self": "e0a1c0443b724d1238410c4a05c48441ee974160cad8cf1103c63b6999f81dd5" + }, + { + "network": "testnet", + "pin_self": "6fee459b35d7013a83070c9ac42ea43ee04a3925deca445c34614c1bd6dc4cb8" + } + ] + }, + "dependencies": [ + { + "contracts": [ + { + "contract": "FungibleToken", + "networks": [ + { + "network": "mainnet", + "address": "0xf233dcee88fe0abe", + "dependency_pin_block_height": 69539302, + "dependency_pin": { + "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", + "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0xf233dcee88fe0abe", + "imports": [] + } + }, + { + "network": "testnet", + "address": "0x9a0766d93b6608b7", + "dependency_pin_block_height": 146201102, + "dependency_pin": { + "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", + "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0x9a0766d93b6608b7", + "imports": [] + } + }, + { + "network": "emulator", + "address": "0xee82856bf20e2aa6", + "dependency_pin_block_height": 0 + } + ] + } + ] + }, + { + "contracts": [ + { + "contract": "FlowToken", + "networks": [ + { + "network": "mainnet", + "address": "0x1654653399040a61", + "dependency_pin_block_height": 69539302, + "dependency_pin": { + "pin": "a341e772da413bfbcf43b0fc167bd50a20c9f40baf10e12d3dbc2f5181526de9", + "pin_self": "0e932728b73bff3c09dd58922f2529fc7b7fe7477f1dcc61169bc8f46948ad91", + "pin_contract_name": "FlowToken", + "pin_contract_address": "0x1654653399040a61", + "imports": [ + { + "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", + "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0xf233dcee88fe0abe", + "imports": [] + } + ] + } + }, + { + "network": "testnet", + "address": "0x7e60df042a9c0868", + "dependency_pin_block_height": 146201102, + "dependency_pin": { + "pin": "9cc21a34a01486ebd6f044e99dbcdd58671850f81fcc345d071181c19f61aaa4", + "pin_self": "6f01c7001e2d6635b667a170d3ccbc13659c40d01bb35e56979fcc7fa2d18646", + "pin_contract_name": "FlowToken", + "pin_contract_address": "0x7e60df042a9c0868", + "imports": [ + { + "pin": "ac0208f93d07829ec96584d618ddbec6af3cf4e2866bd5071249e8ec93c7e0dc", + "pin_self": "cdadd5b5897f2dfe35d8b25f4e41fea9f8fca8f40f8a8b506b33701ef5033076", + "pin_contract_name": "FungibleToken", + "pin_contract_address": "0x9a0766d93b6608b7", + "imports": [] + } + ] + } + }, + { + "network": "emulator", + "address": "0x0ae53cb6e3f42a79", + "dependency_pin_block_height": 0 + } + ] + } + ] + } + ], + "parameters": [ + { + "label": "address", + "index": 0, + "type": "Address", + "messages": [] + } + ] + } +} +` + +func TestBindingReadTokenBalance(t *testing.T) { + generator := NewFclTSCreator() + assert := assert.New(t) + + out, err := generator.Create(ReadTokenScript, "./read-token-balance.template.json") + assert.NoError(err, "ParseTemplate should not return an error") + autogold.ExpectFile(t, out) +} diff --git a/internal/flix_service.go b/internal/flix_service.go index 33bc87a..5016c2c 100644 --- a/internal/flix_service.go +++ b/internal/flix_service.go @@ -6,6 +6,7 @@ import ( "io" "log" "net/http" + "strings" v1 "github.com/onflow/flixkit-go/internal/v1" v1_1 "github.com/onflow/flixkit-go/internal/v1_1" @@ -31,8 +32,7 @@ func NewFlixService(config *FlixServiceConfig) flixService { } type flixService struct { - config *FlixServiceConfig - bindingCreator FclCreator + config *FlixServiceConfig } type FlowInteractionTemplateExecution struct { @@ -48,51 +48,53 @@ type FlowInteractionTemplateCadence interface { IsScript() bool } -func (s flixService) GetTemplate(ctx context.Context, flixQuery string) (string, error) { +func (s flixService) GetTemplate(ctx context.Context, flixQuery string) (string, string, error) { var template string + var source string var err error - switch getType(flixQuery) { + switch getType(flixQuery, s.config.FileReader) { case flixId: - template, err = s.getFlixByID(ctx, flixQuery) + template, source, err = s.getFlixByID(ctx, flixQuery) if err != nil { - return "", fmt.Errorf("could not find flix with id %s: %w", flixQuery, err) + return "", source, fmt.Errorf("could not find flix with id %s: %w", flixQuery, err) } case flixName: - template, err = s.getFlix(ctx, flixQuery) + template, source, err = s.getFlix(ctx, flixQuery) if err != nil { - return "", fmt.Errorf("could not find flix with name %s: %w", flixQuery, err) + return "", source, fmt.Errorf("could not find flix with name %s: %w", flixQuery, err) } case flixPath: + source = flixQuery if s.config.FileReader == nil { - return "", fmt.Errorf("file reader not provided") + return "", source, fmt.Errorf("file reader not provided") } file, err := s.config.FileReader.ReadFile(flixQuery) if err != nil { - return "", fmt.Errorf("could not read flix file %s: %w", flixQuery, err) + return "", source, fmt.Errorf("could not read flix file %s: %w", flixQuery, err) } template = string(file) if err != nil { - return "", fmt.Errorf("could not parse flix from file %s: %w", flixQuery, err) + return "", source, fmt.Errorf("could not parse flix from file %s: %w", flixQuery, err) } case flixUrl: - template, err = fetchFlixWithContext(ctx, flixQuery) + template, source, err = fetchFlixWithContext(ctx, flixQuery) if err != nil { - return "", fmt.Errorf("could not parse flix from url %s: %w", flixQuery, err) + return "", source, fmt.Errorf("could not parse flix from url %s: %w", flixQuery, err) } default: - return "", fmt.Errorf("invalid flix query type: %s", flixQuery) + return "", source, fmt.Errorf("invalid flix query type: %s", flixQuery) } - return template, nil + return template, source, nil } func (s flixService) GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) { - template, err := s.GetTemplate(ctx, templateName) + template, _, err := s.GetTemplate(ctx, templateName) if err != nil { return nil, err } @@ -139,55 +141,71 @@ func (s flixService) GetTemplateAndReplaceImports(ctx context.Context, templateN return &execution, nil } -func (s flixService) GetTemplateAndCreateBinding(ctx context.Context, templateName string, lang string) (string, error) { - template, err := s.GetTemplate(ctx, templateName) +func (s flixService) GetTemplateAndCreateBinding(ctx context.Context, templateName string, lang string, destFileLocation string) (string, error) { + template, source, err := s.GetTemplate(ctx, templateName) if err != nil { return "", err } + language := strings.ToLower(lang) + var gen *FclCreator + + switch language { + case "js", "javascript": + gen = NewFclJSCreator() + case "ts", "typescript": + gen = NewFclTSCreator() + default: + return "", fmt.Errorf("language %s not supported", lang) + } - return s.bindingCreator.Generate(template, templateName) -} + relativeTemplateLocation := source + flixType := getType(source, s.config.FileReader) + if flixType == flixPath && destFileLocation != "" { + relativeTemplateLocation, err = GetRelativePath(templateName, destFileLocation) + if err != nil { + return "", err + } + } -func (s flixService) GenerateBinding(ctx context.Context, flixString string, templateLocation string, lang string) (string, error) { - return s.bindingCreator.Generate(flixString, templateLocation) + return gen.Create(template, relativeTemplateLocation) } -func (s flixService) getFlixRaw(ctx context.Context, templateName string) (string, error) { +func (s flixService) getFlixRaw(ctx context.Context, templateName string) (string, string, error) { url := fmt.Sprintf("%s?name=%s", s.config.FlixServerURL, templateName) return fetchFlixWithContext(ctx, url) } -func (s flixService) getFlix(ctx context.Context, templateName string) (string, error) { - template, err := s.getFlixRaw(ctx, templateName) +func (s flixService) getFlix(ctx context.Context, templateName string) (string, string, error) { + template, url, err := s.getFlixRaw(ctx, templateName) if err != nil { - return "", err + return "", url, err } - return template, nil + return template, url, nil } -func (s flixService) getFlixByIDRaw(ctx context.Context, templateID string) (string, error) { +func (s flixService) getFlixByIDRaw(ctx context.Context, templateID string) (string, string, error) { url := fmt.Sprintf("%s/%s", s.config.FlixServerURL, templateID) return fetchFlixWithContext(ctx, url) } -func (s flixService) getFlixByID(ctx context.Context, templateID string) (string, error) { - template, err := s.getFlixByIDRaw(ctx, templateID) +func (s flixService) getFlixByID(ctx context.Context, templateID string) (string, string, error) { + template, url, err := s.getFlixByIDRaw(ctx, templateID) if err != nil { - return "", err + return "", url, err } - return template, nil + return template, url, nil } -func fetchFlixWithContext(ctx context.Context, url string) (string, error) { +func fetchFlixWithContext(ctx context.Context, url string) (template string, templateUrl string, err error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { - return "", err + return "", url, err } resp, err := http.DefaultClient.Do(req) if err != nil { - return "", err + return "", url, err } defer func() { if err := resp.Body.Close(); err != nil { @@ -197,8 +215,8 @@ func fetchFlixWithContext(ctx context.Context, url string) (string, error) { body, err := io.ReadAll(resp.Body) if err != nil { - return "", err + return "", url, err } - - return string(body), nil + template = string(body) + return template, url, err } diff --git a/internal/flix_service_test.go b/internal/flix_service_test.go index 581dac1..33be6e2 100644 --- a/internal/flix_service_test.go +++ b/internal/flix_service_test.go @@ -247,9 +247,10 @@ func TestFetchFlix(t *testing.T) { defer server.Close() ctx := context.Background() - body, err := fetchFlixWithContext(ctx, server.URL) + body, source, err := fetchFlixWithContext(ctx, server.URL) assert.NoError(err, "GetFlix should not return an error") assert.Equal("Hello World", body, "GetFlix should return the correct body") + assert.Equal(server.URL, source, "GetFlix should return the correct source") } type DefaultReader struct{} @@ -267,11 +268,12 @@ func TestGetFlixRaw(t *testing.T) { })) defer server.Close() - flixService := NewFlixService(&FlixServiceConfig{FlixServerURL: server.URL, FileReader: DefaultReader{}}) + flixService := NewFlixService(&FlixServiceConfig{FlixServerURL: server.URL, FileReader: nil}) ctx := context.Background() - body, err := flixService.GetTemplate(ctx, "templateName") + body, source, err := flixService.GetTemplate(ctx, "templateName") assert.NoError(err, "GetFlixByName should not return an error") assert.Equal("Hello World", body, "GetFlixByName should return the correct body") + assert.Equal(server.URL+"?name=templateName", source, "GetFlixByName should return the correct source") } func TestGetFlixFilename(t *testing.T) { @@ -281,13 +283,13 @@ func TestGetFlixFilename(t *testing.T) { rw.Write([]byte(flix_template)) })) defer server.Close() - flixService := NewFlixService(&FlixServiceConfig{FlixServerURL: server.URL, FileReader: DefaultReader{}}) ctx := context.Background() - flix, err := flixService.GetTemplate(ctx, "./templateFileName") + flix, source, err := flixService.GetTemplate(ctx, "./templateFileName") assert.NoError(err, "GetParsedFlixByName should not return an error") assert.NotNil(flix, "GetParsedFlixByName should not return a nil Flix") assert.Equal(flix_template, flix, "GetParsedFlixByName should return the correct Flix") + assert.Equal("./templateFileName", source, "GetParsedFlixByName should return the correct source") } func TestGetFlixByIDRaw(t *testing.T) { @@ -301,9 +303,10 @@ func TestGetFlixByIDRaw(t *testing.T) { flixService := NewFlixService(&FlixServiceConfig{FlixServerURL: server.URL}) ctx := context.Background() - body, err := flixService.GetTemplate(ctx, "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF") + body, source, err := flixService.GetTemplate(ctx, "1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF") assert.NoError(err, "GetFlixByID should not return an error") assert.Equal("Hello World", body, "GetFlixByID should return the correct body") + assert.Equal(server.URL+"/1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF1234567890ABCDEF", source, "GetFlixByID should return the correct source") } func TestGetFlixByID(t *testing.T) { @@ -316,10 +319,11 @@ func TestGetFlixByID(t *testing.T) { flixService := NewFlixService(&FlixServiceConfig{FlixServerURL: server.URL}) ctx := context.Background() - flix, err := flixService.GetTemplate(ctx, "templateID") + flix, source, err := flixService.GetTemplate(ctx, "templateID") assert.NoError(err, "GetParsedFlixByID should not return an error") assert.NotNil(flix, "GetParsedFlixByID should not return a nil Flix") assert.Equal(flix_template, flix, "GetParsedFlixByID should return the correct Flix") + assert.Equal(server.URL+"?name=templateID", source, "GetParsedFlixByID should return the correct source") } func TestTemplateVersion(t *testing.T) { diff --git a/internal/testdata/TestBindingReadTokenBalance.golden b/internal/testdata/TestBindingReadTokenBalance.golden new file mode 100644 index 0000000..68df772 --- /dev/null +++ b/internal/testdata/TestBindingReadTokenBalance.golden @@ -0,0 +1,33 @@ +`/** + This binding file was auto generated based on FLIX template v1.1.0. + Changes to this file might get overwritten. + Note fcl version 1.9.0 or higher is required to use templates. +**/ + +import * as fcl from "@onflow/fcl" +import flixTemplate from "./read-token-balance.template.json" + +interface RequestParams { + address: string; +} + +/** +* request: +* @param string address - +* @returns {Promise} - +*/ +export async function request({address}: RequestParams): Promise { + const info = await fcl.query({ + cadence: "", + template: flixTemplate, + args: (arg, t) => [arg(address, t.Address)] + }); + + return info +} + + + + + +` diff --git a/internal/testdata/TestJSGenArrayScript.golden b/internal/testdata/TestJSGenArrayScript.golden new file mode 100644 index 0000000..38f5441 --- /dev/null +++ b/internal/testdata/TestJSGenArrayScript.golden @@ -0,0 +1,28 @@ +`/** + This binding file was auto generated based on FLIX template v1.0.0. + Changes to this file might get overwritten. + Note fcl version 1.3.0 or higher is required to use templates. +**/ + +import * as fcl from "@onflow/fcl" +import flixTemplate from "./multiply-numbers.template.json" + +/** +* Multiply numbers in an array +* @param {Object} Parameters - parameters for the cadence +* @param {Array} Parameters.numbers - Array of numbers to be multiplied: Int +*/ +export async function multiplyNumbers({numbers}) { + const info = await fcl.query({ + template: flixTemplate, + args: (arg, t) => [arg(numbers, t.Array(t.Int))] + }); + + return info +} + + + + + +` diff --git a/internal/testdata/TestJSGenMinScript.golden b/internal/testdata/TestJSGenMinScript.golden new file mode 100644 index 0000000..9a70473 --- /dev/null +++ b/internal/testdata/TestJSGenMinScript.golden @@ -0,0 +1,28 @@ +`/** + This binding file was auto generated based on FLIX template v1.0.0. + Changes to this file might get overwritten. + Note fcl version 1.3.0 or higher is required to use templates. +**/ + +import * as fcl from "@onflow/fcl" +import flixTemplate from "./min.template.json" + +/** +* +* @param {Object} Parameters - parameters for the cadence +* @param {Array} Parameters.numbers - : Int +*/ +export async function request({numbers}) { + const info = await fcl.query({ + template: flixTemplate, + args: (arg, t) => [arg(numbers, t.Array(t.Int))] + }); + + return info +} + + + + + +` diff --git a/internal/testdata/TestJSGenNoParamsScript.golden b/internal/testdata/TestJSGenNoParamsScript.golden new file mode 100644 index 0000000..3ba6c64 --- /dev/null +++ b/internal/testdata/TestJSGenNoParamsScript.golden @@ -0,0 +1,27 @@ +`/** + This binding file was auto generated based on FLIX template v1.0.0. + Changes to this file might get overwritten. + Note fcl version 1.3.0 or higher is required to use templates. +**/ + +import * as fcl from "@onflow/fcl" +import flixTemplate from "./min.template.json" + +/** +* +* No parameters needed. +*/ +export async function request() { + const info = await fcl.query({ + template: flixTemplate, + + }); + + return info +} + + + + + +` diff --git a/internal/testdata/TestJSGenScript.golden b/internal/testdata/TestJSGenScript.golden new file mode 100644 index 0000000..df9e616 --- /dev/null +++ b/internal/testdata/TestJSGenScript.golden @@ -0,0 +1,29 @@ +`/** + This binding file was auto generated based on FLIX template v1.0.0. + Changes to this file might get overwritten. + Note fcl version 1.3.0 or higher is required to use templates. +**/ + +import * as fcl from "@onflow/fcl" +import flixTemplate from "./multiply_two_integers.template.json" + +/** +* Multiply two numbers to another +* @param {Object} Parameters - parameters for the cadence +* @param {string} Parameters.x - number to be multiplied: Int +* @param {string} Parameters.y - second number to be multiplied: Int +*/ +export async function multiplyTwoIntegers({x, y}) { + const info = await fcl.query({ + template: flixTemplate, + args: (arg, t) => [arg(x, t.Int), arg(y, t.Int)] + }); + + return info +} + + + + + +` diff --git a/internal/testdata/TestJSGenTransaction.golden b/internal/testdata/TestJSGenTransaction.golden new file mode 100644 index 0000000..0d8aeb5 --- /dev/null +++ b/internal/testdata/TestJSGenTransaction.golden @@ -0,0 +1,29 @@ +`/** + This binding file was auto generated based on FLIX template v1.0.0. + Changes to this file might get overwritten. + Note fcl version 1.3.0 or higher is required to use templates. +**/ + +import * as fcl from "@onflow/fcl" +import flixTemplate from "./transfer_token.json" + +/** +* Transfer tokens from one account to another +* @param {Object} Parameters - parameters for the cadence +* @param {string} Parameters.amount - The amount of FLOW tokens to send: UFix64 +* @param {string} Parameters.to - The Flow account the tokens will go to: Address +* @returns {Promise} - returns a promise which resolves to the transaction id +*/ +export async function transferTokens({amount, to}) { + const transactionId = await fcl.mutate({ + template: flixTemplate, + args: (arg, t) => [arg(amount, t.UFix64), arg(to, t.Address)] + }); + + return transactionId +} + + + + +` diff --git a/internal/testdata/TestTSGenNoParamsScript.golden b/internal/testdata/TestTSGenNoParamsScript.golden new file mode 100644 index 0000000..bddb8a0 --- /dev/null +++ b/internal/testdata/TestTSGenNoParamsScript.golden @@ -0,0 +1,29 @@ +`/** + This binding file was auto generated based on FLIX template v1.1.0. + Changes to this file might get overwritten. + Note fcl version 1.9.0 or higher is required to use templates. +**/ + +import * as fcl from "@onflow/fcl" +import flixTemplate from "./min.template.json" + + +/** +* request: +* @returns {Promise} - Result of some number plus one +*/ +export async function request(): Promise { + const info = await fcl.query({ + cadence: "", + template: flixTemplate, + + }); + + return info +} + + + + + +` diff --git a/internal/testdata/TestTSGenNoParamsTx.golden b/internal/testdata/TestTSGenNoParamsTx.golden new file mode 100644 index 0000000..127eda0 --- /dev/null +++ b/internal/testdata/TestTSGenNoParamsTx.golden @@ -0,0 +1,27 @@ +`/** + This binding file was auto generated based on FLIX template v1.1.0. + Changes to this file might get overwritten. + Note fcl version 1.9.0 or higher is required to use templates. +**/ + +import * as fcl from "@onflow/fcl" +import flixTemplate from "./min.template.json" + + +/** +* request: +* @returns {Promise} - Returns a promise that resolves to the transaction ID +*/ +export async function request(): Promise { + const transactionId = await fcl.mutate({ + template: flixTemplate, + + }); + + return transactionId +} + + + + +` diff --git a/internal/testdata/TestTSGenParamsScript.golden b/internal/testdata/TestTSGenParamsScript.golden new file mode 100644 index 0000000..b66c036 --- /dev/null +++ b/internal/testdata/TestTSGenParamsScript.golden @@ -0,0 +1,33 @@ +`/** + This binding file was auto generated based on FLIX template v1.1.0. + Changes to this file might get overwritten. + Note fcl version 1.9.0 or higher is required to use templates. +**/ + +import * as fcl from "@onflow/fcl" +import flixTemplate from "./min.template.json" + +interface RequestParams { + someNumber: string; +} + +/** +* request: +* @param string someNumber - +* @returns {Promise} - Result of some number plus one +*/ +export async function request({someNumber}: RequestParams): Promise { + const info = await fcl.query({ + cadence: "", + template: flixTemplate, + args: (arg, t) => [arg(someNumber, t.Int)] + }); + + return info +} + + + + + +` diff --git a/internal/testdata/TestTSGenParamsTx.golden b/internal/testdata/TestTSGenParamsTx.golden new file mode 100644 index 0000000..eff4484 --- /dev/null +++ b/internal/testdata/TestTSGenParamsTx.golden @@ -0,0 +1,31 @@ +`/** + This binding file was auto generated based on FLIX template v1.1.0. + Changes to this file might get overwritten. + Note fcl version 1.9.0 or higher is required to use templates. +**/ + +import * as fcl from "@onflow/fcl" +import flixTemplate from "./min.template.json" + +interface UpdateGreetingParams { + greeting: string; +} + +/** +* updateGreeting: Update HelloWorld Greeting +* @param string greeting - +* @returns {Promise} - Returns a promise that resolves to the transaction ID +*/ +export async function updateGreeting({greeting}: UpdateGreetingParams): Promise { + const transactionId = await fcl.mutate({ + template: flixTemplate, + args: (arg, t) => [arg(greeting, t.String)] + }); + + return transactionId +} + + + + +` From 45a11e331d9ce416114f9f29075e8d5c0f90c963 Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Tue, 16 Jan 2024 11:50:07 -0600 Subject: [PATCH 14/17] condensed all functionality into one flix service --- flixkit/flix_generator.go | 19 ------------------- flixkit/flix_service.go | 5 ++++- internal/flix_service.go | 26 ++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 20 deletions(-) delete mode 100644 flixkit/flix_generator.go diff --git a/flixkit/flix_generator.go b/flixkit/flix_generator.go deleted file mode 100644 index 7b4b48d..0000000 --- a/flixkit/flix_generator.go +++ /dev/null @@ -1,19 +0,0 @@ -package flixkit - -import ( - "context" - - "github.com/onflow/flixkit-go/internal" -) - -type FlixGenerator interface { - // GenerateTemplate returns the generated raw template - CreateTemplate(ctx context.Context, code string, preFill string) (string, error) -} - -type FlixTemplateGeneratorConfig = internal.FlixTemplateGeneratorConfig - -// NewFlixGenerator returns a new FlixGenerator -func NewFlixTemplateGenerator(conf FlixTemplateGeneratorConfig) FlixGenerator { - return internal.NewFlixTemplateGenerator(conf) -} diff --git a/flixkit/flix_service.go b/flixkit/flix_service.go index ad516b9..8165270 100644 --- a/flixkit/flix_service.go +++ b/flixkit/flix_service.go @@ -13,10 +13,13 @@ type FlixService interface { GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) // GenerateBinding returns the generated binding given the language GetTemplateAndCreateBinding(ctx context.Context, templateName string, lang string, destFile string) (string, error) + // GenerateTemplate returns the generated raw template + CreateTemplate(ctx context.Context, contractInfos ContractInfos, code string, preFill string) (string, error) } type FlowInteractionTemplateExecution = internal.FlowInteractionTemplateExecution - +type ContractInfos = internal.ContractInfos +type NetworkAddressMap = internal.NetworkAddressMap type FlixServiceConfig = internal.FlixServiceConfig func NewFlixService(config *FlixServiceConfig) FlixService { diff --git a/internal/flix_service.go b/internal/flix_service.go index 5016c2c..b40644e 100644 --- a/internal/flix_service.go +++ b/internal/flix_service.go @@ -10,6 +10,7 @@ import ( v1 "github.com/onflow/flixkit-go/internal/v1" v1_1 "github.com/onflow/flixkit-go/internal/v1_1" + "github.com/onflow/flow-cli/flowkit/output" ) type FileReader interface { @@ -19,6 +20,7 @@ type FileReader interface { type FlixServiceConfig struct { FlixServerURL string FileReader FileReader + logger output.Logger } func NewFlixService(config *FlixServiceConfig) flixService { @@ -42,6 +44,16 @@ type FlowInteractionTemplateExecution struct { IsScript bool } +/* +Deployed contracts to network addresses +*/ +type NetworkAddressMap = v1_1.NetworkAddressMap + +/* +contract name associated with network information +*/ +type ContractInfos = v1_1.ContractInfos + type FlowInteractionTemplateCadence interface { ReplaceCadenceImports(templateName string) (string, error) IsTransaction() bool @@ -170,6 +182,20 @@ func (s flixService) GetTemplateAndCreateBinding(ctx context.Context, templateNa return gen.Create(template, relativeTemplateLocation) } +func (s flixService) CreateTemplate(ctx context.Context, deployedContracts ContractInfos, code string, preFill string) (string, error) { + template, _, err := s.GetTemplate(ctx, preFill) + if err != nil { + return "", err + } + var gen *v1_1.Generator + var err2 error + gen, err2 = v1_1.NewTemplateGenerator(deployedContracts, s.config.logger) + if err2 != nil { + return "", err2 + } + return gen.CreateTemplate(ctx, code, template) +} + func (s flixService) getFlixRaw(ctx context.Context, templateName string) (string, string, error) { url := fmt.Sprintf("%s?name=%s", s.config.FlixServerURL, templateName) return fetchFlixWithContext(ctx, url) From d5b59fb326f74505079daea66ffccb29b6f094b5 Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Tue, 16 Jan 2024 13:57:49 -0600 Subject: [PATCH 15/17] tweaks to support empty prefill template --- internal/flix_service.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/flix_service.go b/internal/flix_service.go index b40644e..ce6ea58 100644 --- a/internal/flix_service.go +++ b/internal/flix_service.go @@ -20,7 +20,7 @@ type FileReader interface { type FlixServiceConfig struct { FlixServerURL string FileReader FileReader - logger output.Logger + Logger output.Logger } func NewFlixService(config *FlixServiceConfig) flixService { @@ -62,9 +62,13 @@ type FlowInteractionTemplateCadence interface { func (s flixService) GetTemplate(ctx context.Context, flixQuery string) (string, string, error) { var template string - var source string + source := flixQuery var err error + if flixQuery == "" { + return "", source, fmt.Errorf("flix query cannot be empty") + } + switch getType(flixQuery, s.config.FileReader) { case flixId: template, source, err = s.getFlixByID(ctx, flixQuery) @@ -183,13 +187,10 @@ func (s flixService) GetTemplateAndCreateBinding(ctx context.Context, templateNa } func (s flixService) CreateTemplate(ctx context.Context, deployedContracts ContractInfos, code string, preFill string) (string, error) { - template, _, err := s.GetTemplate(ctx, preFill) - if err != nil { - return "", err - } + template, _, _ := s.GetTemplate(ctx, preFill) var gen *v1_1.Generator var err2 error - gen, err2 = v1_1.NewTemplateGenerator(deployedContracts, s.config.logger) + gen, err2 = v1_1.NewTemplateGenerator(deployedContracts, s.config.Logger) if err2 != nil { return "", err2 } From b926de9888bd2709db513660a84f7643846564ab Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Wed, 17 Jan 2024 09:37:17 -0600 Subject: [PATCH 16/17] addressed PR comments --- flixkit/flix_service.go | 6 ++++++ internal/common.go | 4 ++-- internal/flix_generator.go | 35 ----------------------------------- internal/flix_service.go | 10 ++++++---- 4 files changed, 14 insertions(+), 41 deletions(-) delete mode 100644 internal/flix_generator.go diff --git a/flixkit/flix_service.go b/flixkit/flix_service.go index 8165270..4f0e3ac 100644 --- a/flixkit/flix_service.go +++ b/flixkit/flix_service.go @@ -17,11 +17,17 @@ type FlixService interface { CreateTemplate(ctx context.Context, contractInfos ContractInfos, code string, preFill string) (string, error) } +// FlowInteractionTemplateCadence is the interface returned from Replacing imports, it provides helper methods to assist in executing the resulting Cadence. type FlowInteractionTemplateExecution = internal.FlowInteractionTemplateExecution + +// ContractInfos is an input into generating a template, it is a map of contract name to network information of deployed contracts of the source Cadence code. type ContractInfos = internal.ContractInfos type NetworkAddressMap = internal.NetworkAddressMap + +// FlixServiceConfig is the configuration for the FlixService that provides a override for FlixServerURL and default values for FileReader and Logger. type FlixServiceConfig = internal.FlixServiceConfig +// NewFlixService returns a new FlixService given a FlixServiceConfig func NewFlixService(config *FlixServiceConfig) FlixService { return internal.NewFlixService(config) } diff --git a/internal/common.go b/internal/common.go index 39c9d52..ea70917 100644 --- a/internal/common.go +++ b/internal/common.go @@ -86,9 +86,9 @@ func getType(s string, f FileReader) flixQueryTypes { } } -// GetRelativePath computes the relative path from generated file to flix json file. +// getRelativePath computes the relative path from generated file to flix json file. // This path is used in the binding file to reference the flix json file. -func GetRelativePath(configFile, bindingFile string) (string, error) { +func getRelativePath(configFile, bindingFile string) (string, error) { relPath, err := filepath.Rel(filepath.Dir(bindingFile), configFile) if err != nil { return "", err diff --git a/internal/flix_generator.go b/internal/flix_generator.go deleted file mode 100644 index 5259036..0000000 --- a/internal/flix_generator.go +++ /dev/null @@ -1,35 +0,0 @@ -package internal - -import ( - "context" - - v1_1 "github.com/onflow/flixkit-go/internal/v1_1" - "github.com/onflow/flow-cli/flowkit/output" -) - -type flixGenerator struct { - generator v1_1.Generator - fileReader FileReader -} - -type FlixTemplateGeneratorConfig struct { - FileReader FileReader - deployedContracts v1_1.ContractInfos - logger output.Logger -} - -func NewFlixTemplateGenerator(conf FlixTemplateGeneratorConfig) flixGenerator { - var gen, err = v1_1.NewTemplateGenerator(conf.deployedContracts, conf.logger) - if err != nil { - panic(err) - } - - return flixGenerator{ - generator: *gen, - fileReader: conf.FileReader, - } -} - -func (s flixGenerator) CreateTemplate(ctx context.Context, code string, preFill string) (string, error) { - return s.generator.CreateTemplate(ctx, code, preFill) -} diff --git a/internal/flix_service.go b/internal/flix_service.go index ce6ea58..ed55ceb 100644 --- a/internal/flix_service.go +++ b/internal/flix_service.go @@ -54,7 +54,7 @@ contract name associated with network information */ type ContractInfos = v1_1.ContractInfos -type FlowInteractionTemplateCadence interface { +type flowInteractionTemplateCadence interface { ReplaceCadenceImports(templateName string) (string, error) IsTransaction() bool IsScript() bool @@ -101,7 +101,9 @@ func (s flixService) GetTemplate(ctx context.Context, flixQuery string) (string, if err != nil { return "", source, fmt.Errorf("could not parse flix from url %s: %w", flixQuery, err) } - + case flixJson: + template = flixQuery + source = "json" default: return "", source, fmt.Errorf("invalid flix query type: %s", flixQuery) } @@ -120,7 +122,7 @@ func (s flixService) GetTemplateAndReplaceImports(ctx context.Context, templateN if err != nil { return nil, fmt.Errorf("invalid flix template version, %w", err) } - var replaceableCadence FlowInteractionTemplateCadence + var replaceableCadence flowInteractionTemplateCadence switch ver { case "1.1.0": replaceableCadence, err = v1_1.ParseFlix(template) @@ -177,7 +179,7 @@ func (s flixService) GetTemplateAndCreateBinding(ctx context.Context, templateNa relativeTemplateLocation := source flixType := getType(source, s.config.FileReader) if flixType == flixPath && destFileLocation != "" { - relativeTemplateLocation, err = GetRelativePath(templateName, destFileLocation) + relativeTemplateLocation, err = getRelativePath(templateName, destFileLocation) if err != nil { return "", err } From fcc06d7437dfcdf1c1f40d2af56b2e84de94dc1d Mon Sep 17 00:00:00 2001 From: Tom Haile Date: Wed, 17 Jan 2024 10:42:38 -0600 Subject: [PATCH 17/17] update readme to reflex code changes --- README.md | 129 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 1602b64..2681cfd 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # FlixKit -> FlixKit is a Go package that provides functionalities for interacting with Flow Interaction Templates (aka FLIX). Please note that this package is currently in alpha and may undergo significant changes. +> FlixKit is a Go package that provides functionalities for interacting with Flow Interaction Templates (aka FLIX). This package supports generating v1.1 FLIX template json, creating binding files for v1.0 and v1.1 and replacing import for v1.0 and v1.1. More information about FLIX [FLIX FLIP](https://github.com/onflow/flips/blob/main/application/20230330-interaction-templates-1.1.0.md) The `flixkit` package is a Go module designed to interact with Flow Interaction Templates (FLIX). It allows users to fetch, parse, generate and create binding files for Flow interaction templates aka FLIX, aka Verified Interactions. ## Structures -See FLIP that descibes json structure, [Here](https://github.com/onflow/flips/blob/main/application/20230330-interaction-templates-1.1.0.md) current version is v1.1.0 +See FLIP that describes json structure, [Here](https://github.com/onflow/flips/blob/main/application/20230330-interaction-templates-1.1.0.md) current version is v1.1.0 This package provides three functionalities. - Getting network specific Cadence from FLIX @@ -15,28 +15,41 @@ This package provides three functionalities. The package also defines the following interfaces: -- `FlixService`: This interface defines methods to interact with the FLIX service such as fetching raw data or parsed data by template name or template ID. -- `Generator`: This interface generates FLIX json given Cadence, metadata can be provided in two ways: - - prefilled out json - - Cadence docs in the form of a pragma -- `Bindings`: This interface has two implementations for javascript and typescript using fcl +- `FlixService`: This interface defines methods to fetch templates using template name or template id (from flix service), URL or local file path. + +### Methods +```go +// GetTemplate returns the raw flix template +GetTemplate(ctx context.Context, templateName string) (string, string, error) +// GetAndReplaceImports returns the raw flix template with cadence imports replaced +GetTemplateAndReplaceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error) +// GenerateBinding returns the generated binding given the language +GetTemplateAndCreateBinding(ctx context.Context, templateName string, lang string, destFile string) (string, error) +// GenerateTemplate returns the generated raw template +CreateTemplate(ctx context.Context, contractInfos ContractInfos, code string, preFill string) (string, error) +``` ## Usage -The package provides a `FlixService` interface with a constructor function `NewFlixService(config *Config)`. `Config` contains `FlixServerURL` which should be provided. If no URL is provided, it defaults to `"https://flix.flow.com/v1/templates"`. +The package provides a `FlixService` interface with a constructor function `NewFlixService(config *FlixServiceConfig)`. `FlixServiceConfig` +contains + - `FlixServerURL` which is defaulted to `"https://flix.flow.com/v1/templates"`. User can provide their own service url endpoint + - `FileReader` which is used to read local FLIX json template files + - `Logger` which is used in creating `flowkit.NewFlowkit` for FLIX template generation The `FlixService` interface provides the following methods: -- `GetTemplate(ctx context.Context, templateName string) (string, error)`: Fetches template and returns as a string. -- `GetAndReplaceCadenceImports(ctx context.Context, templateName string, network string) (*FlowInteractionTemplateExecution, error)`: Fetches and parses a Flix template and provides the cadence for the network provided. +- `GetTemplate`: Fetches template and returns as a string. +- `GetTemplateAndReplaceImports` returns `FlowInteractionTemplateExecution`: Fetches and parses a Flix template and provides the cadence for the network provided. There are two helper methods to assist in determining if the Cadence is a transaction or a script. -- Note: `templateName` parameter can be the id or name of a template from the interactive template service. A local file or url to the FLIX json file. +- Note: `templateName` parameter can be the id or name of a template from the interactive template service. A local file or url to the FLIX json file or the template string itself. Result form GetAndReplaceCadenceImports is a `FlowInteractionTemplateExecution` instance also provides the following methods: - `IsScript() bool`: Checks if the template is of type "script". - `IsTransaction() bool`: Checks if the template is of type "transaction". -- `GetAndReplaceCadenceImports(networkName string) (string, error)`: Replaces cadence import statements in the cadence script or transaction with their respective network addresses. +- `Cadence`: Replaced cadence with respective network addresses. +- `Network`: Name of network used to get import addresses ## Examples @@ -58,53 +71,61 @@ Note: Remember to replace "transfer-flow" with the actual name of the template y To read more about Flow Interaction Templates, [see the docs](https://developers.flow.com/tooling/fcl-js/interaction-templates). -## Bindings +## Binding Files -> Binding files are code files that bind consuming code with FLIX. The `bindings` module in Flixkit generates code that calls the FLIX cadence code. FLIX cadence is primarily transactions and scripts. +> Binding files are client code files used to call Cadence contracts using the scripts or transactions in a FLIX. These client files can be created given a FLIX, currently TypeScript and JavaScript are supported. ### Usage The `bindings` module has two public methods `Generate` and `NewFclJSGenerator`. `FclJSGenerator` takes a template directory. `bindings` has fcl-js templates. - - - `NewFclJSGenerator() *FclJSGenerator` // uses default fcl-js vanilla javascript - - `Generate(template string, templateLocation string) (string, error)` // flix is the hydrated template struct, templateLocation is the file location of the flix json file, isLocal is a flag that indicates if the template is local or on remote server - -### Example - ```go +flixService := flixkit.NewFlixService(&flixkit.Config{ + FileReader: myFileReader +}) -// uses default fcl-js templates -fclJsGen := flixkit.NewFclJSGenerator() - -output, err := fclJsGen.Generate(template, flixQuery, isLocal) +binding, err := flixService.GetTemplateAndCreateBinding(context.Background(), "transfer-flow", "js", "./bindingFiles/transferFlow.js") if err != nil { log.Fatal(err) } -// output is the javascript binding code -fmt.Println(output]) +fmt.Println(binding) +``` +```go +GetTemplateAndCreateBinding(ctx context.Context, templateName string, lang string, destFile string) (string, error) ``` -## Generate + - `templateName` value can be template name, template id, url or local file. + - `lang` values supported are "js", "javascript", "ts", "typescript" + - `destFile` is the location of the destination binding file, this is used to create the relative path if the template is local. If the template is a template name, template id or url `destFile` isn't used -> Generate creates the newest ratified version of FLIX, as of this update, v1.1 has been passed. Version 1.0.0 will be supported with `FlixService` and `bindings`. +## Generate Templates -- `deployedContracts` is an array of v1_1.Contract structs of the contract dependencies the Cadence code depends on, Core contracts are already configured, look in `internal/contracts/core.go` for details +> CreateTemplate creates the newest ratified version of FLIX, as of this update, see link to FLIP Flip above for more information. +```go + CreateTemplate(ctx context.Context, contractInfos ContractInfos, code string, preFill string) (string, error) +``` -### Example +### Usage ```go -generator, err := flixkit.NewGenerator(deployedContracts, logger output.Logger) -// preFilledTemplate is a partially populated flix template with human readable messages -// see FLIX flip for details -prettyJSON, err := generator.Generate(ctx, string(code), preFilledTemplate) +flixService := flixkit.NewFlixService(&flixkit.Config{ + FileReader: myFileReader, + Logger: myLogger, +}) + +prettyJSON, err := flixService.CreateTemplate(ctx, depContracts, string(code), preFilled) fmt.Println(prettyJSON) ``` +- `contractInfos` is an array of v1_1.Contract struct. This provides the network information about the deployed contracts that are dependencies in the FLIX Cadence code. +- `code` is the actual Cadence code the template is based on +- `preFilled` is a partially filled out FLIX template. This can be a template name, template id, url or local file. Alternatively to using a prefilled template, the Cadence itself can provide metadata using a FLIX specific Cadence pragma, more on that below, [See Cadence Doc Flip](https://github.com/onflow/flips/blob/main/application/20230406-interaction-template-cadence-doc.md) + + ### Cadence docs pragma -> Using Cadence pragma the metadata can live with the Cadence code. Therefore a prefilled template isn't necessary +> Using Cadence pragma the metadata can exist along with the Cadence code. Therefore a prefilled template isn't necessary ### Example @@ -114,18 +135,6 @@ fmt.Println(prettyJSON) title: "Transfer Flow", description: "Transfer Flow to account", language: "en-US", - parameters: [ - Parameter( - name: "amount", - title: "Amount", - description: "Amount of Flow to transfer" - ), - Parameter( - name: "to", - title: "Reciever", - description: "Destination address to receive Flow Tokens" - ) - ], ) import "FlowToken" @@ -139,4 +148,28 @@ fmt.Println(prettyJSON) ``` -The pragma describes the transaction parameters and reason for the transaction. \ No newline at end of file +A `pragma` gives special instruction or processors, in this case FLIX specific information that describes the transaction or script. + +Notice: Nested structures in Cadence pragma will be supported in future, this will allow describing parameters +```go +... +#interaction( + version: "1.1.0", + title: "Transfer Flow", + description: "Transfer Flow to account", + language: "en-US", + parameters: [ + Parameter( + name: "amount", + title: "Amount", + description: "Amount of Flow to transfer" + ), + Parameter( + name: "to", + title: "Reciever", + description: "Destination address to receive Flow Tokens" + ) + ], + ) +... +``` \ No newline at end of file