@wormhole-foundation/sdk-algorand-core
Version:
SDK for Algorand, used in conjunction with @wormhole-foundation/sdk
237 lines • 10.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.StorageLogicSig = exports.varint = exports.MAX_BITS = exports.MAX_BYTES = exports.BITS_PER_KEY = exports.BITS_PER_BYTE = exports.MAX_BYTES_PER_KEY = exports.MAX_KEYS = exports.SEED_AMT = void 0;
const sdk_connect_1 = require("@wormhole-foundation/sdk-connect");
const algosdk_1 = require("algosdk");
const sdk_algorand_1 = require("@wormhole-foundation/sdk-algorand");
exports.SEED_AMT = 1002000;
exports.MAX_KEYS = 15;
exports.MAX_BYTES_PER_KEY = 127;
exports.BITS_PER_BYTE = 8;
exports.BITS_PER_KEY = exports.MAX_BYTES_PER_KEY * exports.BITS_PER_BYTE;
exports.MAX_BYTES = exports.MAX_BYTES_PER_KEY * exports.MAX_KEYS;
exports.MAX_BITS = exports.BITS_PER_BYTE * exports.MAX_BYTES;
// Useful for encoding numbers as varints to patch TEAL binary
exports.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 = (0, sdk_algorand_1.safeBigIntToNumber)(i);
if (i < 0)
throw new RangeError("value must be unsigned");
const byteLength = exports.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 (exports.varint._limit < i) {
array[offset++] = (i & exports.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 & exports.varint._limit) << (n * 7);
n++;
} while (0x80 <= b);
return i;
},
};
exports.StorageLogicSig = {
// Get the storage lsig for a Wormhole message ID
forMessageId: (appId, whm) => {
const appAddress = (0, algosdk_1.decodeAddress)((0, algosdk_1.getApplicationAddress)(appId)).publicKey;
const emitterAddr = whm.emitter.toUniversalAddress().toUint8Array();
const chainIdBytes = sdk_connect_1.encoding.bignum.toBytes(BigInt((0, sdk_connect_1.toChainId)(whm.chain)), 2);
const address = sdk_connect_1.encoding.bytes.concat(chainIdBytes, emitterAddr);
return exports.StorageLogicSig.fromData({
appId,
appAddress,
idx: whm.sequence / BigInt(exports.MAX_BITS),
address,
});
},
// Get the storage lsig for a wrapped asset
forWrappedAsset: (appId, token) => {
if ((0, sdk_connect_1.isNative)(token.address))
throw new Error("native asset cannot be a wrapped asset");
const appAddress = (0, algosdk_1.decodeAddress)((0, algosdk_1.getApplicationAddress)(appId)).publicKey;
return exports.StorageLogicSig.fromData({
appId,
appAddress,
idx: BigInt((0, sdk_connect_1.toChainId)(token.chain)),
address: token.address.toUniversalAddress().toUint8Array(),
});
},
// Get the storage lsig for a native asset
forNativeAsset: (appId, tokenId) => {
const appAddress = (0, algosdk_1.decodeAddress)((0, algosdk_1.getApplicationAddress)(appId)).publicKey;
return exports.StorageLogicSig.fromData({
appId,
appAddress,
idx: tokenId,
address: sdk_connect_1.encoding.bytes.encode("native"),
});
},
// Get the storage lsig for the guardian set
forGuardianSet: (appId, idx) => {
const appAddress = (0, algosdk_1.decodeAddress)((0, algosdk_1.getApplicationAddress)(appId)).publicKey;
return exports.StorageLogicSig.fromData({
appId,
appAddress,
idx: BigInt(idx),
address: sdk_connect_1.encoding.bytes.encode("guardian"),
});
},
forEmitter: (appId, emitter) => {
const appAddress = (0, algosdk_1.decodeAddress)((0, algosdk_1.getApplicationAddress)(appId)).publicKey;
return exports.StorageLogicSig.fromData({
appId,
appAddress,
idx: 0n,
address: emitter,
});
},
_encode: (data) => {
if (typeof data === "bigint")
return [sdk_connect_1.encoding.hex.encode(exports.varint.encode(data))];
return [sdk_connect_1.encoding.hex.encode(exports.varint.encode(data.length)), sdk_connect_1.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",
...exports.StorageLogicSig._encode(data.idx),
"4880",
...exports.StorageLogicSig._encode(data.address),
"483110810612443119221244311881",
...exports.StorageLogicSig._encode(data.appId),
"1244312080",
...exports.StorageLogicSig._encode(data.appAddress),
"124431018100124431093203124431153203124422",
];
const bytecode = sdk_connect_1.encoding.hex.decode(byteStrings.join(""));
return new algosdk_1.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, (0, sdk_algorand_1.safeBigIntToNumber)(appId))
.do();
const acctAppInfo = algosdk_1.modelsv2.AccountApplicationResponse.from_obj_for_encoding(ai);
appState = acctAppInfo.appLocalState;
}
catch (e) {
return new Uint8Array();
}
const metaKey = sdk_connect_1.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 = sdk_connect_1.encoding.b64.decode(kv.key)[0];
const value = sdk_connect_1.encoding.b64.decode(kv.value.bytes);
vals.set(key, value);
}
const byteArrays = [];
for (let i = 0; i < exports.MAX_KEYS; i++) {
if (vals.has(i))
byteArrays.push(vals.get(i));
}
return sdk_connect_1.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 = algosdk_1.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(exports.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 = (0, sdk_algorand_1.safeBigIntToNumber)(seq - start);
// s should be in the range [0..15]
const s = Math.floor(beg / exports.BITS_PER_KEY);
const b = Math.floor((beg - s * exports.BITS_PER_KEY) / 8);
const key = sdk_connect_1.encoding.b64.encode(sdk_connect_1.encoding.bignum.toBytes(s, 1));
appState?.forEach((kv) => {
if (kv.key === key) {
const v = Buffer.from(kv.value.bytes, "base64");
const bt = 1 << (0, sdk_algorand_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, (0, sdk_algorand_1.safeBigIntToNumber)(appId))
.do();
return Object.keys(acctAppInfo).length > 0;
}
catch { }
return false;
},
};
//# sourceMappingURL=storage.js.map