Steps taken to generate a potential codon list for a given protein sequence:

First we need a codon table with the frequencies that we prefer. The corresponding data frame has to have the following columns: “AA”, “codon”, “relfreq_adj”, in which the relfreq_adj is a number from 0 to 1 that indicates the relative frequency of codons. If there is only one codon per aminoacid, this frequency would be 1.

To do this, we will recover the table from a text file (in the same folder as the script)

codons_table_selection <- read.delim(file="codon_table_cryptococcus_selection20230305.txt", stringsAsFactors = F, header = T, sep="\t")
codons_table_selection <- codons_table_selection[order(codons_table_selection$AA), ]
print(codons_table_selection)

The sequence of the protein for which we would like to obtain the corresponding coding sequence as a long character string, for example “AAAGGTLLLLLSTTVW”

myprot_seq_raw <- "MAAAGGCTLLLAAAASTTTW
              QAQQQQQTTTTWWWLLLLL"
# if the input is in a FASTA-like format, we need to remove the end of line characters and spaces
myprot_seq_clean <- gsub("[\r\n ]", "", myprot_seq_raw)

An accessory function generate_list_codons creates a list of codons for a given amino acid that has exactly the number of codons as how many times the aminoacid is found in the protein sequence. For example, for the AAAAGGG sequence, we can obtain a list of 4 codons for “Ala” and 3 codons for “Gly”. The codons are chosen randomly from the provided table of selected codons and their relative frequencies.

generate_list_codons <- function(aminoacid, codontable, aanumbers){
  #creates a list that has, for each amino acid a sublist of codons
  #that were obtained from the distribution that using the frequencies
  #present in codontable
  # - - - aminoacid is a single character
  # - - - codontable is a table with the columns "AA", "codon", "relfreq_adj"
  #"relfreq_adj" is the relative frequency for the codons chosen to be "OK"
  #- - - aanumbers is a table in which, to each aminoacid corresponds the number of
  #aminoacids present in a given protein sequence
  aarows <- which(codontable$AA==aminoacid)
  aanumberrows <- which(aanumbers$protein_aa == aminoacid)
  aacodons <- codontable[aarows, "codon"]
  aafreqs <- codontable[aarows, "relfreq_adj"]

  return(sample(aacodons, prob=aafreqs, 
                replace=T, size=aanumbers$Freq[aanumberrows]))
}

To test the function we need to transform the protein sequence in a table of frequencies:

protein_aa <- unlist(strsplit(myprot_seq_clean, split=""))
aanumbers <- as.data.frame(table(protein_aa), stringsAsFactors=F)
print(aanumbers)
# test the function:
generate_list_codons("A", codons_table_selection, aanumbers)
[1] "GCC" "GCC" "GCT" "GCT" "GCT" "GCT" "GCC" "GCC"
generate_list_codons("Q", codons_table_selection, aanumbers)
[1] "CAG" "CAG" "CAG" "CAG" "CAG" "CAG"
#for "Q" there is only one codon in the table, so all the
#codons in the list are identical: "CAG".

The second defined function takes a “clean” protein sequence and the codon frequencies table and uses the first function to create, for each amino acid, a list of codons. Then it combines these lists of codons in a DNA sequence, which is the output of the function.


protein_to_codons <- function(proteinseq, codons_table_in_shape){
  #generates a data frame that contains the initial protein sequence
  #and a proposed succession of codons
  #uses the function "generate_list_codons"
  #the input is a string of characters "proteinseq" and a table of codon frequencies
  #in the shape used by the "generate_list_codons" function.
  set.seed(550) #for reproducible generation of runs of codons for identical protein sequences
  protein_aa <- unlist(strsplit(proteinseq, split=""))
  aanumbers <- as.data.frame(table(protein_aa), stringsAsFactors=F)
  #the obtained df has two columns: "protein_aa" and "Freq"
  #it is already alphabetically sorted
  aacodons <- lapply(aanumbers$protein_aa, 
                     FUN=generate_list_codons, 
                     codons_table_in_shape, 
                     aanumbers)
  #we get the list of codons for each amino acid in a list of lists.
  aa_alphabetical <- protein_aa[order(protein_aa)]
  aa_long <- data.frame(aa=aa_alphabetical, codons=unlist(aacodons))
  #aa_long corresponds to a data frame in which the "aa" column corresponds to aminoacids
  #and the "codons" column to the corresonding codons.
  protein_aa.df <- as.data.frame(x = protein_aa, stringsAsFactors=F)
  #protein_aa.df contains the aminoacids in their order in the initial sequence
  protein_aa.df$idx <- as.integer(row.names(protein_aa.df))
  #define an index for each aminoacid to be able to put them in order at the end
  protein_aa.df <- protein_aa.df[order(protein_aa.df$protein_aa), ]
  #sort the data frame alphabetically, so that it is easy to merge it with the other table
  aatocodons.df <- as.data.frame(cbind(protein_aa.df, aa_long))
  #then reorder the final data frame according to the original order of the aminoacids
  return(aatocodons.df[order(aatocodons.df$idx), ])
}

