Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
7202278
chore: remove dead code getAssetsMetadataByAssetIds
stanleyyconsensys Sep 8, 2026
e20696a
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Sep 8, 2026
201eb4e
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Sep 17, 2026
ec2a57a
chore: add asset migration
stanleyyconsensys Sep 17, 2026
2a59e1d
chore: update code
stanleyyconsensys Sep 21, 2026
5cb3757
chore: update package
stanleyyconsensys Sep 21, 2026
80f60a7
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Sep 23, 2026
3f25320
fix: lint
stanleyyconsensys Sep 23, 2026
e7843a2
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Oct 5, 2026
54164d5
chore: add life data for resolve onChain Account
stanleyyconsensys Oct 6, 2026
63d52c3
chore: get live data from keyring method
stanleyyconsensys Oct 6, 2026
0f34fd9
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Oct 6, 2026
14d3591
fix: rename
stanleyyconsensys Oct 6, 2026
aee1799
fix: setSep41BalanceForAccount
stanleyyconsensys Oct 6, 2026
48ddba5
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Oct 6, 2026
e92e082
fix: lint and test
stanleyyconsensys Oct 6, 2026
576442f
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Oct 7, 2026
346ea10
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Oct 8, 2026
119fefe
chore: update service
stanleyyconsensys Oct 12, 2026
d132073
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Oct 15, 2026
0a960c2
Delete utils.ts
stanleyyconsensys Oct 15, 2026
1e54092
chore: update
stanleyyconsensys Oct 15, 2026
5a40f1e
chore: use stroop limit
stanleyyconsensys Oct 16, 2026
d29cdc6
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Oct 16, 2026
6897f3e
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Oct 16, 2026
b7a16c9
fix: test
stanleyyconsensys Oct 16, 2026
8975245
fix: test
stanleyyconsensys Oct 16, 2026
099f36f
Update AssetMetadataService.test.ts
stanleyyconsensys Oct 16, 2026
9912431
chore: add testing script
stanleyyconsensys Oct 20, 2026
1df9cc9
chore: force config
stanleyyconsensys Oct 20, 2026
deee006
Update horizon-event-fixture.mjs
stanleyyconsensys Oct 20, 2026
5555970
Update horizon-event-fixture.mjs
stanleyyconsensys Oct 20, 2026
20251b0
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Oct 21, 2026
c75da39
chore: clean up
stanleyyconsensys Oct 21, 2026
97053f7
Update config.ts
stanleyyconsensys Oct 21, 2026
ace48bf
Update yarn.lock
stanleyyconsensys Oct 21, 2026
c2021a6
chore: update
stanleyyconsensys Oct 21, 2026
87d24ea
chore: update jsdoc
stanleyyconsensys Oct 21, 2026
b9c8435
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Oct 22, 2026
17385d9
chore: update method name
stanleyyconsensys Oct 22, 2026
9a0c191
chore(stellar): use integer unit for trustline limit
stanleyyconsensys Oct 22, 2026
1fc0615
Update CHANGELOG.md
stanleyyconsensys Oct 22, 2026
6de375c
Update CHANGELOG.md
stanleyyconsensys Oct 22, 2026
1a31377
Merge branch 'dev/stellar/trustline-keyring-balance-limit' into dev/s…
stanleyyconsensys Oct 22, 2026
83f312f
chore: update keyring
stanleyyconsensys Oct 22, 2026
ac05404
fix: lint
stanleyyconsensys Oct 22, 2026
f63fead
fix: lint
stanleyyconsensys Oct 22, 2026
a422f72
fix: comment
stanleyyconsensys Oct 22, 2026
a8cdc55
fix: rename method
stanleyyconsensys Oct 22, 2026
0988a17
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Oct 22, 2026
9f9e515
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Oct 22, 2026
853c070
Delete horizon-event-fixture.mjs
stanleyyconsensys Oct 22, 2026
f82f24a
fix: comment
stanleyyconsensys Oct 23, 2026
0ae6ba5
chore(stellar): tighten chain id
stanleyyconsensys Oct 23, 2026
04f2942
chore(stellar): drop rpc fallback for asset metadata
stanleyyconsensys Oct 23, 2026
30f02bd
Update CHANGELOG.md
stanleyyconsensys Oct 23, 2026
17c138b
Merge branch 'main' into dev/stellar/drop-token-metadata-rpc-fallback
stanleyyconsensys Oct 23, 2026
eb81989
Update CHANGELOG.md
stanleyyconsensys Oct 23, 2026
19963ad
fix: comment
stanleyyconsensys Oct 23, 2026
bd43de1
Merge branch 'dev/stellar/drop-token-metadata-rpc-fallback' into dev/…
stanleyyconsensys Oct 23, 2026
cd67ed9
Merge branch 'dev/stellar/drop-token-metadata-rpc-fallback' into dev/…
stanleyyconsensys Oct 23, 2026
fdc31dd
Merge branch 'main' into dev/stellar/asset-migration
stanleyyconsensys Oct 23, 2026
2d6d8b7
chore: cache 5 second only
stanleyyconsensys Oct 23, 2026
735fe9e
Update config.test.ts
stanleyyconsensys Oct 23, 2026
d4357c3
Update config.test.ts
stanleyyconsensys Oct 23, 2026
2f05b41
chore: update doc
stanleyyconsensys Oct 23, 2026
cf48e48
fix: comment
stanleyyconsensys Oct 23, 2026
9a0f040
Update utils.test.ts
stanleyyconsensys Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/stellar-wallet-snap/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ SECURITY_ALERTS_API_BASE_URL=https://security-alerts.api.cx.metamask.io
# Cache TTL Milliseconds for sep41 asset balance
#STELLAR_SEP41_ASSET_BALANCE_TTL_MILLISECONDS=

