Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 36 additions & 0 deletions .api-sync/contract-check-allowlist.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,5 +166,41 @@
"field": "payout_id",
"reason": "Path parameter (the spec names it id) modeled on the request input type for URL building; never a wire body property. Pre-existing modeling choice, unrelated to the customers rename.",
"owner": "eric@blindpay.com"
},
{
"schema": "ListBlockchainWalletsResponse",
"field": "is_self_custody",
"reason": "Added by blindpay-v2 PR #2632 (BCB Resolution 588 self-custody flag); not in the spec snapshot until that ships and api-sync refreshes it. Remove once the snapshot contains is_self_custody.",
"owner": "darlan@blindpay.com"
},
{
"schema": "CreateBlockchainWalletWithAddressInput",
"field": "is_self_custody",
"reason": "Added by blindpay-v2 PR #2632 (BCB Resolution 588 self-custody flag); not in the spec snapshot until that ships and api-sync refreshes it. Remove once the snapshot contains is_self_custody.",
"owner": "darlan@blindpay.com"
},
{
"schema": "CreateBlockchainWalletWithHashInput",
"field": "is_self_custody",
"reason": "Added by blindpay-v2 PR #2632 (BCB Resolution 588 self-custody flag); not in the spec snapshot until that ships and api-sync refreshes it. Remove once the snapshot contains is_self_custody.",
"owner": "darlan@blindpay.com"
},
{
"schema": "GetBlockchainWalletResponse",
"field": "is_self_custody",
"reason": "Added by blindpay-v2 PR #2632 (BCB Resolution 588 self-custody flag); not in the spec snapshot until that ships and api-sync refreshes it. Remove once the snapshot contains is_self_custody.",
"owner": "darlan@blindpay.com"
},
{
"schema": "CreateBlockchainWalletResponse",
"field": "is_self_custody",
"reason": "Added by blindpay-v2 PR #2632 (BCB Resolution 588 self-custody flag); not in the spec snapshot until that ships and api-sync refreshes it. Remove once the snapshot contains is_self_custody.",
"owner": "darlan@blindpay.com"
},
{
"schema": "SetBlockchainWalletSelfCustodyInput",
"field": "is_self_custody",
"reason": "Added by blindpay-v2 PR #2632 (BCB Resolution 588 self-custody flag); not in the spec snapshot until that ships and api-sync refreshes it. Remove once the snapshot contains is_self_custody.",
"owner": "darlan@blindpay.com"
}
]
12 changes: 12 additions & 0 deletions .api-sync/unmodeled.json
Original file line number Diff line number Diff line change
Expand Up @@ -1316,6 +1316,18 @@
"path": "us.wire",
"reason": "Pre-existing inline nested shape from before the api-sync-hardening pass introduced nested-object coverage checking; recorded intentionally (not modeled) so newly introduced unmapped nested shapes fail loudly instead of silently. Needs a human pass to either model or drop.",
"owner": "eric@blindpay.com"
},
{
"schema": "PayinOut",
"path": "tracking_payment.review_contexts",
"reason": "Inline nested shape added to the payin tracking_payment step alongside review_source/review_context; the SDK does not model it yet (same pending tracking_payment PR). Recorded so main's test suite goes green again.",
"owner": "darlan@blindpay.com"
},
{
"schema": "CreatePayinOut",
"path": "tracking_payment.review_contexts",
"reason": "Inline nested shape added to the payin tracking_payment step alongside review_source/review_context; the SDK does not model it yet (same pending tracking_payment PR). Recorded so main's test suite goes green again.",
"owner": "darlan@blindpay.com"
}
]
}
5 changes: 5 additions & 0 deletions .changeset/blockchain-wallet-self-custody.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@blindpay/node": minor
---

Add self-custody support to external blockchain wallets. `is_self_custody` (`boolean | null`, null = never answered) is now returned on blockchain wallets and accepted as an optional input on `createWithAddress` and `createWithHash` (the API requires it for customers in Brazil). New `wallets.blockchain.setSelfCustody({ customer_id, id, is_self_custody })` records the answer once on an existing wallet. Adds the `blockchainWallet.update` webhook event.
45 changes: 45 additions & 0 deletions src/resources/wallets/blockchain.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
ListBlockchainWalletsResponse,
MintUsdbSolanaResponse,
PrepareSolanaDelegationTransactionResponse,
SetBlockchainWalletSelfCustodyResponse,
} from "./blockchain";

describe("Blockchain wallets", () => {
Expand Down Expand Up @@ -40,6 +41,7 @@ describe("Blockchain wallets", () => {
address: "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C",
signature_tx_hash: "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
is_account_abstraction: false,
is_self_custody: null,
customer_id: "re_000000000000",
},
];
Expand All @@ -64,6 +66,7 @@ describe("Blockchain wallets", () => {
address: "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C",
signature_tx_hash: undefined,
is_account_abstraction: true,
is_self_custody: true,
customer_id: "re_000000000000",
};

Expand All @@ -76,10 +79,15 @@ describe("Blockchain wallets", () => {
name: "Wallet Display Name",
network: "polygon",
address: "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C",
is_self_custody: true,
});

expect(error).toBeNull();
expect(data).toEqual(mockedWallet);
expect(JSON.parse(fetchMock.mock.calls[0][1]?.body as string)).toMatchObject({
is_self_custody: true,
is_account_abstraction: true,
});
});
});