Now, we can test the output of the function for the short sequence myprot_seq_clean and check if the results are about right. It would be much better to have a way to test this output automatically, add error correction and exception handling. Also, it would be good to modify the code in such a way as to avoid repeats and low complexity regions. For version 2…


test <- protein_to_codons(myprot_seq_clean, as.data.frame(codons_table_selection))
print(test[, c("aa", "codons")])

To manually “adjust” codons, one can save the obtained data frame in a tabular format. Otherwise, to obtain just the sequence (and check that it is OK by translating it in ApE):

print(unlist(splitted), quote=F)
[1] ATGGCCGCCGCCGGTGGCTGCACCCTCCTCCTTGCCGCCGCTGCCTCCACCACCACTTGGCAGGCCCAGCAGCAGCAGCAGACCACTACTACTTGGTGGT
[2] GGCTTCTCCTTCTTCTT                                                                                   
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayBmb3IgY29kb24gb3B0aW1pemVkIHNlcXVlbmNlcyBmcm9tIHByb3RlaW5zIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIGh0bWxfZG9jdW1lbnQ6IAogICAga2VlcF9tZDogeWVzCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0Ci0tLQoKU3RlcHMgdGFrZW4gdG8gZ2VuZXJhdGUgYSBwb3RlbnRpYWwgY29kb24gbGlzdCBmb3IgYSBnaXZlbiBwcm90ZWluIHNlcXVlbmNlOgoKRmlyc3Qgd2UgbmVlZCBhIGNvZG9uIHRhYmxlIHdpdGggdGhlIGZyZXF1ZW5jaWVzIHRoYXQgd2UgcHJlZmVyLiBUaGUgY29ycmVzcG9uZGluZyBkYXRhIGZyYW1lIGhhcyB0byBoYXZlIHRoZSBmb2xsb3dpbmcgY29sdW1uczogIkFBIiwgImNvZG9uIiwgInJlbGZyZXFfYWRqIiwgaW4gd2hpY2ggdGhlIHJlbGZyZXFfYWRqIGlzIGEgbnVtYmVyIGZyb20gMCB0byAxIHRoYXQgaW5kaWNhdGVzIHRoZSByZWxhdGl2ZSBmcmVxdWVuY3kgb2YgY29kb25zLiBJZiB0aGVyZSBpcyBvbmx5IG9uZSBjb2RvbiBwZXIgYW1pbm9hY2lkLCB0aGlzIGZyZXF1ZW5jeSB3b3VsZCBiZSAxLgoKVG8gZG8gdGhpcywgd2Ugd2lsbCByZWNvdmVyIHRoZSB0YWJsZSBmcm9tIGEgdGV4dCBmaWxlIChpbiB0aGUgc2FtZSBmb2xkZXIgYXMgdGhlIHNjcmlwdCkKYGBge3J9CmNvZG9uc190YWJsZV9zZWxlY3Rpb24gPC0gcmVhZC5kZWxpbShmaWxlPSJjb2Rvbl90YWJsZV9jcnlwdG9jb2NjdXNfc2VsZWN0aW9uMjAyMzAzMDUudHh0Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEYsIGhlYWRlciA9IFQsIHNlcD0iXHQiKQpjb2RvbnNfdGFibGVfc2VsZWN0aW9uIDwtIGNvZG9uc190YWJsZV9zZWxlY3Rpb25bb3JkZXIoY29kb25zX3RhYmxlX3NlbGVjdGlvbiRBQSksIF0KcHJpbnQoY29kb25zX3RhYmxlX3NlbGVjdGlvbikKYGBgCgoKVGhlIHNlcXVlbmNlIG9mIHRoZSBwcm90ZWluIGZvciB3aGljaCB3ZSB3b3VsZCBsaWtlIHRvIG9idGFpbiB0aGUgY29ycmVzcG9uZGluZyBjb2Rpbmcgc2VxdWVuY2UgYXMgYSBsb25nIGNoYXJhY3RlciBzdHJpbmcsIGZvciBleGFtcGxlICJBQUFHR1RMTExMTFNUVFZXIgoKYGBge3J9Cm15cHJvdF9zZXFfcmF3IDwtICJNQUFBR0dDVExMTEFBQUFTVFRUVwogICAgICAgICAgICAgIFFBUVFRUVFUVFRUV1dXTExMTEwiCiMgaWYgdGhlIGlucHV0IGlzIGluIGEgRkFTVEEtbGlrZSBmb3JtYXQsIHdlIG5lZWQgdG8gcmVtb3ZlIHRoZSBlbmQgb2YgbGluZSBjaGFyYWN0ZXJzIGFuZCBzcGFjZXMKbXlwcm90X3NlcV9jbGVhbiA8LSBnc3ViKCJbXHJcbiBdIiwgIiIsIG15cHJvdF9zZXFfcmF3KQpgYGAKCgpBbiBhY2Nlc3NvcnkgZnVuY3Rpb24gYGdlbmVyYXRlX2xpc3RfY29kb25zYCBjcmVhdGVzIGEgbGlzdCBvZiBjb2RvbnMgZm9yIGEgZ2l2ZW4gYW1pbm8gYWNpZCB0aGF0IGhhcyBleGFjdGx5IHRoZSBudW1iZXIgb2YgY29kb25zIGFzIGhvdyBtYW55IHRpbWVzIHRoZSBhbWlub2FjaWQgaXMgZm91bmQgaW4gdGhlIHByb3RlaW4gc2VxdWVuY2UuIEZvciBleGFtcGxlLCBmb3IgdGhlIEFBQUFHR0cgc2VxdWVuY2UsIHdlIGNhbiBvYnRhaW4gYSBsaXN0IG9mIDQgY29kb25zIGZvciAiQWxhIiBhbmQgMyBjb2RvbnMgZm9yICJHbHkiLiBUaGUgY29kb25zIGFyZSBjaG9zZW4gcmFuZG9tbHkgZnJvbSB0aGUgcHJvdmlkZWQgdGFibGUgb2Ygc2VsZWN0ZWQgY29kb25zIGFuZCB0aGVpciByZWxhdGl2ZSBmcmVxdWVuY2llcy4KCmBgYHtyfQpnZW5lcmF0ZV9saXN0X2NvZG9ucyA8LSBmdW5jdGlvbihhbWlub2FjaWQsIGNvZG9udGFibGUsIGFhbnVtYmVycyl7CiAgI2NyZWF0ZXMgYSBsaXN0IHRoYXQgaGFzLCBmb3IgZWFjaCBhbWlubyBhY2lkIGEgc3VibGlzdCBvZiBjb2RvbnMKICAjdGhhdCB3ZXJlIG9idGFpbmVkIGZyb20gdGhlIGRpc3RyaWJ1dGlvbiB0aGF0IHVzaW5nIHRoZSBmcmVxdWVuY2llcwogICNwcmVzZW50IGluIGNvZG9udGFibGUKICAjIC0gLSAtIGFtaW5vYWNpZCBpcyBhIHNpbmdsZSBjaGFyYWN0ZXIKICAjIC0gLSAtIGNvZG9udGFibGUgaXMgYSB0YWJsZSB3aXRoIHRoZSBjb2x1bW5zICJBQSIsICJjb2RvbiIsICJyZWxmcmVxX2FkaiIKICAjInJlbGZyZXFfYWRqIiBpcyB0aGUgcmVsYXRpdmUgZnJlcXVlbmN5IGZvciB0aGUgY29kb25zIGNob3NlbiB0byBiZSAiT0siCiAgIy0gLSAtIGFhbnVtYmVycyBpcyBhIHRhYmxlIGluIHdoaWNoLCB0byBlYWNoIGFtaW5vYWNpZCBjb3JyZXNwb25kcyB0aGUgbnVtYmVyIG9mCiAgI2FtaW5vYWNpZHMgcHJlc2VudCBpbiBhIGdpdmVuIHByb3RlaW4gc2VxdWVuY2UKICBhYXJvd3MgPC0gd2hpY2goY29kb250YWJsZSRBQT09YW1pbm9hY2lkKQogIGFhbnVtYmVycm93cyA8LSB3aGljaChhYW51bWJlcnMkcHJvdGVpbl9hYSA9PSBhbWlub2FjaWQpCiAgYWFjb2RvbnMgPC0gY29kb250YWJsZVthYXJvd3MsICJjb2RvbiJdCiAgYWFmcmVxcyA8LSBjb2RvbnRhYmxlW2Fhcm93cywgInJlbGZyZXFfYWRqIl0KCiAgcmV0dXJuKHNhbXBsZShhYWNvZG9ucywgcHJvYj1hYWZyZXFzLCAKICAgICAgICAgICAgICAgIHJlcGxhY2U9VCwgc2l6ZT1hYW51bWJlcnMkRnJlcVthYW51bWJlcnJvd3NdKSkKfQoKYGBgCgpUbyB0ZXN0IHRoZSBmdW5jdGlvbiB3ZSBuZWVkIHRvIHRyYW5zZm9ybSB0aGUgcHJvdGVpbiBzZXF1ZW5jZSBpbiBhIHRhYmxlIG9mIGZyZXF1ZW5jaWVzOgoKYGBge3J9CnByb3RlaW5fYWEgPC0gdW5saXN0KHN0cnNwbGl0KG15cHJvdF9zZXFfY2xlYW4sIHNwbGl0PSIiKSkKYWFudW1iZXJzIDwtIGFzLmRhdGEuZnJhbWUodGFibGUocHJvdGVpbl9hYSksIHN0cmluZ3NBc0ZhY3RvcnM9RikKcHJpbnQoYWFudW1iZXJzKQojIHRlc3QgdGhlIGZ1bmN0aW9uOgpnZW5lcmF0ZV9saXN0X2NvZG9ucygiQSIsIGNvZG9uc190YWJsZV9zZWxlY3Rpb24sIGFhbnVtYmVycykKZ2VuZXJhdGVfbGlzdF9jb2RvbnMoIlEiLCBjb2RvbnNfdGFibGVfc2VsZWN0aW9uLCBhYW51bWJlcnMpCiNmb3IgIlEiIHRoZXJlIGlzIG9ubHkgb25lIGNvZG9uIGluIHRoZSB0YWJsZSwgc28gYWxsIHRoZQojY29kb25zIGluIHRoZSBsaXN0IGFyZSBpZGVudGljYWw6ICJDQUciLgpgYGAKVGhlIHNlY29uZCBkZWZpbmVkIGZ1bmN0aW9uIHRha2VzIGEgImNsZWFuIiBwcm90ZWluIHNlcXVlbmNlIGFuZCB0aGUgY29kb24gZnJlcXVlbmNpZXMgdGFibGUgYW5kIHVzZXMgdGhlIGZpcnN0IGZ1bmN0aW9uIHRvIGNyZWF0ZSwgZm9yIGVhY2ggYW1pbm8gYWNpZCwgYSBsaXN0IG9mIGNvZG9ucy4gVGhlbiBpdCBjb21iaW5lcyB0aGVzZSBsaXN0cyBvZiBjb2RvbnMgaW4gYSBETkEgc2VxdWVuY2UsIHdoaWNoIGlzIHRoZSBvdXRwdXQgb2YgdGhlIGZ1bmN0aW9uLgoKYGBge3J9Cgpwcm90ZWluX3RvX2NvZG9ucyA8LSBmdW5jdGlvbihwcm90ZWluc2VxLCBjb2RvbnNfdGFibGVfaW5fc2hhcGUpewogICNnZW5lcmF0ZXMgYSBkYXRhIGZyYW1lIHRoYXQgY29udGFpbnMgdGhlIGluaXRpYWwgcHJvdGVpbiBzZXF1ZW5jZQogICNhbmQgYSBwcm9wb3NlZCBzdWNjZXNzaW9uIG9mIGNvZG9ucwogICN1c2VzIHRoZSBmdW5jdGlvbiAiZ2VuZXJhdGVfbGlzdF9jb2RvbnMiCiAgI3RoZSBpbnB1dCBpcyBhIHN0cmluZyBvZiBjaGFyYWN0ZXJzICJwcm90ZWluc2VxIiBhbmQgYSB0YWJsZSBvZiBjb2RvbiBmcmVxdWVuY2llcwogICNpbiB0aGUgc2hhcGUgdXNlZCBieSB0aGUgImdlbmVyYXRlX2xpc3RfY29kb25zIiBmdW5jdGlvbi4KICBzZXQuc2VlZCg1NTApICNmb3IgcmVwcm9kdWNpYmxlIGdlbmVyYXRpb24gb2YgcnVucyBvZiBjb2RvbnMgZm9yIGlkZW50aWNhbCBwcm90ZWluIHNlcXVlbmNlcwogIHByb3RlaW5fYWEgPC0gdW5saXN0KHN0cnNwbGl0KHByb3RlaW5zZXEsIHNwbGl0PSIiKSkKICBhYW51bWJlcnMgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShwcm90ZWluX2FhKSwgc3RyaW5nc0FzRmFjdG9ycz1GKQogICN0aGUgb2J0YWluZWQgZGYgaGFzIHR3byBjb2x1bW5zOiAicHJvdGVpbl9hYSIgYW5kICJGcmVxIgogICNpdCBpcyBhbHJlYWR5IGFscGhhYmV0aWNhbGx5IHNvcnRlZAogIGFhY29kb25zIDwtIGxhcHBseShhYW51bWJlcnMkcHJvdGVpbl9hYSwgCiAgICAgICAgICAgICAgICAgICAgIEZVTj1nZW5lcmF0ZV9saXN0X2NvZG9ucywgCiAgICAgICAgICAgICAgICAgICAgIGNvZG9uc190YWJsZV9pbl9zaGFwZSwgCiAgICAgICAgICAgICAgICAgICAgIGFhbnVtYmVycykKICAjd2UgZ2V0IHRoZSBsaXN0IG9mIGNvZG9ucyBmb3IgZWFjaCBhbWlubyBhY2lkIGluIGEgbGlzdCBvZiBsaXN0cy4KICBhYV9hbHBoYWJldGljYWwgPC0gcHJvdGVpbl9hYVtvcmRlcihwcm90ZWluX2FhKV0KICBhYV9sb25nIDwtIGRhdGEuZnJhbWUoYWE9YWFfYWxwaGFiZXRpY2FsLCBjb2RvbnM9dW5saXN0KGFhY29kb25zKSkKICAjYWFfbG9uZyBjb3JyZXNwb25kcyB0byBhIGRhdGEgZnJhbWUgaW4gd2hpY2ggdGhlICJhYSIgY29sdW1uIGNvcnJlc3BvbmRzIHRvIGFtaW5vYWNpZHMKICAjYW5kIHRoZSAiY29kb25zIiBjb2x1bW4gdG8gdGhlIGNvcnJlc29uZGluZyBjb2RvbnMuCiAgcHJvdGVpbl9hYS5kZiA8LSBhcy5kYXRhLmZyYW1lKHggPSBwcm90ZWluX2FhLCBzdHJpbmdzQXNGYWN0b3JzPUYpCiAgI3Byb3RlaW5fYWEuZGYgY29udGFpbnMgdGhlIGFtaW5vYWNpZHMgaW4gdGhlaXIgb3JkZXIgaW4gdGhlIGluaXRpYWwgc2VxdWVuY2UKICBwcm90ZWluX2FhLmRmJGlkeCA8LSBhcy5pbnRlZ2VyKHJvdy5uYW1lcyhwcm90ZWluX2FhLmRmKSkKICAjZGVmaW5lIGFuIGluZGV4IGZvciBlYWNoIGFtaW5vYWNpZCB0byBiZSBhYmxlIHRvIHB1dCB0aGVtIGluIG9yZGVyIGF0IHRoZSBlbmQKICBwcm90ZWluX2FhLmRmIDwtIHByb3RlaW5fYWEuZGZbb3JkZXIocHJvdGVpbl9hYS5kZiRwcm90ZWluX2FhKSwgXQogICNzb3J0IHRoZSBkYXRhIGZyYW1lIGFscGhhYmV0aWNhbGx5LCBzbyB0aGF0IGl0IGlzIGVhc3kgdG8gbWVyZ2UgaXQgd2l0aCB0aGUgb3RoZXIgdGFibGUKICBhYXRvY29kb25zLmRmIDwtIGFzLmRhdGEuZnJhbWUoY2JpbmQocHJvdGVpbl9hYS5kZiwgYWFfbG9uZykpCiAgI3RoZW4gcmVvcmRlciB0aGUgZmluYWwgZGF0YSBmcmFtZSBhY2NvcmRpbmcgdG8gdGhlIG9yaWdpbmFsIG9yZGVyIG9mIHRoZSBhbWlub2FjaWRzCiAgcmV0dXJuKGFhdG9jb2RvbnMuZGZbb3JkZXIoYWF0b2NvZG9ucy5kZiRpZHgpLCBdKQp9CgpgYGAKCk5vdywgd2UgY2FuIHRlc3QgdGhlIG91dHB1dCBvZiB0aGUgZnVuY3Rpb24gZm9yIHRoZSBzaG9ydCBzZXF1ZW5jZSBgbXlwcm90X3NlcV9jbGVhbmAgYW5kIGNoZWNrIGlmIHRoZSByZXN1bHRzIGFyZSBhYm91dCByaWdodC4gSXQgd291bGQgYmUgbXVjaCBiZXR0ZXIgdG8gaGF2ZSBhIHdheSB0byB0ZXN0IHRoaXMgb3V0cHV0IGF1dG9tYXRpY2FsbHksIGFkZCBlcnJvciBjb3JyZWN0aW9uIGFuZCBleGNlcHRpb24gaGFuZGxpbmcuIEFsc28sIGl0IHdvdWxkIGJlIGdvb2QgdG8gbW9kaWZ5IHRoZSBjb2RlIGluIHN1Y2ggYSB3YXkgYXMgdG8gYXZvaWQgcmVwZWF0cyBhbmQgbG93IGNvbXBsZXhpdHkgcmVnaW9ucy4gRm9yIHZlcnNpb24gMi4uLgoKYGBge3J9Cgp0ZXN0IDwtIHByb3RlaW5fdG9fY29kb25zKG15cHJvdF9zZXFfY2xlYW4sIGFzLmRhdGEuZnJhbWUoY29kb25zX3RhYmxlX3NlbGVjdGlvbikpCnByaW50KHRlc3RbLCBjKCJhYSIsICJjb2RvbnMiKV0pCmBgYApUbyBtYW51YWxseSAiYWRqdXN0IiBjb2RvbnMsIG9uZSBjYW4gc2F2ZSB0aGUgb2J0YWluZWQgZGF0YSBmcmFtZSBpbiBhIHRhYnVsYXIgZm9ybWF0LiBPdGhlcndpc2UsIHRvIG9idGFpbiBqdXN0IHRoZSBzZXF1ZW5jZSAoYW5kIGNoZWNrIHRoYXQgaXQgaXMgT0sgYnkgdHJhbnNsYXRpbmcgaXQgaW4gQXBFKToKCmBgYHtyfQojcHJpbnQocGFzdGUodGVzdCRjb2RvbnMsIGNvbGxhcHNlID0gIiIpKQojdGhpcyBpcyB1Z2x5LCBhcyBpdCB3cml0ZXMgYSBsb25nIHN0cmluZyB3aXRoIG5vIGVuZCBvZiBsaW5lLiBBIGJldHRlciBzb2x1dGlvbjoKdG9wcmludCA8LSBwYXN0ZSh0ZXN0JGNvZG9ucywgY29sbGFwc2U9IiIpCnNwbGl0dGVkIDwtIHN0cnNwbGl0KGdzdWIoIihbWzphbG51bTpdXXsxMDB9KSIsICJcXDEgIiwgdG9wcmludCksIHNwbGl0PSIgIiwgZml4ZWQ9VCkKcHJpbnQodW5saXN0KHNwbGl0dGVkKSwgcXVvdGU9RikKYGBgCgo=