import algosdk from 'algosdk' import { sha256 } from 'js-sha256' import { canonicalize } from 'json-canonicalize' const algodClient = new algosdk.Algodv2('a'.repeat(64), 'http://localhost', 4001) const allowList: string[] = ['032c6b017bdad49f54d41170fef9c13acdb8e5ff9a76fcaf0cfbecb6b3fdb5d0'] const mockRequest = [ "{\"description\":\"Allows a payment to be made every 25000 blocks of a specific amount to a specific address\",\"name\":\"25000 block payment\",\"program\":\"I3ByYWdtYSB2ZXJzaW9uIDkKCi8vIFZlcmlmeSB0aGlzIGlzIGEgcGF5bWVudAp0eG4gVHlwZUVudW0KaW50IHBheQo9PQoKLy8gVmVyaWZ5IHRoaXMgaXMgbm90IHJla2V5aW5nIHRoZSBzZW5kZXIgYWRkcmVzcwp0eG4gUmVrZXlUbwpnbG9iYWwgWmVyb0FkZHJlc3MKPT0KYXNzZXJ0CgovLyBWZXJpZnkgdGhlIHNlbmRlcidzIGFjY291bnQgaXMgbm90IGJlaW5nIGNsb3NlZAp0eG4gQ2xvc2VSZW1haW5kZXJUbwpnbG9iYWwgWmVyb0FkZHJlc3MKPT0KYXNzZXJ0CgovLyBWZXJpZnkgdGhlIHJlY2VpdmVyIGlzIGVxdWFsIHRvIHRoZSB0ZW1wbGF0ZWQgcmVjZWl2ZXIgYWRkcmVzcwp0eG4gUmVjZWl2ZXIKYWRkciBUTVBMX1JFQ0VJVkVSCj09CmFzc2VydAoKLy8gVmVyaWZ5IHRoZSBhbW91bnQgaXMgZXF1YWwgdG8gdGhlIHRlbXBsYXRlZCBhbW91bnQKdHhuIEFtb3VudAppbnQgVE1QTF9BTU9VTlQKPT0KYXNzZXJ0CgovLyBWZXJpZnkgdGhlIGN1cnJlbnQgcm91bmQgaXMgd2l0aGluIDUwMCByb3VuZHMgb2YgYSBwcm9kdWN0IG9mIDI1XzAwMApnbG9iYWwgUm91bmQKaW50IDI1XzAwMAolCnN0b3JlIDAKCmxvYWQgMAppbnQgNTAwCjw9Cgpsb2FkIDAKaW50IDI0XzUwMAo+PQoKfHwKYXNzZXJ0CgovLyBWZXJpZnkgbGVhc2UgCnR4biBMZWFzZQpieXRlICJzY2hlZHVsZWQgMjVfMDAwIHBheW1lbnQiCnNoYTI1Ngo9PQo=\",\"variables\":[{\"description\":\"Amount of the payment transaction in microAlgos\",\"name\":\"Payment Amount\",\"type\":\"uint64\",\"variable\":\"TMPL_AMOUNT\"},{\"description\":\"Address to which the payment transaction is sent\",\"name\":\"Payment Receiver\",\"type\":\"address\",\"variable\":\"TMPL_RECEIVER\"}]}", "{\"TMPL_AMOUNT\":1000000,\"TMPL_RECEIVER\":\"Y76M3MSY6DKBRHBL7C3NNDXGS5IIMQVQVUAB6MP4XEMMGVF2QWNPL226CA\"}", "6INR7PDVBEVPFXMYOWG2J7KLGMQUWKB7CFX3KW2ERW4E42NW5R7WVB4R3A" ] async function processTemplatedLsig(requestParams: string[], signer: (lsig: algosdk.LogicSig) => Promise<Uint8Array>): Promise<Uint8Array> { const arc47 = JSON.parse(requestParams[0]) const values = JSON.parse(requestParams[1]) const hash = requestParams[2] // allowList is not a required feature of ARC47, but it allows wallets to verify the lsig template before signing if (!allowList.includes(sha256(canonicalize(arc47)))) throw Error('Templated Lsig not in allow list') // base64 decode the program let finalTeal = atob(arc47.program) // substitute the variables for (const variable in values) { finalTeal = finalTeal.replaceAll(variable, values[variable].toString()) } // use algod to compile the TEAL after substituting the variables const compileResponse = await algodClient.compile(finalTeal).do() // verify the compiled hash is the same as the given hash in the request if (compileResponse.hash !== hash) throw Error(`Compiled hash (${compileResponse.hash}) does not match expected hash (${hash})`) // create a LogicSig object from the compiled program const lsig = new algosdk.LogicSig(Buffer.from(compileResponse.result, 'base64')) // signer function is expected to return the signature of the lsig return signer(lsig) } const signature = await processTemplatedLsig(mockRequest, async (lsig) => lsig.signProgram((algosdk.generateAccount()).sk)) console.log(signature)