Skip to content

Commit cf3901a

Browse files
authored
SDP-1660: Add embedded wallet integration tests (#1041)
### What Add integration tests for embedded wallets. Main change in `internal/integrationtests/integration_tests.go` Successful action: https://github.com/stellar/stellar-disbursement-platform-backend/actions/runs/21684717721 Test covers: - Disbursement creation with embedded wallet provider - Embedded wallet token generation after disbursement starts - Receiver create embedded wallet with WebAuthn credentials (P256 public key) - Smart contract deployment via TSS - Receiver wallet auto-registration (no verification) - Payment delivery to contract account ### Why Embedded Wallet ### Known limitations SEP-45 authentication is not covered here. SEP-45 requires WebAuthn/passkey signatures which use the secp256r1 (P-256) curve and browser-based APIs(navigator.credentials). This cannot be simulated in a Go backend. ### Checklist - [ ] Title follows `SDP-1234: Add new feature` or `Chore: Refactor package xyz` format. The Jira ticket code was included if available. - [ ] PR has a focused scope and doesn't mix features with refactoring - [ ] Tests are included (if applicable) - [ ] `CHANGELOG.md` is updated (if applicable) - [ ] If contracts changed, run the `Contract WASM Artifacts` workflow and open a PR to update the WASMs on `dev` - [ ] CONFIG/SECRETS changes are updated in helmcharts and deployments (if applicable) - [ ] Preview deployment works as expected - [ ] Ready for production
1 parent 30443cd commit cf3901a

15 files changed

+878
-28
lines changed

.github/workflows/e2e_integration_test.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ jobs:
3030
- "Stellar-email" # Stellar distribution account where receivers are registered with their email
3131
- "Circle-transfers-phone" # Circle(Transfers) distribution account where receivers are registered with their phone number
3232
- "Circle-payouts-phoneWallet" # Circle(Payouts) distribution account where receivers are registered with their phone number and wallet address
33+
- "Stellar-embeddedWallet" # Embedded wallet (contract account) E2E test with disbursement to C-address
3334
include:
3435
- platform: "Stellar-phone"
3536
environment: "Receiver Registration - E2E Integration Tests (Stellar)"
@@ -76,6 +77,16 @@ jobs:
7677
DISBURSED_ASSET_CODE: "XLM"
7778
NETWORK_PASSPHRASE: "Test SDF Future Network ; October 2022"
7879
HORIZON_URL: "https://horizon-futurenet.stellar.org"
80+
- platform: "Stellar-embeddedWallet"
81+
environment: "Receiver Registration - E2E Integration Tests (Stellar)"
82+
DISTRIBUTION_ACCOUNT_TYPE: "DISTRIBUTION_ACCOUNT.STELLAR.ENV"
83+
DISBURSEMENT_CSV_FILE_NAME: "disbursement_instructions_embedded_wallet.csv"
84+
REGISTRATION_CONTACT_TYPE: "PHONE_NUMBER"
85+
DISBURSED_ASSET_CODE: "XLM"
86+
NETWORK_PASSPHRASE: "Test SDF Network ; September 2015"
87+
HORIZON_URL: "https://horizon-testnet.stellar.org"
88+
RPC_URL: "https://soroban-testnet.stellar.org"
89+
TEST_TYPE: "embedded-wallet"
7990
environment: ${{ matrix.environment }}
8091
env:
8192
DISTRIBUTION_ACCOUNT_TYPE: ${{ matrix.DISTRIBUTION_ACCOUNT_TYPE }}
@@ -86,6 +97,10 @@ jobs:
8697
NETWORK_PASSPHRASE: ${{ matrix.NETWORK_PASSPHRASE }}
8798
HORIZON_URL: ${{ matrix.HORIZON_URL }}
8899
CIRCLE_API_TYPE: ${{ matrix.CIRCLE_API_TYPE || 'TRANSFERS' }}
100+
RPC_URL: ${{ matrix.RPC_URL }}
101+
ENABLE_EMBEDDED_WALLETS: ${{ matrix.ENABLE_EMBEDDED_WALLETS || 'false' }}
102+
EMBEDDED_WALLETS_WASM_HASH: ${{ matrix.EMBEDDED_WALLETS_WASM_HASH }}
103+
TEST_TYPE: ${{ matrix.TEST_TYPE }}
89104
steps:
90105
- name: Checkout
91106
uses: actions/checkout@v6
@@ -148,7 +163,12 @@ jobs:
148163
- name: Start integration test command
149164
run: |
150165
echo "Running integration tests with native SDP SEP services"
151-
docker exec e2e-sdp-api sh -c "./stellar-disbursement-platform integration-tests start"
166+
if [ "${{ env.TEST_TYPE }}" = "embedded-wallet" ]; then
167+
echo "Running embedded wallet integration tests..."
168+
docker exec e2e-sdp-api sh -c "./stellar-disbursement-platform integration-tests start-embedded-wallet"
169+
else
170+
docker exec e2e-sdp-api sh -c "./stellar-disbursement-platform integration-tests start"
171+
fi
152172
shell: bash
153173

154174
- name: Docker logs

cmd/integration_tests.go

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func (c *IntegrationTestsCommand) Command() *cobra.Command {
3232
Name: "disbursed-asset-issuer",
3333
Usage: "Issuer if the asset to be disbursed",
3434
OptType: types.String,
35-
ConfigKey: &integrationTestsOpts.DisbursetAssetIssuer,
35+
ConfigKey: &integrationTestsOpts.DisbursedAssetIssuer,
3636
Required: false,
3737
},
3838
{
@@ -148,7 +148,8 @@ func (c *IntegrationTestsCommand) Command() *cobra.Command {
148148

149149
startIntegrationTestsCmd := c.StartIntegrationTestsCommand(integrationTestsOpts)
150150
createIntegrationTestsDataCmd := c.CreateIntegrationTestsDataCommand(integrationTestsOpts)
151-
integrationTestsCmd.AddCommand(startIntegrationTestsCmd, createIntegrationTestsDataCmd)
151+
startEmbeddedWalletTestsCmd := c.StartEmbeddedWalletTestsCommand(integrationTestsOpts)
152+
integrationTestsCmd.AddCommand(startIntegrationTestsCmd, createIntegrationTestsDataCmd, startEmbeddedWalletTestsCmd)
152153

153154
return integrationTestsCmd
154155
}
@@ -307,3 +308,68 @@ func (c *IntegrationTestsCommand) CreateIntegrationTestsDataCommand(integrationT
307308

308309
return createIntegrationTestsDataCmd
309310
}
311+
312+
func (c *IntegrationTestsCommand) StartEmbeddedWalletTestsCommand(integrationTestsOpts *integrationtests.IntegrationTestsOpts) *cobra.Command {
313+
configOpts := config.ConfigOptions{
314+
{
315+
Name: "embedded-wallets-wasm-hash",
316+
Usage: "The WASM hash of the embedded wallet smart contract",
317+
OptType: types.String,
318+
ConfigKey: &integrationTestsOpts.EmbeddedWalletsWasmHash,
319+
Required: true,
320+
},
321+
{
322+
Name: "rpc-url",
323+
Usage: "URL of the Stellar RPC server used for contract deployment",
324+
OptType: types.String,
325+
CustomSetValue: utils.SetConfigOptionURLString,
326+
ConfigKey: &integrationTestsOpts.RPCUrl,
327+
Required: true,
328+
},
329+
{
330+
Name: "disbursement-csv-file-name",
331+
Usage: "File name of the integration test disbursement file.",
332+
OptType: types.String,
333+
ConfigKey: &integrationTestsOpts.DisbursementCSVFileName,
334+
Required: true,
335+
},
336+
{
337+
Name: "disbursement-csv-file-path",
338+
Usage: "File path of the integration test disbursement file.",
339+
OptType: types.String,
340+
ConfigKey: &integrationTestsOpts.DisbursementCSVFilePath,
341+
Required: true,
342+
},
343+
}
344+
345+
startEmbeddedWalletTestsCmd := &cobra.Command{
346+
Use: "start-embedded-wallet",
347+
Short: "Run the embedded wallet (contract account) e2e tests",
348+
PersistentPreRun: func(cmd *cobra.Command, args []string) {
349+
ctx := cmd.Context()
350+
cmd.Parent().PersistentPreRun(cmd.Parent(), args)
351+
352+
// Validate & ingest input parameters
353+
configOpts.Require()
354+
err := configOpts.SetValues()
355+
if err != nil {
356+
log.Ctx(ctx).Fatalf("Error setting values of config options: %s", err.Error())
357+
}
358+
},
359+
Run: func(cmd *cobra.Command, _ []string) {
360+
ctx := cmd.Context()
361+
362+
err := c.Service.StartEmbeddedWalletIntegrationTests(ctx, *integrationTestsOpts)
363+
if err != nil {
364+
log.Ctx(ctx).Fatalf("Error starting embedded wallet integration tests: %s", err.Error())
365+
}
366+
},
367+
}
368+
369+
err := configOpts.Init(startEmbeddedWalletTestsCmd)
370+
if err != nil {
371+
log.Ctx(startEmbeddedWalletTestsCmd.Context()).Fatalf("Error initializing StartEmbeddedWalletTestsCommand: %s", err.Error())
372+
}
373+
374+
return startEmbeddedWalletTestsCmd
375+
}

cmd/integration_tests_test.go

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ func (m *mockIntegrationTests) CreateTestData(ctx context.Context, opts integrat
2828
return m.Called(ctx, opts).Error(0)
2929
}
3030

31+
func (m *mockIntegrationTests) StartEmbeddedWalletIntegrationTests(ctx context.Context, opts integrationtests.IntegrationTestsOpts) error {
32+
return m.Called(ctx, opts).Error(0)
33+
}
34+
3135
func Test_IntegrationTestsCommand_StartIntegrationTestsCommand(t *testing.T) {
3236
serviceMock := &mockIntegrationTests{}
3337
command := &IntegrationTestsCommand{Service: serviceMock}
@@ -41,7 +45,7 @@ func Test_IntegrationTestsCommand_StartIntegrationTestsCommand(t *testing.T) {
4145
UserEmail: "mockemail@test.com",
4246
UserPassword: "mockPassword123!",
4347
DisbursedAssetCode: "USDC",
44-
DisbursetAssetIssuer: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVV",
48+
DisbursedAssetIssuer: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVV",
4549
WalletName: "walletTest",
4650
DisbursementCSVFilePath: "mockPath",
4751
DisbursementCSVFileName: "file.csv",
@@ -107,7 +111,7 @@ func Test_IntegrationTestsCommand_CreateIntegrationTestsDataCommand(t *testing.T
107111
integrationTestsOpts := &integrationtests.IntegrationTestsOpts{
108112
DatabaseDSN: "randomDatabaseDSN",
109113
DisbursedAssetCode: "USDC",
110-
DisbursetAssetIssuer: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVV",
114+
DisbursedAssetIssuer: "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVV",
111115
WalletName: "walletTest",
112116
WalletHomepage: "https://www.test_wallet.com",
113117
WalletDeepLink: "test-wallet://sdp",
@@ -149,3 +153,52 @@ func Test_IntegrationTestsCommand_CreateIntegrationTestsDataCommand(t *testing.T
149153

150154
serviceMock.AssertExpectations(t)
151155
}
156+
157+
func Test_IntegrationTestsCommand_StartEmbeddedWalletTestsCommand(t *testing.T) {
158+
serviceMock := &mockIntegrationTests{}
159+
command := &IntegrationTestsCommand{Service: serviceMock}
160+
161+
parentCmdMock := &cobra.Command{
162+
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
163+
}
164+
165+
integrationTestsOpts := &integrationtests.IntegrationTestsOpts{
166+
EmbeddedWalletsWasmHash: "0123456789abcdef",
167+
RPCUrl: "https://rpc.test",
168+
DisbursementCSVFileName: "disbursement.csv",
169+
DisbursementCSVFilePath: "/tmp",
170+
}
171+
172+
cmd := command.StartEmbeddedWalletTestsCommand(integrationTestsOpts)
173+
parentCmdMock.AddCommand(cmd)
174+
175+
t.Setenv("ENABLE_EMBEDDED_WALLETS", "true")
176+
t.Setenv("EMBEDDED_WALLETS_WASM_HASH", "0123456789abcdef")
177+
t.Setenv("RPC_URL", "https://rpc.test")
178+
t.Setenv("DISBURSEMENT_CSV_FILE_NAME", "disbursement.csv")
179+
t.Setenv("DISBURSEMENT_CSV_FILE_PATH", "/tmp")
180+
181+
parentCmdMock.SetArgs([]string{
182+
"start-embedded-wallet",
183+
})
184+
185+
t.Run("exit with status 1 when integration tests service fails", func(t *testing.T) {
186+
utils.AssertFuncExitsWithFatal(t, func() {
187+
serviceMock.
188+
On("StartEmbeddedWalletIntegrationTests", context.Background(), *integrationTestsOpts).
189+
Return(errors.New("unexpected error"))
190+
_ = parentCmdMock.Execute()
191+
}, "Error starting embedded wallet integration tests: unexpected error")
192+
})
193+
194+
t.Run("executes the start embedded wallet integration tests command successfully", func(t *testing.T) {
195+
serviceMock.
196+
On("StartEmbeddedWalletIntegrationTests", context.Background(), *integrationTestsOpts).
197+
Return(nil)
198+
199+
err := parentCmdMock.Execute()
200+
require.NoError(t, err)
201+
})
202+
203+
serviceMock.AssertExpectations(t)
204+
}

dev/docker-compose-sdp.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ services:
5757

5858
# SEP-45 configuration
5959
ENABLE_SEP45: ${ENABLE_SEP45:-false}
60-
SEP45_CONTRACT_ID: ${SEP45_CONTRACT_ID:-}
60+
SEP45_CONTRACT_ID: ${SEP45_CONTRACT_ID:-CDY4CS2VWHAZOMYVTKUFKGNZKIVFBCXUFNFQ5KSXOTAHKL5H5ZRTAUTH}
6161

6262
# Embedded wallet configuration
6363
ENABLE_EMBEDDED_WALLETS: ${ENABLE_EMBEDDED_WALLETS:-false}
64-
EMBEDDED_WALLETS_WASM_HASH: ${EMBEDDED_WALLETS_WASM_HASH:-}
64+
EMBEDDED_WALLETS_WASM_HASH: ${EMBEDDED_WALLETS_WASM_HASH:-9b784817dff1620a3e2b223fe1eb8dac56e18980dea9726f692847ccbbd3a853}
6565

6666
# multi-tenant secrets
6767
ADMIN_ACCOUNT: ${ADMIN_ACCOUNT:-SDP-admin}

internal/data/fixtures.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -722,7 +722,7 @@ func CreateEmbeddedWalletFixture(t *testing.T, ctx context.Context, sqlExec db.S
722722
token = randomToken
723723
}
724724
if wasmHash == "" {
725-
wasmHash = "6223a23026480644055230783215652de8695abc8a9dbb56a94972eb341a4663"
725+
wasmHash = "9b784817dff1620a3e2b223fe1eb8dac56e18980dea9726f692847ccbbd3a853"
726726
}
727727

728728
q := fmt.Sprintf(`

internal/data/wallets.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -273,13 +273,13 @@ func (wm *WalletModel) Insert(ctx context.Context, newWallet WalletInsert) (*Wal
273273
return wallet, nil
274274
}
275275

276-
func (wm *WalletModel) GetOrCreate(ctx context.Context, name, homepage, deepLink, sep10Domain string) (*Wallet, error) {
276+
func (wm *WalletModel) GetOrCreate(ctx context.Context, name, homepage, deepLink, sep10Domain string, embedded bool) (*Wallet, error) {
277277
const query = `
278278
WITH create_wallet AS(
279279
INSERT INTO wallets
280-
(name, homepage, deep_link_schema, sep_10_client_domain)
280+
(name, homepage, deep_link_schema, sep_10_client_domain, embedded)
281281
VALUES
282-
($1, $2, $3, $4)
282+
($1, $2, $3, $4, $5)
283283
ON CONFLICT (name, homepage, deep_link_schema) DO NOTHING
284284
RETURNING
285285
*
@@ -293,7 +293,7 @@ func (wm *WalletModel) GetOrCreate(ctx context.Context, name, homepage, deepLink
293293
`
294294

295295
var wallet Wallet
296-
err := wm.dbConnectionPool.GetContext(ctx, &wallet, query, name, homepage, deepLink, sep10Domain)
296+
err := wm.dbConnectionPool.GetContext(ctx, &wallet, query, name, homepage, deepLink, sep10Domain, embedded)
297297
if err != nil {
298298
return nil, fmt.Errorf("error getting or creating wallet: %w", err)
299299
}

internal/data/wallets_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,7 @@ func Test_WalletModelGetOrCreate(t *testing.T) {
748748
deepLinkSchema := "test-wallet://sdp"
749749
sep10ClientDomain := "www.test_wallet.com"
750750

751-
wallet, err := walletModel.GetOrCreate(ctx, name, homepage, deepLinkSchema, sep10ClientDomain)
751+
wallet, err := walletModel.GetOrCreate(ctx, name, homepage, deepLinkSchema, sep10ClientDomain, false)
752752
require.EqualError(t, err, "error getting or creating wallet: pq: duplicate key value violates unique constraint \"wallets_name_key\"")
753753
assert.Empty(t, wallet)
754754
})
@@ -760,7 +760,7 @@ func Test_WalletModelGetOrCreate(t *testing.T) {
760760
deepLinkSchema := "test-wallet://sdp"
761761
sep10ClientDomain := "www.test_wallet.com"
762762

763-
wallet, err := walletModel.GetOrCreate(ctx, name, homepage, deepLinkSchema, sep10ClientDomain)
763+
wallet, err := walletModel.GetOrCreate(ctx, name, homepage, deepLinkSchema, sep10ClientDomain, false)
764764
require.NoError(t, err)
765765
assert.Equal(t, "test_wallet", wallet.Name)
766766
assert.Equal(t, "https://www.test_wallet.com", wallet.Homepage)
@@ -781,7 +781,7 @@ func Test_WalletModelGetOrCreate(t *testing.T) {
781781
deepLinkSchema := "test-wallet://sdp"
782782
sep10ClientDomain := "www.test_wallet.com"
783783

784-
wallet, err := walletModel.GetOrCreate(ctx, name, homepage, deepLinkSchema, sep10ClientDomain)
784+
wallet, err := walletModel.GetOrCreate(ctx, name, homepage, deepLinkSchema, sep10ClientDomain, false)
785785
require.NoError(t, err)
786786
assert.Equal(t, expected.ID, wallet.ID)
787787
})

internal/integrationtests/docker/docker-compose-e2e-tests.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ services:
3535
HORIZON_URL: ${HORIZON_URL:-https://horizon-testnet.stellar.org}
3636
RPC_URL: ${RPC_URL:-https://soroban-testnet.stellar.org}
3737
SEP10_SIGNING_PUBLIC_KEY: ${SEP10_SIGNING_PUBLIC_KEY}
38-
SEP45_CONTRACT_ID: ${SEP45_CONTRACT_ID}
38+
SEP45_CONTRACT_ID: ${SEP45_CONTRACT_ID:-CDY4CS2VWHAZOMYVTKUFKGNZKIVFBCXUFNFQ5KSXOTAHKL5H5ZRTAUTH}
3939
SEP10_SIGNING_PRIVATE_KEY: ${SEP10_SIGNING_PRIVATE_KEY}
4040
DISTRIBUTION_PUBLIC_KEY: ${DISTRIBUTION_PUBLIC_KEY}
4141
RECAPTCHA_SITE_KEY: 6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI
@@ -71,6 +71,10 @@ services:
7171
ADMIN_SERVER_API_KEY: api_key_1234567890
7272
CIRCLE_USDC_WALLET_ID: ${CIRCLE_USDC_WALLET_ID}
7373

74+
# embedded wallet integration tests
75+
ENABLE_EMBEDDED_WALLETS: ${ENABLE_EMBEDDED_WALLETS:-true}
76+
EMBEDDED_WALLETS_WASM_HASH: ${EMBEDDED_WALLETS_WASM_HASH:-9b784817dff1620a3e2b223fe1eb8dac56e18980dea9726f692847ccbbd3a853}
77+
7478
# secrets:
7579
AWS_ACCESS_KEY_ID: MY_AWS_ACCESS_KEY_ID
7680
AWS_REGION: MY_AWS_REGION
@@ -123,6 +127,10 @@ services:
123127
DISTRIBUTION_SEED: ${DISTRIBUTION_SEED}
124128
CHANNEL_ACCOUNT_ENCRYPTION_PASSPHRASE: ${DISTRIBUTION_SEED}
125129
DISTRIBUTION_ACCOUNT_ENCRYPTION_PASSPHRASE: ${DISTRIBUTION_SEED}
130+
# Embedded wallet support - required for Soroban contract deployment
131+
RPC_URL: ${RPC_URL:-https://soroban-testnet.stellar.org}
132+
ENABLE_EMBEDDED_WALLETS: ${ENABLE_EMBEDDED_WALLETS:-true}
133+
EMBEDDED_WALLETS_WASM_HASH: ${EMBEDDED_WALLETS_WASM_HASH:-9b784817dff1620a3e2b223fe1eb8dac56e18980dea9726f692847ccbbd3a853}
126134
depends_on:
127135
- db
128136
- sdp-api

0 commit comments

Comments
 (0)