# Cache TTL Milliseconds for the keyring live account reused by getAccountBalances
#STELLAR_KEYRING_LIVE_ACCOUNT_TTL_MILLISECONDS=

# Cache TTL Milliseconds for spot prices
#STELLAR_SPOT_PRICES_TTL_MILLISECONDS=

Expand Down
2 changes: 2 additions & 0 deletions packages/stellar-wallet-snap/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Read account asset balance from AssetsController whfeature flag `enableAssetMigration` and local flag `enableAssetMigration` is on ([#331](https://github.com/MetaMask/internal-snaps/pull/331))
Comment thread
stanleyyconsensys marked this conversation as resolved.
- Include SEP-41 balances on keyring `getAccountAssets` and `getAccountBalances`.
- Read asset metadata from AssetsController when feature flag `enableAssetMigration` and local flag `enableAssetMigration` is on ([#493](https://github.com/MetaMask/internal-snaps/pull/493))
- `enableAssetMigration` set to off.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Wire format: [SIP-29 Snap Assets API](https://metamask.github.io/SIPs/SIPS/sip-2
4. Persist catalog via `AssetMetadataRepository.saveMany`.
5. Failures are logged / tracked; the cron does not fail the whole Snap lifecycle.

During account / transaction sync, `SynchronizeService` also preloads SEP-41 metadata via `fetchSep41AssetsOrSyncOnce`. That path refreshes the token API catalog when `assetMetadataLastSyncedAt` is missing **or** older than `cache.ttlMilliseconds.assetMetadata` (default 1 hour). The timestamp is stamped only by a successful non-empty full catalog sync (`saveMany(..., true)`), so point-lookup `saveMany` calls cannot postpone the hot reload.
During account / transaction sync, `SynchronizeService` preloads SEP-41 metadata by calling [`AssetMetadataService.synchronizeIfStale`](../../../src/services/asset-metadata/AssetMetadataService.ts), then [`AssetMetadataService.getAssetsByScopeAndType`](../../../src/services/asset-metadata/AssetMetadataService.ts) (`AssetType.Sep41`). `synchronizeIfStale` refreshes the token API catalog when `assetMetadataLastSyncedAt` is missing **or** older than `cache.ttlMilliseconds.assetMetadata` (default 1 hour). `getAssetsByScopeAndType` then returns the persisted rows for that scope and type. The timestamp is stamped only by a successful non-empty full catalog sync (`saveMany(..., true)`), so point-lookup `saveMany` calls cannot postpone the hot reload.

## Sequence

Expand Down
2 changes: 2 additions & 0 deletions packages/stellar-wallet-snap/snap.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ const config: SnapConfig = {
process.env.STELLAR_SIMULATE_TRANSACTION_TTL_MILLISECONDS ?? '',
STELLAR_SEP41_ASSET_BALANCE_TTL_MILLISECONDS:
process.env.STELLAR_SEP41_ASSET_BALANCE_TTL_MILLISECONDS ?? '',
STELLAR_KEYRING_LIVE_ACCOUNT_TTL_MILLISECONDS:
process.env.STELLAR_KEYRING_LIVE_ACCOUNT_TTL_MILLISECONDS ?? '',
STELLAR_ASSET_METADATA_TTL_MILLISECONDS:
process.env.STELLAR_ASSET_METADATA_TTL_MILLISECONDS ?? '',
STELLAR_BASE_FEE_MULTIPLIER: process.env.STELLAR_BASE_FEE_MULTIPLIER ?? '',
Expand Down
4 changes: 4 additions & 0 deletions packages/stellar-wallet-snap/src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const FULL_ENVIRONMENT = {
loadOnChainAccount: '30000',
simulateTransaction: '8000',
sep41AssetBalance: '15000',
fetchOnChainAccount: '5000',
assetMetadata: '900000',
},
},
Expand Down Expand Up @@ -255,6 +256,7 @@ describe('ConfigProvider', () => {
loadOnChainAccount: 30000,
simulateTransaction: 8000,
sep41AssetBalance: 15000,
fetchOnChainAccount: 5000,
assetMetadata: 900000,
});
});
Expand All @@ -270,6 +272,7 @@ describe('ConfigProvider', () => {
loadOnChainAccount: '',
simulateTransaction: undefined,
sep41AssetBalance: '',
fetchOnChainAccount: undefined,
assetMetadata: '',
},
},
Expand All @@ -283,6 +286,7 @@ describe('ConfigProvider', () => {
loadOnChainAccount: 10 * 60 * 1000,
simulateTransaction: 10 * 1000,
sep41AssetBalance: 30 * 1000,
fetchOnChainAccount: 5 * 1000,
assetMetadata: 60 * 60 * 1000,
});
});
Expand Down
4 changes: 4 additions & 0 deletions packages/stellar-wallet-snap/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ export const ConfigStruct = object({
simulateTransaction: parseIntegerStruct(1000, 10 * 1000),
// SEP-41 balance reads (multicall on mainnet)
sep41AssetBalance: parseIntegerStruct(1000, 30 * 1000),
// Keyring getAccountAssets result reused by the following getAccountBalances call
fetchOnChainAccount: parseIntegerStruct(1000, 5 * 1000),
Comment thread
stanleyyconsensys marked this conversation as resolved.
// 1 hour — token API asset catalog. Account sync hot-reloads when older.
assetMetadata: parseIntegerStruct(1000, 60 * 60 * 1000),
}),
Expand Down Expand Up @@ -185,6 +187,8 @@ const ENVIRONMENT = {
process.env.STELLAR_SIMULATE_TRANSACTION_TTL_MILLISECONDS,
sep41AssetBalance:
process.env.STELLAR_SEP41_ASSET_BALANCE_TTL_MILLISECONDS,
fetchOnChainAccount:
process.env.STELLAR_KEYRING_LIVE_ACCOUNT_TTL_MILLISECONDS,
assetMetadata: process.env.STELLAR_ASSET_METADATA_TTL_MILLISECONDS,
},
},
Expand Down
10 changes: 10 additions & 0 deletions packages/stellar-wallet-snap/src/constants.ts

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: nice one!

Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ export const STROOPS_PER_XLM = 10_000_000;
*/
export const BASE_RESERVE_STROOPS = XLM_PER_BASE_RESERVE * STROOPS_PER_XLM;

