@base-org/account
Version:
Base Account SDK
504 lines • 17.1 kB
JavaScript
// Copyright (c) 2018-2025 Coinbase, Inc. <https://www.coinbase.com/>
// Wire types
const WIRE_TYPE_VARINT = 0;
const WIRE_TYPE_LENGTH_DELIMITED = 2;
/**
* Encode a varint (unsigned)
*/
function encodeVarint(value) {
const result = [];
let n = typeof value === 'bigint' ? value : BigInt(value);
if (n < 0n) {
throw new Error('Cannot encode negative varint');
}
do {
let byte = Number(n & 0x7fn);
n >>= 7n;
if (n !== 0n) {
byte |= 0x80;
}
result.push(byte);
} while (n !== 0n);
return new Uint8Array(result);
}
/**
* Decode a varint from buffer
*/
function decodeVarint(buffer, offset) {
let value = 0n;
let shift = 0n;
let length = 0;
while (offset + length < buffer.length) {
const byte = buffer[offset + length];
length++;
value |= BigInt(byte & 0x7f) << shift;
shift += 7n;
if ((byte & 0x80) === 0) {
return { value, length };
}
}
throw new Error('Incomplete varint');
}
/**
* Encode a length-delimited field
*/
function encodeBytes(fieldNumber, value) {
if (value.length === 0)
return new Uint8Array(0); // Omit empty bytes
const tag = (fieldNumber << 3) | WIRE_TYPE_LENGTH_DELIMITED;
const tagBytes = encodeVarint(tag);
const lengthBytes = encodeVarint(value.length);
const result = new Uint8Array(tagBytes.length + lengthBytes.length + value.length);
result.set(tagBytes, 0);
result.set(lengthBytes, tagBytes.length);
result.set(value, tagBytes.length + lengthBytes.length);
return result;
}
/**
* Encode a varint field
*/
function encodeVarintField(fieldNumber, value) {
if (value === 0 || value === 0n)
return new Uint8Array(0); // Omit zero values in proto3
const tag = (fieldNumber << 3) | WIRE_TYPE_VARINT;
const tagBytes = encodeVarint(tag);
const valueBytes = encodeVarint(value);
const result = new Uint8Array(tagBytes.length + valueBytes.length);
result.set(tagBytes, 0);
result.set(valueBytes, tagBytes.length);
return result;
}
/**
* Encode a string field
*/
function encodeString(fieldNumber, value) {
if (!value)
return new Uint8Array(0); // Omit empty strings
const bytes = new TextEncoder().encode(value);
return encodeBytes(fieldNumber, bytes);
}
/**
* Encode a message field
*/
function encodeMessage(fieldNumber, value) {
if (value.length === 0)
return new Uint8Array(0); // Omit empty messages
return encodeBytes(fieldNumber, value);
}
/**
* Concatenate byte arrays
*/
function concat(...arrays) {
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const arr of arrays) {
result.set(arr, offset);
offset += arr.length;
}
return result;
}
/**
* Encode Erc20Transfer message
*/
function encodeErc20Transfer(value) {
return concat(encodeBytes(1, value.token), encodeBytes(2, value.recipient), encodeBytes(3, value.amount));
}
/**
* Encode NativeTransfer message
*/
function encodeNativeTransfer(value) {
return concat(encodeBytes(1, value.recipient), encodeBytes(2, value.amount));
}
/**
* Encode Call message
*/
function encodeCall(value) {
return concat(encodeBytes(1, value.to), encodeBytes(2, value.data), encodeBytes(3, value.value));
}
/**
* Encode GenericCalls message
*/
function encodeGenericCalls(value) {
const parts = [];
for (const call of value.calls) {
const callBytes = encodeCall(call);
parts.push(encodeMessage(1, callBytes));
}
return concat(...parts);
}
/**
* Encode WalletSendCalls message
*/
export function encodeWalletSendCalls(value) {
const parts = [encodeVarintField(1, value.type)];
// Encode transaction data based on type
if (value.transactionData.case === 'erc20Transfer') {
const encoded = encodeErc20Transfer(value.transactionData.value);
parts.push(encodeMessage(10, encoded));
}
else if (value.transactionData.case === 'nativeTransfer') {
const encoded = encodeNativeTransfer(value.transactionData.value);
parts.push(encodeMessage(11, encoded));
}
else if (value.transactionData.case === 'genericCalls') {
const encoded = encodeGenericCalls(value.transactionData.value);
parts.push(encodeMessage(12, encoded));
}
if (value.from) {
parts.push(encodeBytes(3, value.from));
}
if (value.version) {
parts.push(encodeString(4, value.version));
}
return concat(...parts);
}
/**
* Encode SpendPermission message
*/
function encodeSpendPermission(value) {
return concat(encodeBytes(1, value.account), encodeBytes(2, value.spender), encodeBytes(3, value.token), encodeBytes(4, value.allowance), encodeVarintField(5, value.period), encodeVarintField(6, value.start), encodeVarintField(7, value.end), encodeBytes(8, value.salt), encodeBytes(9, value.extraData), encodeBytes(10, value.verifyingContract), encodeString(11, value.domainName), encodeString(12, value.domainVersion));
}
/**
* Encode ReceiveWithAuthorization message
*/
function encodeReceiveWithAuthorization(value) {
return concat(encodeBytes(1, value.from), encodeBytes(2, value.to), encodeBytes(3, value.value), encodeBytes(4, value.validAfter), encodeBytes(5, value.validBefore), encodeBytes(6, value.nonce), encodeBytes(7, value.verifyingContract), encodeString(8, value.domainName), encodeString(9, value.domainVersion));
}
/**
* Encode GenericTypedData message
*/
function encodeGenericTypedData(value) {
return encodeBytes(1, value.typedDataJson);
}
/**
* Encode WalletSign message
*/
export function encodeWalletSign(value) {
const parts = [encodeVarintField(1, value.type)];
if (value.signatureData.case === 'spendPermission') {
const encoded = encodeSpendPermission(value.signatureData.value);
parts.push(encodeMessage(10, encoded));
}
else if (value.signatureData.case === 'receiveWithAuthorization') {
const encoded = encodeReceiveWithAuthorization(value.signatureData.value);
parts.push(encodeMessage(11, encoded));
}
else if (value.signatureData.case === 'genericTypedData') {
const encoded = encodeGenericTypedData(value.signatureData.value);
parts.push(encodeMessage(12, encoded));
}
if (value.version) {
parts.push(encodeString(3, value.version));
}
return concat(...parts);
}
/**
* Encode GenericJsonRpc message
*/
export function encodeGenericJsonRpc(value) {
return concat(encodeString(1, value.method), encodeBytes(2, value.paramsJson), encodeString(3, value.rpcVersion || ''));
}
/**
* Encode map field (for capabilities)
*/
function encodeMap(fieldNumber, map) {
const parts = [];
for (const [key, value] of map.entries()) {
// Each map entry is encoded as a message with field 1 = key, field 2 = value
const entryBytes = concat(encodeString(1, key), encodeBytes(2, value));
parts.push(encodeMessage(fieldNumber, entryBytes));
}
return concat(...parts);
}
/**
* Encode RpcLinkPayload message
*/
export function encodeRpcLinkPayload(value) {
const parts = [
encodeVarintField(1, value.protocolVersion),
encodeVarintField(2, value.chainId || 0),
encodeVarintField(3, value.shortcutId),
encodeVarintField(4, value.shortcutVersion),
];
// Encode body based on shortcut
if (value.body.case === 'generic') {
const encoded = encodeGenericJsonRpc(value.body.value);
parts.push(encodeMessage(10, encoded));
}
else if (value.body.case === 'walletSendCalls') {
const encoded = encodeWalletSendCalls(value.body.value);
parts.push(encodeMessage(11, encoded));
}
else if (value.body.case === 'walletSign') {
const encoded = encodeWalletSign(value.body.value);
parts.push(encodeMessage(12, encoded));
}
// Encode capabilities map
if (value.capabilities && value.capabilities.size > 0) {
parts.push(encodeMap(20, value.capabilities));
}
return concat(...parts);
}
/**
* Decode a protobuf message
* This is a simplified decoder that reads fields sequentially
*/
export function decodeRpcLinkPayload(buffer) {
const fields = parseFields(buffer);
const protocolVersion = Number(fields.get(1) || 0n);
const chainId = fields.get(2) ? Number(fields.get(2)) : undefined;
const shortcutId = Number(fields.get(3) || 0n);
const shortcutVersion = Number(fields.get(4) || 0n);
let body = { case: undefined };
// Decode body based on which field is present
if (fields.has(10)) {
const genericBytes = fields.get(10);
body = { case: 'generic', value: decodeGenericJsonRpc(genericBytes) };
}
else if (fields.has(11)) {
const sendCallsBytes = fields.get(11);
body = { case: 'walletSendCalls', value: decodeWalletSendCalls(sendCallsBytes) };
}
else if (fields.has(12)) {
const signBytes = fields.get(12);
body = { case: 'walletSign', value: decodeWalletSign(signBytes) };
}
// Decode capabilities map
const capabilities = fields.get(20) ? fields.get(20) : undefined;
return {
protocolVersion,
chainId,
shortcutId,
shortcutVersion,
body,
capabilities,
};
}
/**
* Parse protobuf fields from buffer
*/
function parseFields(buffer) {
const fields = new Map();
let offset = 0;
while (offset < buffer.length) {
const { value: tag, length: tagLength } = decodeVarint(buffer, offset);
offset += tagLength;
const fieldNumber = Number(tag >> 3n);
const wireType = Number(tag & 0x7n);
if (wireType === WIRE_TYPE_VARINT) {
const { value, length } = decodeVarint(buffer, offset);
offset += length;
fields.set(fieldNumber, value);
}
else if (wireType === WIRE_TYPE_LENGTH_DELIMITED) {
const { value: length, length: lengthSize } = decodeVarint(buffer, offset);
offset += lengthSize;
const bytes = buffer.slice(offset, offset + Number(length));
offset += Number(length);
// Field 20 is the capabilities map
if (fieldNumber === 20) {
if (!fields.has(20)) {
fields.set(20, new Map());
}
const mapField = fields.get(20);
const entry = parseMapEntry(bytes);
mapField.set(entry.key, entry.value);
}
else {
fields.set(fieldNumber, bytes);
}
}
else {
throw new Error(`Unsupported wire type: ${wireType}`);
}
}
return fields;
}
/**
* Parse a map entry (key-value pair)
*/
function parseMapEntry(buffer) {
const fields = parseFields(buffer);
const keyBytes = fields.get(1);
const valueBytes = fields.get(2);
if (!keyBytes || !valueBytes) {
throw new Error('Invalid map entry: missing key or value');
}
const key = new TextDecoder().decode(keyBytes);
return { key, value: valueBytes };
}
/**
* Decode GenericJsonRpc message
*/
function decodeGenericJsonRpc(buffer) {
const fields = parseFields(buffer);
const methodBytes = fields.get(1);
const paramsJsonBytes = fields.get(2);
const rpcVersionBytes = fields.get(3);
return {
method: new TextDecoder().decode(methodBytes || new Uint8Array()),
paramsJson: paramsJsonBytes || new Uint8Array(),
rpcVersion: rpcVersionBytes ? new TextDecoder().decode(rpcVersionBytes) : undefined,
};
}
/**
* Decode WalletSendCalls message
*/
function decodeWalletSendCalls(buffer) {
const fields = parseFields(buffer);
const type = Number(fields.get(1) || 0n);
const fromBytes = fields.get(3);
const versionBytes = fields.get(4);
let transactionData = { case: undefined };
if (fields.has(10)) {
const erc20Bytes = fields.get(10);
transactionData = { case: 'erc20Transfer', value: decodeErc20Transfer(erc20Bytes) };
}
else if (fields.has(11)) {
const nativeBytes = fields.get(11);
transactionData = { case: 'nativeTransfer', value: decodeNativeTransfer(nativeBytes) };
}
else if (fields.has(12)) {
const genericBytes = fields.get(12);
transactionData = { case: 'genericCalls', value: decodeGenericCalls(genericBytes) };
}
return {
type,
transactionData,
from: fromBytes,
version: versionBytes ? new TextDecoder().decode(versionBytes) : undefined,
};
}
/**
* Decode Erc20Transfer message
*/
function decodeErc20Transfer(buffer) {
const fields = parseFields(buffer);
return {
token: fields.get(1) || new Uint8Array(),
recipient: fields.get(2) || new Uint8Array(),
amount: fields.get(3) || new Uint8Array(),
};
}
/**
* Decode NativeTransfer message
*/
function decodeNativeTransfer(buffer) {
const fields = parseFields(buffer);
return {
recipient: fields.get(1) || new Uint8Array(),
amount: fields.get(2) || new Uint8Array(),
};
}
/**
* Decode Call message
*/
function decodeCall(buffer) {
const fields = parseFields(buffer);
return {
to: fields.get(1) || new Uint8Array(),
data: fields.get(2) || new Uint8Array(),
value: fields.get(3) || new Uint8Array(),
};
}
/**
* Decode GenericCalls message
*/
function decodeGenericCalls(buffer) {
const calls = [];
let offset = 0;
while (offset < buffer.length) {
const { value: tag, length: tagLength } = decodeVarint(buffer, offset);
offset += tagLength;
const fieldNumber = Number(tag >> 3n);
const wireType = Number(tag & 0x7n);
if (fieldNumber === 1 && wireType === WIRE_TYPE_LENGTH_DELIMITED) {
const { value: length, length: lengthSize } = decodeVarint(buffer, offset);
offset += lengthSize;
const callBytes = buffer.slice(offset, offset + Number(length));
offset += Number(length);
calls.push(decodeCall(callBytes));
}
else {
throw new Error(`Unexpected field in GenericCalls: ${fieldNumber}`);
}
}
return { calls };
}
/**
* Decode WalletSign message
*/
function decodeWalletSign(buffer) {
const fields = parseFields(buffer);
const type = Number(fields.get(1) || 0n);
const versionBytes = fields.get(3);
let signatureData = { case: undefined };
if (fields.has(10)) {
const spendBytes = fields.get(10);
signatureData = { case: 'spendPermission', value: decodeSpendPermission(spendBytes) };
}
else if (fields.has(11)) {
const receiveBytes = fields.get(11);
signatureData = {
case: 'receiveWithAuthorization',
value: decodeReceiveWithAuthorization(receiveBytes),
};
}
else if (fields.has(12)) {
const genericBytes = fields.get(12);
signatureData = { case: 'genericTypedData', value: decodeGenericTypedData(genericBytes) };
}
return {
type,
signatureData,
version: versionBytes ? new TextDecoder().decode(versionBytes) : undefined,
};
}
/**
* Decode SpendPermission message
*/
function decodeSpendPermission(buffer) {
const fields = parseFields(buffer);
return {
account: fields.get(1) || new Uint8Array(),
spender: fields.get(2) || new Uint8Array(),
token: fields.get(3) || new Uint8Array(),
allowance: fields.get(4) || new Uint8Array(),
period: fields.get(5) || 0n,
start: fields.get(6) || 0n,
end: fields.get(7) || 0n,
salt: fields.get(8) || new Uint8Array(),
extraData: fields.get(9) || new Uint8Array(),
verifyingContract: fields.get(10) || new Uint8Array(),
domainName: new TextDecoder().decode(fields.get(11) || new Uint8Array()),
domainVersion: new TextDecoder().decode(fields.get(12) || new Uint8Array()),
};
}
/**
* Decode ReceiveWithAuthorization message
*/
function decodeReceiveWithAuthorization(buffer) {
const fields = parseFields(buffer);
return {
from: fields.get(1) || new Uint8Array(),
to: fields.get(2) || new Uint8Array(),
value: fields.get(3) || new Uint8Array(),
validAfter: fields.get(4) || new Uint8Array(),
validBefore: fields.get(5) || new Uint8Array(),
nonce: fields.get(6) || new Uint8Array(),
verifyingContract: fields.get(7) || new Uint8Array(),
domainName: new TextDecoder().decode(fields.get(8) || new Uint8Array()),
domainVersion: new TextDecoder().decode(fields.get(9) || new Uint8Array()),
};
}
/**
* Decode GenericTypedData message
*/
function decodeGenericTypedData(buffer) {
const fields = parseFields(buffer);
return {
typedDataJson: fields.get(1) || new Uint8Array(),
};
}
//# sourceMappingURL=protobuf.js.map