diff --git a/src/scval.js b/src/scval.js index 605b81b8..725ab004 100644 --- a/src/scval.js +++ b/src/scval.js @@ -376,3 +376,27 @@ export function scValToNative(scv) { return scv.value(); } } + +/// Inject a sortable map builder into the xdr module. +xdr.scvSortedMap = (items) => { + let sorted = Array.from(items).sort((a, b) => { + // Both a and b are `ScMapEntry`s, so we need to sort by underlying key. + // + // We couldn't possibly handle every combination of keys since Soroban + // maps don't enforce consistent types, so we do a best-effort and try + // sorting by "number-like" or "string-like." + let nativeA = scValToNative(a.key()), + nativeB = scValToNative(b.key()); + + switch (typeof nativeA) { + case 'number': + case 'bigint': + return nativeA < nativeB ? -1 : 1; + + default: + return nativeA.toString().localeCompare(nativeB.toString()); + } + }); + + return xdr.ScVal.scvMap(sorted); +}; diff --git a/src/xdr.js b/src/xdr.js index 3f53d3f2..d97dbe29 100644 --- a/src/xdr.js +++ b/src/xdr.js @@ -1,27 +1,3 @@ import xdr from './generated/curr_generated'; -import { scValToNative } from './scval'; - -xdr.scvMapSorted = (items) => { - let sorted = Array.from(items).sort((a, b) => { - // Both a and b are `ScMapEntry`s, so we need to sort by underlying key. - // - // We couldn't possibly handle every combination of keys since Soroban - // maps don't enforce consistent types, so we do a best-effort and try - // sorting by "number-like" or "string-like." - let nativeA = scValToNative(a.key()), - nativeB = scValToNative(b.key()); - - switch (typeof nativeA) { - case 'number': - case 'bigint': - return nativeA < nativeB ? -1 : 1; - - default: - return nativeA.toString().localeCompare(nativeB.toString()); - } - }); - - return xdr.ScVal.scvMap(sorted); -}; export default xdr; diff --git a/test/unit/scval_test.js b/test/unit/scval_test.js index 37089414..9bd648ee 100644 --- a/test/unit/scval_test.js +++ b/test/unit/scval_test.js @@ -288,7 +288,7 @@ describe('parsing and building ScVals', function () { expect(sample.value()[idx].key().value()).to.equal(val); }); - const sorted = xdr.scvMapSorted(sample.value()); + const sorted = xdr.scvSortedMap(sample.value()); expect(sorted.switch().name).to.equal('scvMap'); ['a', 'b', 'c'].forEach((val, idx) => { expect(sorted.value()[idx].key().value()).to.equal(val); @@ -323,7 +323,7 @@ describe('parsing and building ScVals', function () { expect(sample.value()[idx].key().value().toBigInt()).to.equal(val); }); - const sorted = xdr.scvMapSorted(sample.value()); + const sorted = xdr.scvSortedMap(sample.value()); expect(sorted.switch().name).to.equal('scvMap'); [1n, 2n, 3n].forEach((val, idx) => { expect(sorted.value()[idx].key().value().toBigInt()).to.equal(val); diff --git a/types/curr.d.ts b/types/curr.d.ts index beafaa43..00be76e4 100644 --- a/types/curr.d.ts +++ b/types/curr.d.ts @@ -44,6 +44,16 @@ export namespace xdr { type Hash = Opaque[]; // workaround, cause unknown + /** + * Returns an {@link ScVal} with a map type and sorted entries. + * + * @param items the key-value pairs to sort. + * + * @warning This only performs "best-effort" sorting, working best when the + * keys are all either numeric or string-like. + */ + function scvSortedMap(items: ScMapEntry[]): ScVal; + interface SignedInt { readonly MAX_VALUE: 2147483647; readonly MIN_VALUE: -2147483648;