UNPKG

@wormhole-foundation/sdk-algorand-core

Version:

SDK for Algorand, used in conjunction with @wormhole-foundation/sdk

234 lines 9.21 kB
import { encoding, isNative, toChainId } from "@wormhole-foundation/sdk-connect"; import { LogicSigAccount, decodeAddress, getApplicationAddress, modelsv2 } from "algosdk"; import { safeBigIntToNumber } from "@wormhole-foundation/sdk-algorand"; export const SEED_AMT = 1002000; export const MAX_KEYS = 15; export const MAX_BYTES_PER_KEY = 127; export const BITS_PER_BYTE = 8; export const BITS_PER_KEY = MAX_BYTES_PER_KEY * BITS_PER_BYTE; export const MAX_BYTES = MAX_BYTES_PER_KEY * MAX_KEYS; export const MAX_BITS = BITS_PER_BYTE * MAX_BYTES; // Useful for encoding numbers as varints to patch TEAL binary export const varint = { // Forever grateful to https://github.com/joeltg/big-varint/blob/main/src/unsigned.ts _limit: 0x7f, encodingLength: (value) => { let i = 0; for (; value >= 0x80; i++) value >>= 7; return i + 1; }, encode: (i, buffer, byteOffset) => { if (typeof i === "bigint") i = safeBigIntToNumber(i); if (i < 0) throw new RangeError("value must be unsigned"); const byteLength = varint.encodingLength(i); buffer = buffer || new ArrayBuffer(byteLength); byteOffset = byteOffset || 0; if (buffer.byteLength < byteOffset + byteLength) throw new RangeError("the buffer is too small to encode the number at the offset"); const array = new Uint8Array(buffer, byteOffset); let offset = 0; while (varint._limit < i) { array[offset++] = (i & varint._limit) | 0x80; i >>= 7; } array[offset] = Number(i); return array; }, decode: (data, offset = 0) => { let i = 0; let n = 0; let b; do { b = data[offset + n]; if (b === undefined) throw new RangeError("offset out of range"); i += (b & varint._limit) << (n * 7); n++; } while (0x80 <= b); return i; }, }; export const StorageLogicSig = { // Get the storage lsig for a Wormhole message ID forMessageId: (appId, whm) => { const appAddress = decodeAddress(getApplicationAddress(appId)).publicKey; const emitterAddr = whm.emitter.toUniversalAddress().toUint8Array(); const chainIdBytes = encoding.bignum.toBytes(BigInt(toChainId(whm.chain)), 2); const address = encoding.bytes.concat(chainIdBytes, emitterAddr); return StorageLogicSig.fromData({ appId, appAddress, idx: whm.sequence / BigInt(MAX_BITS), address, }); }, // Get the storage lsig for a wrapped asset forWrappedAsset: (appId, token) => { if (isNative(token.address)) throw new Error("native asset cannot be a wrapped asset"); const appAddress = decodeAddress(getApplicationAddress(appId)).publicKey; return StorageLogicSig.fromData({ appId, appAddress, idx: BigInt(toChainId(token.chain)), address: token.address.toUniversalAddress().toUint8Array(), }); }, // Get the storage lsig for a native asset forNativeAsset: (appId, tokenId) => { const appAddress = decodeAddress(getApplicationAddress(appId)).publicKey; return StorageLogicSig.fromData({ appId, appAddress, idx: tokenId, address: encoding.bytes.encode("native"), }); }, // Get the storage lsig for the guardian set forGuardianSet: (appId, idx) => { const appAddress = decodeAddress(getApplicationAddress(appId)).publicKey; return StorageLogicSig.fromData({ appId, appAddress, idx: BigInt(idx), address: encoding.bytes.encode("guardian"), }); }, forEmitter: (appId, emitter) => { const appAddress = decodeAddress(getApplicationAddress(appId)).publicKey; return StorageLogicSig.fromData({ appId, appAddress, idx: 0n, address: emitter, }); }, _encode: (data) => { if (typeof data === "bigint") return [encoding.hex.encode(varint.encode(data))]; return [encoding.hex.encode(varint.encode(data.length)), encoding.hex.encode(data)]; }, fromData: (data) => { // This patches the binary of the TEAL program used to store data // to produce a logic sig that can be used to sign transactions // to store data in the its account local state for a given app const byteStrings = [ "0620010181", ...StorageLogicSig._encode(data.idx), "4880", ...StorageLogicSig._encode(data.address), "483110810612443119221244311881", ...StorageLogicSig._encode(data.appId), "1244312080", ...StorageLogicSig._encode(data.appAddress), "124431018100124431093203124431153203124422", ]; const bytecode = encoding.hex.decode(byteStrings.join("")); return new LogicSigAccount(bytecode); }, /** * Returns the local data for an application ID * @param client Algodv2 client * @param appId Application ID of interest * @param address Address of the account * @returns Promise with Uint8Array of data squirreled away */ decodeLocalState: async (client, appId, address) => { let appState; try { const ai = await client .accountApplicationInformation(address, safeBigIntToNumber(appId)) .do(); const acctAppInfo = modelsv2.AccountApplicationResponse.from_obj_for_encoding(ai); appState = acctAppInfo.appLocalState; } catch (e) { return new Uint8Array(); } const metaKey = encoding.b64.encode("meta"); // We don't want the data in the `meta` key // and we want to make sure the sequences come back in order // so first put them in a map by numeric key // then iterate over keys to concat them in the right order let vals = new Map(); for (const kv of appState.keyValue) { if (kv.key === metaKey) continue; // Take the first byte off the key to be the // numeric index const key = encoding.b64.decode(kv.key)[0]; const value = encoding.b64.decode(kv.value.bytes); vals.set(key, value); } const byteArrays = []; for (let i = 0; i < MAX_KEYS; i++) { if (vals.has(i)) byteArrays.push(vals.get(i)); } return encoding.bytes.concat(...byteArrays); }, /** * This function is used to check if a VAA has been redeemed by looking at a specific bit * @param client AlgodV2 client * @param appId Application Id * @param addr Wallet address. Someone has to pay for this * @param seq The sequence number of the redemption * @returns True, if the bit was set and VAA was redeemed, False otherwise */ checkBitsSet: async (client, appId, addr, seq) => { let retval = false; let appState; const acctInfoResp = await client.accountInformation(addr).do(); const acctInfo = modelsv2.Account.from_obj_for_encoding(acctInfoResp); const als = acctInfo.appsLocalState; als && als.forEach((app) => { if (BigInt(app.id) === appId) { appState = app.keyValue; } }); if (appState?.length === 0) { return retval; } const BIG_MAX_BITS = BigInt(MAX_BITS); const BIG_EIGHT = BigInt(8); // Start on a MAX_BITS boundary const start = (seq / BIG_MAX_BITS) * BIG_MAX_BITS; // beg should be in the range [0..MAX_BITS] const beg = safeBigIntToNumber(seq - start); // s should be in the range [0..15] const s = Math.floor(beg / BITS_PER_KEY); const b = Math.floor((beg - s * BITS_PER_KEY) / 8); const key = encoding.b64.encode(encoding.bignum.toBytes(s, 1)); appState?.forEach((kv) => { if (kv.key === key) { const v = Buffer.from(kv.value.bytes, "base64"); const bt = 1 << safeBigIntToNumber(seq % BIG_EIGHT); retval = (v[b] & bt) != 0; // Added non-null assertion return; } }); return retval; }, /** * Checks to see if the account exists for the application * @param client An Algodv2 client * @param appId Application ID * @param acctAddr Account address to check * @returns True, if account exists for application, False otherwise */ storageAccountExists: async (client, address, appId) => { try { const acctAppInfo = await client .accountApplicationInformation(address, safeBigIntToNumber(appId)) .do(); return Object.keys(acctAppInfo).length > 0; } catch { } return false; }, }; //# sourceMappingURL=storage.js.map