/**
* Base-reserve units reserved for the account entry itself (not subentries).
*
* Stellar minimum balance is
* `(2 + subentry_count + num_sponsoring − num_sponsored) × base_reserve`.
*
* @see https://developers.stellar.org/docs/learn/fundamentals/stellar-data-structures/accounts#minimum-balance
*/
export const ACCOUNT_MINIMUM_BASE_RESERVE_UNIT = 2;

/**
* Stellar's coin type
*
Expand Down
2 changes: 2 additions & 0 deletions packages/stellar-wallet-snap/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ const onChainAccountService = new OnChainAccountService({
logger,
networkService,
onChainAccountRepository,
assetsService,
assetMetadataService,
});

Expand Down Expand Up @@ -357,6 +358,7 @@ const clientRequestHandler = new ClientRequestHandler({

/** ------------------------------ Export Handlers ------------------------------ */
export {
assetsService,
clientRequestHandler,
cronjobHandler,
keyringHandler,
Expand Down
27 changes: 11 additions & 16 deletions packages/stellar-wallet-snap/src/handlers/accountResolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ describe('AccountResolver', () => {
const resolveOnChainAccountSpy = jest
.spyOn(OnChainAccountService.prototype, 'resolveOnChainAccount')
.mockResolvedValue(onChainAccount);
const resolveOnChainAccountByKeyringAccountIdSpy = jest.spyOn(
const getOnChainAccountSnapshotSpy = jest.spyOn(
OnChainAccountService.prototype,
'resolveOnChainAccountByKeyringAccountId',
'getOnChainAccountSnapshot',
);
const resolveWalletSpy = jest
.spyOn(WalletService.prototype, 'resolveWallet')
Expand All @@ -86,7 +86,7 @@ describe('AccountResolver', () => {
onChainAccount,
resolveAccountSpy,
resolveOnChainAccountSpy,
resolveOnChainAccountByKeyringAccountIdSpy,
getOnChainAccountSnapshotSpy,
resolveWalletSpy,
};
}
Expand Down Expand Up @@ -156,12 +156,10 @@ describe('AccountResolver', () => {
account,
wallet,
onChainAccount,
resolveOnChainAccountByKeyringAccountIdSpy,
getOnChainAccountSnapshotSpy,
resolveOnChainAccountSpy,
} = setup();
resolveOnChainAccountByKeyringAccountIdSpy.mockResolvedValue(
onChainAccount,
);
getOnChainAccountSnapshotSpy.mockResolvedValue(onChainAccount);

const result = await accountResolver.resolveAccount({
accountId: keyringAccountId,
Expand All @@ -174,17 +172,17 @@ describe('AccountResolver', () => {
onChainAccount,
wallet,
});
expect(resolveOnChainAccountByKeyringAccountIdSpy).toHaveBeenCalledWith(
expect(getOnChainAccountSnapshotSpy).toHaveBeenCalledWith(
account.id,
account.address,
scope,
);
expect(resolveOnChainAccountSpy).not.toHaveBeenCalled();
});

it('throws AccountNotActivatedException when state has no on-chain snapshot', async () => {
const { accountResolver, resolveOnChainAccountByKeyringAccountIdSpy } =
setup();
resolveOnChainAccountByKeyringAccountIdSpy.mockResolvedValue(null);
const { accountResolver, getOnChainAccountSnapshotSpy } = setup();
getOnChainAccountSnapshotSpy.mockResolvedValue(null);

await expect(
accountResolver.resolveAccount({
Expand All @@ -196,8 +194,7 @@ describe('AccountResolver', () => {
});

it('throws DerivedAccountAddressMismatchException when state snapshot address differs from keyring', async () => {
const { accountResolver, resolveOnChainAccountByKeyringAccountIdSpy } =
setup();
const { accountResolver, getOnChainAccountSnapshotSpy } = setup();

const otherAddress = generateStellarAddress();
const mockRawAccount = createMockAccountWithBalances(
Expand All @@ -210,9 +207,7 @@ describe('AccountResolver', () => {
scope,
horizonSource(mockRawAccount, scope),
);
resolveOnChainAccountByKeyringAccountIdSpy.mockResolvedValue(
mismatchedOnChainAccount,
);
getOnChainAccountSnapshotSpy.mockResolvedValue(mismatchedOnChainAccount);

await expect(
accountResolver.resolveAccount({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,8 +161,9 @@ export class AccountResolver {
);
}
const onChainAccount =
await this.#onChainAccountService.resolveOnChainAccountByKeyringAccountId(
await this.#onChainAccountService.getOnChainAccountSnapshot(
account.id,
account.address,
scope,
);
// It is a tradeoff when loading on-chain account from state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,8 @@ describe('OnAmountInputHandler', () => {
jest.spyOn(AccountService.prototype, 'resolveAccount').mockResolvedValue({
account,
});
const resolveOnChainAccountByKeyringAccountIdSpy = jest
.spyOn(
OnChainAccountService.prototype,
'resolveOnChainAccountByKeyringAccountId',
)
const getOnChainAccountSnapshotSpy = jest
.spyOn(OnChainAccountService.prototype, 'getOnChainAccountSnapshot')
.mockResolvedValue(onChainAccount);
jest
.spyOn(WalletService.prototype, 'resolveWallet')
Expand Down Expand Up @@ -125,7 +122,7 @@ describe('OnAmountInputHandler', () => {
onChainAccount,
wallet,
createValidatedSendTransaction,
resolveOnChainAccountByKeyringAccountIdSpy,
getOnChainAccountSnapshotSpy,
};
}

Expand Down Expand Up @@ -177,14 +174,15 @@ describe('OnAmountInputHandler', () => {
account,
onChainAccount,
createValidatedSendTransaction,
resolveOnChainAccountByKeyringAccountIdSpy,
getOnChainAccountSnapshotSpy,
} = setup();

const result = await handler.handle(baseRequest());

expect(result).toStrictEqual({ valid: true, errors: [] });
expect(resolveOnChainAccountByKeyringAccountIdSpy).toHaveBeenCalledWith(
expect(getOnChainAccountSnapshotSpy).toHaveBeenCalledWith(
account.id,
account.address,
scope,
);
expect(createValidatedSendTransaction).toHaveBeenCalledWith({
Expand All @@ -204,13 +202,14 @@ describe('OnAmountInputHandler', () => {
handler,
account,
createValidatedSendTransaction,
resolveOnChainAccountByKeyringAccountIdSpy,
getOnChainAccountSnapshotSpy,
} = setup();

await handler.handle(baseRequest({ assetId: testnetAssetId }));

expect(resolveOnChainAccountByKeyringAccountIdSpy).toHaveBeenCalledWith(
expect(getOnChainAccountSnapshotSpy).toHaveBeenCalledWith(
account.id,
account.address,
KnownCaip2ChainId.Testnet,
);
expect(createValidatedSendTransaction).toHaveBeenCalledWith(
Expand Down Expand Up @@ -289,8 +288,8 @@ describe('OnAmountInputHandler', () => {
});

it('returns invalid when keyring state has no on-chain snapshot', async () => {
const { handler, resolveOnChainAccountByKeyringAccountIdSpy } = setup();
resolveOnChainAccountByKeyringAccountIdSpy.mockResolvedValueOnce(null);
const { handler, getOnChainAccountSnapshotSpy } = setup();
getOnChainAccountSnapshotSpy.mockResolvedValueOnce(null);

expect(await handler.handle(baseRequest())).toStrictEqual({
valid: false,
Expand Down
Loading
Loading