Expand All @@ -92,6 +100,7 @@ describe("Blockchain wallets", () => {
address: "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C",
signature_tx_hash: "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
is_account_abstraction: false,
is_self_custody: null,
customer_id: "re_000000000000",
};

Expand Down Expand Up @@ -120,6 +129,7 @@ describe("Blockchain wallets", () => {
address: "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C",
signature_tx_hash: "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
is_account_abstraction: false,
is_self_custody: null,
customer_id: "re_000000000000",
};

Expand All @@ -137,6 +147,41 @@ describe("Blockchain wallets", () => {
});
});

describe("Set blockchain wallet self-custody", () => {
it("should PATCH the self-custody answer on the customer route", async () => {
const mockedWallet: SetBlockchainWalletSelfCustodyResponse = {
id: "bw_000000000000",
name: "Wallet Display Name",
network: "polygon",
address: "0xDD6a3aD0949396e57C7738ba8FC1A46A5a1C372C",
signature_tx_hash: "0x3c499c542cef5e3811e1192ce70d8cc03d5c3359",
is_account_abstraction: false,
is_self_custody: false,
customer_id: "re_000000000000",
};

fetchMock.mockResponseOnce(JSON.stringify(mockedWallet), {
headers: { "Content-Type": "application/json" },
});

const { data, error } = await blindpay.wallets.blockchain.setSelfCustody({
customer_id: "re_000000000000",
id: "bw_000000000000",
is_self_custody: false,
});

expect(error).toBeNull();
expect(data).toEqual(mockedWallet);

const [url, init] = fetchMock.mock.calls[0];
expect(url).toContain(
"/instances/in_000000000000/customers/re_000000000000/blockchain-wallets/bw_000000000000"
);
expect(init?.method).toBe("PATCH");
expect(JSON.parse(init?.body as string)).toEqual({ is_self_custody: false });
});
});

describe("Delete blockchain wallet", () => {
it("should delete a blockchain wallet", async () => {
fetchMock.mockResponseOnce(JSON.stringify({ data: null }), {
Expand Down
25 changes: 25 additions & 0 deletions src/resources/wallets/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export type ListBlockchainWalletsResponse = Array<{
address?: string;
signature_tx_hash?: string;
is_account_abstraction: boolean;
is_self_custody: boolean | null;
customer_id: string;
}>;

Expand All @@ -24,13 +25,15 @@ export type CreateBlockchainWalletWithAddressInput = {
name: string;
network: Network;
address: string;
is_self_custody?: boolean;
};

export type CreateBlockchainWalletWithHashInput = {
customer_id: string;
name: string;
network: Network;
signature_tx_hash: string;
is_self_custody?: boolean;
};

export type GetBlockchainWalletInput = {
Expand All @@ -50,6 +53,7 @@ export type GetBlockchainWalletResponse = {
address?: string;
signature_tx_hash?: string;
is_account_abstraction: boolean;
is_self_custody: boolean | null;
customer_id: string;
};

Expand All @@ -60,9 +64,18 @@ export type CreateBlockchainWalletResponse = {
address?: string;
signature_tx_hash?: string;
is_account_abstraction: boolean;
is_self_custody: boolean | null;
customer_id: string;
};

export type SetBlockchainWalletSelfCustodyInput = {
customer_id: string;
id: string;
is_self_custody: boolean;
};

export type SetBlockchainWalletSelfCustodyResponse = GetBlockchainWalletResponse;

export type CreateAssetTrustlineInput = string;

export type CreateAssetTrustlineResponse = {
Expand Down Expand Up @@ -157,6 +170,18 @@ export function createBlockchainWalletsResource(instanceId: string, client: Inte
`/instances/${instanceId}/customers/${customer_id}/blockchain-wallets/${id}`
);
},
setSelfCustody({
customer_id,
id,
...data
}: SetBlockchainWalletSelfCustodyInput): Promise<
BlindpayApiResponse<SetBlockchainWalletSelfCustodyResponse>
> {
return client.patch(
`/instances/${instanceId}/customers/${customer_id}/blockchain-wallets/${id}`,
data
);
},
createAssetTrustline(
address: CreateAssetTrustlineInput
): Promise<BlindpayApiResponse<CreateAssetTrustlineResponse>> {
Expand Down
1 change: 1 addition & 0 deletions src/resources/webhooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export type WebhookEvents =
| "payout.complete"
| "payout.partnerFee"
| "blockchainWallet.new"
| "blockchainWallet.update"
| "payin.new"
| "payin.update"
| "payin.complete"
Expand Down
Loading